115 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|