Commit 7e2f60c8 authored by Andrei Mihu's avatar Andrei Mihu
Browse files

New runtime function to programmatically delete user accounts.

parent e174e696
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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.
+14 −10
Original line number Diff line number Diff line
@@ -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
)

/*
@@ -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
+1 −35
Original line number Diff line number Diff line
@@ -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"
@@ -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.")
	}

+0 −10
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@ package server

import (
	"context"
	"database/sql"

	"encoding/json"

	"github.com/gofrs/uuid"
@@ -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 {
+47 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package server
import (
	"context"
	"database/sql"
	"github.com/cockroachdb/cockroach-go/crdb"
	"strconv"
	"strings"

@@ -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