Resilience: - RetryTransport with circuit breaker for 429/5xx resilience - Exponential backoff with jitter, respects Retry-After headers - Circuit breaker auto-resets after 30s of successful requests Performance: - Concurrent gmail thread fetching (fixes N+1 query pattern) - Bounded concurrency with semaphore (max 10 parallel) New calendar commands: - colors: list available event/calendar colors - conflicts: check availability across calendars - search: find events by text query - time: show current time in multiple timezones New gmail commands: - autoforward: get/enable/disable auto-forwarding - delegates: list/add/remove mail delegation - filters: list/create/delete inbox filters - forwarding: manage forwarding addresses - sendas: manage send-as aliases - vacation: get/enable/disable vacation responder - batch: bulk operations (mark-read, archive, label, delete) - watch: Pub/Sub push with webhook forwarding New services: - Sheets: read/write/append spreadsheet data - Tasks: manage tasklists and tasks Developer experience: - Shell completion (bash, zsh, fish, powershell) - version command with build info - --debug flag for verbose logging - lefthook for pre-commit hooks Documentation: - Expanded README with examples - Gmail watch/Pub/Sub guide (docs/watch.md) - Architecture spec (docs/spec.md) - Release process (docs/RELEASING.md)
80 lines
2.2 KiB
Go
80 lines
2.2 KiB
Go
package googleauth
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type Service string
|
|
|
|
const (
|
|
ServiceGmail Service = "gmail"
|
|
ServiceCalendar Service = "calendar"
|
|
ServiceDrive Service = "drive"
|
|
ServiceContacts Service = "contacts"
|
|
ServiceTasks Service = "tasks"
|
|
ServicePeople Service = "people"
|
|
ServiceSheets Service = "sheets"
|
|
)
|
|
|
|
func ParseService(s string) (Service, error) {
|
|
switch Service(strings.ToLower(strings.TrimSpace(s))) {
|
|
case ServiceGmail, ServiceCalendar, ServiceDrive, ServiceContacts, ServiceTasks, ServicePeople, ServiceSheets:
|
|
return Service(strings.ToLower(strings.TrimSpace(s))), nil
|
|
default:
|
|
return "", fmt.Errorf("unknown service %q (expected gmail|calendar|drive|contacts|tasks|people|sheets)", s)
|
|
}
|
|
}
|
|
|
|
func AllServices() []Service {
|
|
return []Service{ServiceGmail, ServiceCalendar, ServiceDrive, ServiceContacts, ServiceTasks, ServicePeople, ServiceSheets}
|
|
}
|
|
|
|
func Scopes(service Service) ([]string, error) {
|
|
switch service {
|
|
case ServiceGmail:
|
|
return []string{"https://mail.google.com/"}, nil
|
|
case ServiceCalendar:
|
|
return []string{"https://www.googleapis.com/auth/calendar"}, nil
|
|
case ServiceDrive:
|
|
return []string{"https://www.googleapis.com/auth/drive"}, nil
|
|
case ServiceContacts:
|
|
return []string{
|
|
"https://www.googleapis.com/auth/contacts",
|
|
"https://www.googleapis.com/auth/contacts.other.readonly",
|
|
"https://www.googleapis.com/auth/directory.readonly",
|
|
}, nil
|
|
case ServiceTasks:
|
|
return []string{"https://www.googleapis.com/auth/tasks"}, nil
|
|
case ServicePeople:
|
|
// Needed for "people/me" requests.
|
|
return []string{"profile"}, nil
|
|
case ServiceSheets:
|
|
return []string{"https://www.googleapis.com/auth/spreadsheets"}, nil
|
|
default:
|
|
return nil, errors.New("unknown service")
|
|
}
|
|
}
|
|
|
|
func ScopesForServices(services []Service) ([]string, error) {
|
|
set := make(map[string]struct{})
|
|
for _, svc := range services {
|
|
scopes, err := Scopes(svc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, s := range scopes {
|
|
set[s] = struct{}{}
|
|
}
|
|
}
|
|
out := make([]string, 0, len(set))
|
|
for s := range set {
|
|
out = append(out, s)
|
|
}
|
|
// stable ordering (useful for tests + auth URL diffs)
|
|
sort.Strings(out)
|
|
return out, nil
|
|
}
|