Loading CHANGELOG.md +2 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr ## [Unreleased] ### Added - Strict validation of socket timeout configuration parameters. - New runtime constants representing storage permissions. - New runtime function to programmatically delete user accounts. ### Changed - Default maximum database connection lifetime is now 1 hour. Loading runtime/runtime.go +14 −10 Original line number Diff line number Diff line Loading @@ -136,21 +136,23 @@ const ( // Tick rate defined for this match. Only applicable to server authoritative multiplayer. RUNTIME_CTX_MATCH_TICK_RATE = "match_tick_rate" ) //PERM_PUBLIC_READ is permission for public read means that any user can read that object. PERM_PUBLIC_READ = 2 const ( // Storage permission for public read, any user can read the object. STORAGE_PERMISSION_PUBLIC_READ = 2 //PERM_OWNER_READ may only be accessed by the user who owns it. No other client may access the object. PERM_OWNER_READ = 1 // Storage permission for owner read, only the user who owns it may access. STORAGE_PERMISSION_OWNER_READ = 1 //PERM_NO_READ is only reachable by server. PERM_NO_READ = 0 // Storage permission for no read. The object is only readable by server runtime. STORAGE_PERMISSION_NO_READ = 0 //PERM_OWNER_WRITE may only be modified by the user who owns it. No other client may access the object. PERM_OWNER_WRITE = 1 // Storage permission for owner write, only the user who owns it may write. STORAGE_PERMISSION_OWNER_WRITE = 1 //PERM_NO_WRITE is only arrangable by server. PERM_NO_WRITE = 0 // Storage permission for no write. The object is only writable by server runtime. STORAGE_PERMISSION_NO_WRITE = 0 ) /* Loading Loading @@ -762,6 +764,8 @@ type NakamaModule interface { AccountsGetId(ctx context.Context, userIDs []string) ([]*api.Account, error) AccountUpdateId(ctx context.Context, userID, username string, metadata map[string]interface{}, displayName, timezone, location, langTag, avatarUrl string) error AccountDeleteId(ctx context.Context, userID string, recorded bool) error UsersGetId(ctx context.Context, userIDs []string) ([]*api.User, error) UsersGetUsername(ctx context.Context, usernames []string) ([]*api.User, error) UsersBanId(ctx context.Context, userIDs []string) error Loading server/console_account.go +1 −35 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ import ( "context" "database/sql" "github.com/cockroachdb/cockroach-go/crdb" "github.com/gofrs/uuid" "github.com/golang/protobuf/ptypes/empty" "github.com/heroiclabs/nakama/api" Loading @@ -34,41 +33,8 @@ func (s *ConsoleServer) DeleteAccount(ctx context.Context, in *console.AccountDe return nil, status.Error(codes.InvalidArgument, "Invalid user ID was provided.") } tx, err := s.db.BeginTx(ctx, nil) err := DeleteAccount(ctx, s.logger, s.db, userID, in.RecordDeletion != nil && in.RecordDeletion.Value) if err != nil { s.logger.Error("Could not begin database transaction.", zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while trying to delete the user.") } if err := crdb.ExecuteInTx(ctx, tx, func() error { count, err := DeleteUser(ctx, tx, userID) if err != nil { s.logger.Debug("Could not delete user", zap.Error(err), zap.String("user_id", in.Id)) return err } else if count == 0 { s.logger.Info("No user was found to delete. Skipping blacklist.", zap.String("user_id", in.Id)) return nil } err = LeaderboardRecordsDeleteAll(ctx, s.logger, tx, userID) if err != nil { s.logger.Debug("Could not delete leaderboard records.", zap.Error(err), zap.String("user_id", in.Id)) return err } err = GroupDeleteAll(ctx, s.logger, tx, userID) if err != nil { s.logger.Debug("Could not delete groups and relationships.", zap.Error(err), zap.String("user_id", in.Id)) return err } if in.RecordDeletion == nil || in.RecordDeletion.GetValue() { return s.RecordAccountDeletion(ctx, tx, userID) } return nil }); err != nil { s.logger.Error("Error occurred while trying to delete the user.", zap.Error(err), zap.String("user_id", in.Id)) return nil, status.Error(codes.Internal, "An error occurred while trying to delete the user.") } Loading server/console_gdpr.go +0 −10 Original line number Diff line number Diff line Loading @@ -16,8 +16,6 @@ package server import ( "context" "database/sql" "encoding/json" "github.com/gofrs/uuid" Loading @@ -29,14 +27,6 @@ import ( "google.golang.org/grpc/status" ) func (s *ConsoleServer) RecordAccountDeletion(ctx context.Context, tx *sql.Tx, userID uuid.UUID) error { if _, err := tx.ExecContext(ctx, `INSERT INTO user_tombstone (user_id) VALUES ($1) ON CONFLICT(user_id) DO NOTHING`, userID); err != nil { s.logger.Debug("Could not insert user ID into tombstone", zap.Error(err), zap.String("user_id", userID.String())) return err } return nil } func (s *ConsoleServer) ExportAccount(ctx context.Context, in *console.AccountIdRequest) (*console.AccountExport, error) { userID := uuid.FromStringOrNil(in.Id) if userID == uuid.Nil { Loading server/core_account.go +47 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package server import ( "context" "database/sql" "github.com/cockroachdb/cockroach-go/crdb" "strconv" "strings" Loading Loading @@ -299,3 +300,49 @@ func UpdateAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, userID u return nil } func DeleteAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, userID uuid.UUID, recorded bool) error { tx, err := db.BeginTx(ctx, nil) if err != nil { logger.Error("Could not begin database transaction.", zap.Error(err)) return err } if err := crdb.ExecuteInTx(ctx, tx, func() error { count, err := DeleteUser(ctx, tx, userID) if err != nil { logger.Debug("Could not delete user", zap.Error(err), zap.String("user_id", userID.String())) return err } else if count == 0 { logger.Info("No user was found to delete. Skipping blacklist.", zap.String("user_id", userID.String())) return nil } err = LeaderboardRecordsDeleteAll(ctx, logger, tx, userID) if err != nil { logger.Debug("Could not delete leaderboard records.", zap.Error(err), zap.String("user_id", userID.String())) return err } err = GroupDeleteAll(ctx, logger, tx, userID) if err != nil { logger.Debug("Could not delete groups and relationships.", zap.Error(err), zap.String("user_id", userID.String())) return err } if recorded { _, err = tx.ExecContext(ctx, `INSERT INTO user_tombstone (user_id) VALUES ($1) ON CONFLICT(user_id) DO NOTHING`, userID) if err != nil { logger.Debug("Could not insert user ID into tombstone", zap.Error(err), zap.String("user_id", userID.String())) return err } } return nil }); err != nil { logger.Error("Error occurred while trying to delete the user.", zap.Error(err), zap.String("user_id", userID.String())) return err } return nil } Loading
CHANGELOG.md +2 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,8 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr ## [Unreleased] ### Added - Strict validation of socket timeout configuration parameters. - New runtime constants representing storage permissions. - New runtime function to programmatically delete user accounts. ### Changed - Default maximum database connection lifetime is now 1 hour. Loading
runtime/runtime.go +14 −10 Original line number Diff line number Diff line Loading @@ -136,21 +136,23 @@ const ( // Tick rate defined for this match. Only applicable to server authoritative multiplayer. RUNTIME_CTX_MATCH_TICK_RATE = "match_tick_rate" ) //PERM_PUBLIC_READ is permission for public read means that any user can read that object. PERM_PUBLIC_READ = 2 const ( // Storage permission for public read, any user can read the object. STORAGE_PERMISSION_PUBLIC_READ = 2 //PERM_OWNER_READ may only be accessed by the user who owns it. No other client may access the object. PERM_OWNER_READ = 1 // Storage permission for owner read, only the user who owns it may access. STORAGE_PERMISSION_OWNER_READ = 1 //PERM_NO_READ is only reachable by server. PERM_NO_READ = 0 // Storage permission for no read. The object is only readable by server runtime. STORAGE_PERMISSION_NO_READ = 0 //PERM_OWNER_WRITE may only be modified by the user who owns it. No other client may access the object. PERM_OWNER_WRITE = 1 // Storage permission for owner write, only the user who owns it may write. STORAGE_PERMISSION_OWNER_WRITE = 1 //PERM_NO_WRITE is only arrangable by server. PERM_NO_WRITE = 0 // Storage permission for no write. The object is only writable by server runtime. STORAGE_PERMISSION_NO_WRITE = 0 ) /* Loading Loading @@ -762,6 +764,8 @@ type NakamaModule interface { AccountsGetId(ctx context.Context, userIDs []string) ([]*api.Account, error) AccountUpdateId(ctx context.Context, userID, username string, metadata map[string]interface{}, displayName, timezone, location, langTag, avatarUrl string) error AccountDeleteId(ctx context.Context, userID string, recorded bool) error UsersGetId(ctx context.Context, userIDs []string) ([]*api.User, error) UsersGetUsername(ctx context.Context, usernames []string) ([]*api.User, error) UsersBanId(ctx context.Context, userIDs []string) error Loading
server/console_account.go +1 −35 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ import ( "context" "database/sql" "github.com/cockroachdb/cockroach-go/crdb" "github.com/gofrs/uuid" "github.com/golang/protobuf/ptypes/empty" "github.com/heroiclabs/nakama/api" Loading @@ -34,41 +33,8 @@ func (s *ConsoleServer) DeleteAccount(ctx context.Context, in *console.AccountDe return nil, status.Error(codes.InvalidArgument, "Invalid user ID was provided.") } tx, err := s.db.BeginTx(ctx, nil) err := DeleteAccount(ctx, s.logger, s.db, userID, in.RecordDeletion != nil && in.RecordDeletion.Value) if err != nil { s.logger.Error("Could not begin database transaction.", zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while trying to delete the user.") } if err := crdb.ExecuteInTx(ctx, tx, func() error { count, err := DeleteUser(ctx, tx, userID) if err != nil { s.logger.Debug("Could not delete user", zap.Error(err), zap.String("user_id", in.Id)) return err } else if count == 0 { s.logger.Info("No user was found to delete. Skipping blacklist.", zap.String("user_id", in.Id)) return nil } err = LeaderboardRecordsDeleteAll(ctx, s.logger, tx, userID) if err != nil { s.logger.Debug("Could not delete leaderboard records.", zap.Error(err), zap.String("user_id", in.Id)) return err } err = GroupDeleteAll(ctx, s.logger, tx, userID) if err != nil { s.logger.Debug("Could not delete groups and relationships.", zap.Error(err), zap.String("user_id", in.Id)) return err } if in.RecordDeletion == nil || in.RecordDeletion.GetValue() { return s.RecordAccountDeletion(ctx, tx, userID) } return nil }); err != nil { s.logger.Error("Error occurred while trying to delete the user.", zap.Error(err), zap.String("user_id", in.Id)) return nil, status.Error(codes.Internal, "An error occurred while trying to delete the user.") } Loading
server/console_gdpr.go +0 −10 Original line number Diff line number Diff line Loading @@ -16,8 +16,6 @@ package server import ( "context" "database/sql" "encoding/json" "github.com/gofrs/uuid" Loading @@ -29,14 +27,6 @@ import ( "google.golang.org/grpc/status" ) func (s *ConsoleServer) RecordAccountDeletion(ctx context.Context, tx *sql.Tx, userID uuid.UUID) error { if _, err := tx.ExecContext(ctx, `INSERT INTO user_tombstone (user_id) VALUES ($1) ON CONFLICT(user_id) DO NOTHING`, userID); err != nil { s.logger.Debug("Could not insert user ID into tombstone", zap.Error(err), zap.String("user_id", userID.String())) return err } return nil } func (s *ConsoleServer) ExportAccount(ctx context.Context, in *console.AccountIdRequest) (*console.AccountExport, error) { userID := uuid.FromStringOrNil(in.Id) if userID == uuid.Nil { Loading
server/core_account.go +47 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package server import ( "context" "database/sql" "github.com/cockroachdb/cockroach-go/crdb" "strconv" "strings" Loading Loading @@ -299,3 +300,49 @@ func UpdateAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, userID u return nil } func DeleteAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, userID uuid.UUID, recorded bool) error { tx, err := db.BeginTx(ctx, nil) if err != nil { logger.Error("Could not begin database transaction.", zap.Error(err)) return err } if err := crdb.ExecuteInTx(ctx, tx, func() error { count, err := DeleteUser(ctx, tx, userID) if err != nil { logger.Debug("Could not delete user", zap.Error(err), zap.String("user_id", userID.String())) return err } else if count == 0 { logger.Info("No user was found to delete. Skipping blacklist.", zap.String("user_id", userID.String())) return nil } err = LeaderboardRecordsDeleteAll(ctx, logger, tx, userID) if err != nil { logger.Debug("Could not delete leaderboard records.", zap.Error(err), zap.String("user_id", userID.String())) return err } err = GroupDeleteAll(ctx, logger, tx, userID) if err != nil { logger.Debug("Could not delete groups and relationships.", zap.Error(err), zap.String("user_id", userID.String())) return err } if recorded { _, err = tx.ExecContext(ctx, `INSERT INTO user_tombstone (user_id) VALUES ($1) ON CONFLICT(user_id) DO NOTHING`, userID) if err != nil { logger.Debug("Could not insert user ID into tombstone", zap.Error(err), zap.String("user_id", userID.String())) return err } } return nil }); err != nil { logger.Error("Error occurred while trying to delete the user.", zap.Error(err), zap.String("user_id", userID.String())) return err } return nil }