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.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+10
-21
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user