91 lines
3.2 KiB
Go
91 lines
3.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// LoginCodeMailer is the publisher contract auth uses to deliver a
|
|
// one-time login code to a user's mailbox. The canonical
|
|
// implementation lives in `backend/internal/mail`; tests can use
|
|
// `NewNoopLoginCodeMailer` to record the outbound code without wiring
|
|
// SMTP.
|
|
type LoginCodeMailer interface {
|
|
EnqueueLoginCode(ctx context.Context, email, code string, ttl time.Duration) error
|
|
}
|
|
|
|
// SessionInvalidator emits the gRPC push session_invalidation event
|
|
// when auth revokes one or more device sessions. The canonical
|
|
// implementation lives in `backend/internal/push`; tests can use
|
|
// `NewNoopSessionInvalidator` for an in-memory log-only fallback.
|
|
type SessionInvalidator interface {
|
|
PublishSessionInvalidation(ctx context.Context, deviceSessionID, userID uuid.UUID, reason string)
|
|
}
|
|
|
|
// UserEnsurer binds a confirmed email to an `accounts.user_id`. The
|
|
// canonical implementation is `*user.Service`; tests can swap in a
|
|
// recording fake.
|
|
type UserEnsurer interface {
|
|
EnsureByEmail(ctx context.Context, email, preferredLanguage, timeZone, declaredCountry string) (uuid.UUID, error)
|
|
}
|
|
|
|
// GeoService provides the geo helpers auth needs at confirm-email-code:
|
|
// a country lookup for the `preferred_language` fallback and a
|
|
// post-commit write of `accounts.declared_country`. Both methods are
|
|
// best-effort — auth never blocks the registration flow on geo failures.
|
|
type GeoService interface {
|
|
LookupCountry(sourceIP string) string
|
|
LanguageForIP(sourceIP string) string
|
|
SetDeclaredCountryAtRegistration(ctx context.Context, userID uuid.UUID, sourceIP string) error
|
|
}
|
|
|
|
// NewNoopLoginCodeMailer returns a LoginCodeMailer that logs the
|
|
// outbound code at info level and returns nil. The wiring code uses
|
|
// the real `mail.Service`; this constructor exists for tests and for
|
|
// local smoke runs that do not want to bring up an SMTP relay.
|
|
func NewNoopLoginCodeMailer(logger *zap.Logger) LoginCodeMailer {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &noopLoginCodeMailer{logger: logger.Named("auth.mail.noop")}
|
|
}
|
|
|
|
type noopLoginCodeMailer struct {
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func (m *noopLoginCodeMailer) EnqueueLoginCode(_ context.Context, email, code string, ttl time.Duration) error {
|
|
m.logger.Info("auth login code (noop publisher)",
|
|
zap.String("email", email),
|
|
zap.String("code", code),
|
|
zap.Duration("ttl", ttl),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
// NewNoopSessionInvalidator returns a SessionInvalidator that logs
|
|
// every invalidation at info level and never blocks. The wiring code
|
|
// uses the real `push.Service`; this constructor exists for tests
|
|
// that need a callable surface without bringing up gRPC.
|
|
func NewNoopSessionInvalidator(logger *zap.Logger) SessionInvalidator {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &noopSessionInvalidator{logger: logger.Named("auth.push.noop")}
|
|
}
|
|
|
|
type noopSessionInvalidator struct {
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func (p *noopSessionInvalidator) PublishSessionInvalidation(_ context.Context, deviceSessionID, userID uuid.UUID, reason string) {
|
|
p.logger.Info("session invalidation (noop publisher)",
|
|
zap.String("device_session_id", deviceSessionID.String()),
|
|
zap.String("user_id", userID.String()),
|
|
zap.String("reason", reason),
|
|
)
|
|
}
|