teamclaude-cloud 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/cloud.js +21 -0
- package/src/index.js +29 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teamclaude-cloud",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "Multi-account Claude proxy with quota-based rotation + cloud token sync and auth-key control",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -35,4 +35,4 @@
|
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|
|
37
37
|
}
|
|
38
|
-
}
|
|
38
|
+
}
|
package/src/cloud.js
CHANGED
|
@@ -305,6 +305,27 @@ export async function cloudRefreshToken(cloud, authKey, accountUuid, minRemainin
|
|
|
305
305
|
};
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Report a locally-rotated token back to the cloud (auth-key authenticated).
|
|
310
|
+
* When a consumer had to refresh a token LOCALLY (cloud refresh unreachable), it
|
|
311
|
+
* MUST push the rotated token back — otherwise the cloud keeps a now-dead refresh
|
|
312
|
+
* token and every other consumer (and the cloud's own /sync/refresh) fails with
|
|
313
|
+
* invalid_grant. Freshest-token-wins on the server, scoped to the key's group.
|
|
314
|
+
* Best-effort at the call site: a failure to report never breaks the local refresh.
|
|
315
|
+
*/
|
|
316
|
+
export async function cloudReportToken(cloud, authKey, accountUuid, { accessToken, refreshToken, expiresAt } = {}) {
|
|
317
|
+
requireCloud(cloud);
|
|
318
|
+
if (!authKey) throw new Error('Auth key required for token report.');
|
|
319
|
+
if (!accountUuid) throw new Error('accountUuid required for token report.');
|
|
320
|
+
const res = await httpJson(`${fnBase(cloud)}/sync/report-token`, {
|
|
321
|
+
method: 'POST',
|
|
322
|
+
headers: fnHeaders(cloud, { 'x-teamclaude-key': authKey }),
|
|
323
|
+
body: { accountUuid, accessToken, refreshToken, expiresAt: normalizeExpiresAt(expiresAt) || 0 },
|
|
324
|
+
});
|
|
325
|
+
if (!res.ok) throw new Error(`Cloud token report failed (${res.status}): ${describe(res.data)}`);
|
|
326
|
+
return { action: res.data.action || 'skipped' };
|
|
327
|
+
}
|
|
328
|
+
|
|
308
329
|
/**
|
|
309
330
|
* Pull allowed accounts+tokens using an auth key (no owner session needed).
|
|
310
331
|
* Returns `{ accounts, allowed, groupId, source }`:
|
package/src/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { loadOrCreateConfig, loadConfig, saveConfig, atomicConfigUpdate, getConf
|
|
|
7
7
|
import { AccountManager } from './account-manager.js';
|
|
8
8
|
import { createProxyServer } from './server.js';
|
|
9
9
|
import { importCredentials, loginOAuth, fetchProfile, refreshAccessToken, isTokenExpiringSoon } from './oauth.js';
|
|
10
|
-
import { cloudLogin, cloudRefreshSession, cloudPush, cloudPushUsage, cloudReportUsage, cloudPull, cloudRefreshToken, cloudKeyCreate, cloudKeyList, cloudKeyRevoke, cloudKeyMove, cloudGroupList, cloudGroupCreate, cloudGroupRename, cloudGroupDelete, cloudGroupSetAccounts, cloudAccountSetEnabled, buildUsageFromQuota, mergePulledAccounts, resolveCloud, DEFAULT_CLOUD, keySource, reconcileRemovedAccounts } from './cloud.js';
|
|
10
|
+
import { cloudLogin, cloudRefreshSession, cloudPush, cloudPushUsage, cloudReportUsage, cloudPull, cloudRefreshToken, cloudReportToken, cloudKeyCreate, cloudKeyList, cloudKeyRevoke, cloudKeyMove, cloudGroupList, cloudGroupCreate, cloudGroupRename, cloudGroupDelete, cloudGroupSetAccounts, cloudAccountSetEnabled, buildUsageFromQuota, mergePulledAccounts, resolveCloud, DEFAULT_CLOUD, keySource, reconcileRemovedAccounts } from './cloud.js';
|
|
11
11
|
import { TUI } from './tui.js';
|
|
12
12
|
|
|
13
13
|
const args = process.argv.slice(2);
|
|
@@ -1432,23 +1432,38 @@ const _autoPushInFlight = new Set();
|
|
|
1432
1432
|
*/
|
|
1433
1433
|
async function maybePushRefreshedToken(config, account, newTokens) {
|
|
1434
1434
|
const key = account.accountUuid || account.name;
|
|
1435
|
+
const hasOwner = !!(config.cloud && config.cloud.session && config.cloud.session.access_token);
|
|
1436
|
+
const pullKey = config.cloud?.pullKey || process.env.TEAMCLAUDE_AUTH_KEY;
|
|
1437
|
+
// Nothing to sync back to unless we're an admin (owner session) OR a consumer
|
|
1438
|
+
// with a pull key. A consumer that rotated a token LOCALLY (cloud refresh was
|
|
1439
|
+
// unreachable) MUST report it back, or the cloud keeps a now-dead refresh token
|
|
1440
|
+
// and every consumer — plus the cloud's own /sync/refresh — fails invalid_grant.
|
|
1441
|
+
if (!hasOwner && !(pullKey && account.accountUuid)) return;
|
|
1435
1442
|
try {
|
|
1436
|
-
if (!config.cloud || !config.cloud.session || !config.cloud.session.access_token) return; // not an admin/pusher
|
|
1437
1443
|
if (_autoPushInFlight.has(key)) return;
|
|
1438
1444
|
_autoPushInFlight.add(key);
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1445
|
+
if (hasOwner) {
|
|
1446
|
+
const cloud = await ensureCloudSession(config).catch(() => config.cloud);
|
|
1447
|
+
await cloudPush(cloud, [{
|
|
1448
|
+
accountUuid: account.accountUuid,
|
|
1449
|
+
name: account.name,
|
|
1450
|
+
subscriptionType: account.subscriptionType,
|
|
1451
|
+
tier: account.tier,
|
|
1452
|
+
type: account.type,
|
|
1453
|
+
accessToken: newTokens.accessToken,
|
|
1454
|
+
refreshToken: newTokens.refreshToken,
|
|
1455
|
+
expiresAt: newTokens.expiresAt,
|
|
1456
|
+
}]);
|
|
1457
|
+
} else {
|
|
1458
|
+
// Consumer path: key-authenticated write-back (freshest-wins on the server).
|
|
1459
|
+
await cloudReportToken(resolveCloud(config), pullKey, account.accountUuid, {
|
|
1460
|
+
accessToken: newTokens.accessToken,
|
|
1461
|
+
refreshToken: newTokens.refreshToken,
|
|
1462
|
+
expiresAt: newTokens.expiresAt,
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1450
1465
|
} catch (err) {
|
|
1451
|
-
console.error(`[TeamClaude] Cloud
|
|
1466
|
+
console.error(`[TeamClaude] Cloud token sync-back failed for ${account.name}: ${err.message}`);
|
|
1452
1467
|
} finally {
|
|
1453
1468
|
_autoPushInFlight.delete(key);
|
|
1454
1469
|
}
|