Skip to content

Commit f37e1ca

Browse files
authored
brands: migrate custom CSS to brands (#13172)
* brands: migrate custom CSS to brands Signed-off-by: Jens Langhammer <[email protected]> * fix missing default Signed-off-by: Jens Langhammer <[email protected]> * fix tests Signed-off-by: Jens Langhammer <[email protected]> * simpler migration Signed-off-by: Jens Langhammer <[email protected]> * add css to brand form Signed-off-by: Jens Langhammer <[email protected]> * fix Signed-off-by: Jens Langhammer <[email protected]> --------- Signed-off-by: Jens Langhammer <[email protected]>
1 parent 70b1f05 commit f37e1ca

File tree

16 files changed

+94
-54
lines changed

16 files changed

+94
-54
lines changed

authentik/brands/api.py

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class Meta:
4949
"branding_title",
5050
"branding_logo",
5151
"branding_favicon",
52+
"branding_custom_css",
5253
"flow_authentication",
5354
"flow_invalidation",
5455
"flow_recovery",
@@ -86,6 +87,7 @@ class CurrentBrandSerializer(PassiveSerializer):
8687
branding_title = CharField()
8788
branding_logo = CharField(source="branding_logo_url")
8889
branding_favicon = CharField(source="branding_favicon_url")
90+
branding_custom_css = CharField()
8991
ui_footer_links = ListField(
9092
child=FooterLinkSerializer(),
9193
read_only=True,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Generated by Django 5.0.12 on 2025-02-22 01:51
2+
3+
from pathlib import Path
4+
from django.db import migrations, models
5+
from django.apps.registry import Apps
6+
7+
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
8+
9+
10+
def migrate_custom_css(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
11+
Brand = apps.get_model("authentik_brands", "brand")
12+
13+
db_alias = schema_editor.connection.alias
14+
15+
path = Path("/web/dist/custom.css")
16+
if not path.exists():
17+
return
18+
with path.read_text() as css:
19+
Brand.objects.using(db_alias).update(branding_custom_css=css)
20+
21+
22+
class Migration(migrations.Migration):
23+
24+
dependencies = [
25+
("authentik_brands", "0007_brand_default_application"),
26+
]
27+
28+
operations = [
29+
migrations.AddField(
30+
model_name="brand",
31+
name="branding_custom_css",
32+
field=models.TextField(blank=True, default=""),
33+
),
34+
migrations.RunPython(migrate_custom_css),
35+
]

authentik/brands/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class Brand(SerializerModel):
3333

3434
branding_logo = models.TextField(default="/static/dist/assets/icons/icon_left_brand.svg")
3535
branding_favicon = models.TextField(default="/static/dist/assets/icons/icon.png")
36+
branding_custom_css = models.TextField(default="", blank=True)
3637

3738
flow_authentication = models.ForeignKey(
3839
Flow, null=True, on_delete=models.SET_NULL, related_name="brand_authentication"

authentik/brands/tests.py

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def test_current_brand(self):
2424
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
2525
"branding_favicon": "/static/dist/assets/icons/icon.png",
2626
"branding_title": "authentik",
27+
"branding_custom_css": "",
2728
"matched_domain": brand.domain,
2829
"ui_footer_links": [],
2930
"ui_theme": Themes.AUTOMATIC,
@@ -43,6 +44,7 @@ def test_brand_subdomain(self):
4344
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
4445
"branding_favicon": "/static/dist/assets/icons/icon.png",
4546
"branding_title": "custom",
47+
"branding_custom_css": "",
4648
"matched_domain": "bar.baz",
4749
"ui_footer_links": [],
4850
"ui_theme": Themes.AUTOMATIC,
@@ -59,6 +61,7 @@ def test_fallback(self):
5961
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
6062
"branding_favicon": "/static/dist/assets/icons/icon.png",
6163
"branding_title": "authentik",
64+
"branding_custom_css": "",
6265
"matched_domain": "fallback",
6366
"ui_footer_links": [],
6467
"ui_theme": Themes.AUTOMATIC,

authentik/core/templates/base/skeleton.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
{% block head_before %}
1717
{% endblock %}
1818
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
19-
<link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject>
19+
<style>{{ brand.branding_custom_css }}</style>
2020
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
2121
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
2222
{% block head %}

blueprints/schema.json

+4
Original file line numberDiff line numberDiff line change
@@ -13016,6 +13016,10 @@
1301613016
"minLength": 1,
1301713017
"title": "Branding favicon"
1301813018
},
13019+
"branding_custom_css": {
13020+
"type": "string",
13021+
"title": "Branding custom css"
13022+
},
1301913023
"flow_authentication": {
1302013024
"type": "string",
1302113025
"format": "uuid",

internal/outpost/proxyv2/templates/error.html

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
<link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png">
99
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css">
1010
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css">
11-
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/custom.css">
1211
<link rel="prefetch" href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg" />
1312
<style>
1413
.pf-c-background-image::before {

schema.yml

+9
Original file line numberDiff line numberDiff line change
@@ -41145,6 +41145,8 @@ components:
4114541145
type: string
4114641146
branding_favicon:
4114741147
type: string
41148+
branding_custom_css:
41149+
type: string
4114841150
flow_authentication:
4114941151
type: string
4115041152
format: uuid
@@ -41204,6 +41206,8 @@ components:
4120441206
branding_favicon:
4120541207
type: string
4120641208
minLength: 1
41209+
branding_custom_css:
41210+
type: string
4120741211
flow_authentication:
4120841212
type: string
4120941213
format: uuid
@@ -42096,6 +42100,8 @@ components:
4209642100
type: string
4209742101
branding_favicon:
4209842102
type: string
42103+
branding_custom_css:
42104+
type: string
4209942105
ui_footer_links:
4210042106
type: array
4210142107
items:
@@ -42122,6 +42128,7 @@ components:
4212242128
type: string
4212342129
readOnly: true
4212442130
required:
42131+
- branding_custom_css
4212542132
- branding_favicon
4212642133
- branding_logo
4212742134
- branding_title
@@ -50125,6 +50132,8 @@ components:
5012550132
branding_favicon:
5012650133
type: string
5012750134
minLength: 1
50135+
branding_custom_css:
50136+
type: string
5012850137
flow_authentication:
5012950138
type: string
5013050139
format: uuid

web/package-lock.json

+5-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@goauthentik/web",
33
"version": "0.0.0",
44
"dependencies": {
5+
"@codemirror/lang-css": "^6.3.1",
56
"@codemirror/lang-html": "^6.4.9",
67
"@codemirror/lang-javascript": "^6.2.2",
78
"@codemirror/lang-python": "^6.1.6",
@@ -182,7 +183,6 @@
182183
"./dist/enterprise/**",
183184
"./dist/poly-*.js",
184185
"./dist/poly-*.js.map",
185-
"./dist/custom.css",
186186
"./dist/theme-dark.css",
187187
"./dist/one-dark.css",
188188
"./dist/patternfly.min.css"

web/scripts/build-web.mjs

-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const definitions = Object.fromEntries(
5252
const assetsFileMappings = [
5353
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
5454
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
55-
["src/custom.css", "."],
5655
["src/common/styles/**", "."],
5756
["src/assets/images/**", "./assets/images"],
5857
["./icons/*", "./assets/icons"],

web/src/admin/brands/BrandForm.ts

+22-17
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,7 @@ export class BrandForm extends ModelForm<Brand, string> {
5151
}
5252

5353
renderForm(): TemplateResult {
54-
return html` <ak-form-element-horizontal
55-
label=${msg("Domain")}
56-
?required=${true}
57-
name="domain"
58-
>
54+
return html` <ak-form-element-horizontal label=${msg("Domain")} required name="domain">
5955
<input
6056
type="text"
6157
value="${first(this.instance?.domain, window.location.host)}"
@@ -90,14 +86,10 @@ export class BrandForm extends ModelForm<Brand, string> {
9086
</p>
9187
</ak-form-element-horizontal>
9288
93-
<ak-form-group .expanded=${true}>
89+
<ak-form-group>
9490
<span slot="header"> ${msg("Branding settings")} </span>
9591
<div slot="body" class="pf-c-form">
96-
<ak-form-element-horizontal
97-
label=${msg("Title")}
98-
?required=${true}
99-
name="brandingTitle"
100-
>
92+
<ak-form-element-horizontal label=${msg("Title")} required name="brandingTitle">
10193
<input
10294
type="text"
10395
value="${first(
@@ -111,11 +103,7 @@ export class BrandForm extends ModelForm<Brand, string> {
111103
${msg("Branding shown in page title and several other places.")}
112104
</p>
113105
</ak-form-element-horizontal>
114-
<ak-form-element-horizontal
115-
label=${msg("Logo")}
116-
?required=${true}
117-
name="brandingLogo"
118-
>
106+
<ak-form-element-horizontal label=${msg("Logo")} required name="brandingLogo">
119107
<input
120108
type="text"
121109
value="${first(this.instance?.brandingLogo, DefaultBrand.brandingLogo)}"
@@ -130,7 +118,7 @@ export class BrandForm extends ModelForm<Brand, string> {
130118
</ak-form-element-horizontal>
131119
<ak-form-element-horizontal
132120
label=${msg("Favicon")}
133-
?required=${true}
121+
required
134122
name="brandingFavicon"
135123
>
136124
<input
@@ -148,6 +136,23 @@ export class BrandForm extends ModelForm<Brand, string> {
148136
${msg("Icon shown in the browser tab.")}
149137
</p>
150138
</ak-form-element-horizontal>
139+
<ak-form-element-horizontal
140+
label=${msg("Custom CSS")}
141+
required
142+
name="brandingCustomCss"
143+
>
144+
<ak-codemirror
145+
mode=${CodeMirrorMode.CSS}
146+
value="${first(
147+
this.instance?.brandingCustomCss,
148+
DefaultBrand.brandingCustomCss,
149+
)}"
150+
>
151+
</ak-codemirror>
152+
<p class="pf-c-form__helper-text">
153+
${msg("Custom CSS to apply to pages when this brand is active.")}
154+
</p>
155+
</ak-form-element-horizontal>
151156
</div>
152157
</ak-form-group>
153158

web/src/custom.css

-1
This file was deleted.

web/src/elements/Base.ts

+6-29
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,6 @@ type AkInterface = HTMLElement & {
2424
export const rootInterface = <T extends AkInterface>(): T | undefined =>
2525
(document.body.querySelector("[data-ak-interface-root]") as T) ?? undefined;
2626

27-
let css: Promise<string[]> | undefined;
28-
function fetchCustomCSS(): Promise<string[]> {
29-
if (!css) {
30-
css = Promise.all(
31-
Array.of(...document.head.querySelectorAll<HTMLLinkElement>("link[data-inject]")).map(
32-
(link) => {
33-
return fetch(link.href)
34-
.then((res) => {
35-
return res.text();
36-
})
37-
.finally(() => {
38-
return "";
39-
});
40-
},
41-
),
42-
);
43-
}
44-
return css;
45-
}
46-
4727
export const QUERY_MEDIA_COLOR_LIGHT = "(prefers-color-scheme: light)";
4828

4929
// Ensure themes are converted to a static instance of CSS Stylesheet, otherwise the
@@ -103,15 +83,12 @@ export class AKElement extends LitElement {
10383
}
10484

10585
async _initCustomCSS(root: DocumentOrShadowRoot): Promise<void> {
106-
const sheets = await fetchCustomCSS();
107-
sheets.map((css) => {
108-
if (css === "") {
109-
return;
110-
}
111-
new CSSStyleSheet().replace(css).then((sheet) => {
112-
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
113-
});
114-
});
86+
const brand = globalAK().brand;
87+
if (!brand) {
88+
return;
89+
}
90+
const sheet = await new CSSStyleSheet().replace(brand.brandingCustomCss);
91+
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
11592
}
11693

11794
_applyTheme(root: DocumentOrShadowRoot, theme?: UiThemeEnum): void {

web/src/elements/CodeMirror.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
2+
import { css as cssLang } from "@codemirror/lang-css";
23
import { html as htmlLang } from "@codemirror/lang-html";
34
import { javascript } from "@codemirror/lang-javascript";
45
import { python } from "@codemirror/lang-python";
@@ -27,6 +28,7 @@ export enum CodeMirrorMode {
2728
XML = "xml",
2829
JavaScript = "javascript",
2930
HTML = "html",
31+
CSS = "css",
3032
Python = "python",
3133
YAML = "yaml",
3234
}
@@ -147,6 +149,8 @@ export class CodeMirrorTextarea<T> extends AKElement {
147149
return htmlLang();
148150
case CodeMirrorMode.Python:
149151
return python();
152+
case CodeMirrorMode.CSS:
153+
return cssLang();
150154
case CodeMirrorMode.YAML:
151155
return new LanguageSupport(StreamLanguage.define(yamlMode.yaml));
152156
}

web/src/elements/sidebar/SidebarBrand.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const DefaultBrand: CurrentBrand = {
2222
brandingLogo: "/static/dist/assets/icons/icon_left_brand.svg",
2323
brandingFavicon: "/static/dist/assets/icons/icon.png",
2424
brandingTitle: "authentik",
25+
brandingCustomCss: "",
2526
uiFooterLinks: [],
2627
uiTheme: UiThemeEnum.Automatic,
2728
matchedDomain: "",

0 commit comments

Comments
 (0)