Add authentication support

Add authentication feature to Glance using external services.

* Add `AuthConfig` struct and `Auth` interface to `internal/glance/config.go`.
* Add `Auth` field to `config` struct and update `newConfigFromYAML` and `isConfigStateValid` functions.
* Add `auth` field to `application` struct in `internal/glance/glance.go`.
* Update `newApplication` function to initialize authentication instance.
* Update `handlePageRequest`, `handlePageContentRequest`, and `handleWidgetRequest` functions to check authentication.
* Update `serveApp` function in `internal/glance/main.go` to pass authentication configuration to `newApplication`.
* Add documentation for authentication configuration in `docs/configuration.md`.
This commit is contained in:
Leon Adomaitis 2025-02-13 03:06:01 +01:00
parent 232cab01f8
commit 4706d9c450
3 changed files with 77 additions and 3 deletions

View file

@ -39,6 +39,7 @@
- [Twitch Top Games](#twitch-top-games)
- [iframe](#iframe)
- [HTML](#html)
- [Authentication](#authentication)
## Preconfigured page
@ -1254,7 +1255,6 @@ Examples:
<div>
<div class="color-highlight size-h3">{{ div (.JSON.Int "usage" | toFloat) 1073741824 | toInt | formatNumber }}GB</div>
<div class="size-h6">USAGE</div>
</div>
</div>
```
</details>
@ -1513,7 +1513,7 @@ Whether to ignore invalid/self-signed certificates.
`same-tab`
Whether to open the link in the same or a new tab.
Whether to open the link in the same tab or a new one.
`alt-status-codes`
@ -2377,3 +2377,27 @@ Example:
```
Note the use of `|` after `source:`, this allows you to insert a multi-line string.
### Authentication
Configure authentication for Glance using external services like Authelia or KeyCloak.
Example:
```yaml
auth:
login-redirect-url: https://auth.example.com/login
is-authenticated-url: https://auth.example.com/is-authenticated
```
#### Properties
| Name | Type | Required | Default |
| ---- | ---- | -------- | ------- |
| login-redirect-url | string | yes | |
| is-authenticated-url | string | yes | |
##### `login-redirect-url`
The URL to redirect the user to when they need to log in.
##### `is-authenticated-url`
The URL to check if the user is authenticated.

View file

@ -49,9 +49,21 @@ type config struct {
FaviconURL string `yaml:"favicon-url"`
} `yaml:"branding"`
Auth AuthConfig `yaml:"auth"`
Pages []page `yaml:"pages"`
}
type AuthConfig struct {
LoginRedirectURL string `yaml:"login-redirect-url"`
IsAuthenticatedURL string `yaml:"is-authenticated-url"`
}
type Auth interface {
IsAuthenticated(macAddress string) (bool, error)
JumpToLogin(macAddress string) error
}
type page struct {
Title string `yaml:"name"`
Slug string `yaml:"slug"`
@ -327,6 +339,14 @@ func isConfigStateValid(config *config) error {
}
}
if config.Auth.LoginRedirectURL == "" {
return fmt.Errorf("auth login-redirect-url is not configured")
}
if config.Auth.IsAuthenticatedURL == "" {
return fmt.Errorf("auth is-authenticated-url is not configured")
}
for i := range config.Pages {
if config.Pages[i].Title == "" {
return fmt.Errorf("page %d has no name", i+1)

View file

@ -24,6 +24,7 @@ type application struct {
Version string
Config config
ParsedThemeStyle template.HTML
auth Auth
slugToPage map[string]*page
widgetByID map[uint64]widget
@ -88,6 +89,11 @@ func newApplication(config *config) (*application, error) {
config.Branding.LogoURL = app.transformUserDefinedAssetPath(config.Branding.LogoURL)
// Initialize authentication instance
app.auth = &authInstance{
config: config.Auth,
}
return app, nil
}
@ -137,13 +143,21 @@ func (a *application) handlePageRequest(w http.ResponseWriter, r *http.Request)
return
}
// Check authentication before serving the page
macAddress := r.Header.Get("X-Mac-Address")
isAuthenticated, err := a.auth.IsAuthenticated(macAddress)
if err != nil || !isAuthenticated {
a.auth.JumpToLogin(macAddress)
return
}
pageData := pageTemplateData{
Page: page,
App: a,
}
var responseBytes bytes.Buffer
err := pageTemplate.Execute(&responseBytes, pageData)
err = pageTemplate.Execute(&responseBytes, pageData)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
@ -161,6 +175,14 @@ func (a *application) handlePageContentRequest(w http.ResponseWriter, r *http.Re
return
}
// Check authentication before serving the page content
macAddress := r.Header.Get("X-Mac-Address")
isAuthenticated, err := a.auth.IsAuthenticated(macAddress)
if err != nil || !isAuthenticated {
a.auth.JumpToLogin(macAddress)
return
}
pageData := pageTemplateData{
Page: page,
}
@ -207,6 +229,14 @@ func (a *application) handleWidgetRequest(w http.ResponseWriter, r *http.Request
return
}
// Check authentication before serving the widget request
macAddress := r.Header.Get("X-Mac-Address")
isAuthenticated, err := a.auth.IsAuthenticated(macAddress)
if err != nil || !isAuthenticated {
a.auth.JumpToLogin(macAddress)
return
}
widget.handleRequest(w, r)
}