package auth import ( "crypto/rand" "encoding/hex" "errors" "fmt" "time" "gorm.io/gorm" ) type UserSession struct { UserID string `json:"user_id"` MicrosoftNameID string `json:"microsoft_name_id"` Email string `json:"email"` Name string `json:"name"` LoggedInAt time.Time `json:"logged_in_at"` ExpiresAt time.Time `json:"expires_at"` } type SessionRecord struct { Token string `gorm:"type:varchar(128);primaryKey;column:token"` UserID string `gorm:"type:varchar(191);index;not null;column:user_id"` MicrosoftNameID string `gorm:"type:varchar(255);index;not null;column:microsoft_name_id"` Email string `gorm:"type:varchar(191);index;not null;column:email"` Name string `gorm:"type:varchar(191);not null;column:name"` LoggedInAt time.Time `gorm:"not null;column:logged_in_at"` ExpiresAt time.Time `gorm:"index;not null;column:expires_at"` CreatedAt time.Time `gorm:"column:created_at"` UpdatedAt time.Time `gorm:"column:updated_at"` } func (SessionRecord) TableName() string { return "saml_sessions" } var sessionDB *gorm.DB func InitSessionStoreDB(db *gorm.DB) error { if db == nil { return fmt.Errorf("database is required") } if err := db.AutoMigrate(&SessionRecord{}); err != nil { return fmt.Errorf("auto migrate saml_sessions: %w", err) } sessionDB = db return nil } func CreateSession(session UserSession) (string, error) { if sessionDB == nil { return "", fmt.Errorf("session store is not initialized") } token, err := generateToken() if err != nil { return "", err } if session.LoggedInAt.IsZero() { session.LoggedInAt = time.Now().UTC() } if session.ExpiresAt.IsZero() { session.ExpiresAt = session.LoggedInAt.Add(8 * time.Hour) } record := SessionRecord{ Token: token, UserID: session.UserID, MicrosoftNameID: session.MicrosoftNameID, Email: session.Email, Name: session.Name, LoggedInAt: session.LoggedInAt, ExpiresAt: session.ExpiresAt, } if err := sessionDB.Create(&record).Error; err != nil { return "", fmt.Errorf("create session: %w", err) } return token, nil } func GetSession(token string) (*UserSession, error) { if sessionDB == nil { return nil, fmt.Errorf("session store is not initialized") } var record SessionRecord err := sessionDB.Where("token = ?", token).First(&record).Error if err != nil { return nil, err } now := time.Now().UTC() if !record.ExpiresAt.After(now) { _ = sessionDB.Delete(&SessionRecord{}, "token = ?", token).Error return nil, gorm.ErrRecordNotFound } return &UserSession{ UserID: record.UserID, MicrosoftNameID: record.MicrosoftNameID, Email: record.Email, Name: record.Name, LoggedInAt: record.LoggedInAt, ExpiresAt: record.ExpiresAt, }, nil } func DeleteSession(token string) error { if sessionDB == nil { return fmt.Errorf("session store is not initialized") } return sessionDB.Delete(&SessionRecord{}, "token = ?", token).Error } func DeleteSessionByNameID(nameID string) error { if sessionDB == nil { return fmt.Errorf("session store is not initialized") } return sessionDB.Delete(&SessionRecord{}, "microsoft_name_id = ?", nameID).Error } func generateToken() (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", fmt.Errorf("generate token: %w", err) } token := hex.EncodeToString(b) if token == "" { return "", errors.New("empty token") } return token, nil }