Skip to content

Commit

Permalink
Update Satori client to comply to latest API (#1271)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Flávio Fernandes <[email protected]>
  • Loading branch information
sesposito and flaviofernandes004 authored Sep 20, 2024
1 parent 5945bcc commit 0c94f92
Show file tree
Hide file tree
Showing 63 changed files with 1,309 additions and 725 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
github.com/heroiclabs/nakama-common v1.33.0
github.com/heroiclabs/nakama-common v1.33.1-0.20240920140332-3cdf52bdf781
github.com/heroiclabs/sql-migrate v0.0.0-20240528102547-233afc8cf05a
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
github.com/jackc/pgx/v5 v5.6.0
Expand All @@ -26,7 +26,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117
google.golang.org/grpc v1.64.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
google.golang.org/protobuf v1.34.1
google.golang.org/protobuf v1.34.2
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZH
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/heroiclabs/nakama-common v1.33.0 h1:ojGPgVZ/goyAH6jrm3Emg7034yGDdqB/f5vTa6AJ5n0=
github.com/heroiclabs/nakama-common v1.33.0/go.mod h1:lPG64MVCs0/tEkh311Cd6oHX9NLx2vAPx7WW7QCJHQ0=
github.com/heroiclabs/nakama-common v1.33.1-0.20240920140332-3cdf52bdf781 h1:mVjenNkPCNqQscldkvoBmmeCVS6MXzoN1rDd2u/+BbE=
github.com/heroiclabs/nakama-common v1.33.1-0.20240920140332-3cdf52bdf781/go.mod h1:lPG64MVCs0/tEkh311Cd6oHX9NLx2vAPx7WW7QCJHQ0=
github.com/heroiclabs/sql-migrate v0.0.0-20240528102547-233afc8cf05a h1:tuL2ZPaeCbNw8rXmV9ywd00nXRv95V4/FmbIGKLQJAE=
github.com/heroiclabs/sql-migrate v0.0.0-20240528102547-233afc8cf05a/go.mod h1:hzCTPoEi/oml2BllVydJcNP63S7b56e5DzeQeLGvw1U=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
Expand Down Expand Up @@ -340,8 +340,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
186 changes: 174 additions & 12 deletions internal/satori/satori.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"

Expand All @@ -34,6 +35,8 @@ import (

var _ runtime.Satori = &SatoriClient{}

type CtxTokenIDKey struct{}

type SatoriClient struct {
logger *zap.Logger
httpc *http.Client
Expand Down Expand Up @@ -117,10 +120,11 @@ func (stc *sessionTokenClaims) Valid() error {
return nil
}

func (s *SatoriClient) generateToken(id string) (string, error) {
func (s *SatoriClient) generateToken(ctx context.Context, id string) (string, error) {
tid, _ := ctx.Value(CtxTokenIDKey{}).(string)
timestamp := time.Now().UTC()
claims := sessionTokenClaims{
SessionID: "",
SessionID: tid,
IdentityId: id,
ExpiresAt: timestamp.Add(time.Duration(s.tokenExpirySec) * time.Second).Unix(),
IssuedAt: timestamp.Unix(),
Expand All @@ -135,23 +139,27 @@ func (s *SatoriClient) generateToken(id string) (string, error) {
}

type authenticateBody struct {
Id string `json:"id"`
Id string `json:"id"`
Default map[string]string `json:"default,omitempty"`
Custom map[string]string `json:"custom,omitempty"`
}

// @group satori
// @summary Create a new identity.
// @param ctx(type=context.Context) The context object represents information about the server and requester.
// @param id(type=string) The identifier of the identity.
// @param ipAddress(type=string, optional=true) An optional client IP address to pass on to Satori for geo-IP lookup.
// @param default(type=map[string]string, optional=true, default=nil) Default properties to update with this call. Set to nil to leave them as they are on the server.
// @param custom(type=map[string]string, optional=true, default=nil) Custom properties to update with this call. Set to nil to leave them as they are on the server.
// @param ipAddress(type=string, optional=true, default="") An optional client IP address to pass on to Satori for geo-IP lookup.
// @return error(error) An optional error value if an error occurred.
func (s *SatoriClient) Authenticate(ctx context.Context, id string, ipAddress ...string) error {
func (s *SatoriClient) Authenticate(ctx context.Context, id string, defaultProperties, customProperties map[string]string, ipAddress ...string) error {
if s.invalidConfig {
return runtime.ErrSatoriConfigurationInvalid
}

url := s.url.String() + "/v1/authenticate"

body := &authenticateBody{Id: id}
body := &authenticateBody{Id: id, Default: defaultProperties, Custom: customProperties}

json, err := json.Marshal(body)
if err != nil {
Expand Down Expand Up @@ -200,7 +208,7 @@ func (s *SatoriClient) PropertiesGet(ctx context.Context, id string) (*runtime.P

url := s.url.String() + "/v1/properties"

sessionToken, err := s.generateToken(id)
sessionToken, err := s.generateToken(ctx, id)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -249,7 +257,7 @@ func (s *SatoriClient) PropertiesUpdate(ctx context.Context, id string, properti

url := s.url.String() + "/v1/properties"

sessionToken, err := s.generateToken(id)
sessionToken, err := s.generateToken(ctx, id)
if err != nil {
return err
}
Expand Down Expand Up @@ -315,7 +323,7 @@ func (s *SatoriClient) EventsPublish(ctx context.Context, id string, events []*r
evts[i].setTimestamp()
}

sessionToken, err := s.generateToken(id)
sessionToken, err := s.generateToken(ctx, id)
if err != nil {
return err
}
Expand Down Expand Up @@ -361,7 +369,7 @@ func (s *SatoriClient) ExperimentsList(ctx context.Context, id string, names ...

url := s.url.String() + "/v1/experiment"

sessionToken, err := s.generateToken(id)
sessionToken, err := s.generateToken(ctx, id)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -419,7 +427,7 @@ func (s *SatoriClient) FlagsList(ctx context.Context, id string, names ...string

url := s.url.String() + "/v1/flag"

sessionToken, err := s.generateToken(id)
sessionToken, err := s.generateToken(ctx, id)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -477,7 +485,7 @@ func (s *SatoriClient) LiveEventsList(ctx context.Context, id string, names ...s

url := s.url.String() + "/v1/live-event"

sessionToken, err := s.generateToken(id)
sessionToken, err := s.generateToken(ctx, id)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -519,3 +527,157 @@ func (s *SatoriClient) LiveEventsList(ctx context.Context, id string, names ...s
return nil, fmt.Errorf("%d status code", res.StatusCode)
}
}

// @group satori
// @summary List messages.
// @param ctx(type=context.Context) The context object represents information about the server and requester.
// @param id(type=string) The identifier of the identity.
// @param limit(type=int) The max number of messages to return.
// @param forward(type=bool) True if listing should be older messages to newer, false if reverse.
// @param cursor(type=string) A pagination cursor, if any.
// @return messages(*runtime.MessageList) The messages list.
// @return error(error) An optional error value if an error occurred.
func (s *SatoriClient) MessagesList(ctx context.Context, id string, limit int, forward bool, cursor string) (*runtime.MessageList, error) {
if s.invalidConfig {
return nil, runtime.ErrSatoriConfigurationInvalid
}

if limit < 1 {
return nil, errors.New("limit must be greater than zero")
}

url := s.url.String() + "/v1/message"

sessionToken, err := s.generateToken(ctx, id)
if err != nil {
return nil, err
}

req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", sessionToken))
q := req.URL.Query()
q.Set("limit", strconv.Itoa(limit))
q.Set("forward", strconv.FormatBool(forward))
if cursor != "" {
q.Set("cursor", cursor)
}
req.URL.RawQuery = q.Encode()

res, err := s.httpc.Do(req)
if err != nil {
return nil, err
}

defer res.Body.Close()

switch res.StatusCode {
case 200:
resBody, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
var messages runtime.MessageList
if err = json.Unmarshal(resBody, &messages); err != nil {
return nil, err
}

return &messages, nil
default:
return nil, fmt.Errorf("%d status code", res.StatusCode)
}
}

// @group satori
// @summary Update message.
// @param ctx(type=context.Context) The context object represents information about the server and requester.
// @param id(type=string) The identifier of the identity.
// @param readTime(type=int64) The time the message was read at the client.
// @param consumeTime(type=int64) The time the message was consumed by the identity.
// @return error(error) An optional error value if an error occurred.
func (s *SatoriClient) MessageUpdate(ctx context.Context, id, messageId string, readTime, consumeTime int64) error {
if s.invalidConfig {
return runtime.ErrSatoriConfigurationInvalid
}

url := s.url.String() + fmt.Sprintf("/v1/message/%s", messageId)

sessionToken, err := s.generateToken(ctx, id)
if err != nil {
return err
}

json, err := json.Marshal(&runtime.MessageUpdate{
ReadTime: readTime,
ConsumeTime: consumeTime,
})
if err != nil {
return err
}

req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewReader(json))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", sessionToken))

res, err := s.httpc.Do(req)
if err != nil {
return err
}

defer res.Body.Close()

switch res.StatusCode {
case 200:
return nil
default:
return fmt.Errorf("%d status code", res.StatusCode)
}
}

// @group satori
// @summary Delete message.
// @param ctx(type=context.Context) The context object represents information about the server and requester.
// @param id(type=string) The identifier of the identity.
// @param messageId(type=string) The identifier of the message.
// @return error(error) An optional error value if an error occurred.
func (s *SatoriClient) MessageDelete(ctx context.Context, id, messageId string) error {
if s.invalidConfig {
return runtime.ErrSatoriConfigurationInvalid
}

if messageId == "" {
return errors.New("message id cannot be an empty string")
}

url := s.url.String() + fmt.Sprintf("/v1/message/%s", messageId)

sessionToken, err := s.generateToken(ctx, id)
if err != nil {
return err
}

req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil)
if err != nil {
return err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", sessionToken))

res, err := s.httpc.Do(req)
if err != nil {
return err
}

defer res.Body.Close()

switch res.StatusCode {
case 200:
return nil
default:
return fmt.Errorf("%d status code", res.StatusCode)
}
}
2 changes: 1 addition & 1 deletion internal/satori/satori_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestSatoriClient_EventsPublish(t *testing.T) {
ctx, ctxCancelFn := context.WithTimeout(context.Background(), 5*time.Second)
defer ctxCancelFn()

if err := client.Authenticate(ctx, identityID); err != nil {
if err := client.Authenticate(ctx, identityID, nil, nil); err != nil {
t.Fatalf("error in client.Authenticate: %+v", err)
}

Expand Down
6 changes: 4 additions & 2 deletions server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"database/sql"
"encoding/base64"
"fmt"
"github.com/heroiclabs/nakama/v3/internal/satori"
"google.golang.org/grpc/grpclog"
"math"
"net"
Expand Down Expand Up @@ -63,6 +64,7 @@ type ctxUserIDKey struct{}
type ctxUsernameKey struct{}
type ctxVarsKey struct{}
type ctxExpiryKey struct{}
type ctxTokenIDKey = satori.CtxTokenIDKey

type ctxFullMethodKey struct{}

Expand Down Expand Up @@ -425,7 +427,7 @@ func securityInterceptorFunc(logger *zap.Logger, config Config, sessionCache Ses
if !sessionCache.IsValidSession(userID, exp, tokenId) {
return nil, status.Error(codes.Unauthenticated, "Auth token invalid")
}
ctx = context.WithValue(context.WithValue(context.WithValue(context.WithValue(ctx, ctxUserIDKey{}, userID), ctxUsernameKey{}, username), ctxVarsKey{}, vars), ctxExpiryKey{}, exp)
ctx = context.WithValue(context.WithValue(context.WithValue(context.WithValue(context.WithValue(ctx, ctxUserIDKey{}, userID), ctxUsernameKey{}, username), ctxVarsKey{}, vars), ctxExpiryKey{}, exp), ctxTokenIDKey{}, tokenId)
default:
// Unless explicitly defined above, handlers require full user authentication.
md, ok := metadata.FromIncomingContext(ctx)
Expand Down Expand Up @@ -453,7 +455,7 @@ func securityInterceptorFunc(logger *zap.Logger, config Config, sessionCache Ses
if !sessionCache.IsValidSession(userID, exp, tokenId) {
return nil, status.Error(codes.Unauthenticated, "Auth token invalid")
}
ctx = context.WithValue(context.WithValue(context.WithValue(context.WithValue(ctx, ctxUserIDKey{}, userID), ctxUsernameKey{}, username), ctxVarsKey{}, vars), ctxExpiryKey{}, exp)
ctx = context.WithValue(context.WithValue(context.WithValue(context.WithValue(context.WithValue(ctx, ctxUserIDKey{}, userID), ctxUsernameKey{}, username), ctxVarsKey{}, vars), ctxExpiryKey{}, exp), ctxTokenIDKey{}, tokenId)
}
return context.WithValue(ctx, ctxFullMethodKey{}, info.FullMethod), nil
}
Expand Down
3 changes: 3 additions & 0 deletions server/runtime_go_nakama.go
Original file line number Diff line number Diff line change
Expand Up @@ -4282,6 +4282,9 @@ func (n *RuntimeGoNakamaModule) GetSatori() runtime.Satori {
return n.satori
}

// @group fleetmanager
// @symmary Get the Fleet Manager client.
// @return fleetManager(runtime.FleetManager) The Fleet Manager client.
func (n *RuntimeGoNakamaModule) GetFleetManager() runtime.FleetManager {
return n.fleetManager
}
Loading

0 comments on commit 0c94f92

Please sign in to comment.