diff --git a/AGENTS.md b/AGENTS.md index 22b156d89281..05b8a86f7626 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,7 +30,7 @@ packages/ # Shared workspace packages, polyglot: web/ # TypeScript web UI (own AGENTS.md) website/ # Docs / integrations / API sites (own AGENTS.md) blueprints/ # YAML declarative config (default/ system/ example/) applied at startup -locale/ # Backend translations (.po) + shared cspell dictionaries (en/dictionaries/) +locale/ # Backend translations (.po) + cspell overrides dictionary (en/dictionaries/) tests/ # Cross-cutting test support: e2e/, integration/, geoip/, openid_conformance/ schemas/ # Third-party XSD/JSON schemas (SAML, WS-*, SCIM) used at runtime scripts/ # Repo automation (schema build, compose generation, node setup, semver) @@ -106,7 +106,7 @@ make web-test # Web UI tests (delegates to web/) ```bash make lint-fix # Auto-fix: black + ruff (Python) and rustfmt (Rust) make lint # Check: bandit, mypy --strict, golangci-lint, cargo deny/machete -make lint-spellcheck # cspell across the repo (shared dictionaries in locale/en/dictionaries/) +make lint-spellcheck # cspell across the repo (typo-only mode: reports known misspellings and forbidden British spellings, not unknown words) make lint-catalogs # pnpm catalog pins in sync across the root/web/website workspaces ``` diff --git a/authentik/lib/utils/reflection.py b/authentik/lib/utils/reflection.py index 57ba00dcd8ae..a40af32a651e 100644 --- a/authentik/lib/utils/reflection.py +++ b/authentik/lib/utils/reflection.py @@ -16,7 +16,7 @@ def all_subclasses[T: type](cls: T, sort=True) -> list[T] | set[T]: - """Recursively return all subclassess of cls""" + """Recursively return all subclasses of cls""" classes = set(cls.__subclasses__()).union( [s for c in cls.__subclasses__() for s in all_subclasses(c, sort=sort)] ) diff --git a/authentik/outposts/signals.py b/authentik/outposts/signals.py index b0086a3fa275..29bdc1ba70ff 100644 --- a/authentik/outposts/signals.py +++ b/authentik/outposts/signals.py @@ -118,7 +118,7 @@ def outpost_related_post_save(sender, instance: OutpostServiceConnection | Outpo def outpost_reverse_related_post_save(sender, instance: CertificateKeyPair | Brand, **_): for field in instance._meta.get_fields(): - # Each field is checked if it has a `related_model` attribute (when ForeginKeys or M2Ms) + # Each field is checked if it has a `related_model` attribute (when ForeignKeys or M2Ms) # are used, and if it has a value if not hasattr(field, "related_model"): continue diff --git a/cspell.config.jsonc b/cspell.config.jsonc index da90b518a7c1..bb0fff787948 100644 --- a/cspell.config.jsonc +++ b/cspell.config.jsonc @@ -5,6 +5,13 @@ "version": "0.2", "readonly": true, "language": "en-US", + // Only report words that are known misspellings (with a suggested fix) or + // explicitly forbidden via `flagWords`. Unknown-but-plausible words — project + // jargon, identifiers, integration names — pass silently, so new terms do not + // require dictionary entries. This mirrors CodeSpell's behavior, which checks + // against a curated list of common misspellings rather than flagging every + // word absent from a dictionary. + "unknownWords": "report-common-typos", "cache": { "useCache": true, "cacheLocation": "./.cspellcache", @@ -19,68 +26,18 @@ } ] ], + // In typo-only mode, a dictionary entry is needed only when a legitimate term + // collides with the common-misspellings list. Record such collisions here + // rather than reviving per-topic dictionary files. "dictionaryDefinitions": [ { - "name": "en-x-authentik-software-terms", - "path": "./locale/en/dictionaries/software-terms.txt", - "description": "English software-related terms", + "name": "en-x-authentik-overrides", + "path": "./locale/en/dictionaries/overrides.txt", + "description": "Legitimate terms that collide with the common-misspellings list", "addWords": true - }, - { - "name": "en-x-authentik-idp", - "path": "./locale/en/dictionaries/idp.txt", - "description": "English IdP words", - "addWords": true - }, - { - "name": "en-x-authentik-python", - "path": "./locale/en/dictionaries/python.txt", - "addWords": true - }, - { - "name": "en-x-authentik-rust", - "path": "./locale/en/dictionaries/rust.txt", - "addWords": true - }, - { - "name": "en-x-authentik-golang", - "path": "./locale/en/dictionaries/golang.txt", - "addWords": true - }, - { - "name": "en-x-authentik-people", - "path": "./locale/en/dictionaries/people.txt", - "description": "People names relevant to authentik", - "addWords": true - }, - { - "name": "en-x-authentik-integrations", - "path": "./locale/en/dictionaries/integrations.txt", - "description": "English integration names", - "addWords": true - }, - { - "name": "en-x-authentik-ignore", - "path": "./locale/en/dictionaries/ignore.txt", - "description": "English ignore list for authentik", - "addWords": true, - "noSuggest": true } ], - "dictionaries": [ - "en-x-authentik-software-terms", - "en-x-authentik-idp", - "en-x-authentik-ignore", - "en-x-authentik-people", - "en-x-authentik-integrations", - "node", - "softwareTerms", - "software-tools", - "computing-acronyms", - "companies", - "cpp-compound-words" - ], - "allowCompoundWords": true, + "dictionaries": ["en-x-authentik-overrides"], // British spellings to reject in favor of American variants. The `->` form // forbids the left word and offers the right word as the suggested fix. "flagWords": [ @@ -162,75 +119,25 @@ "name": "EncodedURI", "description": "Encoded URIs, which are common in authentik's codebase and often contain many false positives.", "pattern": "[a-zA-Z]+%3A%2F%2F.+" - }, - { - "name": "ConfSuffix", - "description": "Variables with `conf` or `config` suffix", - "pattern": ["\\w+(conf|config)\\b", "\\b(conf|config)\\w+"] } ], + // Only content-scoping patterns belong here (encoded blobs, upstream + // identifiers spelled the British way) — not word allowlists. Unknown words + // are no longer reported, so shape-of-word escapes are unnecessary. "ignoreRegExpList": [ - // DB Migrations - "authentik_c_\\w+_[0-9a-fA-F]+_idx", - // Google Analytics - "/G-[0-9A-Z]+/", - // Github Usernames - "@[a-zA-Z0-9_-]+", - // GitHub repositories - "github\\.com/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+", - // Docker images - "docker\\.io/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+", - // Suffix "change", which is common in migration files - "\\w+change\\b", - // Prefix "on", which is common in event handlers - "\\bon\\w+\\b", - // Prefix "pg", which is common in PostgreSQL-related code - "\\bpg\\w+\\b", - // Prefix "pf", which is common in PatternFly-related code - "\\bpf\\w+\\b", - // Prefix "ws", which is common in WebSocket-related code - "\\bws\\w+\\b", - // Suffix "propertymapping" - "\\w+propertymapping\\b", - // Words that end with "source", "provider", "user", "group", or "connection", - // which are common in authentik's codebase and often contain many false positives. - "\\w+(source|provider)(user|group|connection)\\b", - "\\w+(source|provider)(user|group|connection)", + // Encoded URIs + "EncodedURI", // Basic auth header "Basic [a-zA-Z0-9+/=]+", - // "ify" suffix, e.g. "stringify", "classify". - "\\w+l?ify\\b", - // "ified" suffix, e.g. "stringified", "classified". - "\\w+l?ified\\b", - // "ifying" suffix, e.g. "stringifying", "classifying". - "\\w+l?ifying\\b", // PatternFly's grey label modifier is spelled the British way upstream. "pf-m-grey", - // Other terms we needed - "AKQL", - "Base64", - "Email", - "EncodedURI", - "FOUC", // Flash Of Unstyled Content - "HashStrings", - "PublicKey", - "RsaCert", - "SpellCheckerIgnoreInDocSetting", - "SshRsa", - "UnicodeRef", - "Urls", - "analyse-phase", // UK Spelling - "href", - "lit-analyse" // UK Spelling + // lit-analyzer's CLI and its build phase are spelled the British way upstream. + "analyse-phase", + "lit-analyse" ], "languageSettings": [ { "languageId": "markdown,mdx", - "dictionaries": [ - "en-x-authentik-python", - "en-x-authentik-rust", - "en-x-authentik-golang" - ], "ignoreRegExpList": [ // Fenced code blocks "/^\\s*```[\\s\\S]*?^\\s*```/gm", @@ -242,16 +149,6 @@ { "languageId": "typescript,javascript,typescriptreact,javascriptreact,mdx,astro", "ignoreRegExpList": [ - // Event handlers e.g. onClick, onmouseover - "\\bon\\w+\\b", - // Custom web component tags e.g. , - " { /// Contains the host name value passed by the client, as an UTF-8 encoded string. In case of /// TLS being used on the client connection, this is the exact copy of the `server_name` /// extension as defined by RFC3546, section 3.1, often referred to as SNI. There are probably - /// other situations where an authority can be mentionned on a connection without TLS being + /// other situations where an authority can be mentioned on a connection without TLS being /// involved at all. Authority(Cow<'a, str>), diff --git a/packages/geo/test/labels.test.ts b/packages/geo/test/labels.test.ts index 8a7e22af4705..8e0a71112796 100644 --- a/packages/geo/test/labels.test.ts +++ b/packages/geo/test/labels.test.ts @@ -37,7 +37,6 @@ test("normalizePlace accepts legacy pmap:-prefixed names", () => { }); test("normalizePlace rejects unwanted kinds and nameless places", () => { - // spellchecker:disable-next-line expect(normalizePlace({ kind: "neighbourhood", name: "Mitte" }, 0, 0)).toBe(null); expect(normalizePlace({ kind: "locality" }, 0, 0)).toBe(null); }); @@ -99,7 +98,6 @@ test("country reveal zoom is tiered by population, not the dump's min_zoom", () normalizePlace( { "kind": "country", - // spellchecker:disable-next-line "name": "Österreich", "name:en": "Austria", "min_zoom": 4, diff --git a/scripts/node/utils/node.mjs b/scripts/node/utils/node.mjs index bec68ed91ae6..7fa19e087577 100644 --- a/scripts/node/utils/node.mjs +++ b/scripts/node/utils/node.mjs @@ -67,7 +67,7 @@ export function loadJSON(jsonPath) { }); } -const PackageJSONComparisionFields = /** @type {const} */ ([ +const PackageJSONComparisonFields = /** @type {const} */ ([ "name", "dependencies", "devDependencies", @@ -77,14 +77,14 @@ const PackageJSONComparisionFields = /** @type {const} */ ([ ]); /** - * @typedef {typeof PackageJSONComparisionFields[number]} PackageJSONComparisionField + * @typedef {typeof PackageJSONComparisonFields[number]} PackageJSONComparisonField */ /** * Extracts only the dependency fields from a package.json object for comparison purposes. * * @param {PackageJSON} data - * @returns {Pick} + * @returns {Pick} */ export function pluckDependencyFields(data) { /** @@ -92,13 +92,13 @@ export function pluckDependencyFields(data) { */ const result = {}; - for (const field of PackageJSONComparisionFields) { + for (const field of PackageJSONComparisonFields) { if (data[field]) { result[field] = data[field]; } } - return /** @type {Pick} */ (result); + return /** @type {Pick} */ (result); } //#region Versioning diff --git a/web/docs/arch/2026-06-18-How-CSS-Is-Made-And-Next-Steps.md b/web/docs/arch/2026-06-18-How-CSS-Is-Made-And-Next-Steps.md index 86ebbb467faa..758218abbbff 100644 --- a/web/docs/arch/2026-06-18-How-CSS-Is-Made-And-Next-Steps.md +++ b/web/docs/arch/2026-06-18-How-CSS-Is-Made-And-Next-Steps.md @@ -5,9 +5,9 @@ This document describes how the CSS is built in our current system, and what we can do to make that process less challenging and fraught with difficulties: -## styleLoaderPlugin (./bundler/style-loader-pluging/node.js) +## styleLoaderPlugin (./bundler/style-loader-plugin/node.js) -All of our CSS is complied together by this ESBuild plug. +All of our CSS is compiled together by this ESBuild plugin. ## ./scripts/build-web.mjs diff --git a/web/src/admin/providers/radius/RadiusProviderFormForm.ts b/web/src/admin/providers/radius/RadiusProviderFormForm.ts index cd59e22c2197..eba34089eade 100644 --- a/web/src/admin/providers/radius/RadiusProviderFormForm.ts +++ b/web/src/admin/providers/radius/RadiusProviderFormForm.ts @@ -32,7 +32,7 @@ const mfaSupportHelp = msg( ); const clientNetworksHelp = msg( - "List of CIDRs (comma-seperated) that clients can connect from. A more specific CIDR will match before a looser one. Clients connecting from a non-specified CIDR will be dropped.", + "List of CIDRs (comma-separated) that clients can connect from. A more specific CIDR will match before a looser one. Clients connecting from a non-specified CIDR will be dropped.", ); export interface RADIUSProviderFormProps { diff --git a/web/src/admin/users/UserListPage.ts b/web/src/admin/users/UserListPage.ts index 2893219e0d4e..c37edf2c4bdd 100644 --- a/web/src/admin/users/UserListPage.ts +++ b/web/src/admin/users/UserListPage.ts @@ -204,7 +204,7 @@ export class UserListPage extends WithLicenseSummary( [msg("Actions"), null, msg("Row Actions")], ]; - //#region Renderering + //#region Rendering protected override renderToolbarSelected(): TemplateResult { const disabled = this.selectedElements.length < 1; diff --git a/web/src/elements/LicenseNotice.ts b/web/src/elements/LicenseNotice.ts index a0753bc9686f..6ef85dd38150 100644 --- a/web/src/elements/LicenseNotice.ts +++ b/web/src/elements/LicenseNotice.ts @@ -9,7 +9,7 @@ import { css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; @customElement("ak-license-notice") -export class AKLicenceNotice extends WithLicenseSummary(AKElement) { +export class AKLicenseNotice extends WithLicenseSummary(AKElement) { public static styles = [ css` ::part(container) { @@ -44,6 +44,6 @@ export class AKLicenceNotice extends WithLicenseSummary(AKElement) { declare global { interface HTMLElementTagNameMap { - "ak-license-notice": AKLicenceNotice; + "ak-license-notice": AKLicenseNotice; } } diff --git a/web/src/styles/global/locales/ja/globals.css b/web/src/styles/global/locales/ja/globals.css index 4ad83206926b..d2fbcf4929b5 100644 --- a/web/src/styles/global/locales/ja/globals.css +++ b/web/src/styles/global/locales/ja/globals.css @@ -4,16 +4,12 @@ html[lang="ja"], html[lang^="ja-"] { - /* spellchecker:disable */ - --ak-font-family-sans-serif: "M PLUS 2", "Noto Sans JP", "Hiragino Kaku Gothic Pro", "ヒラギノ角ゴ Pro W3", メイリオ, Meiryo, "MS Pゴシック", var(--ak-generic-sans-serif); --ak-font-family-serif: "Noto Serif JP", var(--ak-generic-serif); - /* spellchecker:enable */ - --ak-font-family-heading: var(--ak-font-family-sans-serif); --pf-c-content--h1--FontWeight: 500; diff --git a/web/src/styles/global/locales/zh/globals.css b/web/src/styles/global/locales/zh/globals.css index 4df99813329d..5ea39a0eb741 100644 --- a/web/src/styles/global/locales/zh/globals.css +++ b/web/src/styles/global/locales/zh/globals.css @@ -3,8 +3,6 @@ */ html[lang^="zh"] { - /* spellchecker:disable */ - --ak-font-family-sans-serif: "Noto Sans SC", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif, var(--ak-generic-sans-serif); @@ -15,8 +13,6 @@ html[lang^="zh"] { "Noto Sans SC", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; - /* spellchecker:enable */ - --pf-c-content--h1--FontWeight: 500; body { diff --git a/web/test/unit/unescape-locale-entities.test.ts b/web/test/unit/unescape-locale-entities.test.ts index bfe02c103384..251e3c1a2ba2 100644 --- a/web/test/unit/unescape-locale-entities.test.ts +++ b/web/test/unit/unescape-locale-entities.test.ts @@ -1,4 +1,3 @@ -// cspell:ignore bearbeiten Versuche Beispielsweise nichts sehen Avaa import { sanitizeLocaleModule } from "../../scripts/unescape-locale-entities.mjs"; import { describe, expect, it } from "vitest"; diff --git a/web/types/dom.d.ts b/web/types/dom.d.ts index 2967ea987295..667ca960d458 100644 --- a/web/types/dom.d.ts +++ b/web/types/dom.d.ts @@ -2,8 +2,6 @@ * @file Global DOM-related types. */ -/* spellchecker:ignore closedBy */ - export {}; declare global { diff --git a/web/types/rapi-doc/index.d.ts b/web/types/rapi-doc/index.d.ts index db60955d6e35..c47cd01a29cd 100644 --- a/web/types/rapi-doc/index.d.ts +++ b/web/types/rapi-doc/index.d.ts @@ -2,7 +2,6 @@ declare module "rapidoc" { // eslint-disable-next-line @typescript-eslint/no-unused-vars type Booleanish = "true" | "false"; - // spellchecker:disable /** * Web Component based Swagger & OpenAPI Spec Viewer * @@ -73,7 +72,6 @@ declare module "rapidoc" { * @attr {string} api-key-value - Value of the API key (can be overwritten from UI). * @attr {"omit" | "same-origin" | "include"} fetch-credentials - Credentials mode for cross-domain calls. */ - // spellchecker:enable class RapiDoc extends HTMLElement { /** * Programmatically load a spec. diff --git a/website/AGENTS.md b/website/AGENTS.md index 02915fa9d8ab..7fd449210958 100644 --- a/website/AGENTS.md +++ b/website/AGENTS.md @@ -128,15 +128,14 @@ Avoid renaming/moving pages unless necessary; better organization rarely justifi ## Spell checking -Spell checking uses **cspell** (`make lint-spellcheck`, config `../cspell.config.jsonc`). Custom dictionaries live in `../locale/en/dictionaries/` (`software-terms.txt`, `integrations.txt`, `idp.txt`, language-specific lists, `people.txt`, `ignore.txt`). Add genuinely new product/service/technology terms to the appropriate dictionary rather than rewording correct prose; never disable the checker for a whole page. +Spell checking uses **cspell** (`make lint-spellcheck`, config `../cspell.config.jsonc`) in typo-only mode (`unknownWords: "report-common-typos"`): it reports only words on the common-misspellings list (always with a suggested fix) and forbidden British spellings. Unknown words — product names, jargon, identifiers — pass silently, so a new integration or technology term needs **no** dictionary entry. -For a genuine one-off that does not belong in a dictionary (a deliberate misspelling in an example, an opaque token), use an inline cspell comment scoped as tightly as possible: +If the checker flags a word whose spelling is intentional (a third-party API member, a deliberate misspelling in an example), either: -- `` in Markdown/MDX, or `// spellchecker:ignore ...` in code — allow specific words for the rest of the file. -- `` / `// spellchecker:disable-next-line` — skip just the following line. -- `` … `` — skip a bounded region (avoid; prefer the narrower forms). +- add it to `../locale/en/dictionaries/overrides.txt` if it may recur across files, or +- use an inline comment scoped as tightly as possible for a true one-off: `` in Markdown/MDX (`// spellchecker:ignore ...` in code), or `` for a single line. -Reach for a dictionary entry first — inline ignores are for the rare case the term is truly local to one page. +Never disable the checker for a whole page. ## Deployment @@ -161,6 +160,6 @@ Every PR gets a Netlify Deploy Preview — use it to verify rendering, links, an | Build runtime | Node ≥ 24, npm ≥ 11 (run via `corepack`) | | Package layout | NPM Workspaces (`docs`, `integrations`, `api`, `docusaurus-theme`) | | Lint / format | ESLint 9 (`@goauthentik/eslint-config`) + Prettier (`@goauthentik/prettier-config`) | -| Spell check | cspell + shared dictionaries | +| Spell check | cspell (typo-only mode) | | Types | TypeScript (`tsc -b`) | | Hosting | Netlify + GitHub Actions | diff --git a/website/docs/developer-docs/frontend/cascade-layers.md b/website/docs/developer-docs/frontend/cascade-layers.md new file mode 100644 index 000000000000..e98fc9c152f2 --- /dev/null +++ b/website/docs/developer-docs/frontend/cascade-layers.md @@ -0,0 +1,87 @@ +--- +title: Cascade layers +sidebar_label: Cascade layers +description: How authentik's web UI orders its CSS with @layer, and where each kind of rule belongs. +--- + +authentik's document CSS is ordered with [cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer). Layers make the ordering explicit, so a rule's precedence comes from the layer it is assigned to rather than from where it happens to be imported or how specific its selector is. + +## The layer order + +One declaration, in `web/src/styles/layers.css`, defines the order for the whole document. It is the first import in every entrypoint: + +```css +@layer reset, vendor, components, theme, mode, brand; +``` + +Lowest precedence first: + +| Layer | Holds | +| ------------ | ------------------------------------------------------------------------------------------------ | +| `reset` | Top-level normalization. Sparse — most of the reset is folded into PatternFly. | +| `vendor` | Vendored PatternFly and the bundled typefaces. Frozen; do not hand-edit. | +| `components` | Document-scope component rules, and the `:root` blocks bridging global tokens to component ones. | +| `theme` | The default design tokens. CSS custom property definitions only. | +| `mode` | Light/dark, high-contrast, and reduced-motion overrides of `theme`. | +| `brand` | Per-deployment branding overrides. | + +`layers.css` is the only place the order is declared. Adding a layer anywhere else creates a second, independent ordering rather than extending this one. + +## Assigning a rule to a layer + +Layers are applied **only in the entrypoint files**, on the import itself: + +```css +/* web/src/styles/interface.global.css */ +@import "#styles/layers.css"; + +@import "#styles/global/vendor/patternfly.css" layer(vendor); +@import "@goauthentik/fonts/faces.css" layer(vendor); +@import "#styles/global/theme/variables.css" layer(theme); +@import "#styles/global/reset/globals.css" layer(reset); +@import "#styles/global/mode/mode.css" layer(mode); +@import "#styles/authentik/components/Placeholder/placeholder.css" layer(components); +``` + +The three entrypoints — `interface.global.css` (Admin and User), `flows.global.css` (Flow), and `static.global.css` (Django templates) — contain only `@import` statements. Individual stylesheets never wrap themselves in `@layer`, which keeps every precedence decision in one reviewable file per bundle. + +Import order still matters _within_ a single layer. Across layers it does not: a later layer always wins, regardless of import order or selector specificity. + +## Two cascade contexts + +authentik has two independent cascades, and layers only govern the first. + +**The document.** Driven by the three `.global.css` entrypoints, ordered by the layer stack above. + +**Each shadow root.** Every component extending `AKElement` (`web/src/elements/Base.ts`) adopts, in order: `shadow/patternfly-base.css`, the component's own styles, `shadow/authentik-base.css`, and then brand custom CSS. These sheets are unlayered. + +The bridge between the two is inheritance. CSS custom properties defined at the document level cross the shadow boundary, so a component reads `--ak-color-primary` without importing anything. What crosses is the _computed_ value — whichever declaration won at the document level. + +This has a consequence worth internalizing: + +> Layers order **selectors**, not properties. + +Once a custom property crosses into a shadow root, the layer it was declared in no longer applies. Only its computed value carries over, and a declaration on `:host` inside the shadow root overrides what was inherited. Declare custom properties in consistent "dictionary" containers — `:root` at the document level, `:host` in shadow roots — so their specificity is predictable at the boundary. + +## Brand customization + +Brands customize appearance two ways, and they land in different places: + +- **Custom properties from the database** are injected into the reserved `brand` layer, the last in the order, so they override theme and mode without needing `!important`. +- **A custom CSS file** is adopted per shadow root, after the component's own styles, so it can reach into `::part()` surfaces. + +Because the custom CSS path is adopted last within its shadow root rather than being layered, existing `!important` declarations in brand CSS keep working. They are usually unnecessary. + +## Adding CSS + +- A new document-level component rule goes in `web/src/styles/authentik/components//`, imported into the relevant entrypoint with `layer(components)`. +- A new design token goes in `web/src/styles/global/theme/`, which is already imported into `layer(theme)`. +- A light/dark or accessibility override goes in `web/src/styles/global/mode/`. +- Anything vendored from PatternFly goes through `web/src/styles/global/vendor/patternfly.css`. + +`web/src/styles/README.md` is the in-repo companion to this page, with the full directory layout and a "what is where" index. + +## References + +- [MDN: cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer) +- [MDN: using shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) diff --git a/website/docs/developer-docs/frontend/css-architecture.md b/website/docs/developer-docs/frontend/css-architecture.md new file mode 100644 index 000000000000..079e78a575f9 --- /dev/null +++ b/website/docs/developer-docs/frontend/css-architecture.md @@ -0,0 +1,121 @@ +--- +title: CSS architecture +sidebar_label: CSS architecture +description: How authentik's runtime CSS and design tokens fit together across the theme package, the document cascade, and Lit components. +--- + +authentik's UI is a customized PatternFly 4 system wrapped in Lit web components. Treat PatternFly as a compatibility layer, not the public API to build on: the stable surface is the `--ak-*` token set. + +## The pieces + +- **`@goauthentik/theme`** owns the design tokens. Token modules are written in TypeScript, compiled by [Styleframe](https://styleframe.dev), and emitted as CSS custom properties plus a [DTCG](https://www.designtokens.org/) document. +- **`@goauthentik/fonts`** ships every typeface and its `@font-face` rules, separately from the tokens because they change on a different cadence. `faces.css` holds the RedHat brand text faces; `icons.css` holds the `pficon` and Font Awesome faces the PatternFly icon classes render glyphs from. +- **`web/src/styles/global/theme/token-bridge.css`** maps the semantic tokens onto the PatternFly variables that existing component CSS already reads. +- **`web/src/styles/`** assembles all of it into the three document bundles and the two per-shadow-root sheets. See [Cascade layers](./cascade-layers.md). +- **`web/src/elements/Base.ts`** adopts the shared sheets into every component's shadow root; `web/src/common/stylesheets.ts` centralizes `CSSStyleSheet` creation. + +## Token tiers + +| Tier | Prefix | Stability | Use | +| --------------- | --------------------- | ------------------ | ------------------------------------------ | +| Primitive | internal or generated | private | palette stops, raw scales, build input | +| Semantic | `--ak-*` | public | custom CSS, component styles, docs | +| Component-local | `--_*` or `--ak-c-*` | private by default | implementation detail inside one component | + +Semantic names express a design decision, not a raw value: + +```css +:root { + --ak-color-primary: oklch(0.518 0.1725 259.3 / 1); /* #0066cc */ + --ak-color-surface: oklch(1 0 0 / 1); /* #ffffff */ + --ak-space-md: 1rem; + --ak-radius-sm: 3px; +} +``` + +Do not promote component property names into the public surface: + +```css +/* Avoid */ +--ak-c-button-primary-background-color-hover-padding-left: 1rem; +``` + +## The token pipeline + +Tokens are authored as typed modules under `packages/theme/src/tokens/`, one per category. Styleframe evaluates them into a variable tree, and the package build emits several shapes of the same data: + +```text +packages/theme/src/tokens/*.ts typed token modules + -> Styleframe variable tree + -> dist/index.css every token, one file + -> dist/{color,typography,...}.css per-category slices + -> dist/dtcg/tokens.json DTCG interchange for design tooling +``` + +Colors are authored as hex and transformed to `oklch()` on the way out, each carrying the original hex in a trailing comment so editors still render a swatch. + +DTCG is interchange data for tooling — Figma sync, validation, generated documentation. It is not a runtime format. The browser consumes CSS custom properties; keep runtime styling decoupled from the DTCG document. + +## The PatternFly bridge + +Most component CSS still reads `--pf-*`. `token-bridge.css` maps the semantic layer onto those names, so existing CSS keeps working while new CSS targets the shorter surface: + +```css +:root, +:host { + --pf-global--primary-color--100: var(--ak-color-primary, var(--pf-global--primary-color--100)); + --pf-global--spacer--md: var(--ak-space-md, var(--pf-global--spacer--md)); +} +``` + +Each PatternFly variable falls back to its own prior value, so the bridge only overrides where a token exists. It is imported in two places — `global/theme/variables.css` for the document, and `shadow/patternfly-base.css` for shadow roots — so both cascades resolve the same way. + +## Shadow DOM API + +Custom properties are the configuration surface: + +```css +ak-flow-executor { + --ak-color-primary: oklch(62% 0.2 260); +} +``` + +`::part()` is for exposed structure, and only where a brand can reasonably style that substructure without coupling to internal DOM: + +```css +ak-flow-executor::part(locale-select) { + display: none; +} +``` + +Slots are for composition, not styling. + +## Accessibility defaults + +New component CSS should support `color-scheme: light dark`, `accent-color`, `prefers-color-scheme`, `prefers-reduced-motion`, `prefers-contrast`, `forced-colors`, and logical properties for right-to-left layouts. + +Prefer semantic tokens that media queries adjust over separate per-variant theme files. Document-level light/dark, contrast, and motion overrides belong in `web/src/styles/global/mode/`. + +## Guardrails + +- Keep the public semantic set at roughly 30 to 60 names until real user needs justify more. +- Do not expose every CSS property as a public token. +- Do not document `--ak-c-*` as stable unless it is intentionally promoted. +- Do not generate tokens from PatternFly variable names. +- Keep part names short and structural: `control`, `label`, `icon`, `content`, `footer`. +- Keep direct custom CSS injection an advanced escape hatch, not the primary theming API. + +## Still to do + +- Migrate component markup off PatternFly incrementally, starting where styling is already mostly custom. +- Move component CSS next to the component it styles, rather than under `web/src/styles/authentik/components/`. +- Generate token reference documentation from the DTCG export instead of maintaining the table by hand. +- Offer live previews of token-level brand customization in the product. + +## External references + +- [Design Tokens Community Group](https://www.designtokens.org/) +- [Design Tokens Format Module](https://www.designtokens.org/tr/2025.10/format/) +- [Styleframe](https://styleframe.dev) +- [PatternFly tokens](https://www.patternfly.org/tokens/about-tokens/) +- [MDN: cascade layers](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer) diff --git a/website/docs/releases/2022/v2022.12.md b/website/docs/releases/2022/v2022.12.md index 3265c97aab20..a54285e40490 100644 --- a/website/docs/releases/2022/v2022.12.md +++ b/website/docs/releases/2022/v2022.12.md @@ -137,7 +137,7 @@ image: - web/admin: rework outpost health - web/elements: add grouping and descriptions to search select - web/elements: make ak-search-select limited in height and scroll -- web/elements: render ak-seach-select dropdown correctly in modals +- web/elements: render ak-search-select dropdown correctly in modals - web/user: fix user settings stuck loading ## Fixed in 2022.12.2 diff --git a/website/docs/releases/2024/v2024.2.md b/website/docs/releases/2024/v2024.2.md index b0de6219fc45..1602ad24096a 100644 --- a/website/docs/releases/2024/v2024.2.md +++ b/website/docs/releases/2024/v2024.2.md @@ -213,7 +213,7 @@ helm upgrade authentik authentik/authentik -f values.yaml --version ^2024.2 - enterprise: add full audit log (#8177) - enterprise: fix system task missing set_status (#8455) - enterprise: rework license summary caching (#8501) -- enterrpise: exclude inactive users from license (#8294) +- enterprise: exclude inactive users from license (#8294) - events: add ASN Database reader (#7793) - events: add better fallback for sanitize_item to ensure everything can be saved as JSON (#7694) - events: add graph for event volume (#7639) diff --git a/website/docs/releases/2024/v2024.4.md b/website/docs/releases/2024/v2024.4.md index f510fdcb7b9a..1af33bddeb08 100644 --- a/website/docs/releases/2024/v2024.4.md +++ b/website/docs/releases/2024/v2024.4.md @@ -3,8 +3,6 @@ title: Release 2024.4 slug: /releases/2024.4 --- - - ## Highlights - **OAuth/SAML as authentication factor** :ak-enterprise Use an external provider as part of an MFA authentication flow, including custom implementations diff --git a/website/docs/releases/2025/v2025.12.md b/website/docs/releases/2025/v2025.12.md index a2a957d084e4..187ac34a4403 100644 --- a/website/docs/releases/2025/v2025.12.md +++ b/website/docs/releases/2025/v2025.12.md @@ -3,8 +3,6 @@ title: Release 2025.12 slug: "/releases/2025.12" --- - - ## Highlights - **Endpoint Devices**: :ak-enterprise :ak-preview Endpoint Devices is a new feature set for Windows, macOS, and Linux devices that enables SSH authentication, local device login, and more, all with authentik credentials. See the [Endpoint Devices docs](../../endpoint-devices/index.mdx) for more details. @@ -459,7 +457,7 @@ helm upgrade authentik authentik/authentik -f values.yaml --version ^2025.12 - providers/proxy: move search path to query instead of runtime parameter (cherry-pick #20662 to version-2025.12) (#20692) - providers/radius: fix message authenticator validation (cherry-pick #21824 to version-2025.12) (#21827) - providers/saml: Fix redirect for saml slo (cherry-pick #21258 to version-2025.12) (#21283) -- proviers/ldap: avoid concurrent header writes in API Client (cherry-pick #21223 to version-2025.12) (#21227) +- providers/ldap: avoid concurrent header writes in API Client (cherry-pick #21223 to version-2025.12) (#21227) - root: do not rely on npm cli for version bump (cherry-pick #20276 to version-2025.12) (#20320) - root: fix compose generation for patch releases release candidates (cherry-pick #21353 to version-2025.12) (#21354) - root: update django to 5.2.14 (cherry-pick #22064 to version-2025.12) (#22065) diff --git a/website/docs/releases/2026/v2026.2.md b/website/docs/releases/2026/v2026.2.md index c8ded4a47d6a..247f751cc08b 100644 --- a/website/docs/releases/2026/v2026.2.md +++ b/website/docs/releases/2026/v2026.2.md @@ -3,8 +3,6 @@ title: Release 2026.2 slug: "/releases/2026.2" --- - - ## Highlights - **Object Lifecycle Management**: :ak-enterprise :ak-preview Admins can now automatically schedule periodic reviews of authentik objects (applications, groups, roles) for compliance and auditing purposes. diff --git a/website/docs/releases/2026/v2026.5.md b/website/docs/releases/2026/v2026.5.md index bc5b2601a45d..9361b397101f 100644 --- a/website/docs/releases/2026/v2026.5.md +++ b/website/docs/releases/2026/v2026.5.md @@ -3,8 +3,6 @@ title: Release 2026.5 slug: "/releases/2026.5" --- - - ## Highlights - **Account Lockdown**: :ak-enterprise A new panic button for compromised accounts that can immediately cut off access, revoke tokens, end sessions, and leave an audit trail. @@ -400,7 +398,7 @@ helm upgrade authentik authentik/authentik -f values.yaml --version ^2026.5 - tasks: threads instead of forks (#19476) - tenants: add option to mark flag as deprecated (#22063) - tenants: fix default schema in initial migration (#21114) -- tenants: fix system flags removeable (cherry-pick #22163 to version-2026.5) (#22182) +- tenants: fix system flags removable (cherry-pick #22163 to version-2026.5) (#22182) - tests: add mixin to launch traefik for tests requiring SSL (#22011) - tests: refactor test harness to split apart a single file (#21391) - translate: Updates for project authentik and language bg_BG (#22112) diff --git a/website/docs/security/cves/CVE-2026-49443.md b/website/docs/security/cves/CVE-2026-49443.md index dec302ea657e..e7235aab37f8 100644 --- a/website/docs/security/cves/CVE-2026-49443.md +++ b/website/docs/security/cves/CVE-2026-49443.md @@ -1,5 +1,3 @@ - - # CVE-2026-49443 / GHSA-xp7f-xjjx-gwm8 ## SourceStage bypass via empty POST diff --git a/website/integrations/chat-communication-collaboration/grommunio/index.md b/website/integrations/chat-communication-collaboration/grommunio/index.md index 12140aa00849..cc8d950c99d0 100644 --- a/website/integrations/chat-communication-collaboration/grommunio/index.md +++ b/website/integrations/chat-communication-collaboration/grommunio/index.md @@ -6,8 +6,6 @@ support_level: community import RedirectURI20265Note from "../../\_redirect-uri-2026-5-note.mdx"; - - ## What is grommunio? > grommunio is an open-source groupware server and collaboration platform offering email, calendar, contacts, tasks, video conferencing, and file sync. It is fully compatible with Microsoft Outlook via MAPI/RPC, EWS, and ActiveSync.