Commit 6ff773a9 authored by Andrei Mihu's avatar Andrei Mihu
Browse files

Add social provider integration and user lookup online indicator. (#161)

parent fb64c470
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import (
	"github.com/golang/protobuf/jsonpb"
	"github.com/heroiclabs/nakama/migrate"
	"github.com/heroiclabs/nakama/server"
	"github.com/heroiclabs/nakama/social"
	_ "github.com/lib/pq"
	"go.uber.org/zap"
)
@@ -83,16 +84,18 @@ func main() {
	// Check migration status and log if the schema has diverged.
	migrate.StartupCheck(multiLogger, db)

	socialClient := social.NewClient(5 * time.Second)

	// Start up server components.
	registry := server.NewSessionRegistry()
	tracker := server.StartLocalTracker(jsonLogger, registry, jsonpbMarshaler, config.GetName())
	router := server.NewLocalMessageRouter(registry, tracker, jsonpbMarshaler)
	runtimePool, err := server.NewRuntimePool(jsonLogger, multiLogger, db, config, registry, tracker, router)
	runtimePool, err := server.NewRuntimePool(jsonLogger, multiLogger, db, config, socialClient, registry, tracker, router)
	if err != nil {
		multiLogger.Fatal("Failed initializing runtime modules", zap.Error(err))
	}
	pipeline := server.NewPipeline(config, db, registry, tracker, router, runtimePool)
	apiServer := server.StartApiServer(jsonLogger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, registry, tracker, router, pipeline, runtimePool)
	apiServer := server.StartApiServer(jsonLogger, db, jsonpbMarshaler, jsonpbUnmarshaler, config, socialClient, registry, tracker, router, pipeline, runtimePool)

	// Respect OS stop signals.
	c := make(chan os.Signal, 2)
+11 −8
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import (
	_ "google.golang.org/grpc/encoding/gzip" // enable gzip compression on server for grpc
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"github.com/heroiclabs/nakama/social"
)

// Keys used for storing/retrieving user information in the context of a request after authentication.
@@ -53,11 +54,12 @@ type ApiServer struct {
	runtimePool       *RuntimePool
	tracker           Tracker
	router            MessageRouter
	socialClient      *social.Client
	grpcServer        *grpc.Server
	grpcGatewayServer *http.Server
}

func StartApiServer(logger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, registry *SessionRegistry, tracker Tracker, router MessageRouter, pipeline *pipeline, runtimePool *RuntimePool) *ApiServer {
func StartApiServer(logger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Marshaler, jsonpbUnmarshaler *jsonpb.Unmarshaler, config Config, socialClient *social.Client, registry *SessionRegistry, tracker Tracker, router MessageRouter, pipeline *pipeline, runtimePool *RuntimePool) *ApiServer {
	grpcServer := grpc.NewServer(
		grpc.StatsHandler(ocgrpc.NewServerStatsHandler()),
		grpc.UnaryInterceptor(SecurityInterceptorFunc(logger, config)),
@@ -70,6 +72,7 @@ func StartApiServer(logger *zap.Logger, db *sql.DB, jsonpbMarshaler *jsonpb.Mars
		runtimePool:  runtimePool,
		tracker:      tracker,
		router:       router,
		socialClient: socialClient,
		grpcServer:   grpcServer,
	}

+1 −1
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ import (
func (s *ApiServer) GetAccount(ctx context.Context, in *empty.Empty) (*api.Account, error) {
	userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID)

	user, err := GetAccount(s.db, s.logger, userID)
	user, err := GetAccount(s.db, s.logger, s.tracker, userID)
	if err != nil {
		return nil, status.Error(codes.Internal, "Error retrieving user account.")
	}
+115 −5
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import (

	"github.com/dgrijalva/jwt-go"
	"github.com/heroiclabs/nakama/api"
	"github.com/satori/go.uuid"
	"golang.org/x/net/context"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
@@ -130,19 +131,128 @@ func (s *ApiServer) AuthenticateEmail(ctx context.Context, in *api.AuthenticateE
}

func (s *ApiServer) AuthenticateFacebook(ctx context.Context, in *api.AuthenticateFacebookRequest) (*api.Session, error) {
	return nil, nil
	if in.Account == nil || in.Account.Token == "" {
		return nil, status.Error(codes.InvalidArgument, "Facebook access token is required.")
	}

	username := in.Username
	username = strings.ToLower(username)
	if username == "" {
		username = generateUsername()
	} else if invalidCharsRegex.MatchString(username) {
		return nil, status.Error(codes.InvalidArgument, "Username invalid, no spaces or control characters allowed.")
	} else if len(username) > 128 {
		return nil, status.Error(codes.InvalidArgument, "Username invalid, must be 1-128 bytes.")
	}

	create := in.Create == nil || in.Create.Value

	dbUserID, dbUsername, err := AuthenticateFacebook(s.logger, s.db, s.socialClient, in.Account.Token, username, create)
	if err != nil {
		return nil, err
	}

	// Import friends if requested.
	if in.Import == nil || in.Import.Value {
		importFacebookFriends(s.logger, s.db, s.socialClient, uuid.FromStringOrNil(dbUserID), dbUsername, in.Account.Token)
	}

	token := generateToken(s.config, dbUserID, dbUsername)
	return &api.Session{Token: token}, nil
}

func (s *ApiServer) AuthenticateGameCenter(ctx context.Context, in *api.AuthenticateGameCenterRequest) (*api.Session, error) {
	return nil, nil
	if in.Account == nil {
		return nil, status.Error(codes.InvalidArgument, "GameCenter access credentials are required.")
	} else if in.Account.BundleId == "" {
		return nil, status.Error(codes.InvalidArgument, "GameCenter bundle ID is required.")
	} else if in.Account.PlayerId == "" {
		return nil, status.Error(codes.InvalidArgument, "GameCenter player ID is required.")
	} else if in.Account.PublicKeyUrl == "" {
		return nil, status.Error(codes.InvalidArgument, "GameCenter public key URL is required.")
	} else if in.Account.Salt == "" {
		return nil, status.Error(codes.InvalidArgument, "GameCenter salt is required.")
	} else if in.Account.Signature == "" {
		return nil, status.Error(codes.InvalidArgument, "GameCenter signature is required.")
	} else if in.Account.TimestampSeconds == 0 {
		return nil, status.Error(codes.InvalidArgument, "GameCenter timestamp is required.")
	}

	username := in.Username
	username = strings.ToLower(username)
	if username == "" {
		username = generateUsername()
	} else if invalidCharsRegex.MatchString(username) {
		return nil, status.Error(codes.InvalidArgument, "Username invalid, no spaces or control characters allowed.")
	} else if len(username) > 128 {
		return nil, status.Error(codes.InvalidArgument, "Username invalid, must be 1-128 bytes.")
	}

	create := in.Create == nil || in.Create.Value

	dbUserID, dbUsername, err := AuthenticateGameCenter(s.logger, s.db, s.socialClient, in.Account.PlayerId, in.Account.BundleId, in.Account.TimestampSeconds, in.Account.Salt, in.Account.Signature, in.Account.PublicKeyUrl, username, create)
	if err != nil {
		return nil, err
	}

	token := generateToken(s.config, dbUserID, dbUsername)
	return &api.Session{Token: token}, nil
}

func (s *ApiServer) AuthenticateGoogle(ctx context.Context, in *api.AuthenticateGoogleRequest) (*api.Session, error) {
	return nil, nil
	if in.Account == nil || in.Account.Token == "" {
		return nil, status.Error(codes.InvalidArgument, "Google access token is required.")
	}

	username := in.Username
	username = strings.ToLower(username)
	if username == "" {
		username = generateUsername()
	} else if invalidCharsRegex.MatchString(username) {
		return nil, status.Error(codes.InvalidArgument, "Username invalid, no spaces or control characters allowed.")
	} else if len(username) > 128 {
		return nil, status.Error(codes.InvalidArgument, "Username invalid, must be 1-128 bytes.")
	}

	create := in.Create == nil || in.Create.Value

	dbUserID, dbUsername, err := AuthenticateGoogle(s.logger, s.db, s.socialClient, in.Account.Token, username, create)
	if err != nil {
		return nil, err
	}

	token := generateToken(s.config, dbUserID, dbUsername)
	return &api.Session{Token: token}, nil
}

func (s *ApiServer) AuthenticateSteam(ctx context.Context, in *api.AuthenticateSteamRequest) (*api.Session, error) {
	return nil, nil
	if s.config.GetSocial().Steam.PublisherKey == "" || s.config.GetSocial().Steam.AppID == 0 {
		return nil, status.Error(codes.FailedPrecondition, "Steam authentication is not configured.")
	}

	if in.Account == nil || in.Account.Token == "" {
		return nil, status.Error(codes.InvalidArgument, "Steam access token is required.")
	}

	username := in.Username
	username = strings.ToLower(username)
	if username == "" {
		username = generateUsername()
	} else if invalidCharsRegex.MatchString(username) {
		return nil, status.Error(codes.InvalidArgument, "Username invalid, no spaces or control characters allowed.")
	} else if len(username) > 128 {
		return nil, status.Error(codes.InvalidArgument, "Username invalid, must be 1-128 bytes.")
	}

	create := in.Create == nil || in.Create.Value

	dbUserID, dbUsername, err := AuthenticateSteam(s.logger, s.db, s.socialClient, s.config.GetSocial().Steam.AppID, s.config.GetSocial().Steam.PublisherKey, in.Account.Token, username, create)
	if err != nil {
		return nil, err
	}

	token := generateToken(s.config, dbUserID, dbUsername)
	return &api.Session{Token: token}, nil
}

func generateToken(config Config, userID, username string) string {
+1 −1
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ import (
func (s *ApiServer) ListFriends(ctx context.Context, in *empty.Empty) (*api.Friends, error) {
	userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID)

	friends, err := GetFriends(s.logger, s.db, userID)
	friends, err := GetFriends(s.logger, s.db, s.tracker, userID)
	if err != nil {
		return nil, status.Error(codes.Internal, "Error while trying to list friends.")
	}
Loading