1
Fork 0

Add user management to api

This commit is contained in:
viktorstrate 2020-02-16 12:22:00 +01:00
parent 108de3d6b8
commit a87e9fc56d
6 changed files with 535 additions and 23 deletions

View File

@ -1,7 +1,7 @@
CREATE TABLE IF NOT EXISTS user ( CREATE TABLE IF NOT EXISTS user (
user_id int NOT NULL AUTO_INCREMENT, user_id int NOT NULL AUTO_INCREMENT,
username varchar(256) NOT NULL UNIQUE, username varchar(256) NOT NULL UNIQUE,
password varchar(256) NOT NULL, password varchar(256),
root_path varchar(512), root_path varchar(512),
admin boolean NOT NULL DEFAULT 0, admin boolean NOT NULL DEFAULT 0,

View File

@ -69,13 +69,16 @@ type ComplexityRoot struct {
Mutation struct { Mutation struct {
AuthorizeUser func(childComplexity int, username string, password string) int AuthorizeUser func(childComplexity int, username string, password string) int
CreateUser func(childComplexity int, username string, rootPath string, password *string, admin bool) int
DeleteShareToken func(childComplexity int, token string) int DeleteShareToken func(childComplexity int, token string) int
DeleteUser func(childComplexity int, id int) int
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
RegisterUser func(childComplexity int, username string, password string, rootPath string) int RegisterUser func(childComplexity int, username string, password string, rootPath string) int
ScanAll func(childComplexity int) int ScanAll func(childComplexity int) int
ScanUser func(childComplexity int, userID int) int ScanUser func(childComplexity int, userID int) int
ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int
SharePhoto func(childComplexity int, photoID int, expire *time.Time, password *string) int SharePhoto func(childComplexity int, photoID int, expire *time.Time, password *string) int
UpdateUser func(childComplexity int, id int, username *string, rootPath *string, admin *bool) int
} }
Photo struct { Photo struct {
@ -172,6 +175,9 @@ type MutationResolver interface {
ShareAlbum(ctx context.Context, albumID int, expire *time.Time, password *string) (*models.ShareToken, error) ShareAlbum(ctx context.Context, albumID int, expire *time.Time, password *string) (*models.ShareToken, error)
SharePhoto(ctx context.Context, photoID int, expire *time.Time, password *string) (*models.ShareToken, error) SharePhoto(ctx context.Context, photoID int, expire *time.Time, password *string) (*models.ShareToken, error)
DeleteShareToken(ctx context.Context, token string) (*models.ShareToken, error) DeleteShareToken(ctx context.Context, token string) (*models.ShareToken, error)
UpdateUser(ctx context.Context, id int, username *string, rootPath *string, admin *bool) (*models.User, error)
CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error)
DeleteUser(ctx context.Context, id int) (*models.User, error)
} }
type PhotoResolver interface { type PhotoResolver interface {
Thumbnail(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error) Thumbnail(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error)
@ -319,6 +325,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.AuthorizeUser(childComplexity, args["username"].(string), args["password"].(string)), true return e.complexity.Mutation.AuthorizeUser(childComplexity, args["username"].(string), args["password"].(string)), true
case "Mutation.createUser":
if e.complexity.Mutation.CreateUser == nil {
break
}
args, err := ec.field_Mutation_createUser_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.CreateUser(childComplexity, args["username"].(string), args["rootPath"].(string), args["password"].(*string), args["admin"].(bool)), true
case "Mutation.deleteShareToken": case "Mutation.deleteShareToken":
if e.complexity.Mutation.DeleteShareToken == nil { if e.complexity.Mutation.DeleteShareToken == nil {
break break
@ -331,6 +349,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.DeleteShareToken(childComplexity, args["token"].(string)), true return e.complexity.Mutation.DeleteShareToken(childComplexity, args["token"].(string)), true
case "Mutation.deleteUser":
if e.complexity.Mutation.DeleteUser == nil {
break
}
args, err := ec.field_Mutation_deleteUser_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.DeleteUser(childComplexity, args["id"].(int)), true
case "Mutation.initialSetupWizard": case "Mutation.initialSetupWizard":
if e.complexity.Mutation.InitialSetupWizard == nil { if e.complexity.Mutation.InitialSetupWizard == nil {
break break
@ -398,6 +428,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.SharePhoto(childComplexity, args["photoId"].(int), args["expire"].(*time.Time), args["password"].(*string)), true return e.complexity.Mutation.SharePhoto(childComplexity, args["photoId"].(int), args["expire"].(*time.Time), args["password"].(*string)), true
case "Mutation.updateUser":
if e.complexity.Mutation.UpdateUser == nil {
break
}
args, err := ec.field_Mutation_updateUser_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.UpdateUser(childComplexity, args["id"].(int), args["username"].(*string), args["rootPath"].(*string), args["admin"].(*bool)), true
case "Photo.album": case "Photo.album":
if e.complexity.Photo.Album == nil { if e.complexity.Photo.Album == nil {
break break
@ -881,7 +923,7 @@ type Mutation {
): AuthorizeResult ): AuthorizeResult
"Scan all users for new photos" "Scan all users for new photos"
scanAll: ScannerResult! scanAll: ScannerResult! @isAdmin
"Scan a single user for new photos" "Scan a single user for new photos"
scanUser(userId: Int!): ScannerResult! scanUser(userId: Int!): ScannerResult!
@ -891,6 +933,20 @@ type Mutation {
sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken
"Delete a share token by it's token value" "Delete a share token by it's token value"
deleteShareToken(token: String!): ShareToken deleteShareToken(token: String!): ShareToken
updateUser(
id: Int!
username: String
rootPath: String
admin: Boolean
): User @isAdmin
createUser(
username: String!
rootPath: String!
password: String
admin: Boolean!
): User @isAdmin
deleteUser(id: Int!): User @isAdmin
} }
type AuthorizeResult { type AuthorizeResult {
@ -1066,6 +1122,44 @@ func (ec *executionContext) field_Mutation_authorizeUser_args(ctx context.Contex
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_createUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 string
if tmp, ok := rawArgs["username"]; ok {
arg0, err = ec.unmarshalNString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["username"] = arg0
var arg1 string
if tmp, ok := rawArgs["rootPath"]; ok {
arg1, err = ec.unmarshalNString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["rootPath"] = arg1
var arg2 *string
if tmp, ok := rawArgs["password"]; ok {
arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["password"] = arg2
var arg3 bool
if tmp, ok := rawArgs["admin"]; ok {
arg3, err = ec.unmarshalNBoolean2bool(ctx, tmp)
if err != nil {
return nil, err
}
}
args["admin"] = arg3
return args, nil
}
func (ec *executionContext) field_Mutation_deleteShareToken_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_deleteShareToken_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -1080,6 +1174,20 @@ func (ec *executionContext) field_Mutation_deleteShareToken_args(ctx context.Con
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_deleteUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 int
if tmp, ok := rawArgs["id"]; ok {
arg0, err = ec.unmarshalNInt2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["id"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_initialSetupWizard_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_initialSetupWizard_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -1214,6 +1322,44 @@ func (ec *executionContext) field_Mutation_sharePhoto_args(ctx context.Context,
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_updateUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 int
if tmp, ok := rawArgs["id"]; ok {
arg0, err = ec.unmarshalNInt2int(ctx, tmp)
if err != nil {
return nil, err
}
}
args["id"] = arg0
var arg1 *string
if tmp, ok := rawArgs["username"]; ok {
arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["username"] = arg1
var arg2 *string
if tmp, ok := rawArgs["rootPath"]; ok {
arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["rootPath"] = arg2
var arg3 *bool
if tmp, ok := rawArgs["admin"]; ok {
arg3, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp)
if err != nil {
return nil, err
}
}
args["admin"] = arg3
return args, nil
}
func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -1949,8 +2095,28 @@ func (ec *executionContext) _Mutation_scanAll(ctx context.Context, field graphql
ctx = graphql.WithResolverContext(ctx, rctx) ctx = graphql.WithResolverContext(ctx, rctx)
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children directive0 := func(rctx context.Context) (interface{}, error) {
return ec.resolvers.Mutation().ScanAll(rctx) ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().ScanAll(rctx)
}
directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.IsAdmin == nil {
return nil, errors.New("directive isAdmin is not implemented")
}
return ec.directives.IsAdmin(ctx, nil, directive0)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, err
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*models.ScannerResult); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/viktorstrate/photoview/api/graphql/models.ScannerResult`, tmp)
}) })
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@ -2135,6 +2301,189 @@ func (ec *executionContext) _Mutation_deleteShareToken(ctx context.Context, fiel
return ec.marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res) return ec.marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res)
} }
func (ec *executionContext) _Mutation_updateUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
ec.Tracer.EndFieldExecution(ctx)
}()
rctx := &graphql.ResolverContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithResolverContext(ctx, rctx)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_updateUser_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
rctx.Args = args
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().UpdateUser(rctx, args["id"].(int), args["username"].(*string), args["rootPath"].(*string), args["admin"].(*bool))
}
directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.IsAdmin == nil {
return nil, errors.New("directive isAdmin is not implemented")
}
return ec.directives.IsAdmin(ctx, nil, directive0)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, err
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*models.User); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/viktorstrate/photoview/api/graphql/models.User`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*models.User)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalOUser2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_createUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
ec.Tracer.EndFieldExecution(ctx)
}()
rctx := &graphql.ResolverContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithResolverContext(ctx, rctx)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_createUser_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
rctx.Args = args
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().CreateUser(rctx, args["username"].(string), args["rootPath"].(string), args["password"].(*string), args["admin"].(bool))
}
directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.IsAdmin == nil {
return nil, errors.New("directive isAdmin is not implemented")
}
return ec.directives.IsAdmin(ctx, nil, directive0)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, err
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*models.User); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/viktorstrate/photoview/api/graphql/models.User`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*models.User)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalOUser2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_deleteUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
ec.Tracer.EndFieldExecution(ctx)
}()
rctx := &graphql.ResolverContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithResolverContext(ctx, rctx)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_deleteUser_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
rctx.Args = args
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().DeleteUser(rctx, args["id"].(int))
}
directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.IsAdmin == nil {
return nil, errors.New("directive isAdmin is not implemented")
}
return ec.directives.IsAdmin(ctx, nil, directive0)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, err
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*models.User); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/viktorstrate/photoview/api/graphql/models.User`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*models.User)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalOUser2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx, field.Selections, res)
}
func (ec *executionContext) _Photo_id(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) { func (ec *executionContext) _Photo_id(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
ctx = ec.Tracer.StartFieldExecution(ctx, field) ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { defer func() {
@ -5401,6 +5750,12 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
out.Values[i] = ec._Mutation_sharePhoto(ctx, field) out.Values[i] = ec._Mutation_sharePhoto(ctx, field)
case "deleteShareToken": case "deleteShareToken":
out.Values[i] = ec._Mutation_deleteShareToken(ctx, field) out.Values[i] = ec._Mutation_deleteShareToken(ctx, field)
case "updateUser":
out.Values[i] = ec._Mutation_updateUser(ctx, field)
case "createUser":
out.Values[i] = ec._Mutation_createUser(ctx, field)
case "deleteUser":
out.Values[i] = ec._Mutation_deleteUser(ctx, field)
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@ -7030,6 +7385,17 @@ func (ec *executionContext) marshalOTime2ᚖtimeᚐTime(ctx context.Context, sel
return ec.marshalOTime2timeᚐTime(ctx, sel, *v) return ec.marshalOTime2timeᚐTime(ctx, sel, *v)
} }
func (ec *executionContext) marshalOUser2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx context.Context, sel ast.SelectionSet, v models.User) graphql.Marshaler {
return ec._User(ctx, sel, &v)
}
func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx context.Context, sel ast.SelectionSet, v *models.User) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._User(ctx, sel, v)
}
func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler { func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler {
if v == nil { if v == nil {
return graphql.Null return graphql.Null

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"os"
"time" "time"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -14,7 +15,7 @@ import (
type User struct { type User struct {
UserID int UserID int
Username string Username string
Password string Password *string
RootPath string RootPath string
Admin bool Admin bool
} }
@ -66,7 +67,11 @@ func AuthorizeUser(database *sql.DB, username string, password string) (*User, e
} }
} }
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { 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 {
if err == bcrypt.ErrMismatchedHashAndPassword { if err == bcrypt.ErrMismatchedHashAndPassword {
return nil, ErrorInvalidUserCredentials return nil, ErrorInvalidUserCredentials
} else { } else {
@ -77,15 +82,37 @@ func AuthorizeUser(database *sql.DB, username string, password string) (*User, e
return user, nil return user, nil
} }
func RegisterUser(database *sql.Tx, username string, password string, rootPath string) (*User, error) { var ErrorInvalidRootPath = errors.New("invalid root path")
hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
if err != nil {
return nil, err
}
hashedPass := string(hashedPassBytes)
if _, err := database.Exec("INSERT INTO user (username, password, root_path) VALUES (?, ?, ?)", username, hashedPass, rootPath); err != nil { func ValidRootPath(rootPath string) bool {
return nil, err _, err := os.Stat(rootPath)
if err != nil {
log.Printf("Warn: invalid root path: '%s'\n%s\n", rootPath, err)
return false
}
return true
}
func RegisterUser(database *sql.Tx, username string, password *string, rootPath string, admin bool) (*User, error) {
if !ValidRootPath(rootPath) {
return nil, ErrorInvalidRootPath
}
if password != nil {
hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(*password), 12)
if err != nil {
return nil, err
}
hashedPass := string(hashedPassBytes)
if _, err := database.Exec("INSERT INTO user (username, password, root_path, admin) VALUES (?, ?, ?, ?)", username, hashedPass, rootPath, admin); err != nil {
return nil, err
}
} else {
if _, err := database.Exec("INSERT INTO user (username, root_path, admin) VALUES (?, ?, ?)", username, rootPath, admin); err != nil {
return nil, err
}
} }
row := database.QueryRow("SELECT * FROM user WHERE username = ?", username) row := database.QueryRow("SELECT * FROM user WHERE username = ?", username)

View File

@ -3,6 +3,7 @@ package resolvers
import ( import (
"context" "context"
"errors" "errors"
"log"
"github.com/viktorstrate/photoview/api/graphql/auth" "github.com/viktorstrate/photoview/api/graphql/auth"
"github.com/viktorstrate/photoview/api/graphql/models" "github.com/viktorstrate/photoview/api/graphql/models"
@ -81,7 +82,7 @@ func (r *mutationResolver) RegisterUser(ctx context.Context, username string, pa
return nil, err return nil, err
} }
user, err := models.RegisterUser(tx, username, password, rootPath) user, err := models.RegisterUser(tx, username, &password, rootPath, false)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return &models.AuthorizeResult{ return &models.AuthorizeResult{
@ -127,7 +128,7 @@ func (r *mutationResolver) InitialSetupWizard(ctx context.Context, username stri
return nil, err return nil, err
} }
user, err := models.RegisterUser(tx, username, password, rootPath) user, err := models.RegisterUser(tx, username, &password, rootPath, true)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return &models.AuthorizeResult{ return &models.AuthorizeResult{
@ -136,11 +137,6 @@ func (r *mutationResolver) InitialSetupWizard(ctx context.Context, username stri
}, nil }, nil
} }
if _, err := tx.Exec("UPDATE user SET admin = true WHERE user_id = ?", user.UserID); err != nil {
tx.Rollback()
return nil, err
}
token, err := user.GenerateAccessToken(tx) token, err := user.GenerateAccessToken(tx)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
@ -157,3 +153,108 @@ func (r *mutationResolver) InitialSetupWizard(ctx context.Context, username stri
Token: &token.Value, Token: &token.Value,
}, nil }, nil
} }
// Admin queries
func (r *mutationResolver) UpdateUser(ctx context.Context, id int, username *string, rootPath *string, admin *bool) (*models.User, error) {
user_rows, err := r.Database.Query("SELECT * FROM user WHERE user_id = ?", id)
if err != nil {
return nil, err
}
if user_rows.Next() == false {
return nil, errors.New("user not found")
}
user_rows.Close()
update_str := ""
update_args := make([]interface{}, 0)
if username != nil {
update_str += "username = ?, "
update_args = append(update_args, username)
}
if rootPath != nil {
if !models.ValidRootPath(*rootPath) {
return nil, errors.New("invalid root path")
}
update_str += "root_path = ?, "
update_args = append(update_args, rootPath)
}
if admin != nil {
update_str += "admin = ?, "
update_args = append(update_args, admin)
}
if len(update_str) == 0 {
return nil, errors.New("no updates requested")
}
update_str = update_str[:len(update_str)-2]
log.Printf("Updating user with update string: %s\n", update_str)
update_args = append(update_args, id)
res, err := r.Database.Exec("UPDATE user SET "+update_str+" WHERE user_id = ?", update_args...)
if err != nil {
return nil, err
}
rows_aff, err := res.RowsAffected()
if err != nil {
return nil, err
}
if rows_aff == 0 {
return nil, errors.New("no users were updated")
}
row := r.Database.QueryRow("SELECT * FROM user WHERE user_id = ?", id)
user, err := models.NewUserFromRow(row)
if err != nil {
return nil, err
}
return user, nil
}
func (r *mutationResolver) CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error) {
tx, err := r.Database.Begin()
if err != nil {
return nil, err
}
user, err := models.RegisterUser(tx, username, password, rootPath, admin)
if err != nil {
tx.Rollback()
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
return user, nil
}
func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (*models.User, error) {
row := r.Database.QueryRow("SELECT * FROM user WHERE user_id = ?", id)
user, err := models.NewUserFromRow(row)
if err != nil {
return nil, err
}
res, err := r.Database.Exec("DELETE FROM user WHERE user_id = ?", id)
if err != nil {
return nil, err
}
rows, err := res.RowsAffected()
if err != nil {
return nil, err
}
if rows == 0 {
return nil, errors.New("no users deleted")
}
return user, nil
}

View File

@ -53,7 +53,7 @@ type Mutation {
): AuthorizeResult ): AuthorizeResult
"Scan all users for new photos" "Scan all users for new photos"
scanAll: ScannerResult! scanAll: ScannerResult! @isAdmin
"Scan a single user for new photos" "Scan a single user for new photos"
scanUser(userId: Int!): ScannerResult! scanUser(userId: Int!): ScannerResult!
@ -63,6 +63,20 @@ type Mutation {
sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken sharePhoto(photoId: Int!, expire: Time, password: String): ShareToken
"Delete a share token by it's token value" "Delete a share token by it's token value"
deleteShareToken(token: String!): ShareToken deleteShareToken(token: String!): ShareToken
updateUser(
id: Int!
username: String
rootPath: String
admin: Boolean
): User @isAdmin
createUser(
username: String!
rootPath: String!
password: String
admin: Boolean!
): User @isAdmin
deleteUser(id: Int!): User @isAdmin
} }
type AuthorizeResult { type AuthorizeResult {

View File

@ -5,7 +5,11 @@ import { Table, Button, Input, Checkbox } from 'semantic-ui-react'
import gql from 'graphql-tag' import gql from 'graphql-tag'
const createUserMutation = gql` const createUserMutation = gql`
mutation createUser($username: String, $rootPath: String, $admin: Boolean) { mutation createUser(
$username: String!
$rootPath: String!
$admin: Boolean!
) {
createUser(username: $username, rootPath: $rootPath, admin: $admin) { createUser(username: $username, rootPath: $rootPath, admin: $admin) {
id id
username username