Skip to content

Commit fe560dd

Browse files
wesmclaude
andcommitted
Add regression tests for --oauth-app "" token validation
Covers both paths: mismatched token rejected, matching token accepted when explicitly clearing to default app. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6c81da9 commit fe560dd

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed

cmd/msgvault/cmd/addaccount_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,146 @@ func TestAddAccount_NewRegistrationRejectsMismatchedToken(t *testing.T) {
265265
}
266266
}
267267

268+
// TestAddAccount_ExplicitDefaultRejectsMismatchedToken verifies that
269+
// --oauth-app "" rejects a token minted by a different client.
270+
func TestAddAccount_ExplicitDefaultRejectsMismatchedToken(t *testing.T) {
271+
tmpDir := t.TempDir()
272+
273+
tokensDir := filepath.Join(tmpDir, "tokens")
274+
if err := os.MkdirAll(tokensDir, 0700); err != nil {
275+
t.Fatalf("mkdir tokens: %v", err)
276+
}
277+
// Token with a client_id that does NOT match the default secrets
278+
tokenData, _ := json.Marshal(map[string]string{
279+
"access_token": "fake-access",
280+
"refresh_token": "fake-refresh",
281+
"token_type": "Bearer",
282+
"client_id": "wrong-client.apps.googleusercontent.com",
283+
})
284+
if err := os.WriteFile(
285+
filepath.Join(tokensDir, "user@example.com.json"),
286+
tokenData, 0600,
287+
); err != nil {
288+
t.Fatalf("write token: %v", err)
289+
}
290+
291+
secretsPath := filepath.Join(tmpDir, "secret.json")
292+
if err := os.WriteFile(
293+
secretsPath, []byte(fakeClientSecrets), 0600,
294+
); err != nil {
295+
t.Fatalf("write secrets: %v", err)
296+
}
297+
298+
savedCfg := cfg
299+
savedLogger := logger
300+
savedOAuthApp := oauthAppName
301+
defer func() {
302+
cfg = savedCfg
303+
logger = savedLogger
304+
oauthAppName = savedOAuthApp
305+
}()
306+
307+
cfg = &config.Config{
308+
HomeDir: tmpDir,
309+
Data: config.DataConfig{DataDir: tmpDir},
310+
OAuth: config.OAuthConfig{ClientSecrets: secretsPath},
311+
}
312+
logger = slog.New(slog.NewTextHandler(os.Stderr, nil))
313+
314+
ctx, cancel := context.WithCancel(context.Background())
315+
cancel()
316+
317+
testCmd := &cobra.Command{
318+
Use: "add-account <email>",
319+
Args: cobra.ExactArgs(1),
320+
RunE: addAccountCmd.RunE,
321+
}
322+
testCmd.Flags().StringVar(&oauthAppName, "oauth-app", "", "")
323+
testCmd.Flags().BoolVar(&headless, "headless", false, "")
324+
testCmd.Flags().BoolVar(&forceReauth, "force", false, "")
325+
testCmd.Flags().StringVar(&accountDisplayName, "display-name", "", "")
326+
327+
root := newTestRootCmd()
328+
root.AddCommand(testCmd)
329+
root.SetArgs([]string{
330+
"add-account", "user@example.com", "--oauth-app", "",
331+
})
332+
333+
err := root.ExecuteContext(ctx)
334+
if err == nil {
335+
t.Fatal("expected error: mismatched token should be rejected with explicit --oauth-app \"\"")
336+
}
337+
}
338+
339+
// TestAddAccount_ExplicitDefaultAcceptsMatchingToken verifies that
340+
// --oauth-app "" accepts a token minted by the default client.
341+
func TestAddAccount_ExplicitDefaultAcceptsMatchingToken(t *testing.T) {
342+
tmpDir := t.TempDir()
343+
344+
tokensDir := filepath.Join(tmpDir, "tokens")
345+
if err := os.MkdirAll(tokensDir, 0700); err != nil {
346+
t.Fatalf("mkdir tokens: %v", err)
347+
}
348+
// Token with client_id matching the fake secrets
349+
tokenData, _ := json.Marshal(map[string]string{
350+
"access_token": "fake-access",
351+
"refresh_token": "fake-refresh",
352+
"token_type": "Bearer",
353+
"client_id": "test.apps.googleusercontent.com",
354+
})
355+
if err := os.WriteFile(
356+
filepath.Join(tokensDir, "user@example.com.json"),
357+
tokenData, 0600,
358+
); err != nil {
359+
t.Fatalf("write token: %v", err)
360+
}
361+
362+
secretsPath := filepath.Join(tmpDir, "secret.json")
363+
if err := os.WriteFile(
364+
secretsPath, []byte(fakeClientSecrets), 0600,
365+
); err != nil {
366+
t.Fatalf("write secrets: %v", err)
367+
}
368+
369+
savedCfg := cfg
370+
savedLogger := logger
371+
savedOAuthApp := oauthAppName
372+
defer func() {
373+
cfg = savedCfg
374+
logger = savedLogger
375+
oauthAppName = savedOAuthApp
376+
}()
377+
378+
cfg = &config.Config{
379+
HomeDir: tmpDir,
380+
Data: config.DataConfig{DataDir: tmpDir},
381+
OAuth: config.OAuthConfig{ClientSecrets: secretsPath},
382+
}
383+
logger = slog.New(slog.NewTextHandler(os.Stderr, nil))
384+
385+
testCmd := &cobra.Command{
386+
Use: "add-account <email>",
387+
Args: cobra.ExactArgs(1),
388+
RunE: addAccountCmd.RunE,
389+
}
390+
testCmd.Flags().StringVar(&oauthAppName, "oauth-app", "", "")
391+
testCmd.Flags().BoolVar(&headless, "headless", false, "")
392+
testCmd.Flags().BoolVar(&forceReauth, "force", false, "")
393+
testCmd.Flags().StringVar(&accountDisplayName, "display-name", "", "")
394+
395+
root := newTestRootCmd()
396+
root.AddCommand(testCmd)
397+
root.SetArgs([]string{
398+
"add-account", "user@example.com", "--oauth-app", "",
399+
})
400+
401+
// Should succeed: token's client_id matches default secrets
402+
err := root.Execute()
403+
if err != nil {
404+
t.Fatalf("unexpected error: %v", err)
405+
}
406+
}
407+
268408
func TestAddAccount_ForceRebindPreservesBindingOnFailure(t *testing.T) {
269409
tmpDir := t.TempDir()
270410
dbPath := filepath.Join(tmpDir, "msgvault.db")

0 commit comments

Comments
 (0)