Compare commits

..

4 Commits

Author SHA1 Message Date
Stavros
4275aa684a refactor: use current time to set new expiry 2025-12-24 14:52:44 +02:00
Stavros
d1c41a116b feat: refresh session cookie when session is active 2025-12-23 23:01:07 +02:00
Stavros
ef25872fc3 feat: add support for Envoy proxy (#538)
* feat: add support for 'envoy' proxy in proxyHandler validation

* refactor: simplify proxy route setup by consolidating envoy handling

* feat(proxy): add method validation for proxy authentication

* fix(proxy): reorder method validation for proxy authentication

* refactor: use a slice to check for supported proxies

---------

Co-authored-by: pushpinderbal <me@s1ngh.ca>
Co-authored-by: Pushpinder Singh <53684951+pushpinderbal@users.noreply.github.com>
Co-authored-by: Pushpinder Singh <pushpinder.singh@arcticwolf.com>
2025-12-22 22:28:34 +02:00
Stavros
03ed18343e feat: unified config (#533)
* chore: add yaml config ref

* feat: add initial implementation of a traefik like cli

* refactor: remove dependency on traefik

* chore: update example env

* refactor: update build

* chore: remove unused code

* fix: fix translations not loading

* feat: add experimental config file support

* chore: mod tidy

* fix: review comments

* refactor: move tinyauth to separate package

* chore: add quotes to all env variables

* chore: resolve go mod and sum conflicts

* chore: go mod tidy

* fix: review comments
2025-12-22 22:13:40 +02:00
5 changed files with 61 additions and 9 deletions

5
.gitignore vendored
View File

@@ -33,4 +33,7 @@
# binary out # binary out
/tinyauth.db /tinyauth.db
/resources /resources
# debug files
__debug_*

View File

@@ -3,6 +3,7 @@ package controller
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"slices"
"strings" "strings"
"tinyauth/internal/config" "tinyauth/internal/config"
"tinyauth/internal/service" "tinyauth/internal/service"
@@ -13,6 +14,8 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
var SupportedProxies = []string{"nginx", "traefik", "caddy", "envoy"}
type Proxy struct { type Proxy struct {
Proxy string `uri:"proxy" binding:"required"` Proxy string `uri:"proxy" binding:"required"`
} }
@@ -40,6 +43,7 @@ func NewProxyController(config ProxyControllerConfig, router *gin.RouterGroup, a
func (controller *ProxyController) SetupRoutes() { func (controller *ProxyController) SetupRoutes() {
proxyGroup := controller.router.Group("/auth") proxyGroup := controller.router.Group("/auth")
proxyGroup.GET("/:proxy", controller.proxyHandler) proxyGroup.GET("/:proxy", controller.proxyHandler)
proxyGroup.POST("/:proxy", controller.proxyHandler)
} }
func (controller *ProxyController) proxyHandler(c *gin.Context) { func (controller *ProxyController) proxyHandler(c *gin.Context) {
@@ -55,7 +59,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return return
} }
if req.Proxy != "nginx" && req.Proxy != "traefik" && req.Proxy != "caddy" { if !slices.Contains(SupportedProxies, req.Proxy) {
log.Warn().Str("proxy", req.Proxy).Msg("Invalid proxy") log.Warn().Str("proxy", req.Proxy).Msg("Invalid proxy")
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"status": 400, "status": 400,

View File

@@ -92,6 +92,18 @@ func TestProxyHandler(t *testing.T) {
assert.Equal(t, 307, recorder.Code) assert.Equal(t, 307, recorder.Code)
assert.Equal(t, "http://localhost:8080/login?redirect_uri=https%3A%2F%2Fexample.com%2Fsomepath", recorder.Header().Get("Location")) assert.Equal(t, "http://localhost:8080/login?redirect_uri=https%3A%2F%2Fexample.com%2Fsomepath", recorder.Header().Get("Location"))
// Test logged out user (envoy)
recorder = httptest.NewRecorder()
req = httptest.NewRequest("POST", "/api/auth/envoy", nil)
req.Header.Set("X-Forwarded-Proto", "https")
req.Header.Set("X-Forwarded-Host", "example.com")
req.Header.Set("X-Forwarded-Uri", "/somepath")
req.Header.Set("Accept", "text/html")
router.ServeHTTP(recorder, req)
assert.Equal(t, 307, recorder.Code)
assert.Equal(t, "http://localhost:8080/login?redirect_uri=https%3A%2F%2Fexample.com%2Fsomepath", recorder.Header().Get("Location"))
// Test logged out user (nginx) // Test logged out user (nginx)
recorder = httptest.NewRecorder() recorder = httptest.NewRecorder()
req = httptest.NewRequest("GET", "/api/auth/nginx", nil) req = httptest.NewRequest("GET", "/api/auth/nginx", nil)

View File

@@ -65,6 +65,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
goto basic goto basic
} }
m.auth.RefreshSessionCookie(c)
c.Set("context", &config.UserContext{ c.Set("context", &config.UserContext{
Username: cookie.Username, Username: cookie.Username,
Name: cookie.Name, Name: cookie.Name,
@@ -89,6 +90,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
goto basic goto basic
} }
m.auth.RefreshSessionCookie(c)
c.Set("context", &config.UserContext{ c.Set("context", &config.UserContext{
Username: cookie.Username, Username: cookie.Username,
Name: cookie.Name, Name: cookie.Name,

View File

@@ -1,7 +1,6 @@
package service package service
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"regexp" "regexp"
@@ -43,7 +42,6 @@ type AuthService struct {
loginMutex sync.RWMutex loginMutex sync.RWMutex
ldap *LdapService ldap *LdapService
database *gorm.DB database *gorm.DB
ctx context.Context
} }
func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapService, database *gorm.DB) *AuthService { func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapService, database *gorm.DB) *AuthService {
@@ -57,7 +55,6 @@ func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapS
} }
func (auth *AuthService) Init() error { func (auth *AuthService) Init() error {
auth.ctx = context.Background()
return nil return nil
} }
@@ -217,7 +214,7 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio
OAuthName: data.OAuthName, OAuthName: data.OAuthName,
} }
err = gorm.G[model.Session](auth.database).Create(auth.ctx, &session) err = gorm.G[model.Session](auth.database).Create(c, &session)
if err != nil { if err != nil {
return err return err
@@ -228,6 +225,40 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio
return nil return nil
} }
func (auth *AuthService) RefreshSessionCookie(c *gin.Context) error {
cookie, err := c.Cookie(auth.config.SessionCookieName)
if err != nil {
return err
}
session, err := gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).First(c)
if err != nil {
return err
}
currentTime := time.Now().Unix()
if session.Expiry-currentTime > int64(time.Hour.Seconds()) {
return nil
}
newExpiry := currentTime + int64(time.Hour.Seconds())
_, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Updates(c, model.Session{
Expiry: newExpiry,
})
if err != nil {
return err
}
c.SetCookie(auth.config.SessionCookieName, cookie, int(time.Hour.Seconds()), "/", fmt.Sprintf(".%s", auth.config.CookieDomain), auth.config.SecureCookie, true)
return nil
}
func (auth *AuthService) DeleteSessionCookie(c *gin.Context) error { func (auth *AuthService) DeleteSessionCookie(c *gin.Context) error {
cookie, err := c.Cookie(auth.config.SessionCookieName) cookie, err := c.Cookie(auth.config.SessionCookieName)
@@ -235,7 +266,7 @@ func (auth *AuthService) DeleteSessionCookie(c *gin.Context) error {
return err return err
} }
_, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Delete(auth.ctx) _, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Delete(c)
if err != nil { if err != nil {
return err return err
@@ -253,7 +284,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
return config.SessionCookie{}, err return config.SessionCookie{}, err
} }
session, err := gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).First(auth.ctx) session, err := gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).First(c)
if err != nil { if err != nil {
return config.SessionCookie{}, err return config.SessionCookie{}, err
@@ -266,7 +297,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
currentTime := time.Now().Unix() currentTime := time.Now().Unix()
if currentTime > session.Expiry { if currentTime > session.Expiry {
_, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Delete(auth.ctx) _, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Delete(c)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to delete expired session") log.Error().Err(err).Msg("Failed to delete expired session")
} }