Browse Source

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`.
Leon Adomaitis 5 months ago
parent
commit
4706d9c450
3 changed files with 77 additions and 3 deletions
  1. 26 2
      docs/configuration.md
  2. 20 0
      internal/glance/config.go
  3. 31 1
      internal/glance/glance.go

+ 26 - 2
docs/configuration.md

@@ -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.

+ 20 - 0
internal/glance/config.go

@@ -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)

+ 31 - 1
internal/glance/glance.go

@@ -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)
 }