refactor: split app context and user context (#48)

* refactor: split app context and user context

* tests: fix api tests

* chore: rename dockerfiles

* fix: use correct forwardauth address
This commit is contained in:
Stavros
2025-03-14 20:38:09 +02:00
committed by GitHub
parent 7e39cb0dfe
commit 52f189563b
21 changed files with 320 additions and 143 deletions

View File

@@ -372,7 +372,7 @@ func (api *API) SetupRoutes() {
api.Router.POST("/api/totp", func(c *gin.Context) {
// Create totp struct
var totpReq types.Totp
var totpReq types.TotpRequest
// Bind JSON
err := c.BindJSON(&totpReq)
@@ -461,11 +461,8 @@ func (api *API) SetupRoutes() {
})
})
api.Router.GET("/api/status", func(c *gin.Context) {
log.Debug().Msg("Checking status")
// Get user context
userContext := api.Hooks.UseUserContext(c)
api.Router.GET("/api/app", func(c *gin.Context) {
log.Debug().Msg("Getting app context")
// Get configured providers
configuredProviders := api.Providers.GetConfiguredProviders()
@@ -475,33 +472,48 @@ func (api *API) SetupRoutes() {
configuredProviders = append(configuredProviders, "username")
}
// Fill status struct with data from user context and api config
status := types.Status{
Username: userContext.Username,
IsLoggedIn: userContext.IsLoggedIn,
Oauth: userContext.OAuth,
Provider: userContext.Provider,
// Create app context struct
appContext := types.AppContext{
Status: 200,
Message: "Ok",
ConfiguredProviders: configuredProviders,
DisableContinue: api.Config.DisableContinue,
Title: api.Config.Title,
GenericName: api.Config.GenericName,
TotpPending: userContext.TotpPending,
}
// Return app context
c.JSON(200, appContext)
})
api.Router.GET("/api/user", func(c *gin.Context) {
log.Debug().Msg("Getting user context")
// Get user context
userContext := api.Hooks.UseUserContext(c)
// Create user context response
userContextResponse := types.UserContextResponse{
Status: 200,
IsLoggedIn: userContext.IsLoggedIn,
Username: userContext.Username,
Provider: userContext.Provider,
Oauth: userContext.OAuth,
TotpPending: userContext.TotpPending,
}
// If we are not logged in we set the status to 401 and add the WWW-Authenticate header else we set it to 200
if !userContext.IsLoggedIn {
log.Debug().Msg("Unauthorized")
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
status.Status = 401
status.Message = "Unauthorized"
userContextResponse.Message = "Unauthorized"
} else {
log.Debug().Interface("userContext", userContext).Strs("configuredProviders", configuredProviders).Bool("disableContinue", api.Config.DisableContinue).Msg("Authenticated")
status.Status = 200
status.Message = "Authenticated"
log.Debug().Interface("userContext", userContext).Msg("Authenticated")
userContextResponse.Message = "Authenticated"
}
// Return data
c.JSON(200, status)
// Return user context
c.JSON(200, userContextResponse)
})
api.Router.GET("/api/oauth/url/:provider", func(c *gin.Context) {
@@ -710,7 +722,12 @@ func (api *API) Run() {
log.Info().Str("address", api.Config.Address).Int("port", api.Config.Port).Msg("Starting server")
// Run server
api.Router.Run(fmt.Sprintf("%s:%d", api.Config.Address, api.Config.Port))
err := api.Router.Run(fmt.Sprintf("%s:%d", api.Config.Address, api.Config.Port))
// Check error
if err != nil {
log.Fatal().Err(err).Msg("Failed to start server")
}
}
// handleError logs the error and redirects to the error page (only meant for stuff the user may access does not apply for login paths)

View File

@@ -2,6 +2,7 @@ package api_test
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
@@ -122,9 +123,9 @@ func TestLogin(t *testing.T) {
}
}
// Test status
func TestStatus(t *testing.T) {
t.Log("Testing status")
// Test user context
func TestUserContext(t *testing.T) {
t.Log("Testing user context")
// Get API
api := getAPI(t)
@@ -133,7 +134,7 @@ func TestStatus(t *testing.T) {
recorder := httptest.NewRecorder()
// Create request
req, err := http.NewRequest("GET", "/api/status", nil)
req, err := http.NewRequest("GET", "/api/user", nil)
// Check if there was an error
if err != nil {
@@ -152,11 +153,31 @@ func TestStatus(t *testing.T) {
// Assert
assert.Equal(t, recorder.Code, http.StatusOK)
// Parse the body
body := recorder.Body.String()
// Read the body of the response
body, bodyErr := io.ReadAll(recorder.Body)
if !strings.Contains(body, "user") {
t.Fatalf("Expected user in body")
// Check if there was an error
if bodyErr != nil {
t.Fatalf("Error getting body: %v", bodyErr)
}
// Unmarshal the body into the user struct
type User struct {
Username string `json:"username"`
}
var user User
jsonErr := json.Unmarshal(body, &user)
// Check if there was an error
if jsonErr != nil {
t.Fatalf("Error unmarshalling body: %v", jsonErr)
}
// We should get the username back
if user.Username != "user" {
t.Fatalf("Expected user, got %s", user.Username)
}
}

View File

@@ -55,6 +55,7 @@ type Config struct {
SessionExpiry int `mapstructure:"session-expiry"`
LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"`
Title string `mapstructure:"app-title"`
EnvFile string `mapstructure:"env-file"`
}
// UserContext is the context for the user
@@ -138,22 +139,28 @@ type Proxy struct {
Proxy string `uri:"proxy" binding:"required"`
}
// Status response
type Status struct {
// User Context response is the response for the user context endpoint
type UserContextResponse struct {
Status int `json:"status"`
Message string `json:"message"`
IsLoggedIn bool `json:"isLoggedIn"`
Username string `json:"username"`
Provider string `json:"provider"`
Oauth bool `json:"oauth"`
TotpPending bool `json:"totpPending"`
}
// App Context is the response for the app context endpoint
type AppContext struct {
Status int `json:"status"`
Message string `json:"message"`
IsLoggedIn bool `json:"isLoggedIn"`
Username string `json:"username"`
Provider string `json:"provider"`
Oauth bool `json:"oauth"`
ConfiguredProviders []string `json:"configuredProviders"`
DisableContinue bool `json:"disableContinue"`
Title string `json:"title"`
GenericName string `json:"genericName"`
TotpPending bool `json:"totpPending"`
}
// Totp request
type Totp struct {
// Totp request is the request for the totp endpoint
type TotpRequest struct {
Code string `json:"code"`
}