Unverified Commit d1e894f3 authored by Fernando Takagi's avatar Fernando Takagi Committed by GitHub
Browse files

Console session handling improvements. (#979)

parent d225d207
Loading
Loading
Loading
Loading
+22 −7
Original line number Diff line number Diff line
@@ -175,7 +175,7 @@ func StartConsoleServer(logger *zap.Logger, startupLogger *zap.Logger, db *sql.D
	serverOpts := []grpc.ServerOption{
		//grpc.StatsHandler(&ocgrpc.ServerHandler{IsPublicEndpoint: true}),
		grpc.MaxRecvMsgSize(int(config.GetConsole().MaxMessageSizeBytes)),
		grpc.UnaryInterceptor(consoleInterceptorFunc(logger, config, consoleSessionCache)),
		grpc.UnaryInterceptor(consoleInterceptorFunc(logger, config, consoleSessionCache, loginAttemptCache)),
	}
	grpcServer := grpc.NewServer(serverOpts...)

@@ -438,7 +438,7 @@ func (s *ConsoleServer) Stop() {
	s.grpcServer.GracefulStop()
}

func consoleInterceptorFunc(logger *zap.Logger, config Config, sessionCache SessionCache) func(context.Context, interface{}, *grpc.UnaryServerInfo, grpc.UnaryHandler) (interface{}, error) {
func consoleInterceptorFunc(logger *zap.Logger, config Config, sessionCache SessionCache, loginAttmeptCache LoginAttemptCache) func(context.Context, interface{}, *grpc.UnaryServerInfo, grpc.UnaryHandler) (interface{}, error) {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		if info.FullMethod == "/nakama.console.Console/Authenticate" {
			// Skip authentication check for Login endpoint.
@@ -464,7 +464,7 @@ func consoleInterceptorFunc(logger *zap.Logger, config Config, sessionCache Sess
			return nil, status.Error(codes.Unauthenticated, "Console authentication required.")
		}

		if ctx, ok = checkAuth(ctx, config, auth[0], sessionCache); !ok {
		if ctx, ok = checkAuth(ctx, logger, config, auth[0], sessionCache, loginAttmeptCache); !ok {
			return nil, status.Error(codes.Unauthenticated, "Console authentication invalid.")
		}
		role := ctx.Value(ctxConsoleRoleKey{}).(console.UserRole)
@@ -478,7 +478,7 @@ func consoleInterceptorFunc(logger *zap.Logger, config Config, sessionCache Sess
	}
}

func checkAuth(ctx context.Context, config Config, auth string, sessionCache SessionCache) (context.Context, bool) {
func checkAuth(ctx context.Context, logger *zap.Logger, config Config, auth string, sessionCache SessionCache, loginAttemptCache LoginAttemptCache) (context.Context, bool) {
	const basicPrefix = "Basic "
	const bearerPrefix = "Bearer "

@@ -488,9 +488,24 @@ func checkAuth(ctx context.Context, config Config, auth string, sessionCache Ses
		if !ok {
			return ctx, false
		}

		if username != config.GetConsole().Username || password != config.GetConsole().Password {
			// Username and/or password do not match.
		ip, _ := extractClientAddressFromContext(logger, ctx)
		if !loginAttemptCache.Allow(username, ip) {
			return ctx, false
		}
		if username == config.GetConsole().Username {
			if password != config.GetConsole().Password {
				// Admin password does not match.
				if lockout, until := loginAttemptCache.Add(config.GetConsole().Username, ip); lockout != LockoutTypeNone {
					switch lockout {
					case LockoutTypeAccount:
						logger.Info(fmt.Sprintf("Console admin account locked until %v.", until))
					case LockoutTypeIp:
						logger.Info(fmt.Sprintf("Console admin IP locked until %v.", until))
					}
				}
				return ctx, false
			}
		} else {
			return ctx, false
		}

+1 −1
Original line number Diff line number Diff line
@@ -54,7 +54,7 @@ func (s *ConsoleServer) importStorage(w http.ResponseWriter, r *http.Request) {
		}
		return
	}
	ctx, ok := checkAuth(r.Context(), s.config, auth, s.consoleSessionCache)
	ctx, ok := checkAuth(r.Context(), s.logger, s.config, auth, s.consoleSessionCache, s.loginAttemptCache)
	if !ok {
		w.WriteHeader(401)
		if _, err := w.Write([]byte("Console authentication invalid.")); err != nil {
+12 −12
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package server
import (
	"bytes"
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"github.com/jackc/pgconn"
@@ -118,13 +119,14 @@ func (s *ConsoleServer) dbInsertConsoleUser(ctx context.Context, in *console.Add
}

func (s *ConsoleServer) DeleteUser(ctx context.Context, in *console.Username) (*emptypb.Empty, error) {

	if deleted, err := s.dbDeleteConsoleUser(ctx, in.Username); err != nil {
	deleted, id, err := s.dbDeleteConsoleUser(ctx, in.Username)
	if err != nil {
		s.logger.Error("failed to delete console user", zap.Error(err), zap.String("username", in.Username))
		return nil, status.Error(codes.Internal, "Internal Server Error")
	} else if !deleted {
		return nil, status.Error(codes.InvalidArgument, "User not found")
	}
	s.consoleSessionCache.RemoveAll(id)

	return &emptypb.Empty{}, nil
}
@@ -154,17 +156,15 @@ func (s *ConsoleServer) dbListConsoleUsers(ctx context.Context) ([]*console.User
	return result, nil
}

func (s *ConsoleServer) dbDeleteConsoleUser(ctx context.Context, username string) (bool, error) {
	res, err := s.db.ExecContext(ctx, "DELETE FROM console_user WHERE username = $1", username)
	if err != nil {
		return false, err
func (s *ConsoleServer) dbDeleteConsoleUser(ctx context.Context, username string) (bool, uuid.UUID, error) {
	var deletedID uuid.UUID
	if err := s.db.QueryRowContext(ctx, "DELETE FROM console_user WHERE username = $1 RETURNING id", username).Scan(&deletedID); err != nil {
		if err == sql.ErrNoRows {
			return false, uuid.Nil, nil
		}
	if n, err := res.RowsAffected(); err != nil {
		return false, err
	} else if n == 0 {
		return false, nil
		return false, uuid.Nil, err
	}
	return true, nil
	return true, deletedID, nil
}

func isValidPassword(pwd string) bool {