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}