Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Console session handling improvements. (#979)
  • Loading branch information
ftkg committed Jan 25, 2023
1 parent d225d20 commit d1e894f
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 20 deletions.
29 changes: 22 additions & 7 deletions server/console.go
Expand Up @@ -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...)

Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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 "

Expand All @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion server/console_storage_import.go
Expand Up @@ -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 {
Expand Down
24 changes: 12 additions & 12 deletions server/console_user.go
Expand Up @@ -17,6 +17,7 @@ package server
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"errors"
"github.com/jackc/pgconn"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
if n, err := res.RowsAffected(); err != nil {
return false, err
} else if n == 0 {
return false, nil
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
}
return false, uuid.Nil, err
}
return true, nil
return true, deletedID, nil
}

func isValidPassword(pwd string) bool {
Expand Down

0 comments on commit d1e894f

Please sign in to comment.