package photosapi import ( "errors" "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "gorm.io/gorm" ) // Errors var ( ErrSessionNotFound = errors.New("session not found") ErrSessionInvalid = errors.New("session invalid") ErrTokenMissing = errors.New("token missing") ) // Model type Session struct { ID uint32 `gorm:"primary_key"` Token string `gorm:"size:36"` Account *Account `gorm:"constraint:OnDelete:CASCADE,OnUpdate:CASCADE"` AccountId uint32 `gorm:"not null"` CreatedAt time.Time UpdatedAt time.Time } func (s *Session) BeforeCreate(tx *gorm.DB) error { uuid, err := uuid.NewRandom() if err != nil { return err } s.Token = uuid.String() return nil } func NewSession(tx *gorm.DB, login string, password string) (*Session, error) { account := NewAccount(login, password) if err := tx.Where( "login = ? and encrypted_password = ?", account.Login, account.EncryptedPassword, ).First(account).Error; err != nil { return nil, err } session := &Session{Account: account} if err := tx.Create(session).Error; err != nil { return nil, err } return session, nil } // Service func (s *Service) RequireAuthToken(c *gin.Context) { tokenAuth := c.GetHeader("Authorization") tokenCookie, _ := c.Cookie("photoapitoken") if tokenAuth != "" { if !strings.HasPrefix(tokenAuth, "Private ") { c.AbortWithError(http.StatusForbidden, ErrTokenMissing) } else { c.Set("token", tokenAuth[8:]) } } else if tokenCookie != "" { c.Set("token", tokenCookie) } else { c.AbortWithError(http.StatusForbidden, ErrTokenMissing) } } func (s *Service) RequireSession(c *gin.Context) { s.RequireAuthToken(c) if c.IsAborted() { return } sess := &Session{} if err := s.DB.Preload("Account").Where("token = ?", c.GetString("token")).First(sess).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { c.AbortWithError(http.StatusForbidden, ErrSessionNotFound) } else { c.AbortWithError(http.StatusForbidden, err) } return } if sess.Account == nil { c.AbortWithError(http.StatusInternalServerError, ErrSessionInvalid) return } s.DB.Select("updated_at").Save(sess) s.LogOk.Printf("Session", "User: %s", sess.Account.Login) c.Set("session", sess) } func (s *Service) CurrentSession(c *gin.Context) *Session { return c.MustGet("session").(*Session) } func (s *Service) SessionCleaner() { for range time.Tick(time.Minute) { t := time.Now().UTC().Add(-30 * time.Minute).Truncate(time.Minute) // s.LogOk.Printf("Session", "Cleaning old session < %s", t) if err := s.DB.Where("updated_at < ?", t).Delete(&Session{}).Error; err != nil { s.LogErr.Printf("Session", "Cleaning failed: %s", err) } } }