package identity import ( "context" "encoding/json" "errors" "fmt" "tug/internal/storage" ) const ( AuditGoogleSignInRefused = "google.signin_refused" GoogleRefusedDisabled = "disabled" GoogleRefusedUnverified = "email_unverified" GoogleRefusedDomain = "domain" ) var ( ErrGoogleUnlinked = errors.New("identity: no account is linked to this Google account") ErrDisabled = errors.New("identity: the user is disabled") ) func (s *Service) GoogleUser(ctx context.Context, googleSub string) (storage.GoogleSignIn, error) { g, err := s.store.LookupGoogleSignIn(ctx, googleSub) switch { case errors.Is(err, storage.ErrNotFound), errors.Is(err, storage.ErrInvalid): return storage.GoogleSignIn{}, fmt.Errorf("%w: %w", ErrGoogleUnlinked, err) case err != nil: return storage.GoogleSignIn{}, googleFailure(err) case g.Disabled: return g, ErrDisabled } return g, nil } func googleFailure(err error) error { if errors.Is(err, storage.ErrBusy) || errors.Is(err, storage.ErrLocked) || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("%w: %v", ErrUnavailable, err) } return err } func (s *Service) GoogleRefused(ctx context.Context, userID, reason string) error { detail, _ := json.Marshal(map[string]string{"reason": reason}) _, err := s.store.AppendAudit(ctx, storage.AppendAuditInput{Actor: userID, Action: AuditGoogleSignInRefused, Subject: userID, Detail: string(detail)}) return err } func (s *Service) GoogleRefusedSubject(ctx context.Context, googleSub, reason string) error { link, err := s.store.GoogleLinkBySubject(ctx, googleSub) switch { case errors.Is(err, storage.ErrNotFound), errors.Is(err, storage.ErrInvalid): return nil case err != nil: return err } return s.GoogleRefused(ctx, link.UserID, reason) }