Compare commits

...

3 commits

Author SHA1 Message Date
4ef9e19fbc
Add cleaner services
Some checks are pending
/ test (push) Waiting to run
Initial cleaner is for expiring access tokens
2025-04-04 16:16:08 +02:00
6f16289b41
Missing from previous commit 2025-04-04 16:15:52 +02:00
6a2b213787
Add access token check to auth 2025-04-04 16:15:25 +02:00
7 changed files with 152 additions and 7 deletions

29
auth-new/accessTokens.go Normal file
View file

@ -0,0 +1,29 @@
package auth
import (
"time"
"git.mstar.dev/mstar/goutils/other"
"gorm.io/gorm"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
"git.mstar.dev/mstar/linstrom/storage-new/models"
)
// Check whether a given access token is valid (exists and hasn't expired).
// If it is, returns the user it belongs to
func (a *Authenticator) IsValidAccessToken(token string) (*models.User, error) {
dbToken, err := dbgen.AccessToken.GetTokenIfValid(token)
switch err {
case nil:
if dbToken.ExpiresAt.Before(time.Now()) {
return nil, ErrTokenExpired
} else {
return &dbToken.User, nil
}
case gorm.ErrRecordNotFound:
return nil, ErrTokenNotFound
default:
return nil, other.Error("auth", "failed to check for token", err)
}
}

View file

@ -26,6 +26,10 @@ var (
ErrInvalidPasskeyRegistrationData = errors.New(
"stored passkey registration data was formatted badly",
)
// The given token has expired
ErrTokenExpired = errors.New("token expired")
// The given token doesn't exist
ErrTokenNotFound = errors.New("token not found")
)
// Helper error type to combine two errors into one

View file

@ -18,12 +18,6 @@ import (
"git.mstar.dev/mstar/linstrom/storage-new/models"
)
const (
dbName = "linstrom"
dbUser = "linstrom"
dbPass = "linstrom"
)
func main() {
other.SetupFlags()
flag.Parse()
@ -32,7 +26,6 @@ func main() {
db, err := gorm.Open(
postgres.Open(config.GlobalConfig.Storage.BuildPostgresDSN()),
// postgres.Open(pgContainer.MustConnectionString(context.Background())),
&gorm.Config{
PrepareStmt: false,
Logger: shared.NewGormLogger(log.Logger),
@ -54,6 +47,7 @@ func main() {
log.Info().Msg("Basic operations applied, applying extra features")
g.ApplyInterface(func(models.INotification) {}, models.Notification{})
g.ApplyInterface(func(models.IUser) {}, models.User{})
g.ApplyInterface(func(models.IAccessToken) {}, models.AccessToken{})
log.Info().Msg("Extra features applied, starting generation")
g.Execute()

View file

@ -0,0 +1,19 @@
package cleaners
import (
"time"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
)
func init() {
cleanerBuilders = append(cleanerBuilders, buildExpireAccessTokens)
}
func tickExpireAccessTokens(now time.Time) {
dbgen.AccessToken.Where(dbgen.AccessToken.ExpiresAt.Lt(time.Now())).Delete()
}
func buildExpireAccessTokens() (onTick func(time.Time), name string, tickSpeed time.Duration) {
return tickExpireAccessTokens, "expire-access-tokens", time.Hour
}

View file

@ -0,0 +1,70 @@
package cleaners
import (
"sync"
"time"
)
type CleanerManager struct {
activeCleaners map[string]bool
activeCleanerLock sync.Mutex
exitChans []chan any
}
var cleanerBuilders = []func() (onTick func(time.Time), name string, tickSpeed time.Duration){}
func NewManager() *CleanerManager {
activeCleaners := make(map[string]bool)
exitChans := []chan any{}
cm := &CleanerManager{
activeCleaners: activeCleaners,
exitChans: exitChans,
}
// Launch all cleaner tickers in a new goroutine each
for _, builder := range cleanerBuilders {
exitChan := make(chan any, 1)
onTick, name, tickSpeed := builder()
cm.exitChans = append(cm.exitChans, exitChan)
go cm.tickOrExit(tickSpeed, name, exitChan, onTick)
}
return cm
}
func (m *CleanerManager) Stop() {
for _, exitChan := range m.exitChans {
exitChan <- 1
}
}
func (m *CleanerManager) tickOrExit(
tickSpeed time.Duration,
name string,
exitChan chan any,
onTick func(time.Time),
) {
ticker := time.Tick(tickSpeed)
for {
select {
case now := <-ticker:
go m.wrapOnTick(name, now, onTick)
case <-exitChan:
return
}
}
}
func (m *CleanerManager) wrapOnTick(name string, now time.Time, onTick func(time.Time)) {
m.activeCleanerLock.Lock()
if m.activeCleaners[name] {
m.activeCleanerLock.Unlock()
return
}
m.activeCleaners[name] = true
m.activeCleanerLock.Unlock()
onTick(now)
m.activeCleanerLock.Lock()
m.activeCleaners[name] = false
m.activeCleanerLock.Unlock()
}

View file

@ -6,6 +6,7 @@ package dbgen
import (
"context"
"strings"
"git.mstar.dev/mstar/linstrom/storage-new/models"
"gorm.io/gorm"
@ -435,6 +436,25 @@ type IAccessTokenDo interface {
Returning(value interface{}, columns ...string) IAccessTokenDo
UnderlyingDB() *gorm.DB
schema.Tabler
GetTokenIfValid(token string) (result *models.AccessToken, err error)
}
// Get the data for a token if it hasn't expired yet
//
// SELECT * FROM @@table WHERE token = @token AND expires_at < NOW() LIMIT 1
func (a accessTokenDo) GetTokenIfValid(token string) (result *models.AccessToken, err error) {
var params []interface{}
var generateSQL strings.Builder
params = append(params, token)
generateSQL.WriteString("SELECT * FROM access_tokens WHERE token = ? AND expires_at < NOW() LIMIT 1 ")
var executeSQL *gorm.DB
executeSQL = a.UnderlyingDB().Raw(generateSQL.String(), params...).Take(&result) // ignore_security_alert
err = executeSQL.Error
return
}
func (a accessTokenDo) Debug() IAccessTokenDo {

View file

@ -2,6 +2,8 @@ package models
import (
"time"
"gorm.io/gen"
)
// AccessToken maps a unique token to one account.
@ -18,3 +20,10 @@ type AccessToken struct {
// at a point in the future this server should never reach
ExpiresAt time.Time `gorm:"default:TIMESTAMP WITH TIME ZONE '9999-12-30 23:59:59+00'"`
}
type IAccessToken interface {
// Get the data for a token
//
// SELECT * FROM @@table WHERE token = @token
GetTokenIfValid(token string) (*gen.T, error)
}