mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
fix(cliproxy): clear stale auth quota cache
- invalidate per-account quota cache entries after successful OAuth account registration - add regression coverage for the Claude stale-reauth dashboard path
This commit is contained in:
@@ -33,6 +33,7 @@ import {
|
||||
} from '../../cliproxy/proxy-target-resolver';
|
||||
import { fetchRemoteAuthStatus } from '../../cliproxy/remote-auth-fetcher';
|
||||
import { ensureManagedModelPrefixes } from '../../cliproxy/managed-model-prefixes';
|
||||
import { invalidateQuotaCache } from '../../cliproxy/quota-response-cache';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import { tryKiroImport } from '../../cliproxy/auth/kiro-import';
|
||||
import {
|
||||
@@ -190,6 +191,13 @@ function shouldKeepWaitingForLocalToken(
|
||||
);
|
||||
}
|
||||
|
||||
function invalidateQuotaForRegisteredAccount(account: {
|
||||
provider: CLIProxyProvider;
|
||||
id: string;
|
||||
}): void {
|
||||
invalidateQuotaCache(account.provider, account.id);
|
||||
}
|
||||
|
||||
function parseKiroMethod(raw: unknown): { method: KiroAuthMethod; invalid: boolean } {
|
||||
if (raw === undefined || raw === null) {
|
||||
return { method: normalizeKiroAuthMethod(), invalid: false };
|
||||
@@ -1022,6 +1030,7 @@ router.get('/:provider/status', async (req: Request, res: Response): Promise<voi
|
||||
} catch {
|
||||
// Keep manual callback success path non-fatal when prefix repair cannot run.
|
||||
}
|
||||
invalidateQuotaForRegisteredAccount(account);
|
||||
res.json({
|
||||
status: 'ok',
|
||||
account: {
|
||||
@@ -1173,6 +1182,7 @@ router.post('/:provider/submit-callback', async (req: Request, res: Response): P
|
||||
// Keep manual callback success path non-fatal when prefix repair cannot run.
|
||||
}
|
||||
}
|
||||
invalidateQuotaForRegisteredAccount(account);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -1205,6 +1215,8 @@ router.post('/:provider/submit-callback', async (req: Request, res: Response): P
|
||||
return;
|
||||
}
|
||||
|
||||
invalidateQuotaForRegisteredAccount(account);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
account: {
|
||||
|
||||
@@ -6,6 +6,11 @@ import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import type { Server } from 'http';
|
||||
import cliproxyAuthRoutes from '../../../src/web-server/routes/cliproxy-auth-routes';
|
||||
import {
|
||||
clearQuotaCache,
|
||||
getCachedQuota,
|
||||
setCachedQuota,
|
||||
} from '../../../src/cliproxy/quota-response-cache';
|
||||
import { restoreFetch, mockFetch } from '../../mocks';
|
||||
|
||||
describe('cliproxy-auth-routes manual callback nickname persistence', () => {
|
||||
@@ -49,6 +54,7 @@ describe('cliproxy-auth-routes manual callback nickname persistence', () => {
|
||||
|
||||
afterEach(() => {
|
||||
restoreFetch();
|
||||
clearQuotaCache();
|
||||
|
||||
if (originalCcsHome === undefined) {
|
||||
delete process.env.CCS_HOME;
|
||||
@@ -564,4 +570,62 @@ describe('cliproxy-auth-routes manual callback nickname persistence', () => {
|
||||
|
||||
expect(registry.providers.codex.accounts['new@example.com']?.email).toBe('new@example.com');
|
||||
});
|
||||
|
||||
it('clears stale Claude quota cache after polling auth success', async () => {
|
||||
mockFetch([
|
||||
{
|
||||
url: /\/v0\/management\/anthropic-auth-url\?is_webui=true$/,
|
||||
response: {
|
||||
auth_url: 'https://auth.example.com/authorize?state=state-claude-cache-clear',
|
||||
state: 'state-claude-cache-clear',
|
||||
},
|
||||
},
|
||||
{
|
||||
url: /\/v0\/management\/get-auth-status\?state=state-claude-cache-clear$/,
|
||||
response: { status: 'ok' },
|
||||
},
|
||||
]);
|
||||
|
||||
const startResponse = await postJson('/api/cliproxy/auth/claude/start-url', {});
|
||||
expect(startResponse.status).toBe(200);
|
||||
|
||||
const accountId = 'claude-team@example.com';
|
||||
setCachedQuota('claude', accountId, {
|
||||
success: false,
|
||||
error: 'Authentication required for policy limits',
|
||||
needsReauth: true,
|
||||
});
|
||||
expect(getCachedQuota('claude', accountId)).not.toBeNull();
|
||||
|
||||
const tokenDir = path.join(tempHome, '.ccs', 'cliproxy', 'auth');
|
||||
fs.mkdirSync(tokenDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(tokenDir, 'claude-claude-team@example.com.json'),
|
||||
JSON.stringify({
|
||||
type: 'claude',
|
||||
email: accountId,
|
||||
access_token: 'fresh-token',
|
||||
refresh_token: 'refresh-token',
|
||||
expired: '2099-01-01T00:00:00.000Z',
|
||||
}),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const statusResponse = await getJson(
|
||||
'/api/cliproxy/auth/claude/status?state=state-claude-cache-clear'
|
||||
);
|
||||
|
||||
expect(statusResponse.status).toBe(200);
|
||||
expect(statusResponse.body).toEqual({
|
||||
status: 'ok',
|
||||
account: {
|
||||
id: accountId,
|
||||
email: accountId,
|
||||
nickname: 'claude-team',
|
||||
provider: 'claude',
|
||||
isDefault: true,
|
||||
},
|
||||
});
|
||||
expect(getCachedQuota('claude', accountId)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user