2020-01-31 17:36:48 +01:00
|
|
|
package models
|
|
|
|
|
|
|
|
import (
|
2020-01-31 18:51:24 +01:00
|
|
|
"crypto/rand"
|
|
|
|
"fmt"
|
2020-01-31 23:30:34 +01:00
|
|
|
"log"
|
2020-02-16 12:22:00 +01:00
|
|
|
"os"
|
2020-01-31 18:51:24 +01:00
|
|
|
"time"
|
2020-01-31 17:36:48 +01:00
|
|
|
|
2020-07-10 18:35:37 +02:00
|
|
|
"github.com/pkg/errors"
|
2020-01-31 17:36:48 +01:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
2020-11-23 19:39:44 +01:00
|
|
|
"gorm.io/gorm"
|
2020-01-31 17:36:48 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type User struct {
|
2020-11-28 21:29:31 +01:00
|
|
|
Model
|
2021-01-19 17:31:37 +01:00
|
|
|
Username string `gorm:"unique;size:128"`
|
2021-01-19 18:35:19 +01:00
|
|
|
Password *string `gorm:"size:256"`
|
2020-12-20 17:10:00 +01:00
|
|
|
// RootPath string `gorm:"size:512`
|
|
|
|
Albums []Album `gorm:"many2many:user_albums"`
|
|
|
|
Admin bool `gorm:"default:false"`
|
2020-01-31 18:51:24 +01:00
|
|
|
}
|
|
|
|
|
2020-12-20 17:10:00 +01:00
|
|
|
type UserMediaData struct {
|
|
|
|
ModelTimestamps
|
|
|
|
UserID int `gorm:"primaryKey;autoIncrement:false"`
|
|
|
|
MediaID int `gorm:"primaryKey;autoIncrement:false"`
|
|
|
|
Favorite bool `gorm:"not null;default:false"`
|
|
|
|
}
|
2020-01-31 23:30:34 +01:00
|
|
|
|
2020-01-31 18:51:24 +01:00
|
|
|
type AccessToken struct {
|
2020-11-28 21:29:31 +01:00
|
|
|
Model
|
2021-01-19 17:31:37 +01:00
|
|
|
UserID int `gorm:"not null;index"`
|
2020-11-28 21:49:33 +01:00
|
|
|
User User `gorm:"constraint:OnDelete:CASCADE;"`
|
2021-01-19 18:35:19 +01:00
|
|
|
Value string `gorm:"not null;size:24;index"`
|
2021-01-19 17:31:37 +01:00
|
|
|
Expire time.Time `gorm:"not null;index"`
|
2020-01-31 17:36:48 +01:00
|
|
|
}
|
|
|
|
|
2020-01-31 23:30:34 +01:00
|
|
|
var ErrorInvalidUserCredentials = errors.New("invalid credentials")
|
2020-01-31 17:36:48 +01:00
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
func AuthorizeUser(db *gorm.DB, username string, password string) (*User, error) {
|
|
|
|
var user User
|
|
|
|
|
|
|
|
result := db.Where("username = ?", username).First(&user)
|
|
|
|
if result.Error != nil {
|
|
|
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
2020-02-02 00:29:42 +01:00
|
|
|
return nil, ErrorInvalidUserCredentials
|
|
|
|
}
|
2020-11-23 19:39:44 +01:00
|
|
|
return nil, errors.Wrap(result.Error, "failed to get user by username when authorizing")
|
2020-01-31 17:36:48 +01:00
|
|
|
}
|
|
|
|
|
2020-02-16 12:22:00 +01:00
|
|
|
if user.Password == nil {
|
|
|
|
return nil, errors.New("user does not have a password")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(password)); err != nil {
|
2020-01-31 17:36:48 +01:00
|
|
|
if err == bcrypt.ErrMismatchedHashAndPassword {
|
2020-01-31 23:30:34 +01:00
|
|
|
return nil, ErrorInvalidUserCredentials
|
2020-01-31 17:36:48 +01:00
|
|
|
} else {
|
2020-07-10 18:35:37 +02:00
|
|
|
return nil, errors.Wrap(err, "compare user password hash")
|
2020-01-31 17:36:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
return &user, nil
|
2020-01-31 17:36:48 +01:00
|
|
|
}
|
|
|
|
|
2020-02-16 12:22:00 +01:00
|
|
|
var ErrorInvalidRootPath = errors.New("invalid root path")
|
|
|
|
|
|
|
|
func ValidRootPath(rootPath string) bool {
|
|
|
|
_, err := os.Stat(rootPath)
|
2020-01-31 17:36:48 +01:00
|
|
|
if err != nil {
|
2020-02-16 12:22:00 +01:00
|
|
|
log.Printf("Warn: invalid root path: '%s'\n%s\n", rootPath, err)
|
|
|
|
return false
|
2020-01-31 17:36:48 +01:00
|
|
|
}
|
|
|
|
|
2020-02-16 12:22:00 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-12-20 17:10:00 +01:00
|
|
|
func RegisterUser(db *gorm.DB, username string, password *string, admin bool) (*User, error) {
|
|
|
|
// if !ValidRootPath(rootPath) {
|
|
|
|
// return nil, ErrorInvalidRootPath
|
|
|
|
// }
|
2020-02-16 12:22:00 +01:00
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
user := User{
|
|
|
|
Username: username,
|
2020-12-20 17:10:00 +01:00
|
|
|
// RootPath: rootPath,
|
|
|
|
Admin: admin,
|
2020-11-23 19:39:44 +01:00
|
|
|
}
|
|
|
|
|
2020-02-16 12:22:00 +01:00
|
|
|
if password != nil {
|
|
|
|
hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(*password), 12)
|
|
|
|
if err != nil {
|
2020-07-10 18:35:37 +02:00
|
|
|
return nil, errors.Wrap(err, "failed to hash password")
|
2020-02-16 12:22:00 +01:00
|
|
|
}
|
|
|
|
hashedPass := string(hashedPassBytes)
|
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
user.Password = &hashedPass
|
2020-01-31 17:36:48 +01:00
|
|
|
}
|
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
result := db.Create(&user)
|
|
|
|
if result.Error != nil {
|
|
|
|
return nil, errors.Wrap(result.Error, "insert new user with password into database")
|
2020-01-31 17:36:48 +01:00
|
|
|
}
|
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
return &user, nil
|
2020-01-31 17:36:48 +01:00
|
|
|
}
|
2020-01-31 18:51:24 +01:00
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
func (user *User) GenerateAccessToken(db *gorm.DB) (*AccessToken, error) {
|
2020-01-31 18:51:24 +01:00
|
|
|
bytes := make([]byte, 24)
|
|
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
|
|
return nil, errors.New(fmt.Sprintf("Could not generate token: %s\n", err.Error()))
|
|
|
|
}
|
2020-02-02 00:29:42 +01:00
|
|
|
const CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
2020-01-31 18:51:24 +01:00
|
|
|
for i, b := range bytes {
|
|
|
|
bytes[i] = CHARACTERS[b%byte(len(CHARACTERS))]
|
|
|
|
}
|
|
|
|
|
|
|
|
token_value := string(bytes)
|
|
|
|
expire := time.Now().Add(14 * 24 * time.Hour)
|
2020-11-23 19:39:44 +01:00
|
|
|
// expireString := expire.UTC().Format("2006-01-02 15:04:05")
|
2020-01-31 18:51:24 +01:00
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
// if _, err := database.Exec("INSERT INTO access_token (value, expire, user_id) VALUES (?, ?, ?)", token_value, expireString, user.UserID); err != nil {
|
|
|
|
// return nil, err
|
|
|
|
// }
|
2020-01-31 18:51:24 +01:00
|
|
|
|
|
|
|
token := AccessToken{
|
2020-11-23 19:39:44 +01:00
|
|
|
UserID: user.ID,
|
2020-01-31 18:51:24 +01:00
|
|
|
Value: token_value,
|
|
|
|
Expire: expire,
|
|
|
|
}
|
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
result := db.Create(&token)
|
|
|
|
if result.Error != nil {
|
|
|
|
return nil, errors.Wrap(result.Error, "saving access token to database")
|
|
|
|
}
|
|
|
|
|
2020-01-31 18:51:24 +01:00
|
|
|
return &token, nil
|
|
|
|
}
|
2020-01-31 23:30:34 +01:00
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
func VerifyTokenAndGetUser(db *gorm.DB, token string) (*User, error) {
|
2020-01-31 23:30:34 +01:00
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
var accessToken AccessToken
|
2020-11-28 21:49:33 +01:00
|
|
|
result := db.Where("expire > ? AND value = ?", time.Now(), token).First(&accessToken)
|
2020-11-23 19:39:44 +01:00
|
|
|
if result.Error != nil {
|
|
|
|
return nil, result.Error
|
2020-01-31 23:30:34 +01:00
|
|
|
}
|
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
var user User
|
2020-11-30 16:42:13 +01:00
|
|
|
result = db.First(&user, accessToken.UserID)
|
2020-11-23 19:39:44 +01:00
|
|
|
if result.Error != nil {
|
|
|
|
return nil, result.Error
|
2020-01-31 23:30:34 +01:00
|
|
|
}
|
|
|
|
|
2020-11-23 19:39:44 +01:00
|
|
|
return &user, nil
|
2020-01-31 23:30:34 +01:00
|
|
|
}
|
2020-12-22 01:14:43 +01:00
|
|
|
|
|
|
|
// FillAlbums fill user.Albums with albums from database
|
|
|
|
func (user *User) FillAlbums(db *gorm.DB) error {
|
|
|
|
// Albums already present
|
|
|
|
if len(user.Albums) > 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := db.Model(&user).Association("Albums").Find(&user.Albums); err != nil {
|
|
|
|
return errors.Wrap(err, "fill user albums")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (user *User) OwnsAlbum(db *gorm.DB, album *Album) (bool, error) {
|
|
|
|
|
|
|
|
// user.QueryUserAlbums(db, db.Where("id = ?", album.ID))
|
|
|
|
|
|
|
|
// TODO: Implement this
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (user *User) OwnsMedia(db *gorm.DB, media *Media) (bool, error) {
|
|
|
|
// TODO: implement this
|
|
|
|
return true, nil
|
|
|
|
}
|