From 0f73d685c28c3aebfd358ac8617ad411ff9b75ad Mon Sep 17 00:00:00 2001 From: Arjun Patel Date: Mon, 27 Apr 2026 16:14:37 -0700 Subject: [PATCH] refactor: narrow auth service to smaller units Easier for testing, and omits need for full dep injection for pusher service, where we passed in nil for deps dangerously. --- go/cmd/pusherservice/main.go | 6 ++--- go/internal/auth/reader.go | 45 ++++++++++++++++++++++++++++++++++++ go/internal/auth/service.go | 31 ++++++++----------------- go/internal/pusher/server.go | 4 ++-- 4 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 go/internal/auth/reader.go diff --git a/go/cmd/pusherservice/main.go b/go/cmd/pusherservice/main.go index e850a20..53a0ae3 100644 --- a/go/cmd/pusherservice/main.go +++ b/go/cmd/pusherservice/main.go @@ -36,8 +36,8 @@ func main() { pusherRedis := internal.ConnectAndTestRedis(db.RedisDBPusher) // Services - authSvc := auth.NewAuthService(authRedis, nil, nil) // nil aeroSvc / fbAuth — pusher only calls GetSession - networkReader := network.NewReader(db.Pool()) // pusher only checks membership + sessionReader := auth.NewSessionReader(authRedis) // pusher only validates sessions + networkReader := network.NewReader(db.Pool()) // pusher only checks membership // Pod identity (use hostname in k8s, which is the pod name) podID, err := os.Hostname() @@ -54,7 +54,7 @@ func main() { authorizer := pusher.NewAuthorizer(networkReader) hub := pusher.NewHub(bridge, authorizer) bridge.SetHub(hub) - server := pusher.NewServer(ctx, hub, bridge, authSvc) + server := pusher.NewServer(ctx, hub, bridge, sessionReader) // Start hub event loop go hub.Run(ctx) diff --git a/go/internal/auth/reader.go b/go/internal/auth/reader.go new file mode 100644 index 0000000..037b637 --- /dev/null +++ b/go/internal/auth/reader.go @@ -0,0 +1,45 @@ +package auth + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/redis/go-redis/v9" +) + +type SessionReader interface { + // GetSession returns ErrSessionNotFound if no valid session. + GetSession(ctx context.Context, sessionToken string) (*Session, error) +} + +type sessionReaderImpl struct { + redisClient *redis.Client +} + +// newSessionReader returns the concrete reader. Used by NewAuthService to +// embed without going through the SessionReader interface (which would hide +// redisClient from the rest of authServiceImpl). +func newSessionReader(redisClient *redis.Client) *sessionReaderImpl { + return &sessionReaderImpl{redisClient: redisClient} +} + +func NewSessionReader(redisClient *redis.Client) SessionReader { + return newSessionReader(redisClient) +} + +func (r *sessionReaderImpl) GetSession(ctx context.Context, token string) (*Session, error) { + sessionInfo, err := r.redisClient.Get(ctx, token).Result() + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, ErrSessionNotFound + } + return nil, fmt.Errorf("error getting session: %w", err) + } + + var session Session + err = json.Unmarshal([]byte(sessionInfo), &session) + + return &session, nil +} diff --git a/go/internal/auth/service.go b/go/internal/auth/service.go index 3091790..40b12a9 100644 --- a/go/internal/auth/service.go +++ b/go/internal/auth/service.go @@ -45,14 +45,14 @@ type Session struct { } type AuthService interface { + SessionReader + // RequestSignInCode generates a code and emails it to the provided email. // To retrieve a session, client must verify with VerifySignInCode. RequestSignInCode(ctx context.Context, email string) error // VerifySignInCode returns ErrInvalidCode if incorrect code, otherwise creates a session. // humanId is stored in the session alongside the email. VerifySignInCode(ctx context.Context, email, code, humanId string) (sessionToken string, err error) - // GetSession returns ErrSessionNotFound if no valid session - GetSession(ctx context.Context, sessionToken string) (*Session, error) // ExtendSession returns ErrSessionNotFound if no valid session ExtendSession(ctx context.Context, sessionToken string) error SignOut(ctx context.Context, sessionToken string) error @@ -64,13 +64,17 @@ type AuthService interface { } type authServiceImpl struct { - redisClient *redis.Client - aeroSvc pbaero.PrimaryClient - fbAuth *firebaseauth.Client + *sessionReaderImpl + aeroSvc pbaero.PrimaryClient + fbAuth *firebaseauth.Client } func NewAuthService(redisClient *redis.Client, aeroSvc pbaero.PrimaryClient, fbAuth *firebaseauth.Client) AuthService { - return &authServiceImpl{redisClient: redisClient, aeroSvc: aeroSvc, fbAuth: fbAuth} + return &authServiceImpl{ + sessionReaderImpl: newSessionReader(redisClient), + aeroSvc: aeroSvc, + fbAuth: fbAuth, + } } func (a *authServiceImpl) MintFirebaseCustomToken(ctx context.Context, humanId string) (string, error) { @@ -161,21 +165,6 @@ func (a *authServiceImpl) VerifySignInCode(ctx context.Context, email, code, hum return token, nil } -func (a *authServiceImpl) GetSession(ctx context.Context, token string) (*Session, error) { - sessionInfo, err := a.redisClient.Get(ctx, token).Result() - if err != nil { - if errors.Is(err, redis.Nil) { - return nil, ErrSessionNotFound - } - return nil, fmt.Errorf("error getting session: %w", err) - } - - var session Session - err = json.Unmarshal([]byte(sessionInfo), &session) - - return &session, nil -} - func (a *authServiceImpl) ExtendSession(ctx context.Context, token string) error { ttl, err := a.redisClient.TTL(ctx, token).Result() if err != nil { diff --git a/go/internal/pusher/server.go b/go/internal/pusher/server.go index 1b4dd9d..53349f3 100644 --- a/go/internal/pusher/server.go +++ b/go/internal/pusher/server.go @@ -18,12 +18,12 @@ type Server struct { ctx context.Context // server-scoped context for graceful shutdown hub *Hub bridge *RedisBridge - authSvc auth.AuthService + authSvc auth.SessionReader } // NewServer creates a new pusher server. The ctx controls the lifetime of all // WebSocket connections — when cancelled, all connections are closed gracefully. -func NewServer(ctx context.Context, hub *Hub, bridge *RedisBridge, authSvc auth.AuthService) *Server { +func NewServer(ctx context.Context, hub *Hub, bridge *RedisBridge, authSvc auth.SessionReader) *Server { return &Server{ ctx: ctx, hub: hub,