2022-05-26 17:56:22 +02:00

115 lines
2.7 KiB
Go

package photosapi
import (
"errors"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"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 {
u, err := uuid.NewRandom()
if err != nil {
return err
}
s.Token = u.String()
return nil
}
func NewSession(tx *gorm.DB, login string, password string) (*Session, error) {
account := &Account{}
if err := tx.Where(
"login = ?",
login,
).First(account).Error; err != nil {
return nil, err
}
if err := bcrypt.CompareHashAndPassword(account.Password, []byte(password)); 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)
}
}
}