diff --git a/config.template.yml b/config.template.yml
index b1d6cda6b..2fa3825e9 100644
--- a/config.template.yml
+++ b/config.template.yml
@@ -7,7 +7,7 @@
## Certificates directory specifies where Authelia will load trusted certificates (public portion) from in addition to
## the system certificates store.
## They should be in base64 format, and have one of the following extensions: *.cer, *.crt, *.pem.
-# certificates_directory: /config/certificates
+# certificates_directory: /config/certificates/
## The theme to display: light, dark, grey, auto.
theme: light
@@ -40,6 +40,10 @@ server:
## Must be alphanumeric chars and should not contain any slashes.
path: ""
+ ## Set the path on disk to Authelia assets.
+ ## Useful to allow overriding of specific static assets.
+ # asset_path: /config/assets/
+
## Buffers usually should be configured to be the same value.
## Explanation at https://www.authelia.com/docs/configuration/server.html
## Read buffer size adjusts the server's max incoming request size in bytes.
diff --git a/docs/configuration/server.md b/docs/configuration/server.md
index 1ccae5f6d..ba8cc8d5d 100644
--- a/docs/configuration/server.md
+++ b/docs/configuration/server.md
@@ -87,6 +87,33 @@ server:
path: authelia
```
+### asset_path
+
+
+Authelia by default serves all static assets from an embedded filesystem in the Go binary.
+
+Modifying this setting will allow you to override and serve specific assets for Authelia from a specified path.
+All files that can be overridden are documented below and must be placed in the `asset_path` with a flat file structure.
+
+Example:
+```console
+/config/assets/
+├── favicon.ico
+└── logo.png
+```
+
+|Asset |File name|
+|:-----:|:---------------:|
+|Favicon|favicon.ico |
+|Logo |logo.png |
+
### read_buffer_size
type: integer
@@ -189,3 +216,8 @@ The path to the public certificate for TLS connections. Must be in DER base64/PE
The read and write buffer sizes generally should be the same. This is because when Authelia verifies
if the user is authorized to visit a URL, it also sends back nearly the same size response as the request. However
you're able to tune these individually depending on your needs.
+
+### Asset Overrides
+
+If replacing the Logo for your Authelia portal it is recommended to upload a transparent PNG of your desired logo.
+Authelia will automatically resize the logo to an appropriate size to present in the frontend.
\ No newline at end of file
diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml
index b1d6cda6b..2fa3825e9 100644
--- a/internal/configuration/config.template.yml
+++ b/internal/configuration/config.template.yml
@@ -7,7 +7,7 @@
## Certificates directory specifies where Authelia will load trusted certificates (public portion) from in addition to
## the system certificates store.
## They should be in base64 format, and have one of the following extensions: *.cer, *.crt, *.pem.
-# certificates_directory: /config/certificates
+# certificates_directory: /config/certificates/
## The theme to display: light, dark, grey, auto.
theme: light
@@ -40,6 +40,10 @@ server:
## Must be alphanumeric chars and should not contain any slashes.
path: ""
+ ## Set the path on disk to Authelia assets.
+ ## Useful to allow overriding of specific static assets.
+ # asset_path: /config/assets/
+
## Buffers usually should be configured to be the same value.
## Explanation at https://www.authelia.com/docs/configuration/server.html
## Read buffer size adjusts the server's max incoming request size in bytes.
diff --git a/internal/configuration/schema/server.go b/internal/configuration/schema/server.go
index d07165cb0..b05c448f8 100644
--- a/internal/configuration/schema/server.go
+++ b/internal/configuration/schema/server.go
@@ -5,6 +5,7 @@ type ServerConfiguration struct {
Host string `koanf:"host"`
Port int `koanf:"port"`
Path string `koanf:"path"`
+ AssetPath string `koanf:"asset_path"`
ReadBufferSize int `koanf:"read_buffer_size"`
WriteBufferSize int `koanf:"write_buffer_size"`
EnablePprof bool `koanf:"enable_endpoint_pprof"`
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index b6339ca33..b56819e1c 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -147,6 +147,7 @@ var ValidKeys = []string{
"server.read_buffer_size",
"server.write_buffer_size",
"server.path",
+ "server.asset_path",
"server.enable_pprof",
"server.enable_expvars",
"server.disable_healthcheck",
diff --git a/internal/middlewares/asset_override.go b/internal/middlewares/asset_override.go
new file mode 100644
index 000000000..649ed965b
--- /dev/null
+++ b/internal/middlewares/asset_override.go
@@ -0,0 +1,29 @@
+package middlewares
+
+import (
+ "os"
+ "strings"
+
+ "github.com/valyala/fasthttp"
+
+ "github.com/authelia/authelia/v4/internal/utils"
+)
+
+// AssetOverrideMiddleware allows overriding and serving of specific embedded assets from disk.
+func AssetOverrideMiddleware(assetPath string, next fasthttp.RequestHandler) fasthttp.RequestHandler {
+ return func(ctx *fasthttp.RequestCtx) {
+ uri := string(ctx.RequestURI())
+ file := uri[strings.LastIndex(uri, "/")+1:]
+
+ if assetPath != "" && utils.IsStringInSlice(file, validOverrideAssets) {
+ _, err := os.Stat(assetPath + file)
+ if err != nil {
+ next(ctx)
+ } else {
+ fasthttp.FSHandler(assetPath, strings.Count(uri, "/")-1)(ctx)
+ }
+ } else {
+ next(ctx)
+ }
+ }
+}
diff --git a/internal/middlewares/const.go b/internal/middlewares/const.go
index 1d404a708..27c157eb9 100644
--- a/internal/middlewares/const.go
+++ b/internal/middlewares/const.go
@@ -29,3 +29,4 @@ const (
)
var protoHostSeparator = []byte("://")
+var validOverrideAssets = []string{"favicon.ico", "logo.png"}
diff --git a/internal/server/const.go b/internal/server/const.go
index 9251aa95e..a01473383 100644
--- a/internal/server/const.go
+++ b/internal/server/const.go
@@ -1,9 +1,14 @@
package server
-const embeddedAssets = "public_html/"
-const swaggerAssets = embeddedAssets + "api/"
-const apiFile = "openapi.yml"
-const indexFile = "index.html"
+const (
+ embeddedAssets = "public_html/"
+ swaggerAssets = embeddedAssets + "api/"
+ apiFile = "openapi.yml"
+ indexFile = "index.html"
+ logoFile = "logo.png"
+)
+
+var rootFiles = []string{"favicon.ico", "manifest.json", "robots.txt"}
const dev = "dev"
diff --git a/internal/server/server.go b/internal/server/server.go
index 907ecb0ce..1f99bfef2 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -32,13 +32,12 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
embeddedPath, _ := fs.Sub(assets, "public_html")
embeddedFS := fasthttpadaptor.NewFastHTTPHandler(http.FileServer(http.FS(embeddedPath)))
- rootFiles := []string{"favicon.ico", "manifest.json", "robots.txt"}
https := configuration.Server.TLS.Key != "" && configuration.Server.TLS.Certificate != ""
- serveIndexHandler := ServeTemplatedFile(embeddedAssets, indexFile, rememberMe, resetPassword, configuration.Session.Name, configuration.Theme, https)
- serveSwaggerHandler := ServeTemplatedFile(swaggerAssets, indexFile, rememberMe, resetPassword, configuration.Session.Name, configuration.Theme, https)
- serveSwaggerAPIHandler := ServeTemplatedFile(swaggerAssets, apiFile, rememberMe, resetPassword, configuration.Session.Name, configuration.Theme, https)
+ serveIndexHandler := ServeTemplatedFile(embeddedAssets, indexFile, configuration.Server.AssetPath, rememberMe, resetPassword, configuration.Session.Name, configuration.Theme, https)
+ serveSwaggerHandler := ServeTemplatedFile(swaggerAssets, indexFile, configuration.Server.AssetPath, rememberMe, resetPassword, configuration.Session.Name, configuration.Theme, https)
+ serveSwaggerAPIHandler := ServeTemplatedFile(swaggerAssets, apiFile, configuration.Server.AssetPath, rememberMe, resetPassword, configuration.Session.Name, configuration.Theme, https)
r := router.New()
r.GET("/", serveIndexHandler)
@@ -48,10 +47,10 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
r.GET("/api/"+apiFile, serveSwaggerAPIHandler)
for _, f := range rootFiles {
- r.GET("/"+f, embeddedFS)
+ r.GET("/"+f, middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
}
- r.GET("/static/{filepath:*}", embeddedFS)
+ r.GET("/static/{filepath:*}", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
r.ANY("/api/{filepath:*}", embeddedFS)
r.GET("/api/health", autheliaMiddleware(handlers.HealthGet))
diff --git a/internal/server/template.go b/internal/server/template.go
index a6e658452..6b6a39163 100644
--- a/internal/server/template.go
+++ b/internal/server/template.go
@@ -16,7 +16,7 @@ import (
// ServeTemplatedFile serves a templated version of a specified file,
// this is utilised to pass information between the backend and frontend
// and generate a nonce to support a restrictive CSP while using material-ui.
-func ServeTemplatedFile(publicDir, file, rememberMe, resetPassword, session, theme string, https bool) fasthttp.RequestHandler {
+func ServeTemplatedFile(publicDir, file, assetPath, rememberMe, resetPassword, session, theme string, https bool) fasthttp.RequestHandler {
logger := logging.Logger()
f, err := assets.Open(publicDir + file)
@@ -40,6 +40,14 @@ func ServeTemplatedFile(publicDir, file, rememberMe, resetPassword, session, the
base = baseURL.(string)
}
+ logoOverride := "false"
+
+ if assetPath != "" {
+ if _, err := os.Stat(assetPath + logoFile); err == nil {
+ logoOverride = "true"
+ }
+ }
+
var scheme = "https"
if !https {
@@ -71,7 +79,7 @@ func ServeTemplatedFile(publicDir, file, rememberMe, resetPassword, session, the
ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("default-src 'self' ; object-src 'none'; style-src 'self' 'nonce-%s'", nonce))
}
- err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, BaseURL, CSPNonce, RememberMe, ResetPassword, Session, Theme string }{Base: base, BaseURL: baseURL, CSPNonce: nonce, RememberMe: rememberMe, ResetPassword: resetPassword, Session: session, Theme: theme})
+ err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, BaseURL, CSPNonce, LogoOverride, RememberMe, ResetPassword, Session, Theme string }{Base: base, BaseURL: baseURL, CSPNonce: nonce, LogoOverride: logoOverride, RememberMe: rememberMe, ResetPassword: resetPassword, Session: session, Theme: theme})
if err != nil {
ctx.Error("an error occurred", 503)
logger.Errorf("Unable to execute template: %v", err)
diff --git a/internal/suites/Traefik/configuration.yml b/internal/suites/Traefik/configuration.yml
index 046c181dc..d76301826 100644
--- a/internal/suites/Traefik/configuration.yml
+++ b/internal/suites/Traefik/configuration.yml
@@ -7,6 +7,7 @@ jwt_secret: unsecure_secret
server:
port: 9091
+ asset_path: /config/assets/
tls:
certificate: /config/ssl/cert.pem
key: /config/ssl/key.pem
diff --git a/internal/suites/Traefik2/configuration.yml b/internal/suites/Traefik2/configuration.yml
index c2427fdc7..6390464aa 100644
--- a/internal/suites/Traefik2/configuration.yml
+++ b/internal/suites/Traefik2/configuration.yml
@@ -7,6 +7,7 @@ jwt_secret: unsecure_secret
server:
port: 9091
+ asset_path: /config/assets/
tls:
certificate: /config/ssl/cert.pem
key: /config/ssl/key.pem
@@ -46,6 +47,9 @@ access_control:
- domain: "singlefactor.example.com"
policy: one_factor
+ntp:
+ version: 3
+
notifier:
smtp:
host: smtp
diff --git a/internal/suites/Traefik2/docker-compose.yml b/internal/suites/Traefik2/docker-compose.yml
index 3d2c16e79..4cdf56953 100644
--- a/internal/suites/Traefik2/docker-compose.yml
+++ b/internal/suites/Traefik2/docker-compose.yml
@@ -5,5 +5,7 @@ services:
volumes:
- './Traefik2/configuration.yml:/config/configuration.yml:ro'
- './Traefik2/users.yml:/config/users.yml'
+ - './Traefik2/favicon.ico:/config/assets/favicon.ico'
+ - './Traefik2/logo.png:/config/assets/logo.png'
- './common/ssl:/config/ssl:ro'
...
diff --git a/internal/suites/Traefik2/favicon.ico b/internal/suites/Traefik2/favicon.ico
new file mode 100644
index 000000000..46d3d0e2a
Binary files /dev/null and b/internal/suites/Traefik2/favicon.ico differ
diff --git a/internal/suites/Traefik2/logo.png b/internal/suites/Traefik2/logo.png
new file mode 100644
index 000000000..0b2e35e47
Binary files /dev/null and b/internal/suites/Traefik2/logo.png differ
diff --git a/web/.env.development b/web/.env.development
index 80deba03d..7e7d77ed4 100644
--- a/web/.env.development
+++ b/web/.env.development
@@ -1,4 +1,5 @@
VITE_HMR_PORT=8080
+VITE_LOGO_OVERRIDE=false
VITE_PUBLIC_URL=""
VITE_REMEMBER_ME=true
VITE_RESET_PASSWORD=true
diff --git a/web/.env.production b/web/.env.production
index b9208b2f2..9c94810ec 100644
--- a/web/.env.production
+++ b/web/.env.production
@@ -1,3 +1,4 @@
+VITE_LOGO_OVERRIDE={{.LogoOverride}}
VITE_PUBLIC_URL={{.Base}}
VITE_REMEMBER_ME={{.RememberMe}}
VITE_RESET_PASSWORD={{.ResetPassword}}
diff --git a/web/index.html b/web/index.html
index b51a1f864..e6858e07d 100644
--- a/web/index.html
+++ b/web/index.html
@@ -13,7 +13,7 @@
Login - Authelia
-
+
diff --git a/web/package.json b/web/package.json
index 7f52c6f5a..2606e0228 100644
--- a/web/package.json
+++ b/web/package.json
@@ -56,7 +56,7 @@
"sourcemap": true
}
],
- "^.+\\.(css|svg)$": "jest-transform-stub"
+ "^.+\\.(css|png|svg)$": "jest-transform-stub"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$"
diff --git a/web/src/layouts/LoginLayout.tsx b/web/src/layouts/LoginLayout.tsx
index 376880ab1..a47e98793 100644
--- a/web/src/layouts/LoginLayout.tsx
+++ b/web/src/layouts/LoginLayout.tsx
@@ -4,6 +4,7 @@ import { Grid, makeStyles, Container, Typography, Link } from "@material-ui/core
import { grey } from "@material-ui/core/colors";
import { ReactComponent as UserSvg } from "@assets/images/user.svg";
+import { getLogoOverride } from "@utils/Configuration";
export interface Props {
id?: string;
@@ -14,12 +15,17 @@ export interface Props {
const LoginLayout = function (props: Props) {
const style = useStyles();
+ const logo = getLogoOverride() ? (
+
+ ) : (
+
+ );
return (
-
+ {logo}
{props.title ? (
diff --git a/web/src/utils/Configuration.ts b/web/src/utils/Configuration.ts
index a84dc308a..73aab612e 100644
--- a/web/src/utils/Configuration.ts
+++ b/web/src/utils/Configuration.ts
@@ -7,6 +7,10 @@ export function getEmbeddedVariable(variableName: string) {
return value;
}
+export function getLogoOverride() {
+ return getEmbeddedVariable("logooverride") === "true";
+}
+
export function getRememberMe() {
return getEmbeddedVariable("rememberme") === "true";
}