From a1a907e81b6c07cc31cc20f6440d2efd507538e0 Mon Sep 17 00:00:00 2001 From: gilex-dev Date: Fri, 2 Feb 2024 21:23:32 +0100 Subject: [PATCH] add hasPrivilege & asUser directive, fix nil dereference in UpdateUser, add & rephrase error messages --- cmd/root.go | 4 +- database/crypto_helpers.go | 52 +++-- database/role.go | 8 +- database/todo.go | 41 +++- database/user.go | 55 ++++- graph/generated.go | 448 +++++++++++++++++++++++++++++++++---- graph/model/models_gen.go | 47 ++++ graph/schema.graphqls | 29 ++- graph/schema.resolvers.go | 71 +++++- server/main.go | 34 ++- 10 files changed, 695 insertions(+), 94 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 539b327..8b72e71 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -185,6 +185,6 @@ func initDB() { globals.Logger.Println("Connecting to SQLite3", db_path) globals.DB = database.InitSQLite3(db_path, globals.DB_schema, globals.Logger, []byte(viper.GetString("database.secret")), viper.GetString("database.initialAdmin.userName"), viper.GetString("database.initialAdmin.password")) - globals.DB.CleanExpiredRefreshTokensTicker(time.Minute * 10) - globals.DB.CleanRevokedAccessTokensTicker(time.Minute * 10) + globals.DB.CleanExpiredRefreshTokensTicker(time.Minute * 10) //TODO: add to viper + globals.DB.CleanRevokedAccessTokensTicker(time.Minute * 10) //TODO: add to viper } diff --git a/database/crypto_helpers.go b/database/crypto_helpers.go index dc95a0e..9af8326 100644 --- a/database/crypto_helpers.go +++ b/database/crypto_helpers.go @@ -94,13 +94,13 @@ func (db CustomDB) GenerateHashFromPassword(password string) (passwordHash []byt } // GetRefreshTokenOwner takes a tokenId and return the owner's userId. Call before Update/Get/DeleteRefreshToken when not IS_admin. -func (db CustomDB) GetRefreshTokenOwner(tokenId string) (string, error) { +func (db CustomDB) GetRefreshTokenOwner(tokenId string) (ownerId string, err error) { numTokenId, err := strconv.Atoi(tokenId) if err != nil { - return "", errors.New("invalid tokenId") + return "", errors.New("malformed refresh token Id") } - statement, err := db.connection.Prepare("SELECT FK_User_userId, FROM RefreshToken WHERE tokenId = ?") + statement, err := db.connection.Prepare("SELECT FK_User_userId FROM RefreshToken WHERE tokenId = ?") if err != nil { return "", err } @@ -108,6 +108,9 @@ func (db CustomDB) GetRefreshTokenOwner(tokenId string) (string, error) { result := statement.QueryRow(numTokenId) var owner string if err := result.Scan(&owner); err != nil { + if err == sql.ErrNoRows { + return "", errors.New("invalid refresh token Id") + } return "", err } @@ -118,7 +121,7 @@ func (db CustomDB) ValidateUserCredentials(userId *string, userName *string, pas var result *sql.Row var hash string - if userId != nil { // user userId + if userId != nil { // use userId numUserId, err := strconv.Atoi(*userId) if err != nil { return "", errors.New("userId not numeric") @@ -131,9 +134,12 @@ func (db CustomDB) ValidateUserCredentials(userId *string, userName *string, pas result = statement.QueryRow(numUserId) if err := result.Scan(&hash); err != nil { + if err == sql.ErrNoRows { + return "", errors.New("invalid user Id") + } return "", err } - } else if userName != nil { // user userName + } else if userName != nil { // use userName statement, err := db.connection.Prepare("SELECT userId, passwordHash FROM User WHERE userName = ?") if err != nil { return "", err @@ -141,6 +147,9 @@ func (db CustomDB) ValidateUserCredentials(userId *string, userName *string, pas result = statement.QueryRow(&userName) if err := result.Scan(&userId, &hash); err != nil { + if err == sql.ErrNoRows { + return "", errors.New("invalid user Id") + } return "", err } } else { @@ -184,6 +193,9 @@ func (db CustomDB) IssueRefreshToken(userId string, tokenName *string) (refreshT result := statement.QueryRow(numUserId, encSelector, base64.RawURLEncoding.EncodeToString(tokenHash[:]), &tokenName) if err := result.Scan(&tokenId, &expiryDate); err != nil { + if err == sql.ErrNoRows { + return nil, "", errors.New("failed to add new refresh token") + } return nil, "", err } @@ -193,7 +205,7 @@ func (db CustomDB) IssueRefreshToken(userId string, tokenName *string) (refreshT func (db CustomDB) GetRefreshToken(token *model.RefreshToken) (*model.RefreshToken, error) { numTokenId, err := strconv.Atoi(token.ID) if err != nil { - return nil, errors.New("invalid tokenId") + return nil, errors.New("malformed refresh token Id") } statement, err := db.connection.Prepare("SELECT expiryDate, tokenName FROM RefreshToken WHERE tokenId = ?") @@ -202,7 +214,10 @@ func (db CustomDB) GetRefreshToken(token *model.RefreshToken) (*model.RefreshTok } result := statement.QueryRow(numTokenId) - if err := result.Scan(&token.ID, &token.ExpiryDate, &token.TokenName); err != nil { + if err := result.Scan(&token.ExpiryDate, &token.TokenName); err != nil { + if err == sql.ErrNoRows { + return nil, errors.New("invalid refresh token Id") + } return nil, err } @@ -212,7 +227,7 @@ func (db CustomDB) GetRefreshToken(token *model.RefreshToken) (*model.RefreshTok func (db CustomDB) GetRefreshTokensFrom(userId string) ([]*model.RefreshToken, error) { numUserId, err := strconv.Atoi(userId) if err != nil { - return nil, errors.New("invalid userId") + return nil, errors.New("malformed userId") } statement, err := db.connection.Prepare("SELECT tokenId, expiaryDate, tokenName FROM RefreshToken WHERE FK_User_userId = ?") if err != nil { @@ -264,10 +279,10 @@ func (db CustomDB) GetAllRefreshTokens() ([]*model.RefreshToken, error) { func (db CustomDB) UpdateRefreshToken(tokenId string, changes *model.UpdateRefreshToken) (*model.RefreshToken, error) { numTokenId, err := strconv.Atoi(tokenId) if err != nil { - return nil, errors.New("invalid tokenId") + return nil, errors.New("malformed refresh token Id") } - statement, err := db.connection.Prepare("UPDATE AuthToken SET tokenName = ? WHERE tokenId = ?") + statement, err := db.connection.Prepare("UPDATE RefreshToken SET tokenName = ? WHERE tokenId = ?") if err != nil { return nil, err } @@ -300,7 +315,7 @@ func (db CustomDB) RevokeRefreshToken(tokenId string) (*string, error) { // TODO: return string instead of *string numTokenId, err := strconv.Atoi(tokenId) if err != nil { - return nil, errors.New("invalid tokenId") + return nil, errors.New("malformed refresh token Id") } statement, err := db.connection.Prepare("DELETE FROM RefreshToken WHERE tokenId = ? RETURNING FK_User_userId") @@ -312,6 +327,9 @@ func (db CustomDB) RevokeRefreshToken(tokenId string) (*string, error) { var userId string if err := result.Scan(&userId); err != nil { + if err == sql.ErrNoRows { + return nil, errors.New("invalid refresh token Id") + } return nil, err } @@ -321,7 +339,7 @@ func (db CustomDB) RevokeRefreshToken(tokenId string) (*string, error) { // IssueAccessToken issues an access token if the passed refresh token is valid. Returned access token must be passed to SignAccessToken to be accepted. func (db CustomDB) IssueAccessToken(refreshToken *RefreshToken) (*AccessToken, error) { - statement, err := db.connection.Prepare("SELECT RefreshToken.tokenHash, RefreshToken.FK_User_userId, Role.IS_admin, ROLE.IS_userCreator FROM RefreshToken INNER JOIN R_User_Role ON RefreshToken.FK_User_userId = R_User_Role.FK_User_userId INNER JOIN Role ON R_User_Role.FK_Role_roleId = Role.roleId WHERE RefreshToken.selector = ? AND RefreshToken.expiryDate >= unixepoch('now')") + statement, err := db.connection.Prepare("SELECT tokenHash, FK_User_userId FROM RefreshToken WHERE selector = ? AND expiryDate >= unixepoch('now')") if err != nil { return nil, err } @@ -330,7 +348,15 @@ func (db CustomDB) IssueAccessToken(refreshToken *RefreshToken) (*AccessToken, e var tokenHash string var newAccessToken AccessToken - if err := result.Scan(&tokenHash, &newAccessToken.UserId, &newAccessToken.IsAdmin, &newAccessToken.IsUserCreator); err != nil { + if err := result.Scan(&tokenHash, &newAccessToken.UserId); err != nil { + if err == sql.ErrNoRows { + return nil, errors.New("invalid refresh token selector") + } + return nil, err + } + + newAccessToken.IsAdmin, newAccessToken.IsUserCreator, err = db.GetUserPermissions(newAccessToken.UserId) + if err != nil { return nil, err } diff --git a/database/role.go b/database/role.go index 1b6f0a0..4ca2005 100644 --- a/database/role.go +++ b/database/role.go @@ -17,6 +17,7 @@ along with this program. If not, see . package database import ( + "database/sql" "errors" "strconv" @@ -62,6 +63,9 @@ func (db CustomDB) GetRole(role *model.Role) (*model.Role, error) { result := statement.QueryRow(id) if err := result.Scan(&role.RoleName, &role.IsAdmin, &role.IsUserCreator); err != nil { + if err == sql.ErrNoRows { + return nil, errors.New("invalid role Id") + } return nil, err } @@ -71,7 +75,7 @@ func (db CustomDB) GetRole(role *model.Role) (*model.Role, error) { func (db CustomDB) GetRolesFrom(userId string) ([]*model.RelationUserRole, error) { numUserId, err := strconv.Atoi(userId) if err != nil { - return nil, errors.New("invalid userId") + return nil, errors.New("malformed userId") } statement, err := db.connection.Prepare("SELECT Role.roleId, Role.roleName, Role.IS_admin, Role.IS_userCreator, R_User_Role.IS_roleManager FROM Role INNER JOIN R_User_Role ON R_User_Role.FK_Role_roleId = Role.roleId WHERE R_User_Role.FK_User_userId = ?") if err != nil { @@ -152,7 +156,7 @@ func (db CustomDB) UpdateRole(roleId string, changes *model.UpdateRole) (*model. id, err := strconv.Atoi(roleId) if err != nil { - return nil, errors.New("invalid userId") + return nil, errors.New("malformed userId") } statement, err := db.connection.Prepare("UPDATE Role SET roleName = IFNULL(?, roleName), IS_admin = IFNULL(?, IS_admin), IS_userCreator = IFNULL(?, IS_userCreator) WHERE roleId = ?") diff --git a/database/todo.go b/database/todo.go index 262ba9d..3afccaa 100644 --- a/database/todo.go +++ b/database/todo.go @@ -17,6 +17,7 @@ along with this program. If not, see . package database import ( + "database/sql" "errors" "strconv" @@ -27,7 +28,7 @@ import ( func (db CustomDB) GetOwner(todoId string) (string, error) { numTodoId, err := strconv.Atoi(todoId) if err != nil { - return "", errors.New("invalid todoId") + return "", errors.New("malformed todoId") } statement, err := db.connection.Prepare("SELECT FK_User_userId, FROM Todo WHERE todoId = ?") @@ -38,6 +39,9 @@ func (db CustomDB) GetOwner(todoId string) (string, error) { result := statement.QueryRow(numTodoId) var owner string if err := result.Scan(&owner); err != nil { + if err == sql.ErrNoRows { + return "", errors.New("invalid todo Id") + } return "", err } @@ -48,7 +52,7 @@ func (db CustomDB) GetOwner(todoId string) (string, error) { func (db CustomDB) GetTodo(todo *model.Todo) (*model.Todo, error) { numTodoId, err := strconv.Atoi(todo.ID) if err != nil { - return nil, errors.New("invalid todoId") + return nil, errors.New("malformed todoId malformatted todoId") } statement, err := db.connection.Prepare("SELECT text, IS_done, FK_User_userId FROM Todo WHERE todoId = ?") @@ -59,6 +63,9 @@ func (db CustomDB) GetTodo(todo *model.Todo) (*model.Todo, error) { todo.User = &model.User{} // TODO: check if this overrides something result := statement.QueryRow(numTodoId) if err := result.Scan(&todo.Text, &todo.Done, &todo.User.ID); err != nil { + if err == sql.ErrNoRows { + return nil, errors.New("invalid todo Id") + } return nil, err } @@ -69,7 +76,7 @@ func (db CustomDB) GetTodo(todo *model.Todo) (*model.Todo, error) { func (db CustomDB) GetTodosFrom(user *model.User) ([]*model.Todo, error) { id, err := strconv.Atoi(user.ID) if err != nil { - return nil, errors.New("invalid userId") + return nil, errors.New("malformed userId") } statement, err := db.connection.Prepare("SELECT todoId, text, IS_done FROM Todo WHERE FK_User_userId = ?") if err != nil { @@ -146,7 +153,7 @@ func (db CustomDB) UpdateTodo(todoId string, changes *model.UpdateTodo) (*model. numTodoId, err := strconv.Atoi(todoId) if err != nil { - return nil, errors.New("invalid todoId") + return nil, errors.New("malformed todoId") } statement, err := db.connection.Prepare("UPDATE Todo SET text = IFNULL(?, text), IS_done = IFNULL(?, IS_done) WHERE todoId = ?") @@ -174,7 +181,7 @@ func (db CustomDB) UpdateTodo(todoId string, changes *model.UpdateTodo) (*model. func (db CustomDB) DeleteTodo(todoId string) (deletedTodoId *string, err error) { numTodoId, err := strconv.Atoi(todoId) if err != nil { - return nil, errors.New("invalid todoId") + return nil, errors.New("malformed todoId") } statement, err := db.connection.Prepare("DELETE FROM Todo WHERE todoId = ?") @@ -198,3 +205,27 @@ func (db CustomDB) DeleteTodo(todoId string) (deletedTodoId *string, err error) return &todoId, nil } + +// GetTodoOwner takes a *model.Todo with at least ID set and returns an *model.User with Id set to the todo's owner Id. +func (db CustomDB) GetTodoOwner(todo *model.Todo) (owner *model.User, err error) { + numTodoId, err := strconv.Atoi(todo.ID) + if err != nil { + return nil, errors.New("malformed todoId") + } + + statement, err := db.connection.Prepare("SELECT FK_User_userId FROM Todo WHERE todoId = ?") + if err != nil { + return nil, err + } + + user := &model.User{} // TODO: check if this overrides something + result := statement.QueryRow(numTodoId) + if err := result.Scan(&user.ID); err != nil { + if err == sql.ErrNoRows { + return nil, errors.New("invalid todo Id") + } + return nil, err + } + + return user, nil +} diff --git a/database/user.go b/database/user.go index b1f72f4..99955c2 100644 --- a/database/user.go +++ b/database/user.go @@ -17,6 +17,7 @@ along with this program. If not, see . package database import ( + "database/sql" "errors" "strconv" "time" @@ -68,7 +69,7 @@ func (db CustomDB) CreateUser(newUser model.NewUser) (*model.User, error) { func (db CustomDB) GetUser(user *model.User) (*model.User, error) { numUserId, err := strconv.Atoi(user.ID) if err != nil { - return nil, errors.New("invalid userId") + return nil, errors.New("malformed userId") } statement, err := db.connection.Prepare("SELECT userID, userName, fullName FROM User WHERE userId = ? OR userName = ?") if err != nil { @@ -77,6 +78,9 @@ func (db CustomDB) GetUser(user *model.User) (*model.User, error) { result := statement.QueryRow(numUserId, user.UserName) if err := result.Scan(&user.ID, &user.UserName, &user.FullName); err != nil { + if err == sql.ErrNoRows { + return nil, errors.New("invalid user Id") + } return nil, err } @@ -108,24 +112,22 @@ func (db CustomDB) UpdateUser(userId string, changes *model.UpdateUser) (*model. id, err := strconv.Atoi(userId) if err != nil { - return nil, errors.New("invalid userId") + return nil, errors.New("malformed userId") } - statement, err := db.connection.Prepare("UPDATE User SET userName = IFNULL(?, userName), fullName = IFNULL(NULLIF(?, ''), fullName), passwordHash = IFNULL(?, passwordHash) WHERE userId = ?") + statement, err := db.connection.Prepare("UPDATE User SET userName = IFNULL(?, userName), fullName = NULLIF(?, ''), passwordHash = IFNULL(?, passwordHash) WHERE userId = ?") if err != nil { return nil, err } - if *changes.UserName == "" { // interpret empty string as nil - changes.UserName = nil - } else { + if changes.UserName != nil && *changes.UserName != "" { // ignore empty string if err := ValidateUserName(*changes.UserName); err != nil { return nil, err } needAccessTokenRefresh = true } - if *changes.Password == "" { // interpret empty string as nil + if changes.Password == nil { // interpret empty string as nil passwordHash = nil } else { if err := ValidatePassword(*changes.Password); err != nil { @@ -188,7 +190,7 @@ func (db CustomDB) DeleteUser(userId string) (*string, error) { func (db CustomDB) AddUserRole(userId string, roleId string, isRoleManager bool) (relationId string, err error) { encUserId, err := strconv.Atoi(userId) if err != nil { - return "", errors.New("invalid userId") + return "", errors.New("malformed userId") } encRoleId, err := strconv.Atoi(roleId) if err != nil { @@ -226,7 +228,7 @@ func (db CustomDB) AddUserRole(userId string, roleId string, isRoleManager bool) func (db CustomDB) UpdateUserRole(userId string, roleId string, isRoleManager bool) (relationId string, err error) { encUserId, err := strconv.Atoi(userId) if err != nil { - return "", errors.New("invalid userId") + return "", errors.New("malformed userId") } encRoleId, err := strconv.Atoi(roleId) if err != nil { @@ -264,7 +266,7 @@ func (db CustomDB) UpdateUserRole(userId string, roleId string, isRoleManager bo func (db CustomDB) RemoveUserRole(userId string, roleId string) (relationId string, err error) { encUserId, err := strconv.Atoi(userId) if err != nil { - return "", errors.New("invalid userId") + return "", errors.New("malformed userId") } encRoleId, err := strconv.Atoi(roleId) if err != nil { @@ -293,3 +295,36 @@ func (db CustomDB) RemoveUserRole(userId string, roleId string) (relationId stri RevokeAccessToken(&AccessToken{UserId: userId, ExpiryDate: int(time.Now().Add(accessTokenLifetime).Unix())}) return strconv.FormatInt(int64(encRoleId), 10), nil } + +func (db CustomDB) GetUserPermissions(userId string) (isAdmin bool, isUserCreator bool, err error) { + numUserId, err := strconv.Atoi(userId) + if err != nil { + return false, false, errors.New("malformed userId") + } + statement, err := db.connection.Prepare("SELECT Role.IS_admin, Role.IS_userCreator FROM R_User_Role INNER JOIN Role ON R_User_Role.FK_Role_roleId = Role.roleId WHERE R_User_Role.FK_User_userId = ?") + if err != nil { + return false, false, err + } + + rows, err := statement.Query(numUserId) + if err != nil { + return false, false, err + } + + defer rows.Close() + + var admin, userCreator bool + for rows.Next() { + var gotAdmin, gotUserCreator bool + if err := rows.Scan(&gotAdmin, &gotUserCreator); err != nil { + return false, false, err + } + if gotAdmin { + admin = true + } + if gotUserCreator { + userCreator = true + } + } + return admin, userCreator, nil +} diff --git a/graph/generated.go b/graph/generated.go index 042fe29..b283935 100644 --- a/graph/generated.go +++ b/graph/generated.go @@ -47,6 +47,8 @@ type ResolverRoot interface { } type DirectiveRoot struct { + AsUser func(ctx context.Context, obj interface{}, next graphql.Resolver, id string) (res interface{}, err error) + HasPrivilege func(ctx context.Context, obj interface{}, next graphql.Resolver, privilege model.Privilege) (res interface{}, err error) } type ComplexityRoot struct { @@ -347,12 +349,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.UpdateUser(childComplexity, args["id"].(string), args["changes"].(model.UpdateUser)), true - case "Mutation.UpdateUserRole": + case "Mutation.updateUserRole": if e.complexity.Mutation.UpdateUserRole == nil { break } - args, err := ec.field_Mutation_UpdateUserRole_args(context.TODO(), rawArgs) + args, err := ec.field_Mutation_updateUserRole_args(context.TODO(), rawArgs) if err != nil { return 0, false } @@ -630,7 +632,9 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { if first { first = false ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap) - data = ec._Query(ctx, rc.Operation.SelectionSet) + data = ec._queryMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error) { + return ec._Query(ctx, rc.Operation.SelectionSet), nil + }) } else { if atomic.LoadInt32(&ec.pendingDeferred) > 0 { result := <-ec.deferredResults @@ -660,7 +664,9 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { } first = false ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap) - data := ec._Mutation(ctx, rc.Operation.SelectionSet) + data := ec._mutationMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error) { + return ec._Mutation(ctx, rc.Operation.SelectionSet), nil + }) var buf bytes.Buffer data.MarshalGQL(&buf) @@ -735,36 +741,33 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** -func (ec *executionContext) field_Mutation_UpdateUserRole_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { +func (ec *executionContext) dir_asUser_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["userId"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userId")) + if tmp, ok := rawArgs["id"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) arg0, err = ec.unmarshalNID2string(ctx, tmp) if err != nil { return nil, err } } - args["userId"] = arg0 - var arg1 string - if tmp, ok := rawArgs["roleId"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roleId")) - arg1, err = ec.unmarshalNID2string(ctx, tmp) + args["id"] = arg0 + return args, nil +} + +func (ec *executionContext) dir_hasPrivilege_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.Privilege + if tmp, ok := rawArgs["privilege"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("privilege")) + arg0, err = ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, tmp) if err != nil { return nil, err } } - args["roleId"] = arg1 - var arg2 bool - if tmp, ok := rawArgs["userIsRoleManager"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userIsRoleManager")) - arg2, err = ec.unmarshalNBoolean2bool(ctx, tmp) - if err != nil { - return nil, err - } - } - args["userIsRoleManager"] = arg2 + args["privilege"] = arg0 return args, nil } @@ -1017,6 +1020,39 @@ func (ec *executionContext) field_Mutation_updateTodo_args(ctx context.Context, return args, nil } +func (ec *executionContext) field_Mutation_updateUserRole_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["userId"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userId")) + arg0, err = ec.unmarshalNID2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["userId"] = arg0 + var arg1 string + if tmp, ok := rawArgs["roleId"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roleId")) + arg1, err = ec.unmarshalNID2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["roleId"] = arg1 + var arg2 bool + if tmp, ok := rawArgs["userIsRoleManager"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userIsRoleManager")) + arg2, err = ec.unmarshalNBoolean2bool(ctx, tmp) + if err != nil { + return nil, err + } + } + args["userIsRoleManager"] = arg2 + 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{}{} @@ -1150,6 +1186,72 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg // region ************************** directives.gotpl ************************** +func (ec *executionContext) _queryMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler { + + for _, d := range obj.Directives { + switch d.Name { + case "asUser": + rawArgs := d.ArgumentMap(ec.Variables) + args, err := ec.dir_asUser_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + n := next + next = func(ctx context.Context) (interface{}, error) { + if ec.directives.AsUser == nil { + return nil, errors.New("directive asUser is not implemented") + } + return ec.directives.AsUser(ctx, obj, n, args["id"].(string)) + } + } + } + tmp, err := next(ctx) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if data, ok := tmp.(graphql.Marshaler); ok { + return data + } + ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp) + return graphql.Null + +} + +func (ec *executionContext) _mutationMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler { + + for _, d := range obj.Directives { + switch d.Name { + case "asUser": + rawArgs := d.ArgumentMap(ec.Variables) + args, err := ec.dir_asUser_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + n := next + next = func(ctx context.Context) (interface{}, error) { + if ec.directives.AsUser == nil { + return nil, errors.New("directive asUser is not implemented") + } + return ec.directives.AsUser(ctx, obj, n, args["id"].(string)) + } + } + } + tmp, err := next(ctx) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if data, ok := tmp.(graphql.Marshaler); ok { + return data + } + ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp) + return graphql.Null + +} + // endregion ************************** directives.gotpl ************************** // region **************************** field.gotpl ***************************** @@ -1167,8 +1269,32 @@ func (ec *executionContext) _Mutation_createUser(ctx context.Context, field grap } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().CreateUser(rctx, fc.Args["input"].(model.NewUser)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateUser(rctx, fc.Args["input"].(model.NewUser)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isUserCreator") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*model.User); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model.User`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -1299,8 +1425,32 @@ func (ec *executionContext) _Mutation_createRole(ctx context.Context, field grap } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().CreateRole(rctx, fc.Args["input"].(model.NewRole)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CreateRole(rctx, fc.Args["input"].(model.NewRole)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isAdmin") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*model.Role); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model.Role`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -1567,8 +1717,32 @@ func (ec *executionContext) _Mutation_updateRole(ctx context.Context, field grap } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().UpdateRole(rctx, fc.Args["id"].(string), fc.Args["changes"].(model.UpdateRole)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdateRole(rctx, fc.Args["id"].(string), fc.Args["changes"].(model.UpdateRole)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isAdmin") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*model.Role); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model.Role`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -1703,8 +1877,32 @@ func (ec *executionContext) _Mutation_deleteUser(ctx context.Context, field grap } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().DeleteUser(rctx, fc.Args["id"].(string)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().DeleteUser(rctx, fc.Args["id"].(string)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isAdmin") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*string); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *string`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -1807,8 +2005,32 @@ func (ec *executionContext) _Mutation_deleteRole(ctx context.Context, field grap } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().DeleteRole(rctx, fc.Args["id"].(string)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().DeleteRole(rctx, fc.Args["id"].(string)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isAdmin") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*string); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *string`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -1911,8 +2133,32 @@ func (ec *executionContext) _Mutation_addUserRole(ctx context.Context, field gra } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().AddUserRole(rctx, fc.Args["userId"].(string), fc.Args["roleId"].(string), fc.Args["userIsRoleManager"].(bool)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().AddUserRole(rctx, fc.Args["userId"].(string), fc.Args["roleId"].(string), fc.Args["userIsRoleManager"].(bool)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isAdmin") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.([]*model.RelationUserRole); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be []*somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model.RelationUserRole`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -1959,8 +2205,8 @@ func (ec *executionContext) fieldContext_Mutation_addUserRole(ctx context.Contex return fc, nil } -func (ec *executionContext) _Mutation_UpdateUserRole(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Mutation_UpdateUserRole(ctx, field) +func (ec *executionContext) _Mutation_updateUserRole(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_updateUserRole(ctx, field) if err != nil { return graphql.Null } @@ -1972,8 +2218,32 @@ func (ec *executionContext) _Mutation_UpdateUserRole(ctx context.Context, field } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().UpdateUserRole(rctx, fc.Args["userId"].(string), fc.Args["roleId"].(string), fc.Args["userIsRoleManager"].(bool)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdateUserRole(rctx, fc.Args["userId"].(string), fc.Args["roleId"].(string), fc.Args["userIsRoleManager"].(bool)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isAdmin") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.([]*model.RelationUserRole); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be []*somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model.RelationUserRole`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -1990,7 +2260,7 @@ func (ec *executionContext) _Mutation_UpdateUserRole(ctx context.Context, field return ec.marshalNRelationUserRole2ᚕᚖsomepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐRelationUserRoleᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Mutation_UpdateUserRole(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Mutation_updateUserRole(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Mutation", Field: field, @@ -2013,7 +2283,7 @@ func (ec *executionContext) fieldContext_Mutation_UpdateUserRole(ctx context.Con } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Mutation_UpdateUserRole_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Mutation_updateUserRole_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } @@ -2033,8 +2303,32 @@ func (ec *executionContext) _Mutation_removeUserRole(ctx context.Context, field } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().RemoveUserRole(rctx, fc.Args["userId"].(string), fc.Args["roleId"].(string)) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().RemoveUserRole(rctx, fc.Args["userId"].(string), fc.Args["roleId"].(string)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isAdmin") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.([]*model.RelationUserRole); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be []*somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model.RelationUserRole`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -2094,8 +2388,32 @@ func (ec *executionContext) _Query_todos(ctx context.Context, field graphql.Coll } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Todos(rctx) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Todos(rctx) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isAdmin") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.([]*model.Todo); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be []*somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model.Todo`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -2260,8 +2578,32 @@ func (ec *executionContext) _Query_refreshTokens(ctx context.Context, field grap } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().RefreshTokens(rctx) + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().RefreshTokens(rctx) + } + directive1 := func(ctx context.Context) (interface{}, error) { + privilege, err := ec.unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx, "isAdmin") + if err != nil { + return nil, err + } + if ec.directives.HasPrivilege == nil { + return nil, errors.New("directive hasPrivilege is not implemented") + } + return ec.directives.HasPrivilege(ctx, nil, directive0, privilege) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.([]*model.RefreshToken); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be []*somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model.RefreshToken`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -6005,9 +6347,9 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } - case "UpdateUserRole": + case "updateUserRole": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { - return ec._Mutation_UpdateUserRole(ctx, field) + return ec._Mutation_updateUserRole(ctx, field) }) if out.Values[i] == graphql.Null { out.Invalids++ @@ -7095,6 +7437,16 @@ func (ec *executionContext) unmarshalNNewUser2somepiᚗddnsᚗnetᚋgiteaᚋgile return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx context.Context, v interface{}) (model.Privilege, error) { + var res model.Privilege + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNPrivilege2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐPrivilege(ctx context.Context, sel ast.SelectionSet, v model.Privilege) graphql.Marshaler { + return v +} + func (ec *executionContext) marshalNRefreshToken2somepiᚗddnsᚗnetᚋgiteaᚋgilexᚑdevᚋYetAnotherToDoListᚋgraphᚋmodelᚐRefreshToken(ctx context.Context, sel ast.SelectionSet, v model.RefreshToken) graphql.Marshaler { return ec._RefreshToken(ctx, sel, &v) } diff --git a/graph/model/models_gen.go b/graph/model/models_gen.go index e169de4..edb4618 100644 --- a/graph/model/models_gen.go +++ b/graph/model/models_gen.go @@ -2,6 +2,12 @@ package model +import ( + "fmt" + "io" + "strconv" +) + type NewRefreshToken struct { TokenName *string `json:"tokenName,omitempty"` } @@ -78,3 +84,44 @@ type User struct { Todos []*Todo `json:"todos"` Roles []*RelationUserRole `json:"roles"` } + +type Privilege string + +const ( + PrivilegeIsAdmin Privilege = "isAdmin" + PrivilegeIsUserCreator Privilege = "isUserCreator" +) + +var AllPrivilege = []Privilege{ + PrivilegeIsAdmin, + PrivilegeIsUserCreator, +} + +func (e Privilege) IsValid() bool { + switch e { + case PrivilegeIsAdmin, PrivilegeIsUserCreator: + return true + } + return false +} + +func (e Privilege) String() string { + return string(e) +} + +func (e *Privilege) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = Privilege(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid Privilege", str) + } + return nil +} + +func (e Privilege) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/graph/schema.graphqls b/graph/schema.graphqls index 0fe6eee..9cb2705 100644 --- a/graph/schema.graphqls +++ b/graph/schema.graphqls @@ -60,10 +60,10 @@ type RefreshToken { } type Query { - todos: [Todo!]! + todos: [Todo!]! @hasPrivilege(privilege: isAdmin) users: [User!]! roles: [Role!]! - refreshTokens: [RefreshToken!]! + refreshTokens: [RefreshToken!]! @hasPrivilege(privilege: isAdmin) user(id: ID!): User! todo(id: ID!): Todo! role(id: ID!): Role! @@ -113,27 +113,38 @@ input UpdateRefreshToken { } type Mutation { - createUser(input: NewUser!): User! + createUser(input: NewUser!): User! @hasPrivilege(privilege: isUserCreator) createTodo(input: NewTodo!): Todo! - createRole(input: NewRole!): Role! + createRole(input: NewRole!): Role! @hasPrivilege(privilege: isAdmin) createRefreshToken(input: NewRefreshToken!): RefreshToken! updateTodo(id: ID!, changes: UpdateTodo!): Todo! updateUser(id: ID!, changes: UpdateUser!): User! updateRole(id: ID!, changes: UpdateRole!): Role! + @hasPrivilege(privilege: isAdmin) updateRefreshToken(id: ID!, changes: UpdateRefreshToken!): RefreshToken! - deleteUser(id: ID!): ID + deleteUser(id: ID!): ID @hasPrivilege(privilege: isAdmin) deleteTodo(id: ID!): ID - deleteRole(id: ID!): ID + deleteRole(id: ID!): ID @hasPrivilege(privilege: isAdmin) deleteRefreshToken(id: ID!): ID addUserRole( userId: ID! roleId: ID! userIsRoleManager: Boolean! - ): [RelationUserRole!]! - UpdateUserRole( + ): [RelationUserRole!]! @hasPrivilege(privilege: isAdmin) + updateUserRole( userId: ID! roleId: ID! userIsRoleManager: Boolean! - ): [RelationUserRole!]! + ): [RelationUserRole!]! @hasPrivilege(privilege: isAdmin) removeUserRole(userId: ID!, roleId: ID!): [RelationUserRole!]! + @hasPrivilege(privilege: isAdmin) +} + +directive @hasPrivilege(privilege: Privilege!) on FIELD_DEFINITION + +directive @asUser(id: ID!) on MUTATION | QUERY + +enum Privilege { + isAdmin + isUserCreator } diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go index 4d3a7b0..8ebd08a 100644 --- a/graph/schema.resolvers.go +++ b/graph/schema.resolvers.go @@ -15,6 +15,7 @@ import ( // CreateUser is the resolver for the createUser field. func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) { + // Access managed by directive todo, err := globals.DB.CreateUser(input) if err != nil { globals.Logger.Println("Failed to add new user:", err) @@ -25,6 +26,9 @@ func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) // CreateTodo is the resolver for the createTodo field. func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) { + if auth.ForContext(ctx).UserId != input.UserID { + return nil, errors.New("only the owner can create a todo") + } todo, err := globals.DB.CreateTodo(input) if err != nil { globals.Logger.Println("Failed to add new todo:", err) @@ -35,6 +39,9 @@ func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) // CreateRole is the resolver for the createRole field. func (r *mutationResolver) CreateRole(ctx context.Context, input model.NewRole) (*model.Role, error) { + if !auth.ForContext(ctx).IsAdmin { + return nil, errors.New("only an admin can create a role") + } role, err := globals.DB.CreateRole(&input) if err != nil { globals.Logger.Println("Failed to add new role:", err) @@ -46,57 +53,92 @@ func (r *mutationResolver) CreateRole(ctx context.Context, input model.NewRole) // CreateRefreshToken is the resolver for the createRefreshToken field. func (r *mutationResolver) CreateRefreshToken(ctx context.Context, input model.NewRefreshToken) (*model.RefreshToken, error) { // TODO: unify model.RefreshToken & auth.RefreshToken - userToken := auth.ForContext(ctx) - refreshToken, tokenId, err := globals.DB.IssueRefreshToken(userToken.UserId, input.TokenName) + userId := auth.ForContext(ctx).UserId + refreshToken, tokenId, err := globals.DB.IssueRefreshToken(userId, input.TokenName) if err != nil { globals.Logger.Println("Failed to create refresh token:", err) return nil, errors.New("failed to create refresh token") } - return &model.RefreshToken{ID: tokenId, ExpiryDate: refreshToken.ExpiryDate, TokenName: input.TokenName, Selector: &refreshToken.Selector, Token: &refreshToken.Token, UserID: userToken.UserId}, nil + return &model.RefreshToken{ID: tokenId, ExpiryDate: refreshToken.ExpiryDate, TokenName: input.TokenName, Selector: &refreshToken.Selector, Token: &refreshToken.Token, UserID: userId}, nil } // UpdateTodo is the resolver for the updateTodo field. func (r *mutationResolver) UpdateTodo(ctx context.Context, id string, changes model.UpdateTodo) (*model.Todo, error) { + owner, err := globals.DB.GetTodoOwner(&model.Todo{ID: id}) + if err != nil { + return nil, err + } + if owner.ID != auth.ForContext(ctx).UserId && !auth.ForContext(ctx).IsAdmin { + return nil, errors.New("only the owner can update a todo") + } return globals.DB.UpdateTodo(id, &changes) } // UpdateUser is the resolver for the updateUser field. func (r *mutationResolver) UpdateUser(ctx context.Context, id string, changes model.UpdateUser) (*model.User, error) { + if auth.ForContext(ctx).UserId != id { + return nil, errors.New("can only update yourself") + } return globals.DB.UpdateUser(id, &changes) } // UpdateRole is the resolver for the updateRole field. func (r *mutationResolver) UpdateRole(ctx context.Context, id string, changes model.UpdateRole) (*model.Role, error) { + // Access managed by directive return globals.DB.UpdateRole(id, &changes) } // UpdateRefreshToken is the resolver for the updateRefreshToken field. func (r *mutationResolver) UpdateRefreshToken(ctx context.Context, id string, changes model.UpdateRefreshToken) (*model.RefreshToken, error) { + ownerId, err := globals.DB.GetRefreshTokenOwner(id) + if err != nil { + return nil, err + } + if ownerId != auth.ForContext(ctx).UserId && !auth.ForContext(ctx).IsAdmin { + return nil, errors.New("only the owner can update a refresh token") + } return globals.DB.UpdateRefreshToken(id, &changes) } // DeleteUser is the resolver for the deleteUser field. func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (*string, error) { + // Access managed by directive return globals.DB.DeleteUser(id) } // DeleteTodo is the resolver for the deleteTodo field. func (r *mutationResolver) DeleteTodo(ctx context.Context, id string) (*string, error) { + owner, err := globals.DB.GetTodoOwner(&model.Todo{ID: id}) + if err != nil { + return nil, err + } + if owner.ID != auth.ForContext(ctx).UserId && !auth.ForContext(ctx).IsAdmin { + return nil, errors.New("only the owner can delete a todo") + } return globals.DB.DeleteTodo(id) } // DeleteRole is the resolver for the deleteRole field. func (r *mutationResolver) DeleteRole(ctx context.Context, id string) (*string, error) { + // Access managed by directive return globals.DB.DeleteRole(id) } // DeleteRefreshToken is the resolver for the deleteRefreshToken field. func (r *mutationResolver) DeleteRefreshToken(ctx context.Context, id string) (*string, error) { + ownerId, err := globals.DB.GetRefreshTokenOwner(id) + if err != nil { + return nil, err + } + if ownerId != auth.ForContext(ctx).UserId && !auth.ForContext(ctx).IsAdmin { + return nil, errors.New("only the owner can delete a refresh token") + } return globals.DB.RevokeRefreshToken(id) } // AddUserRole is the resolver for the addUserRole field. func (r *mutationResolver) AddUserRole(ctx context.Context, userID string, roleID string, userIsRoleManager bool) ([]*model.RelationUserRole, error) { + // Access managed by directive if _, err := globals.DB.AddUserRole(userID, roleID, userIsRoleManager); err != nil { return nil, err } @@ -105,6 +147,7 @@ func (r *mutationResolver) AddUserRole(ctx context.Context, userID string, roleI // UpdateUserRole is the resolver for the UpdateUserRole field. func (r *mutationResolver) UpdateUserRole(ctx context.Context, userID string, roleID string, userIsRoleManager bool) ([]*model.RelationUserRole, error) { + // Access managed by directive if _, err := globals.DB.UpdateUserRole(userID, roleID, userIsRoleManager); err != nil { return nil, err } @@ -113,6 +156,7 @@ func (r *mutationResolver) UpdateUserRole(ctx context.Context, userID string, ro // RemoveUserRole is the resolver for the RemoveUserRole field. func (r *mutationResolver) RemoveUserRole(ctx context.Context, userID string, roleID string) ([]*model.RelationUserRole, error) { + // Access managed by directive if _, err := globals.DB.RemoveUserRole(userID, roleID); err != nil { return nil, err } @@ -121,6 +165,7 @@ func (r *mutationResolver) RemoveUserRole(ctx context.Context, userID string, ro // Todos is the resolver for the todos field. func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) { + // Access managed by directive return globals.DB.GetAllTodos() } @@ -136,6 +181,7 @@ func (r *queryResolver) Roles(ctx context.Context) ([]*model.Role, error) { // RefreshTokens is the resolver for the refreshTokens field. func (r *queryResolver) RefreshTokens(ctx context.Context) ([]*model.RefreshToken, error) { + // Access managed by directive return globals.DB.GetAllRefreshTokens() } @@ -146,6 +192,13 @@ func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error // Todo is the resolver for the todo field. func (r *queryResolver) Todo(ctx context.Context, id string) (*model.Todo, error) { + owner, err := globals.DB.GetTodoOwner(&model.Todo{ID: id}) + if err != nil { + return nil, err + } + if owner.ID != auth.ForContext(ctx).UserId && !auth.ForContext(ctx).IsAdmin { + return nil, errors.New("only the owner can view a todo") + } return globals.DB.GetTodo(&model.Todo{ID: id}) } @@ -156,7 +209,14 @@ func (r *queryResolver) Role(ctx context.Context, id string) (*model.Role, error // RefreshToken is the resolver for the refreshToken field. func (r *queryResolver) RefreshToken(ctx context.Context, id string) (*model.RefreshToken, error) { - return globals.DB.GetRefreshToken(&model.RefreshToken{ID: id}) + ownerId, err := globals.DB.GetRefreshTokenOwner(id) + if err != nil { + return nil, err + } + if ownerId != auth.ForContext(ctx).UserId && !auth.ForContext(ctx).IsAdmin { + return nil, errors.New("only the owner can view a refresh token") + } + return globals.DB.GetRefreshToken(&model.RefreshToken{ID: id, UserID: ownerId}) } // RoleMembers is the resolver for the roleMembers field. @@ -172,6 +232,9 @@ func (r *todoResolver) User(ctx context.Context, obj *model.Todo) (*model.User, // Todos is the resolver for the todos field. func (r *userResolver) Todos(ctx context.Context, obj *model.User) ([]*model.Todo, error) { + if auth.ForContext(ctx).UserId != obj.ID && !auth.ForContext(ctx).IsAdmin { + return nil, errors.New("only the owner can see this") + } return globals.DB.GetTodosFrom(obj) } diff --git a/server/main.go b/server/main.go index 6a0f055..010bcfc 100644 --- a/server/main.go +++ b/server/main.go @@ -17,15 +17,18 @@ along with this program. If not, see . package server import ( + "context" "fmt" "net/http" "strconv" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/globals" "somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph" + "somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model" "somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/server/auth" ) @@ -39,11 +42,40 @@ func StartServer(portHTTP int, portHTTPS int, certFile string, keyFile string) { fmt.Fprintf(w, "%s %s", globals.Version, globals.CommitHash) }) - srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}})) router.HandleFunc("/auth/login", auth.IssueRefreshTokenHandler) router.HandleFunc("/auth", auth.IssueAccessTokenHandler) router.Group(func(r chi.Router) { + config := graph.Config{Resolvers: &graph.Resolver{}} + config.Directives.HasPrivilege = func(ctx context.Context, obj interface{}, next graphql.Resolver, privilege model.Privilege) (interface{}, error) { + switch privilege { + case model.PrivilegeIsUserCreator: + if !auth.ForContext(ctx).IsUserCreator { + return nil, fmt.Errorf("access denied: you need IsUserCreator") + + } + case model.PrivilegeIsAdmin: + if !auth.ForContext(ctx).IsAdmin { + return nil, fmt.Errorf("access denied: you need IsAdmin") + } + + } + // or let it pass through + return next(ctx) + } + + config.Directives.AsUser = func(ctx context.Context, obj interface{}, next graphql.Resolver, id string) (interface{}, error) { + if !auth.ForContext(ctx).IsAdmin { + // block calling the next resolver + return nil, fmt.Errorf("access denied: you need IsAdmin to use the asUser directive") + } + // or let it pass through + fmt.Printf("Running as %s instead of %s\n", id, auth.ForContext(ctx).UserId) //DEBUG + auth.ForContext(ctx).UserId = id + return next(ctx) + } + + srv := handler.NewDefaultServer(graph.NewExecutableSchema(config)) r.Use(auth.Middleware()) r.Handle("/api", srv) r.HandleFunc("/protected", func(w http.ResponseWriter, r *http.Request) {