Skip to content

Commit 27856ec

Browse files
authored
brands: add option to set global default flow background (#13079)
* brands: add option to set global default flow background Signed-off-by: Jens Langhammer <[email protected]> * test Signed-off-by: Jens Langhammer <[email protected]> --------- Signed-off-by: Jens Langhammer <[email protected]>
1 parent e4a8c05 commit 27856ec

File tree

12 files changed

+99
-10
lines changed

12 files changed

+99
-10
lines changed

authentik/brands/api.py

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Meta:
5050
"branding_logo",
5151
"branding_favicon",
5252
"branding_custom_css",
53+
"branding_default_flow_background",
5354
"flow_authentication",
5455
"flow_invalidation",
5556
"flow_recovery",
@@ -127,6 +128,7 @@ class BrandViewSet(UsedByMixin, ModelViewSet):
127128
"branding_title",
128129
"branding_logo",
129130
"branding_favicon",
131+
"branding_default_flow_background",
130132
"flow_authentication",
131133
"flow_invalidation",
132134
"flow_recovery",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.0.13 on 2025-03-19 22:54
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("authentik_brands", "0008_brand_branding_custom_css"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="brand",
15+
name="branding_default_flow_background",
16+
field=models.TextField(default="/static/dist/assets/images/flow_background.jpg"),
17+
),
18+
]

authentik/brands/models.py

+9
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class Brand(SerializerModel):
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")
3636
branding_custom_css = models.TextField(default="", blank=True)
37+
branding_default_flow_background = models.TextField(
38+
default="/static/dist/assets/images/flow_background.jpg"
39+
)
3740

3841
flow_authentication = models.ForeignKey(
3942
Flow, null=True, on_delete=models.SET_NULL, related_name="brand_authentication"
@@ -85,6 +88,12 @@ def branding_favicon_url(self) -> str:
8588
return CONFIG.get("web.path", "/")[:-1] + self.branding_favicon
8689
return self.branding_favicon
8790

91+
def branding_default_flow_background_url(self) -> str:
92+
"""Get branding_default_flow_background with the correct prefix"""
93+
if self.branding_default_flow_background.startswith("/static"):
94+
return CONFIG.get("web.path", "/")[:-1] + self.branding_default_flow_background
95+
return self.branding_default_flow_background
96+
8897
@property
8998
def serializer(self) -> Serializer:
9099
from authentik.brands.api import BrandSerializer

authentik/brands/tests.py

+24
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,27 @@ def test_webfinger_oidc(self):
124124
"subject": None,
125125
},
126126
)
127+
128+
def test_branding_url(self):
129+
"""Test branding attributes return correct values"""
130+
brand = create_test_brand()
131+
brand.branding_default_flow_background = "https://goauthentik.io/img/icon.png"
132+
brand.branding_favicon = "https://goauthentik.io/img/icon.png"
133+
brand.branding_logo = "https://goauthentik.io/img/icon.png"
134+
brand.save()
135+
self.assertEqual(
136+
brand.branding_default_flow_background_url(), "https://goauthentik.io/img/icon.png"
137+
)
138+
self.assertJSONEqual(
139+
self.client.get(reverse("authentik_api:brand-current")).content.decode(),
140+
{
141+
"branding_logo": "https://goauthentik.io/img/icon.png",
142+
"branding_favicon": "https://goauthentik.io/img/icon.png",
143+
"branding_title": "authentik",
144+
"branding_custom_css": "",
145+
"matched_domain": brand.domain,
146+
"ui_footer_links": [],
147+
"ui_theme": Themes.AUTOMATIC,
148+
"default_locale": "",
149+
},
150+
)

authentik/core/templates/login/base_full.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{% load i18n %}
55

66
{% block head_before %}
7-
<link rel="prefetch" href="{% static 'dist/assets/images/flow_background.jpg' %}" />
7+
<link rel="prefetch" href="{{ request.brand.branding_default_flow_background_url }}" />
88
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
99
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
1010
{% include "base/header_js.html" %}
@@ -13,7 +13,7 @@
1313
{% block head %}
1414
<style>
1515
:root {
16-
--ak-flow-background: url("{% static 'dist/assets/images/flow_background.jpg' %}");
16+
--ak-flow-background: url("{{ request.brand.branding_default_flow_background_url }}");
1717
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
1818
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
1919
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);

authentik/flows/models.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from uuid import uuid4
77

88
from django.db import models
9+
from django.http import HttpRequest
910
from django.utils.translation import gettext_lazy as _
1011
from model_utils.managers import InheritanceManager
1112
from rest_framework.serializers import BaseSerializer
@@ -178,14 +179,11 @@ class Flow(SerializerModel, PolicyBindingModel):
178179
help_text=_("Required level of authentication and authorization to access a flow."),
179180
)
180181

181-
@property
182-
def background_url(self) -> str:
182+
def background_url(self, request: HttpRequest) -> str:
183183
"""Get the URL to the background image. If the name is /static or starts with http
184184
it is returned as-is"""
185185
if not self.background:
186-
return (
187-
CONFIG.get("web.path", "/")[:-1] + "/static/dist/assets/images/flow_background.jpg"
188-
)
186+
return request.brand.branding_default_flow_background_url()
189187
if self.background.name.startswith("http"):
190188
return self.background.name
191189
if self.background.name.startswith("/static"):

authentik/flows/stage.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def _get_challenge(self, *args, **kwargs) -> Challenge:
184184
flow_info = ContextualFlowInfo(
185185
data={
186186
"title": self.format_title(),
187-
"background": self.executor.flow.background_url,
187+
"background": self.executor.flow.background_url(self.request),
188188
"cancel_url": reverse("authentik_flows:cancel"),
189189
"layout": self.executor.flow.layout,
190190
}

authentik/flows/tests/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ def assertStageResponse(
2727
self.assertIsNotNone(raw_response["component"])
2828
if flow:
2929
self.assertIn("flow_info", raw_response)
30-
self.assertEqual(raw_response["flow_info"]["background"], flow.background_url)
3130
self.assertEqual(
3231
raw_response["flow_info"]["cancel_url"], reverse("authentik_flows:cancel")
3332
)

authentik/flows/tests/test_inspector.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def test(self):
4949
"captcha_stage": None,
5050
"component": "ak-stage-identification",
5151
"flow_info": {
52-
"background": flow.background_url,
52+
"background": "/static/dist/assets/images/flow_background.jpg",
5353
"cancel_url": reverse("authentik_flows:cancel"),
5454
"title": flow.title,
5555
"layout": "stacked",

blueprints/schema.json

+5
Original file line numberDiff line numberDiff line change
@@ -13020,6 +13020,11 @@
1302013020
"type": "string",
1302113021
"title": "Branding custom css"
1302213022
},
13023+
"branding_default_flow_background": {
13024+
"type": "string",
13025+
"minLength": 1,
13026+
"title": "Branding default flow background"
13027+
},
1302313028
"flow_authentication": {
1302413029
"type": "string",
1302513030
"format": "uuid",

schema.yml

+12
Original file line numberDiff line numberDiff line change
@@ -4447,6 +4447,10 @@ paths:
44474447
schema:
44484448
type: string
44494449
format: uuid
4450+
- in: query
4451+
name: branding_default_flow_background
4452+
schema:
4453+
type: string
44504454
- in: query
44514455
name: branding_favicon
44524456
schema:
@@ -41147,6 +41151,8 @@ components:
4114741151
type: string
4114841152
branding_custom_css:
4114941153
type: string
41154+
branding_default_flow_background:
41155+
type: string
4115041156
flow_authentication:
4115141157
type: string
4115241158
format: uuid
@@ -41208,6 +41214,9 @@ components:
4120841214
minLength: 1
4120941215
branding_custom_css:
4121041216
type: string
41217+
branding_default_flow_background:
41218+
type: string
41219+
minLength: 1
4121141220
flow_authentication:
4121241221
type: string
4121341222
format: uuid
@@ -50134,6 +50143,9 @@ components:
5013450143
minLength: 1
5013550144
branding_custom_css:
5013650145
type: string
50146+
branding_default_flow_background:
50147+
type: string
50148+
minLength: 1
5013750149
flow_authentication:
5013850150
type: string
5013950151
format: uuid

web/src/admin/brands/BrandForm.ts

+22
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,28 @@ export class BrandForm extends ModelForm<Brand, string> {
136136
${msg("Icon shown in the browser tab.")}
137137
</p>
138138
</ak-form-element-horizontal>
139+
<ak-form-element-horizontal
140+
label=${msg("Default flow background")}
141+
?required=${true}
142+
name="brandingDefaultFlowBackground"
143+
>
144+
<input
145+
type="text"
146+
value="${first(
147+
this.instance?.brandingDefaultFlowBackground,
148+
"/static/dist/assets/images/flow_background.jpg",
149+
)}"
150+
class="pf-c-form-control pf-m-monospace"
151+
autocomplete="off"
152+
spellcheck="false"
153+
required
154+
/>
155+
<p class="pf-c-form__helper-text">
156+
${msg(
157+
"Default background used during flow execution. Can be overridden per flow.",
158+
)}
159+
</p>
160+
</ak-form-element-horizontal>
139161
<ak-form-element-horizontal
140162
label=${msg("Custom CSS")}
141163
required

0 commit comments

Comments
 (0)