2020-02-09 21:25:33 +01:00
package resolvers
import (
"context"
"time"
2020-06-14 15:07:07 +02:00
"github.com/pkg/errors"
2020-11-23 19:39:44 +01:00
"gorm.io/gorm"
2020-12-17 22:29:24 +01:00
"gorm.io/gorm/clause"
2020-06-14 15:07:07 +02:00
2020-12-17 22:51:43 +01:00
api "github.com/photoview/photoview/api/graphql"
"github.com/photoview/photoview/api/graphql/auth"
"github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/utils"
2020-02-09 21:25:33 +01:00
"golang.org/x/crypto/bcrypt"
)
type shareTokenResolver struct {
* Resolver
}
func ( r * Resolver ) ShareToken ( ) api . ShareTokenResolver {
return & shareTokenResolver { r }
}
2020-12-17 22:29:24 +01:00
func ( r * shareTokenResolver ) Owner ( ctx context . Context , obj * models . ShareToken ) ( * models . User , error ) {
return & obj . Owner , nil
}
2020-11-27 16:02:10 +01:00
2020-12-17 22:29:24 +01:00
func ( r * shareTokenResolver ) Album ( ctx context . Context , obj * models . ShareToken ) ( * models . Album , error ) {
return obj . Album , nil
}
2020-11-27 16:02:10 +01:00
2020-12-17 22:29:24 +01:00
func ( r * shareTokenResolver ) Media ( ctx context . Context , obj * models . ShareToken ) ( * models . Media , error ) {
return obj . Media , nil
}
2020-02-09 21:25:33 +01:00
2020-06-14 15:07:07 +02:00
func ( r * shareTokenResolver ) HasPassword ( ctx context . Context , obj * models . ShareToken ) ( bool , error ) {
hasPassword := obj . Password != nil
return hasPassword , nil
}
2020-06-14 17:58:50 +02:00
func ( r * queryResolver ) ShareToken ( ctx context . Context , tokenValue string , password * string ) ( * models . ShareToken , error ) {
2020-02-09 21:25:33 +01:00
2020-11-27 16:02:10 +01:00
var token models . ShareToken
2020-12-17 22:29:24 +01:00
if err := r . Database . Preload ( clause . Associations ) . Where ( "value = ?" , tokenValue ) . First ( & token ) . Error ; err != nil {
2020-11-27 16:02:10 +01:00
if errors . Is ( err , gorm . ErrRecordNotFound ) {
2020-02-11 15:36:12 +01:00
return nil , errors . New ( "share not found" )
2020-02-11 14:32:35 +01:00
} else {
2020-07-10 18:35:37 +02:00
return nil , errors . Wrap ( err , "failed to get share token from database" )
2020-02-11 14:32:35 +01:00
}
}
2020-06-14 20:56:48 +02:00
if token . Password != nil {
if err := bcrypt . CompareHashAndPassword ( [ ] byte ( * token . Password ) , [ ] byte ( * password ) ) ; err != nil {
if err == bcrypt . ErrMismatchedHashAndPassword {
return nil , errors . New ( "unauthorized" )
} else {
2020-07-10 18:35:37 +02:00
return nil , errors . Wrap ( err , "failed to compare token password hashes" )
2020-06-14 20:56:48 +02:00
}
}
2020-06-14 17:58:50 +02:00
}
2020-11-27 16:02:10 +01:00
return & token , nil
2020-02-09 21:25:33 +01:00
}
2020-06-14 20:56:48 +02:00
func ( r * queryResolver ) ShareTokenValidatePassword ( ctx context . Context , tokenValue string , password * string ) ( bool , error ) {
2020-11-27 16:02:10 +01:00
var token models . ShareToken
if err := r . Database . Where ( "value = ?" , tokenValue ) . First ( & token ) . Error ; err != nil {
if errors . Is ( err , gorm . ErrRecordNotFound ) {
2020-06-14 18:28:12 +02:00
return false , errors . New ( "share not found" )
} else {
2020-07-10 18:35:37 +02:00
return false , errors . Wrap ( err , "failed to get share token from database" )
2020-06-14 18:28:12 +02:00
}
}
2020-06-14 20:56:48 +02:00
if token . Password == nil {
return true , nil
}
if password == nil {
return false , nil
}
if err := bcrypt . CompareHashAndPassword ( [ ] byte ( * token . Password ) , [ ] byte ( * password ) ) ; err != nil {
if err == bcrypt . ErrMismatchedHashAndPassword {
return false , nil
} else {
2020-07-10 18:35:37 +02:00
return false , errors . Wrap ( err , "could not compare token password hashes" )
2020-06-14 20:56:48 +02:00
}
}
return true , nil
2020-06-14 18:28:12 +02:00
}
2020-11-28 21:29:31 +01:00
func ( r * mutationResolver ) ShareAlbum ( ctx context . Context , albumID int , expire * time . Time , password * string ) ( * models . ShareToken , error ) {
2020-02-09 21:25:33 +01:00
user := auth . UserFromContext ( ctx )
if user == nil {
return nil , auth . ErrUnauthorized
}
2020-11-27 16:02:10 +01:00
var count int64
if err := r . Database . Model ( & models . Album { } ) . Where ( "owner_id = ?" , user . ID ) . Count ( & count ) . Error ; err != nil {
2020-07-10 18:35:37 +02:00
return nil , errors . Wrap ( err , "failed to validate album owner with database" )
2020-02-09 21:25:33 +01:00
}
2020-11-27 16:02:10 +01:00
if count == 0 {
2020-02-09 21:25:33 +01:00
return nil , auth . ErrUnauthorized
}
2020-11-27 16:02:10 +01:00
var hashedPassword * string = nil
2020-02-09 21:25:33 +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 token password" )
2020-02-09 21:25:33 +01:00
}
2020-11-27 16:02:10 +01:00
hashedStr := string ( hashedPassBytes )
hashedPassword = & hashedStr
2020-02-09 21:25:33 +01:00
}
2020-11-27 16:02:10 +01:00
shareToken := models . ShareToken {
Value : utils . GenerateToken ( ) ,
OwnerID : user . ID ,
Expire : expire ,
Password : hashedPassword ,
AlbumID : & albumID ,
MediaID : nil ,
2020-02-09 21:25:33 +01:00
}
2020-11-27 16:02:10 +01:00
if err := r . Database . Create ( & shareToken ) . Error ; err != nil {
return nil , errors . Wrap ( err , "failed to insert new share token into database" )
2020-02-09 21:25:33 +01:00
}
2020-11-27 16:02:10 +01:00
return & shareToken , nil
2020-02-09 21:25:33 +01:00
}
2020-11-28 21:29:31 +01:00
func ( r * mutationResolver ) ShareMedia ( ctx context . Context , mediaID int , expire * time . Time , password * string ) ( * models . ShareToken , error ) {
2020-02-11 15:36:12 +01:00
user := auth . UserFromContext ( ctx )
if user == nil {
return nil , auth . ErrUnauthorized
}
2020-11-27 16:02:10 +01:00
var media models . Media
2020-12-17 22:29:24 +01:00
if err := r . Database . Joins ( "Album" ) . Where ( "Album.owner_id = ?" , user . ID ) . First ( & media , mediaID ) . Error ; err != nil {
2020-11-27 16:02:10 +01:00
if errors . Is ( err , gorm . ErrRecordNotFound ) {
return nil , auth . ErrUnauthorized
} else {
return nil , errors . Wrap ( err , "failed to validate media owner with database" )
}
}
var count int64
2020-12-17 22:29:24 +01:00
err := r . Database . Raw ( "SELECT owner_id FROM albums, media WHERE media.id = ? AND media.album_id = albums.id AND albums.owner_id = ?" , mediaID , user . ID ) . Count ( & count ) . Error
2020-02-11 15:36:12 +01:00
if err != nil {
2020-07-10 18:35:37 +02:00
return nil , errors . Wrap ( err , "error validating owner of media with database" )
2020-02-11 15:36:12 +01:00
}
2020-11-27 16:02:10 +01:00
if count == 0 {
2020-02-11 15:36:12 +01:00
return nil , auth . ErrUnauthorized
}
2020-11-27 16:02:10 +01:00
hashedPassword , err := hashSharePassword ( password )
2020-06-14 15:07:07 +02:00
if err != nil {
return nil , err
2020-02-11 15:36:12 +01:00
}
2020-11-27 16:02:10 +01:00
shareToken := models . ShareToken {
Value : utils . GenerateToken ( ) ,
OwnerID : user . ID ,
Expire : expire ,
Password : hashedPassword ,
AlbumID : nil ,
MediaID : & mediaID ,
2020-02-11 15:36:12 +01:00
}
2020-11-27 16:02:10 +01:00
if err := r . Database . Create ( & shareToken ) . Error ; err != nil {
return nil , errors . Wrap ( err , "failed to insert new share token into database" )
2020-02-11 15:36:12 +01:00
}
2020-11-27 16:02:10 +01:00
return & shareToken , nil
2020-02-11 15:36:12 +01:00
}
func ( r * mutationResolver ) DeleteShareToken ( ctx context . Context , tokenValue string ) ( * models . ShareToken , error ) {
user := auth . UserFromContext ( ctx )
if user == nil {
return nil , auth . ErrUnauthorized
}
2020-06-14 15:07:07 +02:00
token , err := getUserToken ( r . Database , user , tokenValue )
if err != nil {
return nil , err
}
2020-11-27 16:02:10 +01:00
if err := r . Database . Delete ( & token ) . Error ; err != nil {
2020-07-10 14:26:19 +02:00
return nil , errors . Wrapf ( err , "failed to delete share token (%s) from database" , tokenValue )
2020-06-14 15:07:07 +02:00
}
return token , nil
}
func ( r * mutationResolver ) ProtectShareToken ( ctx context . Context , tokenValue string , password * string ) ( * models . ShareToken , error ) {
user := auth . UserFromContext ( ctx )
if user == nil {
return nil , auth . ErrUnauthorized
}
token , err := getUserToken ( r . Database , user , tokenValue )
if err != nil {
return nil , err
}
2020-11-27 16:02:10 +01:00
hashedPassword , err := hashSharePassword ( password )
2020-06-14 15:07:07 +02:00
if err != nil {
return nil , err
}
2020-11-27 16:02:10 +01:00
token . Password = hashedPassword
if err := r . Database . Save ( & token ) . Error ; err != nil {
2020-07-10 14:26:19 +02:00
return nil , errors . Wrap ( err , "failed to update password for share token" )
2020-06-14 15:07:07 +02:00
}
2020-11-27 16:02:10 +01:00
return token , nil
2020-06-14 15:07:07 +02:00
}
func hashSharePassword ( password * string ) ( * string , error ) {
2020-11-27 16:02:10 +01:00
var hashedPassword * string = nil
2020-06-14 15:07:07 +02: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 generate hash for share password" )
2020-06-14 15:07:07 +02:00
}
2020-11-27 16:02:10 +01:00
hashedStr := string ( hashedPassBytes )
hashedPassword = & hashedStr
2020-06-14 15:07:07 +02:00
}
2020-11-27 16:02:10 +01:00
return hashedPassword , nil
2020-06-14 15:07:07 +02:00
}
2020-11-23 19:39:44 +01:00
func getUserToken ( db * gorm . DB , user * models . User , tokenValue string ) ( * models . ShareToken , error ) {
2020-11-27 16:02:10 +01:00
var token models . ShareToken
2020-12-17 22:29:24 +01:00
err := db . Where ( "share_tokens.value = ?" , tokenValue ) . Joins ( "Owner" ) . Where ( "Owner.id = ? OR Owner.admin = TRUE" , user . ID ) . First ( & token ) . Error
2020-11-27 16:02:10 +01:00
2020-02-11 15:36:12 +01:00
if err != nil {
2020-07-10 18:35:37 +02:00
return nil , errors . Wrap ( err , "failed to get user share token from database" )
2020-02-11 15:36:12 +01:00
}
2020-11-27 16:02:10 +01:00
return & token , nil
2020-02-09 21:25:33 +01:00
}