Implement self service user password change
This commit is contained in:
parent
8cdc4cd136
commit
a3b25f4afa
10
api/go.mod
10
api/go.mod
|
@ -33,6 +33,7 @@ require (
|
|||
|
||||
require (
|
||||
github.com/agnivade/levenshtein v1.1.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
|
@ -46,9 +47,18 @@ require (
|
|||
github.com/jackc/pgx/v4 v4.16.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/matryer/moq v0.2.7 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.14 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.8.1 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
@ -21,6 +21,7 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I
|
|||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -123,6 +124,7 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
|
||||
github.com/matryer/moq v0.2.7 h1:RtpiPUM8L7ZSCbSwK+QcZH/E9tgqAkFjKQxsRs25b4w=
|
||||
github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
|
@ -153,6 +155,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
|
||||
|
@ -179,6 +182,7 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/strukturag/libheif v1.12.0 h1:Z5V5lCC5xbI59V77b4kGXcHsOohA6tQIvzLGIjhLsfY=
|
||||
github.com/strukturag/libheif v1.12.0/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
|
||||
github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
|
||||
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
|
||||
github.com/vektah/gqlparser/v2 v2.4.6 h1:Yjzp66g6oVq93Jihbi0qhGnf/6zIWjcm8H6gA27zstE=
|
||||
github.com/vektah/gqlparser/v2 v2.4.6/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
|
||||
|
@ -186,6 +190,7 @@ github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlV
|
|||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
|
||||
github.com/xor-gate/goexif2 v1.1.0 h1:OvTZ5iEvsDhRWFjV5xY3wT7uHFna28nSSP7ucau+cXQ=
|
||||
github.com/xor-gate/goexif2 v1.1.0/go.mod h1:eRjn3VSkAwpNpxEx/CGmd0zg0JFGL3akrSMxnJ581AY=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
|
@ -219,6 +224,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
|
|||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
@ -243,6 +249,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -263,12 +270,14 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
|
|
|
@ -173,6 +173,7 @@ type ComplexityRoot struct {
|
|||
SetThumbnailDownsampleMethod func(childComplexity int, method models.ThumbnailFilter) int
|
||||
ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int
|
||||
ShareMedia func(childComplexity int, mediaID int, expire *time.Time, password *string) int
|
||||
UpdatePassword func(childComplexity int, currentPassword string, newPassword string) int
|
||||
UpdateUser func(childComplexity int, id int, username *string, password *string, admin *bool) int
|
||||
UserAddRootPath func(childComplexity int, id int, rootPath string) int
|
||||
UserRemoveRootAlbum func(childComplexity int, userID int, albumID int) int
|
||||
|
@ -189,6 +190,11 @@ type ComplexityRoot struct {
|
|||
Type func(childComplexity int) int
|
||||
}
|
||||
|
||||
PasswordChangeResult struct {
|
||||
Message func(childComplexity int) int
|
||||
Success func(childComplexity int) int
|
||||
}
|
||||
|
||||
Query struct {
|
||||
Album func(childComplexity int, id int, tokenCredentials *models.ShareTokenCredentials) int
|
||||
FaceGroup func(childComplexity int, id int) int
|
||||
|
@ -322,6 +328,7 @@ type MutationResolver interface {
|
|||
ProtectShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error)
|
||||
FavoriteMedia(ctx context.Context, mediaID int, favorite bool) (*models.Media, error)
|
||||
UpdateUser(ctx context.Context, id int, username *string, password *string, admin *bool) (*models.User, error)
|
||||
UpdatePassword(ctx context.Context, currentPassword string, newPassword string) (*models.PasswordChangeResult, error)
|
||||
CreateUser(ctx context.Context, username string, password *string, admin bool) (*models.User, error)
|
||||
DeleteUser(ctx context.Context, id int) (*models.User, error)
|
||||
UserAddRootPath(ctx context.Context, id int, rootPath string) (*models.Album, error)
|
||||
|
@ -1096,6 +1103,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Mutation.ShareMedia(childComplexity, args["mediaId"].(int), args["expire"].(*time.Time), args["password"].(*string)), true
|
||||
|
||||
case "Mutation.updatePassword":
|
||||
if e.complexity.Mutation.UpdatePassword == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_updatePassword_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.UpdatePassword(childComplexity, args["currentPassword"].(string), args["newPassword"].(string)), true
|
||||
|
||||
case "Mutation.updateUser":
|
||||
if e.complexity.Mutation.UpdateUser == nil {
|
||||
break
|
||||
|
@ -1188,6 +1207,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Notification.Type(childComplexity), true
|
||||
|
||||
case "PasswordChangeResult.message":
|
||||
if e.complexity.PasswordChangeResult.Message == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.PasswordChangeResult.Message(childComplexity), true
|
||||
|
||||
case "PasswordChangeResult.success":
|
||||
if e.complexity.PasswordChangeResult.Success == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.PasswordChangeResult.Success(childComplexity), true
|
||||
|
||||
case "Query.album":
|
||||
if e.complexity.Query.Album == nil {
|
||||
break
|
||||
|
@ -2259,6 +2292,30 @@ func (ec *executionContext) field_Mutation_shareMedia_args(ctx context.Context,
|
|||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_updatePassword_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["currentPassword"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("currentPassword"))
|
||||
arg0, err = ec.unmarshalNString2string(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["currentPassword"] = arg0
|
||||
var arg1 string
|
||||
if tmp, ok := rawArgs["newPassword"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("newPassword"))
|
||||
arg1, err = ec.unmarshalNString2string(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["newPassword"] = arg1
|
||||
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{}{}
|
||||
|
@ -6683,6 +6740,87 @@ func (ec *executionContext) fieldContext_Mutation_updateUser(ctx context.Context
|
|||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_updatePassword(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_updatePassword(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
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().UpdatePassword(rctx, fc.Args["currentPassword"].(string), fc.Args["newPassword"].(string))
|
||||
}
|
||||
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||
if ec.directives.IsAuthorized == nil {
|
||||
return nil, errors.New("directive isAuthorized is not implemented")
|
||||
}
|
||||
return ec.directives.IsAuthorized(ctx, nil, directive0)
|
||||
}
|
||||
|
||||
tmp, err := directive1(rctx)
|
||||
if err != nil {
|
||||
return nil, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
if tmp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if data, ok := tmp.(*models.PasswordChangeResult); ok {
|
||||
return data, nil
|
||||
}
|
||||
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/photoview/photoview/api/graphql/models.PasswordChangeResult`, tmp)
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*models.PasswordChangeResult)
|
||||
fc.Result = res
|
||||
return ec.marshalNPasswordChangeResult2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPasswordChangeResult(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_updatePassword(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Mutation",
|
||||
Field: field,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "success":
|
||||
return ec.fieldContext_PasswordChangeResult_success(ctx, field)
|
||||
case "message":
|
||||
return ec.fieldContext_PasswordChangeResult_message(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type PasswordChangeResult", field.Name)
|
||||
},
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = ec.Recover(ctx, r)
|
||||
ec.Error(ctx, err)
|
||||
}
|
||||
}()
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
if fc.Args, err = ec.field_Mutation_updatePassword_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_createUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_createUser(ctx, field)
|
||||
if err != nil {
|
||||
|
@ -8305,6 +8443,91 @@ func (ec *executionContext) fieldContext_Notification_timeout(ctx context.Contex
|
|||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _PasswordChangeResult_success(ctx context.Context, field graphql.CollectedField, obj *models.PasswordChangeResult) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_PasswordChangeResult_success(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Success, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(bool)
|
||||
fc.Result = res
|
||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_PasswordChangeResult_success(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "PasswordChangeResult",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type Boolean does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _PasswordChangeResult_message(ctx context.Context, field graphql.CollectedField, obj *models.PasswordChangeResult) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_PasswordChangeResult_message(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Message, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*string)
|
||||
fc.Result = res
|
||||
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_PasswordChangeResult_message(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "PasswordChangeResult",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type String does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Query_siteInfo(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Query_siteInfo(ctx, field)
|
||||
if err != nil {
|
||||
|
@ -14755,6 +14978,15 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
|||
return ec._Mutation_updateUser(ctx, field)
|
||||
})
|
||||
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "updatePassword":
|
||||
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_updatePassword(ctx, field)
|
||||
})
|
||||
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
|
@ -14969,6 +15201,38 @@ func (ec *executionContext) _Notification(ctx context.Context, sel ast.Selection
|
|||
return out
|
||||
}
|
||||
|
||||
var passwordChangeResultImplementors = []string{"PasswordChangeResult"}
|
||||
|
||||
func (ec *executionContext) _PasswordChangeResult(ctx context.Context, sel ast.SelectionSet, obj *models.PasswordChangeResult) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, passwordChangeResultImplementors)
|
||||
out := graphql.NewFieldSet(fields)
|
||||
var invalids uint32
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("PasswordChangeResult")
|
||||
case "success":
|
||||
|
||||
out.Values[i] = ec._PasswordChangeResult_success(ctx, field, obj)
|
||||
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "message":
|
||||
|
||||
out.Values[i] = ec._PasswordChangeResult_message(ctx, field, obj)
|
||||
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch()
|
||||
if invalids > 0 {
|
||||
return graphql.Null
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var queryImplementors = []string{"Query"}
|
||||
|
||||
func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
|
||||
|
@ -16682,6 +16946,20 @@ func (ec *executionContext) marshalNNotificationType2githubᚗcomᚋphotoviewᚋ
|
|||
return v
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNPasswordChangeResult2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPasswordChangeResult(ctx context.Context, sel ast.SelectionSet, v models.PasswordChangeResult) graphql.Marshaler {
|
||||
return ec._PasswordChangeResult(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNPasswordChangeResult2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPasswordChangeResult(ctx context.Context, sel ast.SelectionSet, v *models.PasswordChangeResult) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return ec._PasswordChangeResult(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNScannerResult2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐScannerResult(ctx context.Context, sel ast.SelectionSet, v models.ScannerResult) graphql.Marshaler {
|
||||
return ec._ScannerResult(ctx, sel, &v)
|
||||
}
|
||||
|
|
|
@ -63,6 +63,11 @@ type Pagination struct {
|
|||
Offset *int `json:"offset"`
|
||||
}
|
||||
|
||||
type PasswordChangeResult struct {
|
||||
Success bool `json:"success"`
|
||||
Message *string `json:"message"`
|
||||
}
|
||||
|
||||
type ScannerResult struct {
|
||||
Finished bool `json:"finished"`
|
||||
Success bool `json:"success"`
|
||||
|
|
|
@ -73,6 +73,21 @@ func (u *UserPreferences) BeforeSave(tx *gorm.DB) error {
|
|||
|
||||
var ErrorInvalidUserCredentials = errors.New("invalid credentials")
|
||||
|
||||
func ValidateUserPassword(user *User, password string) error {
|
||||
if user.Password == nil {
|
||||
return errors.New("user does not have a password")
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(password)); err != nil {
|
||||
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||
return ErrorInvalidUserCredentials
|
||||
} else {
|
||||
return errors.Wrap(err, "compare user password hash")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AuthorizeUser(db *gorm.DB, username string, password string) (*User, error) {
|
||||
var user User
|
||||
|
||||
|
@ -84,16 +99,9 @@ func AuthorizeUser(db *gorm.DB, username string, password string) (*User, error)
|
|||
return nil, errors.Wrap(result.Error, "failed to get user by username when authorizing")
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, ErrorInvalidUserCredentials
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "compare user password hash")
|
||||
}
|
||||
err := ValidateUserPassword(&user, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
|
|
|
@ -200,7 +200,6 @@ func (r *mutationResolver) ChangeUserPreferences(ctx context.Context, language *
|
|||
return &userPref, nil
|
||||
}
|
||||
|
||||
// Admin queries
|
||||
func (r *mutationResolver) UpdateUser(ctx context.Context, id int, username *string, password *string, admin *bool) (*models.User, error) {
|
||||
db := r.DB(ctx)
|
||||
|
||||
|
@ -238,6 +237,39 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id int, username *str
|
|||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UpdatePassword(ctx context.Context, currentPassword, password string) (*models.PasswordChangeResult, error) {
|
||||
db := r.DB(ctx)
|
||||
user := auth.UserFromContext(ctx)
|
||||
|
||||
if err := models.ValidateUserPassword(user, currentPassword); err != nil {
|
||||
return createPasswordChangeErrorResult("Invalid current password")
|
||||
}
|
||||
|
||||
hashedPassBytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashedPass := string(hashedPassBytes)
|
||||
|
||||
user.Password = &hashedPass
|
||||
|
||||
if err := db.Save(&user).Error; err != nil {
|
||||
return createPasswordChangeErrorResult("Failed to update user your password has not changed")
|
||||
}
|
||||
|
||||
return &models.PasswordChangeResult{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createPasswordChangeErrorResult(msg string) (*models.PasswordChangeResult, error) {
|
||||
result := &models.PasswordChangeResult{
|
||||
Success: false,
|
||||
Message: &msg,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) CreateUser(ctx context.Context, username string, password *string, admin bool) (*models.User, error) {
|
||||
|
||||
var user *models.User
|
||||
|
|
|
@ -136,6 +136,7 @@ type Mutation {
|
|||
password: String
|
||||
admin: Boolean
|
||||
): User! @isAdmin
|
||||
updatePassword(currentPassword: String!, newPassword: String!): PasswordChangeResult! @isAuthorized
|
||||
"Create a new user"
|
||||
createUser(
|
||||
username: String!
|
||||
|
@ -186,6 +187,12 @@ type Mutation {
|
|||
detachImageFaces(imageFaceIDs: [ID!]!): FaceGroup! @isAuthorized
|
||||
}
|
||||
|
||||
type PasswordChangeResult {
|
||||
success: Boolean!
|
||||
message: String
|
||||
}
|
||||
|
||||
|
||||
type Subscription {
|
||||
notification: Notification!
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/99designs/gqlgen"
|
||||
)
|
|
@ -9,6 +9,7 @@ import ThumbnailPreferences from './ThumbnailPreferences'
|
|||
import UsersTable from './Users/UsersTable'
|
||||
import VersionInfo from './VersionInfo'
|
||||
import classNames from 'classnames'
|
||||
import PasswordChange from './Users/PasswordChange'
|
||||
|
||||
type SectionTitleProps = {
|
||||
children: string
|
||||
|
@ -43,6 +44,7 @@ const SettingsPage = () => {
|
|||
return (
|
||||
<Layout title={t('title.settings', 'Settings')}>
|
||||
<UserPreferences />
|
||||
{!isAdmin && <PasswordChange />}
|
||||
{isAdmin && (
|
||||
<>
|
||||
<ScannerSection />
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
import React, { useState } from 'react'
|
||||
import { Button, TextField } from '../../../primitives/form/Input'
|
||||
import { ApolloError, gql, useMutation } from '@apollo/client'
|
||||
import {
|
||||
updatePassword,
|
||||
updatePasswordVariables,
|
||||
} from './__generated__/updatePassword'
|
||||
import { SectionTitle } from '../SettingsPage'
|
||||
|
||||
export const USER_CHANGE_PASSWORD_MUTATION = gql`
|
||||
mutation updatePassword($currentPassword: String!, $newPassword: String!) {
|
||||
updatePassword(
|
||||
currentPassword: $currentPassword
|
||||
newPassword: $newPassword
|
||||
) {
|
||||
success
|
||||
message
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const initialState = {
|
||||
password1: '',
|
||||
password2: '',
|
||||
currentPassword: '',
|
||||
}
|
||||
|
||||
const errorMessage = (
|
||||
data: updatePassword | null | undefined,
|
||||
error: ApolloError | undefined
|
||||
) => {
|
||||
return (
|
||||
<div>
|
||||
{error && <span>Something went wrong</span>}
|
||||
{data &&
|
||||
data.updatePassword &&
|
||||
(data.updatePassword.success ? (
|
||||
<span>Successfully updated password</span>
|
||||
) : (
|
||||
<span style={{ color: 'red' }}>{data.updatePassword.message}</span>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const PasswordChange = () => {
|
||||
const [state, setState] = useState(initialState)
|
||||
|
||||
const [updatePassword, { data, error }] = useMutation<
|
||||
updatePassword,
|
||||
updatePasswordVariables
|
||||
>(USER_CHANGE_PASSWORD_MUTATION, {
|
||||
onCompleted: data => {
|
||||
if (data?.updatePassword.success) {
|
||||
setState(initialState)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function updateInput(
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
key: string
|
||||
) {
|
||||
setState({
|
||||
...state,
|
||||
[key]: event.target.value,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* TODO Add password confirmation field. */}
|
||||
<SectionTitle nospace>Change Password</SectionTitle>
|
||||
<TextField
|
||||
type="password"
|
||||
label="current password"
|
||||
value={state.currentPassword}
|
||||
onChange={e => updateInput(e, 'currentPassword')}
|
||||
/>
|
||||
<TextField
|
||||
type="password"
|
||||
label="new password"
|
||||
value={state.password1}
|
||||
onChange={e => updateInput(e, 'password1')}
|
||||
/>
|
||||
<TextField
|
||||
type="password"
|
||||
label="confirm password"
|
||||
value={state.password2}
|
||||
onChange={e => updateInput(e, 'password2')}
|
||||
/>
|
||||
{(state.password1 !== state.password2 && (
|
||||
<span style={{ color: 'red' }}>Passwords do not match</span>
|
||||
)) ||
|
||||
(state.password1 !== '' && (
|
||||
<Button
|
||||
style={{ marginTop: '5px' }}
|
||||
onClick={() =>
|
||||
updatePassword({
|
||||
variables: {
|
||||
currentPassword: state.currentPassword,
|
||||
newPassword: state.password1,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
Update Password
|
||||
</Button>
|
||||
))}
|
||||
{errorMessage(data, error)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PasswordChange
|
|
@ -0,0 +1,23 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: updatePassword
|
||||
// ====================================================
|
||||
|
||||
export interface updatePassword_updatePassword {
|
||||
__typename: 'PasswordChangeResult'
|
||||
success: boolean
|
||||
message: string | null
|
||||
}
|
||||
|
||||
export interface updatePassword {
|
||||
updatePassword: updatePassword_updatePassword
|
||||
}
|
||||
|
||||
export interface updatePasswordVariables {
|
||||
currentPassword: string
|
||||
newPassword: string
|
||||
}
|
Loading…
Reference in New Issue