Compare commits

...

3 Commits

Author SHA1 Message Date
Stavros
6647c6cd78 refactor: use gorm generics api for database actions 2025-10-19 19:16:53 +03:00
Stavros
7231efcbc3 feat: add routine to cleanup expired sessions 2025-10-19 19:10:24 +03:00
Stavros
5482430907 refactor: generate a verifier on every oauth auth session 2025-10-19 19:03:38 +03:00
7 changed files with 53 additions and 18 deletions

View File

@@ -2,6 +2,7 @@ package bootstrap
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
@@ -13,11 +14,13 @@ import (
"tinyauth/internal/config" "tinyauth/internal/config"
"tinyauth/internal/controller" "tinyauth/internal/controller"
"tinyauth/internal/middleware" "tinyauth/internal/middleware"
"tinyauth/internal/model"
"tinyauth/internal/service" "tinyauth/internal/service"
"tinyauth/internal/utils" "tinyauth/internal/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gorm.io/gorm"
) )
type Controller interface { type Controller interface {
@@ -277,6 +280,10 @@ func (app *BootstrapApp) Setup() error {
go app.heartbeat() go app.heartbeat()
} }
// Start DB cleanup routine
log.Debug().Msg("Starting database cleanup routine")
go app.dbCleanup(database)
// Start server // Start server
address := fmt.Sprintf("%s:%d", app.config.Address, app.config.Port) address := fmt.Sprintf("%s:%d", app.config.Address, app.config.Port)
log.Info().Msgf("Starting server on %s", address) log.Info().Msgf("Starting server on %s", address)
@@ -338,3 +345,17 @@ func (app *BootstrapApp) heartbeat() {
} }
} }
} }
func (app *BootstrapApp) dbCleanup(db *gorm.DB) {
ticker := time.NewTicker(time.Duration(30) * time.Minute)
defer ticker.Stop()
ctx := context.Background()
for ; true; <-ticker.C {
log.Debug().Msg("Cleaning up old database sessions")
_, err := gorm.G[model.Session](db).Where("expiry < ?", time.Now().UnixMilli()).Delete(ctx)
if err != nil {
log.Error().Err(err).Msg("Failed to cleanup old sessions")
}
}
}

View File

@@ -72,6 +72,7 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
return return
} }
service.GenerateVerifier()
state := service.GenerateState() state := service.GenerateState()
authURL := service.GetAuthURL(state) authURL := service.GetAuthURL(state)
c.SetCookie(controller.config.CSRFCookieName, state, int(time.Hour.Seconds()), "/", fmt.Sprintf(".%s", controller.config.CookieDomain), controller.config.SecureCookie, true) c.SetCookie(controller.config.CSRFCookieName, state, int(time.Hour.Seconds()), "/", fmt.Sprintf(".%s", controller.config.CookieDomain), controller.config.SecureCookie, true)

View File

@@ -1,6 +1,8 @@
package service package service
import ( import (
"context"
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
@@ -41,6 +43,7 @@ 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 {
@@ -54,6 +57,7 @@ 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
} }
@@ -213,7 +217,7 @@ func (auth *AuthService) CreateSessionCookie(c *gin.Context, data *config.Sessio
OAuthName: data.OAuthName, OAuthName: data.OAuthName,
} }
err = auth.database.Create(&session).Error err = gorm.G[model.Session](auth.database).Create(auth.ctx, &session)
if err != nil { if err != nil {
return err return err
@@ -231,10 +235,10 @@ func (auth *AuthService) DeleteSessionCookie(c *gin.Context) error {
return err return err
} }
res := auth.database.Unscoped().Where("uuid = ?", cookie).Delete(&model.Session{}) _, err = gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).Delete(auth.ctx)
if res.Error != nil { if err != nil {
return res.Error return err
} }
c.SetCookie(auth.config.SessionCookieName, "", -1, "/", fmt.Sprintf(".%s", auth.config.CookieDomain), auth.config.SecureCookie, true) c.SetCookie(auth.config.SessionCookieName, "", -1, "/", fmt.Sprintf(".%s", auth.config.CookieDomain), auth.config.SecureCookie, true)
@@ -249,15 +253,13 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (config.SessionCookie,
return config.SessionCookie{}, err return config.SessionCookie{}, err
} }
var session model.Session session, err := gorm.G[model.Session](auth.database).Where("uuid = ?", cookie).First(auth.ctx)
res := auth.database.Unscoped().Where("uuid = ?", cookie).First(&session) if err != nil {
return config.SessionCookie{}, err
if res.Error != nil {
return config.SessionCookie{}, res.Error
} }
if res.RowsAffected == 0 { if errors.Is(err, gorm.ErrRecordNotFound) {
return config.SessionCookie{}, fmt.Errorf("session not found") return config.SessionCookie{}, fmt.Errorf("session not found")
} }

View File

@@ -59,10 +59,8 @@ func (generic *GenericOAuthService) Init() error {
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
verifier := oauth2.GenerateVerifier()
generic.context = ctx generic.context = ctx
generic.verifier = verifier
return nil return nil
} }
@@ -76,6 +74,12 @@ func (generic *GenericOAuthService) GenerateState() string {
return state return state
} }
func (generic *GenericOAuthService) GenerateVerifier() string {
verifier := oauth2.GenerateVerifier()
generic.verifier = verifier
return verifier
}
func (generic *GenericOAuthService) GetAuthURL(state string) string { func (generic *GenericOAuthService) GetAuthURL(state string) string {
return generic.config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(generic.verifier)) return generic.config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(generic.verifier))
} }

View File

@@ -53,10 +53,7 @@ func (github *GithubOAuthService) Init() error {
httpClient := &http.Client{} httpClient := &http.Client{}
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
verifier := oauth2.GenerateVerifier()
github.context = ctx github.context = ctx
github.verifier = verifier
return nil return nil
} }
@@ -70,6 +67,12 @@ func (github *GithubOAuthService) GenerateState() string {
return state return state
} }
func (github *GithubOAuthService) GenerateVerifier() string {
verifier := oauth2.GenerateVerifier()
github.verifier = verifier
return verifier
}
func (github *GithubOAuthService) GetAuthURL(state string) string { func (github *GithubOAuthService) GetAuthURL(state string) string {
return github.config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(github.verifier)) return github.config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(github.verifier))
} }

View File

@@ -48,10 +48,7 @@ func (google *GoogleOAuthService) Init() error {
httpClient := &http.Client{} httpClient := &http.Client{}
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
verifier := oauth2.GenerateVerifier()
google.context = ctx google.context = ctx
google.verifier = verifier
return nil return nil
} }
@@ -65,6 +62,12 @@ func (oauth *GoogleOAuthService) GenerateState() string {
return state return state
} }
func (google *GoogleOAuthService) GenerateVerifier() string {
verifier := oauth2.GenerateVerifier()
google.verifier = verifier
return verifier
}
func (google *GoogleOAuthService) GetAuthURL(state string) string { func (google *GoogleOAuthService) GetAuthURL(state string) string {
return google.config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(google.verifier)) return google.config.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(google.verifier))
} }

View File

@@ -11,6 +11,7 @@ import (
type OAuthService interface { type OAuthService interface {
Init() error Init() error
GenerateState() string GenerateState() string
GenerateVerifier() string
GetAuthURL(state string) string GetAuthURL(state string) string
VerifyCode(code string) error VerifyCode(code string) error
Userinfo() (config.Claims, error) Userinfo() (config.Claims, error)