fix(auth): check .credentials.json for Linux profile authentication (#1492)

* fix(auth): check .credentials.json for Linux profile authentication

Fixes ACS-388: Custom Claude profiles never mark as authenticated on Linux.

The isProfileAuthenticated function was not checking .credentials.json,
which is where the Claude CLI stores OAuth tokens on Linux. This caused
custom profiles to always show "Needs Auth" even after successful OAuth.

Changes:
- Add .credentials.json to possibleAuthFiles array in profile-utils.ts
- Includes comment explaining Linux-specific usage

Refs: ACS-388

* refactor(auth): validate .credentials.json content structure

Implement Gemini Code Assist suggestion to make .credentials.json
authentication check more robust by validating the JSON content
structure instead of just checking file length.

The previous check only verified content.length > 10, which could
pass for invalid token files. The new validation properly parses and
validates the OAuth data structure:

- claudeAiOauth with accessToken, refreshToken, email, or emailAddress
- oauthAccount.emailAddress (alternative structure)
- Generic token fields (legacy formats)

This matches the robust validation pattern used for .claude.json
and aligns with the existing checkProfileAuthentication function
in claude-code-handlers.ts.

Refs: #1492

---------

Co-authored-by: StillKnotKnown <stillknotknown@users.noreply.github.com>
Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
This commit is contained in:
StillKnotKnown
2026-01-24 15:53:25 +02:00
committed by GitHub
parent 1185a558c8
commit 8d2f662914
@@ -88,6 +88,39 @@ export function isProfileAuthenticated(profile: ClaudeProfile): boolean {
}
}
// Check for .credentials.json with OAuth tokens (Linux CLI storage)
// On Linux, the Claude CLI stores OAuth tokens in this file
const credentialsJsonPath = join(configDir, '.credentials.json');
if (existsSync(credentialsJsonPath)) {
try {
const content = readFileSync(credentialsJsonPath, 'utf-8');
const data = JSON.parse(content);
// Validate OAuth data structure
// Check for claudeAiOauth (primary Linux structure)
if (data && typeof data === 'object' && data.claudeAiOauth) {
// Validate that claudeAiOauth contains actual auth data
const hasValidAuth = data.claudeAiOauth.accessToken ||
data.claudeAiOauth.refreshToken ||
data.claudeAiOauth.email ||
data.claudeAiOauth.emailAddress;
if (hasValidAuth) {
return true;
}
}
// Check for oauthAccount (alternative structure)
if (data && typeof data === 'object' && data.oauthAccount?.emailAddress) {
return true;
}
// Check for generic token fields (legacy formats)
if (data && typeof data === 'object' && (data.accessToken || data.refreshToken || data.token)) {
return true;
}
} catch (error) {
// Log parse errors for debugging, but fall through to legacy checks
console.warn(`[profile-utils] Failed to read or parse ${credentialsJsonPath}:`, error);
}
}
// Legacy: Claude stores auth in .claude/credentials or similar files
// Check for common auth indicators
const possibleAuthFiles = [