Raw
 1package identity
 2
 3import (
 4	"context"
 5	"encoding/json"
 6	"errors"
 7	"fmt"
 8
 9	"tug/internal/storage"
10)
11
12const (
13	AuditGoogleSignInRefused = "google.signin_refused"
14	GoogleRefusedDisabled    = "disabled"
15	GoogleRefusedUnverified  = "email_unverified"
16	GoogleRefusedDomain      = "domain"
17)
18
19var (
20	ErrGoogleUnlinked = errors.New("identity: no account is linked to this Google account")
21	ErrDisabled       = errors.New("identity: the user is disabled")
22)
23
24func (s *Service) GoogleUser(ctx context.Context, googleSub string) (storage.GoogleSignIn, error) {
25	g, err := s.store.LookupGoogleSignIn(ctx, googleSub)
26	switch {
27	case errors.Is(err, storage.ErrNotFound), errors.Is(err, storage.ErrInvalid):
28		return storage.GoogleSignIn{}, fmt.Errorf("%w: %w", ErrGoogleUnlinked, err)
29	case err != nil:
30		return storage.GoogleSignIn{}, googleFailure(err)
31	case g.Disabled:
32		return g, ErrDisabled
33	}
34	return g, nil
35}
36
37func googleFailure(err error) error {
38	if errors.Is(err, storage.ErrBusy) || errors.Is(err, storage.ErrLocked) || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
39		return fmt.Errorf("%w: %v", ErrUnavailable, err)
40	}
41	return err
42}
43
44func (s *Service) GoogleRefused(ctx context.Context, userID, reason string) error {
45	detail, _ := json.Marshal(map[string]string{"reason": reason})
46	_, err := s.store.AppendAudit(ctx, storage.AppendAuditInput{Actor: userID, Action: AuditGoogleSignInRefused, Subject: userID, Detail: string(detail)})
47	return err
48}
49
50func (s *Service) GoogleRefusedSubject(ctx context.Context, googleSub, reason string) error {
51	link, err := s.store.GoogleLinkBySubject(ctx, googleSub)
52	switch {
53	case errors.Is(err, storage.ErrNotFound), errors.Is(err, storage.ErrInvalid):
54		return nil
55	case err != nil:
56		return err
57	}
58	return s.GoogleRefused(ctx, link.UserID, reason)
59}