Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0b0b338

Browse files
committed
fix: harden file keyring OAuth reauth
1 parent 9c46b18 commit 0b0b338

10 files changed

Lines changed: 64 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
### Fixed
1616

17+
- Auth: keep fresh OAuth saves working even when old file-keyring token entries are unreadable, and clarify that `--services all` means all user OAuth services while Workspace-only services use service accounts.
1718
- Gmail: reject off-palette `gmail labels style` colors locally instead of forwarding an opaque Gmail API error.
1819
- Drive: make `drive share --dry-run` stop before permission creation for user and domain shares, including `--notify`.
1920
- Forms: make `forms create --description` apply the description with a follow-up batch update, and preserve zero-valued indexes in `forms move-question`.

docs/commands/gog-auth-add.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ gog auth add <email> [flags]
4444
| `--remote` | `bool` | | Remote/server-friendly manual flow (print URL, then exchange code) |
4545
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
4646
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
47-
| `--services` | `string` | user | Services to authorize: user\|all or comma-separated gmail,calendar,chat,classroom,drive,driveactivity,drivelabels,docs,slides,contacts,tasks,sheets,people,forms,sites,meet,appscript,analytics,searchconsole,ads,youtube,photos (Keep uses service account: gog auth service-account set) |
47+
| `--services` | `string` | user | Services to authorize: user\|all-user or comma-separated gmail,calendar,chat,classroom,drive,driveactivity,drivelabels,docs,slides,contacts,tasks,sheets,people,forms,sites,meet,appscript,analytics,searchconsole,ads,youtube,photos; all means all user OAuth services. Workspace service-account-only services: admin, groups, keep |
4848
| `--step` | `int` | | Remote auth step: 1=print URL, 2=exchange code |
4949
| `--timeout` | `time.Duration` | | Authorization timeout (manual flows default to 5m) |
5050
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |

docs/commands/gog-auth-manage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ gog auth manage (login) [flags]
3636
| `--redirect-host` | `string` | | Hostname for OAuth callback; builds https://{host}/oauth2/callback |
3737
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
3838
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
39-
| `--services` | `string` | user | Services to authorize: user\|all or comma-separated gmail,calendar,chat,classroom,drive,driveactivity,drivelabels,docs,slides,contacts,tasks,sheets,people,forms,sites,meet,appscript,analytics,searchconsole,ads,youtube,photos (Keep uses service account: gog auth service-account set) |
39+
| `--services` | `string` | user | Services to authorize: user\|all-user or comma-separated gmail,calendar,chat,classroom,drive,driveactivity,drivelabels,docs,slides,contacts,tasks,sheets,people,forms,sites,meet,appscript,analytics,searchconsole,ads,youtube,photos; all means all user OAuth services. Workspace service-account-only services: admin, groups, keep |
4040
| `--timeout` | `time.Duration` | 10m | Server timeout duration |
4141
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |
4242
| `--version` | `kong.VersionFlag` | | Print version and exit |

docs/commands/gog-login.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ gog login <email> [flags]
4444
| `--remote` | `bool` | | Remote/server-friendly manual flow (print URL, then exchange code) |
4545
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
4646
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
47-
| `--services` | `string` | user | Services to authorize: user\|all or comma-separated gmail,calendar,chat,classroom,drive,driveactivity,drivelabels,docs,slides,contacts,tasks,sheets,people,forms,sites,meet,appscript,analytics,searchconsole,ads,youtube,photos (Keep uses service account: gog auth service-account set) |
47+
| `--services` | `string` | user | Services to authorize: user\|all-user or comma-separated gmail,calendar,chat,classroom,drive,driveactivity,drivelabels,docs,slides,contacts,tasks,sheets,people,forms,sites,meet,appscript,analytics,searchconsole,ads,youtube,photos; all means all user OAuth services. Workspace service-account-only services: admin, groups, keep |
4848
| `--step` | `int` | | Remote auth step: 1=print URL, 2=exchange code |
4949
| `--timeout` | `time.Duration` | | Authorization timeout (manual flows default to 5m) |
5050
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |

docs/spec.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ Flag aliases:
175175
- `gog auth credentials list`
176176
- `gog auth credentials remove [<client>|all]`
177177
- `gog --client <name> auth credentials <credentials.json|->`
178-
- `gog auth add <email> [--services user|all|gmail,calendar,chat,classroom,drive,driveactivity,drivelabels,docs,slides,contacts,tasks,sheets,people,forms,photos,appscript,ads,groups,keep,admin] [--readonly] [--drive-scope full|readonly|file] [--gmail-scope full|readonly] [--extra-scopes CSV] [--manual] [--remote] [--step 1|2] [--auth-url URL] [--listen-addr HOST[:PORT]] [--redirect-host HOST] [--timeout DURATION] [--force-consent]`
178+
- `gog auth add <email> [--services user|all-user|all|gmail,calendar,chat,classroom,drive,driveactivity,drivelabels,docs,slides,contacts,tasks,sheets,people,forms,sites,meet,photos,appscript,analytics,searchconsole,ads,youtube] [--readonly] [--drive-scope full|readonly|file] [--gmail-scope full|readonly] [--extra-scopes CSV] [--manual] [--remote] [--step 1|2] [--auth-url URL] [--listen-addr HOST[:PORT]] [--redirect-host HOST] [--timeout DURATION] [--force-consent]`
179179
- `gog auth services [--markdown]`
180180
- `gog auth manage [--services ...] [--listen-addr HOST[:PORT]] [--redirect-host HOST]`
181181
- `gog auth keep <email> --key <service-account.json>` (Google Keep; Workspace only)

internal/cmd/auth.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ type AuthCmd struct {
5757

5858
func parseAuthServices(servicesCSV string) ([]googleauth.Service, error) {
5959
trimmed := strings.ToLower(strings.TrimSpace(servicesCSV))
60-
if trimmed == "" || trimmed == "user" || trimmed == literalAll {
60+
if trimmed == "" || trimmed == "user" || trimmed == "all-user" || trimmed == literalAll {
6161
return googleauth.UserServices(), nil
6262
}
6363

@@ -69,8 +69,9 @@ func parseAuthServices(servicesCSV string) ([]googleauth.Service, error) {
6969
if err != nil {
7070
return nil, err
7171
}
72-
if svc == googleauth.ServiceKeep {
73-
return nil, usage("Keep auth is Workspace-only and requires a service account. Use: gog auth service-account set <email> --key <service-account.json>")
72+
switch svc {
73+
case googleauth.ServiceAdmin, googleauth.ServiceGroups, googleauth.ServiceKeep:
74+
return nil, usage(fmt.Sprintf("%s auth is Workspace/service-account-only. Use: gog auth service-account set <email> --key <service-account.json>", svc))
7475
}
7576
if _, ok := seen[svc]; ok {
7677
continue

internal/cmd/auth_accounts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ func (c *AuthRemoveCmd) Run(ctx context.Context, flags *RootFlags) error {
254254

255255
type AuthManageCmd struct {
256256
ForceConsent bool `name:"force-consent" help:"Force consent screen when adding accounts"`
257-
ServicesCSV string `name:"services" help:"Services to authorize: user|all or comma-separated ${auth_services} (Keep uses service account: gog auth service-account set)" default:"user"`
257+
ServicesCSV string `name:"services" help:"Services to authorize: user|all-user or comma-separated ${auth_services}; all means all user OAuth services. Workspace service-account-only services: admin, groups, keep" default:"user"`
258258
Timeout time.Duration `name:"timeout" help:"Server timeout duration" default:"10m"`
259259
ListenAddr string `name:"listen-addr" help:"Address to listen on for OAuth callback (for example 0.0.0.0 or 0.0.0.0:8080)"`
260260
RedirectHost string `name:"redirect-host" help:"Hostname for OAuth callback; builds https://{host}/oauth2/callback"`

internal/cmd/auth_add.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type AuthAddCmd struct {
2828
AuthCode string `name:"auth-code" hidden:"" help:"UNSAFE: Authorization code from browser (manual flow; skips state check; not valid with --remote)"`
2929
Timeout time.Duration `name:"timeout" help:"Authorization timeout (manual flows default to 5m)"`
3030
ForceConsent bool `name:"force-consent" help:"Force consent screen to obtain a refresh token"`
31-
ServicesCSV string `name:"services" help:"Services to authorize: user|all or comma-separated ${auth_services} (Keep uses service account: gog auth service-account set)" default:"user"`
31+
ServicesCSV string `name:"services" help:"Services to authorize: user|all-user or comma-separated ${auth_services}; all means all user OAuth services. Workspace service-account-only services: admin, groups, keep" default:"user"`
3232
Readonly bool `name:"readonly" help:"Use read-only scopes where available (still includes OIDC identity scopes)"`
3333
DriveScope string `name:"drive-scope" help:"Drive scope mode: full|readonly|file" enum:"full,readonly,file" default:"full"`
3434
GmailScope string `name:"gmail-scope" help:"Gmail scope mode: full|readonly" enum:"full,readonly" default:"full"`

internal/cmd/auth_add_test.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,54 @@ func (s *setTokenErrorStore) SetToken(string, string, secrets.Token) error {
138138
return s.err
139139
}
140140

141+
type listTokenErrorStore struct {
142+
*memSecretsStore
143+
err error
144+
}
145+
146+
func (s *listTokenErrorStore) ListTokens() ([]secrets.Token, error) {
147+
return nil, s.err
148+
}
149+
150+
func TestAuthAddCmd_ListTokenFailureDoesNotBlockFreshTokenSave(t *testing.T) {
151+
origAuth := authorizeGoogle
152+
origOpen := openSecretsStore
153+
origKeychain := ensureKeychainAccess
154+
origFetch := fetchAuthorizedIdentity
155+
t.Cleanup(func() {
156+
authorizeGoogle = origAuth
157+
openSecretsStore = origOpen
158+
ensureKeychainAccess = origKeychain
159+
fetchAuthorizedIdentity = origFetch
160+
})
161+
162+
ensureKeychainAccess = func() error { return nil }
163+
authorizeGoogle = func(context.Context, googleauth.AuthorizeOptions) (string, error) {
164+
return "rt", nil
165+
}
166+
fetchAuthorizedIdentity = func(context.Context, string, string, []string, time.Duration) (googleauth.Identity, error) {
167+
return googleauth.Identity{Subject: "sub-123", Email: "[email protected]"}, nil
168+
}
169+
170+
store := &listTokenErrorStore{
171+
memSecretsStore: newMemSecretsStore(),
172+
err: errors.New("read encoded file keyring item: aes.KeyUnwrap(): integrity check failed"),
173+
}
174+
openSecretsStore = func() (secrets.Store, error) { return store, nil }
175+
176+
if err := Execute([]string{"auth", "add", "[email protected]", "--services", "gmail", "--manual"}); err != nil {
177+
t.Fatalf("Execute: %v", err)
178+
}
179+
180+
tok, err := store.GetToken(config.DefaultClientName, "[email protected]")
181+
if err != nil {
182+
t.Fatalf("GetToken: %v", err)
183+
}
184+
if tok.RefreshToken != "rt" || tok.Subject != "sub-123" {
185+
t.Fatalf("unexpected saved token: %#v", tok)
186+
}
187+
}
188+
141189
func TestAuthAddCmd_StoreFailureReportsOAuthCompleted(t *testing.T) {
142190
origAuth := authorizeGoogle
143191
origOpen := openSecretsStore
@@ -240,7 +288,7 @@ func TestAuthAddCmd_KeepRejected(t *testing.T) {
240288
if !errors.As(err, &ee) || ee.Code != 2 {
241289
t.Fatalf("expected exit code 2, got %T %#v", err, err)
242290
}
243-
if !strings.Contains(err.Error(), "Keep auth") {
291+
if !strings.Contains(err.Error(), "keep auth") {
244292
t.Fatalf("unexpected error: %v", err)
245293
}
246294
if authorizeCalled {

internal/googleauth/identity_migration.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ func MigrateStoredSubjectIdentity(store secrets.Store, client string, identity I
1818

1919
tokens, err := store.ListTokens()
2020
if err != nil {
21-
return "", fmt.Errorf("list tokens for subject identity migration: %w", err)
21+
// Subject migration is best-effort compatibility cleanup. A stale or
22+
// corrupted token must not make a freshly completed OAuth flow fail
23+
// before the new refresh token is saved.
24+
return "", nil
2225
}
2326

2427
for _, tok := range tokens {

0 commit comments

Comments
 (0)