Files
web_template/AUTHELIA_INTEGRATION.md
jared f61705afb8
Lint / JS (eslint) (push) Successful in 12s
docs: add Authelia portal integration guide
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:30:39 -04:00

496 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Authelia Portal — LotusGuild Terminal Design System Integration
This document covers everything that had to be **written from scratch** to theme the
Authelia authentication portal using the LotusGuild Terminal Design System v1.2.
It is a companion to `base.css` / `base.js` for future integrations with third-party
applications that use their own component frameworks.
---
## Why a separate file at all?
`base.css` targets exclusively `.lt-*` class names — the design system's own prefix.
Authelia's frontend is a React single-page application built on
**Material UI (MUI) v5**, which generates its own stable class names like
`.MuiCard-root`, `.MuiButton-containedPrimary`, etc.
Bridging the two requires a translation layer: the same design tokens and visual
patterns from `base.css`, re-expressed against MUI's class structure.
Additionally, Authelia v4.x (latest: 4.39.x) does **not** support native CSS
injection — `server.asset_path` only handles `logo.png` and `favicon.ico`.
The CSS is injected via **nginx `sub_filter`** in Nginx Proxy Manager, which rewrites
Authelia's HTML on the fly to include a `<link>` tag before `</head>`.
---
## Deployment architecture
```
Browser
└─► https://auth.lotusguild.org (NPM — LXC 139)
├─ GET /custom.css ──────── served from /data/custom_assets/authelia-custom.css
│ (nginx alias, no upstream request)
└─ GET /* ──────────────── proxied to Authelia (LXC 167 :9091)
HTML response is rewritten by sub_filter:
</head> → <link rel="stylesheet" href="/custom.css"></head>
```
**NPM config changed:** `/data/nginx/proxy_host/29.conf` (auth.lotusguild.org proxy host)
```nginx
# Serve LotusGuild Terminal custom CSS
location = /custom.css {
alias /data/custom_assets/authelia-custom.css;
add_header Content-Type "text/css";
add_header Cache-Control "public, max-age=3600";
}
location / {
...existing headers...
# Prevent upstream gzip so sub_filter can inspect the body
proxy_set_header Accept-Encoding "";
# Inject CSS link before </head>
sub_filter '</head>' '<link rel="stylesheet" href="/custom.css"></head>';
sub_filter_once on;
include /etc/nginx/conf.d/include/proxy.conf;
}
```
> **Warning:** NPM regenerates proxy_host conf files when you edit a proxy host in
> the UI. If host 29 is ever modified through NPM, re-apply the `sub_filter` block
> and the `/custom.css` location manually, or set the config via NPM's
> "Advanced" tab before saving.
**Authelia config changed:** `/etc/authelia/configuration.yml` — added `asset_path`
under `server:` so Authelia serves the resized logo and favicon:
```yaml
server:
address: tcp://0.0.0.0:9091
asset_path: /etc/authelia/assets
```
Assets in `/etc/authelia/assets/`:
- `logo.png` — 256 × 256 PNG resized from Tinker Tickets `assets/images/favicon.png`
- `favicon.ico` — 32 × 32 ICO from the same source
---
## What is NOT in `base.css` and had to be written
The sections below are ordered as they appear in `authelia-custom.css`.
---
### 1. Re-declaring design tokens with `!important`
**In `base.css`:** `:root { ... }` is declared once, without `!important`.
**What had to be added:** The entire `:root` block is repeated in `custom.css` because
Authelia's HTML is a self-contained SPA — `base.css` is never loaded. More critically,
MUI injects its own inline styles and CSS-in-JS rules at very high specificity.
Every property that needs to override MUI requires `!important`. `base.css` never
uses `!important` because it owns its namespace; here we are guests in MUI's DOM.
```css
/* base.css does NOT use !important anywhere */
body { background-color: var(--bg-primary); }
/* custom.css must fight MUI's inline styles */
body { background-color: var(--bg-primary) !important; }
```
The token values themselves are identical to `base.css` — only the override
mechanism is new.
---
### 2. Universal box-sizing reset with `!important`
**In `base.css`:** `*, *::before, *::after { box-sizing: border-box; }` — no flag needed
because `base.css` loads first.
**What had to be added:**
```css
*, *::before, *::after {
box-sizing: border-box !important;
}
```
MUI components set `box-sizing` inline on some elements. Without the flag the
clip-path geometry breaks on inputs and buttons.
---
### 3. MUI typography selectors
**In `base.css`:** Typography rules target `h1h6`, `a`, `p`, `body` — standard HTML
elements. No MUI class names exist in the design system.
**What had to be added:** Authelia renders text almost exclusively through MUI's
`<Typography>` component, which produces elements with `.MuiTypography-root` and
variant classes. Without targeting these, all text inherits MUI's Roboto font and
light grey colour instead of JetBrains Mono and `--text-primary`.
```css
.MuiTypography-root,
.MuiInputBase-input,
.MuiFormLabel-root,
.MuiFormHelperText-root,
label, p, span, div {
font-family: var(--font-mono) !important;
color: var(--text-primary) !important;
}
h1, h2, h3, h4, h5, h6,
.MuiTypography-h5,
.MuiTypography-h6 {
color: var(--accent-orange) !important;
text-shadow: var(--glow-orange) !important;
...
}
```
---
### 4. `.MuiCard-root` / `.MuiPaper-root` — the login card
**In `base.css`:** `.lt-card` implements the terminal card with `clip-path`, border,
background, and the `::before` corner triangle accent.
**What had to be added:** Authelia wraps its login form in a MUI `<Card>` (which
extends `<Paper>`). Neither selector exists in `base.css`. The visual result is
identical to `.lt-card` — the clip-path polygon, cyan border, box-glow, and
corner triangle are all copied — but they target different class names and require
`!important` throughout to override MUI's elevation shadows and `border-radius: 4px`.
```css
.MuiCard-root,
.MuiPaper-root {
background: var(--bg-card) !important;
border: 1px solid var(--border-color) !important;
border-radius: 0 !important; /* ← overrides MUI default */
clip-path: polygon(...) !important; /* same geometry as .lt-card */
...
}
.MuiCard-root::before,
.MuiPaper-root::before {
/* same corner triangle as .lt-card::before */
}
```
---
### 5. Logo element — `img[alt="Authelia"]`
**In `base.css`:** No equivalent. The design system has no logo slot component.
**What had to be added:** Authelia renders the portal logo as
`<img alt="Authelia" src="./static/media/logo.png">`. This selector targets it
specifically to apply the cyan drop-shadow and orange hover glow. Without it the
logo renders without any terminal aesthetic treatment.
```css
img[alt="Authelia"] {
filter: drop-shadow(0 0 12px rgba(0,212,255,0.4)) !important;
transition: filter 0.25s ease !important;
}
img[alt="Authelia"]:hover {
filter: drop-shadow(0 0 18px rgba(255,107,0,0.5)) !important;
}
```
---
### 6. MUI outlined input family
**In `base.css`:** `.lt-input`, `.lt-select`, `.lt-textarea` — custom elements with
`clip-path`, terminal background, and cyan focus ring.
**What had to be added:** MUI's outlined text field is a composition of three
separate elements that each need individual targeting:
| MUI class | What it controls | Equivalent in `base.css` |
|-----------|-----------------|--------------------------|
| `.MuiOutlinedInput-root` | Outer wrapper — background, clip-path | `.lt-input` outer |
| `.MuiOutlinedInput-notchedOutline` | The SVG border element | `.lt-input` border |
| `.MuiInputBase-input` | The actual `<input>` inside | `.lt-input` inner text |
| `.MuiInputLabel-root` | Floating label | `.lt-label` |
| `.MuiFormHelperText-root` | Error / hint text below field | `.lt-form-hint` |
MUI splits the border rendering into a separate SVG `<fieldset>` element
(`.MuiOutlinedInput-notchedOutline`), which requires its own border-color override.
`base.css` has no equivalent splitting since `.lt-input` is a single element with a
CSS border.
The `.Mui-focused` and `.Mui-error` state classes also need explicit overrides because
MUI applies its own colour through CSS-in-JS with high specificity.
```css
.MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline {
border-color: var(--accent-cyan) !important;
box-shadow: var(--box-glow-cyan) !important;
}
.MuiFormHelperText-root.Mui-error {
color: var(--accent-red) !important;
text-shadow: var(--glow-red) !important;
}
```
Additionally, `caret-color` is set explicitly on the input because MUI does not
expose this through its theme — the blinking text cursor would otherwise be white.
---
### 7. MUI button family
**In `base.css`:** `.lt-btn`, `.lt-btn-primary`, `.lt-btn-danger`, `.lt-btn-ghost`
all using `clip-path` hexagon cuts, transparent backgrounds, and border+glow styling.
**What had to be added:** MUI uses four separate button variant classes. The primary
(sign-in) button is `.MuiButton-containedPrimary` which by default renders as a
solid filled rectangle. The entire button appearance — transparent background,
border, clip-path, text-transform, letter-spacing — has to be explicitly overridden
because MUI's `contained` variant applies an opaque `background-color` inline.
```css
/* MUI default: background-color: #1976d2; border-radius: 4px; box-shadow: ... */
.MuiButton-containedPrimary {
background: transparent !important; /* fight the inline fill */
border: 1px solid var(--accent-orange-border) !important;
border-radius: 0 !important;
clip-path: polygon(...) !important;
...
}
```
Text/link buttons (`.MuiButton-text`) are separate from contained buttons in MUI but
map to the same `.lt-btn-ghost` pattern in `base.css`.
---
### 8. MUI checkbox and switch
**In `base.css`:** `.lt-checkbox`, `.lt-switch` — fully custom-drawn using CSS only.
**What had to be added:** Authelia uses MUI's `<Checkbox>` (remember-me) and
`<Switch>` (settings toggles). These are SVG-based components. Because MUI controls
their colour through injected CSS variables, the only reliable override is to target
`.Mui-checked` state classes and use `!important`. There is no equivalent split
between `track` and `thumb` in `base.css`.
```css
.MuiSwitch-switchBase.Mui-checked { color: var(--accent-cyan) !important; }
.MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track {
background-color: var(--accent-cyan) !important;
opacity: 0.5 !important;
}
```
---
### 9. MUI alert / notification banners
**In `base.css`:** `.lt-inline-*` for inline messages (`.lt-inline-success`,
`.lt-inline-error`, etc.) and `.lt-toast-*` for toasts. These use a left border
accent pattern.
**What had to be added:** Authelia uses MUI `<Alert>` for login errors, success
messages, and 2FA prompts. MUI Alert has its own severity system with four variant
classes (`.MuiAlert-standardError`, etc.). Each severity needs explicit colour,
background, and border-color overrides. The `border-left: 3px solid currentColor`
pattern is adapted from `base.css`'s inline message style but applied to MUI's
class names with `clip-path: none !important` to cancel MUI's rounded corners.
---
### 10. MUI select, menu, and popover
**In `base.css`:** `.lt-select` for the select element itself; no dropdown overlay
component exists since the design system uses native `<select>`.
**What had to be added:** MUI's `<Select>` renders its dropdown as a `<Paper>`
element portalled to `<body>` with class `.MuiMenu-paper`. Without targeting this
separately, the dropdown popup inherits the browser default white background and
Roboto font, appearing completely unstyled. `.MuiMenuItem-root` hover and selected
states also need individual rules.
---
### 11. OTP / 2FA digit inputs — `input[type="tel"]`, `input[type="number"]`
**In `base.css`:** No equivalent. The design system has no numeric code input.
**What had to be added:** Authelia's one-time-password entry uses `<input type="tel">`
elements arranged in a row. These need oversized, centered, cyan-glowing characters
with wide letter-spacing to render like a retro code display.
```css
input[type="tel"],
input[type="number"] {
font-size: 1.2rem !important;
font-weight: 700 !important;
color: var(--accent-cyan) !important;
text-shadow: var(--glow-cyan) !important;
letter-spacing: 0.15em !important;
text-align: center !important;
caret-color: var(--accent-orange) !important;
}
```
---
### 12. MUI stepper — 2FA flow breadcrumb
**In `base.css`:** `.lt-stepper`, `.lt-step`, `.lt-step-num` — a custom CSS stepper
for multi-step wizard flows.
**What had to be added:** Authelia renders a MUI `<Stepper>` at the top of the 2FA
flow showing steps like "Username → Password → TOTP". MUI Stepper uses distinct
classes for label, icon, and connector that don't map 1-to-1 to `.lt-step-*`.
The step icon is an SVG circle rendered by React so it can only be tinted via
`color` and `filter: drop-shadow(...)`. The connector line between steps is a
separate `<hr>` with class `.MuiStepConnector-line`.
```css
.MuiStepIcon-root.Mui-active {
color: var(--accent-orange) !important;
filter: drop-shadow(0 0 6px rgba(255,107,0,0.6)) !important;
}
.MuiStepIcon-root.Mui-completed {
color: var(--accent-green) !important;
}
```
---
### 13. MUI linear and circular progress
**In `base.css`:** `.lt-progress` / `.lt-progress-bar` — a standard CSS progress bar
using `width` transitions. `.lt-spinner` — a rotating pseudo-element spinner.
**What had to be added:** Authelia uses MUI `<LinearProgress>` during API calls and
`<CircularProgress>` as the main loading spinner. MUI Linear Progress animates its
own internal elements (`.MuiLinearProgress-bar`) with keyframe animations; it cannot
be replaced with the `.lt-progress` pattern. The cyan-to-orange gradient and
`box-shadow` glow are new here — `base.css`'s progress bar uses a solid
`--accent-orange` fill.
```css
.MuiLinearProgress-bar {
background: linear-gradient(90deg, var(--accent-cyan), var(--accent-orange)) !important;
box-shadow: 0 0 8px rgba(0,212,255,0.5) !important;
}
```
---
### 14. MUI tooltip
**In `base.css`:** `.lt-tooltip` — a CSS-only tooltip shown via `:hover` on a
`[data-tooltip]` attribute.
**What had to be added:** MUI `<Tooltip>` portals its bubble to `<body>` with class
`.MuiTooltip-tooltip`. It is a separate DOM node, not a pseudo-element, so
`.lt-tooltip` styles cannot reach it. A standalone rule sets the terminal background,
monospace font, and squared border.
---
### 15. `*:focus-visible` global ring
**In `base.css`:** Focus rings are defined individually per component
(`.lt-btn:focus-visible`, `.lt-input:focus-visible`, etc.).
**What had to be added:** A single global rule is more practical here because
Authelia contains many interactive MUI elements not known at design time, and
adding per-component rules for every possible focusable element would be brittle.
```css
*:focus-visible {
outline: 2px solid var(--accent-cyan) !important;
outline-offset: 2px !important;
box-shadow: var(--box-glow-cyan) !important;
}
```
---
### 16. Branding footer watermark — `#root::after`
**In `base.css`:** No equivalent. The design system does not add page-level
watermarks.
**What had to be added:** Authelia has no footer. The `#root::after` pseudo-element
attaches a fixed-position text watermark reading
`LOTUSGUILD SECURE PORTAL // AUTH.LOTUSGUILD.ORG` at the bottom of the viewport.
This uses `--text-muted`, `--font-mono`, uppercase, and wide letter-spacing to match
the terminal aesthetic without adding any HTML.
```css
#root::after {
content: 'LOTUSGUILD SECURE PORTAL // AUTH.LOTUSGUILD.ORG';
position: fixed;
bottom: 12px;
left: 50%;
transform: translateX(-50%);
font-size: 0.55rem;
letter-spacing: 0.18em;
color: var(--text-muted);
opacity: 0.6;
pointer-events: none;
}
```
---
## Patterns shared with `base.css` (not new)
The following are **not** documented above because they are direct re-uses of
existing `base.css` patterns, only re-targeted to new selectors:
| Pattern | `base.css` source | Re-used in `custom.css` for |
|---------|------------------|-----------------------------|
| Dot-grid background | `html { background-image: radial-gradient(...) }` | `body` |
| Scanlines overlay | `body::before` | `body::before` (same rule, `!important` added) |
| Corner vignette | `body::after` | `body::after` (same rule, `!important` added) |
| Card clip-path polygon | `.lt-card { clip-path: polygon(...) }` | `.MuiCard-root` |
| Card corner triangle | `.lt-card::before` | `.MuiCard-root::before` |
| Button clip-path hexagon | `.lt-btn { clip-path: polygon(...) }` | `.MuiButton-containedPrimary` |
| Input clip-path cut | `.lt-input { clip-path: polygon(...) }` | `.MuiOutlinedInput-root` |
| Scrollbar | `::-webkit-scrollbar-*` section 45 | identical, `border-radius: 0` added |
| Link colours | `a { color: var(--accent-cyan) }` | `a` (same, `!important` added) |
| Divider | `.lt-divider` border-color | `.MuiDivider-root` |
---
## Notes for future third-party integrations
1. **Always check for `!important` requirements.** CSS-in-JS frameworks (MUI, Emotion,
styled-components) inject styles at runtime with high specificity. Without
`!important`, most design token overrides will lose to injected inline styles.
2. **The split border problem.** MUI (and many component libraries) separate the
border from the element it visually belongs to. Always inspect the actual DOM
before writing selectors — what looks like one element is often three.
3. **Portalled overlays.** Dropdowns, tooltips, and modals in MUI are rendered into
a separate DOM tree (`<body>` portal). Scoped selectors inside a card or form
won't reach them. Target their root classes (`.MuiMenu-paper`, `.MuiTooltip-tooltip`)
directly.
4. **State classes.** MUI uses `.Mui-focused`, `.Mui-checked`, `.Mui-error`,
`.Mui-active`, `.Mui-completed`, `.Mui-disabled` as state modifiers. These are the
equivalent of `:focus`, `:checked`, etc. but applied by JavaScript — pseudo-class
selectors alone will sometimes not fire.
5. **SVG-based components.** Checkboxes, radio buttons, step icons, and spinners are
SVGs injected by React. They cannot be styled with `background`, `border`, or
`clip-path`. Use `color` (SVG `currentColor` inheritance) and `filter:
drop-shadow()` instead.