Conversation
auth.json was a flat blob describing one account, holding the whole
user('me') response. It is now { version, activeProfile, profiles,
secretsBackend }, so it can hold N accounts. Nothing puts a second one
there yet, and users see no change.
New src/lib/auth-file.ts owns the file: reading, an atomic write, the
v1 to v2 migration, and the profile accessors. credentials.ts, login,
logout, getLocalUserInfo() and the rental notice all go through it.
The migration backs the old file up as auth.json.v1.bak, runs after
ensureMigrated() as a separate step, is idempotent and single-flight,
and never throws. Fields nothing reads are dropped: email, plan,
effectivePlatformFeatures, isPaying, createdAt and proxy.groups.
Closes #1419
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Secrets lived under one fixed name per kind, so a second account would overwrite the first one's token. Both the keyring and the file backend are now keyed by user ID, and existing secrets are re-keyed in place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
TL;DR — Secrets sat under one fixed name per kind, so a second account would overwrite the first one's token. Both backends are now keyed by user ID — keyring services
com.apify.cli.token/com.apify.cli.proxy-passwordwith the user ID as the account, and the file backend inside the matching profile object. Existing secrets are re-keyed in place. No UX change.Stacked on #1434 (Stage-1, subtask 4). Base branch is
claude/auth-json-v2-1419, notmaster.Closes #1420. Part of #1383.
Change
com.apify.cli/token,proxy-passwordcom.apify.cli.token,com.apify.cli.proxy-password/<userId>auth.json.token,auth.json.proxy.passwordprofiles[<userId>]Service per kind, not a composite account.
token:<userId>under one service would depend on:being legal in an account name on macOS Keychain, libsecret and Windows Credential Manager, and it reads worse in Keychain Access.getToken/setToken/getProxyPassword/setProxyPasswordcollapse intogetSecret(userId, kind)andsetSecret(userId, kind, value). One implementation, and the key is visible at every call site.Both backends in one PR, because
downgradeBackendToFile()flips the backend inside a single process. Split across two releases, a downgrade would write the secret under a name the next read does not look for.Migration
ensureSecretsKeyed()is a separate step from the two that already exist. A v2 file whose secrets still sit under the old names is a supported state — every user is in it between #1434's release and this one — so the two migrations stay independent.New
ensureCredentialsCurrent()pins the order the three migrations have to run in: v1 secrets out ofauth.json, then the v2 profile shape, then the per-user keys — the third needs the user ID the second writes. It replaces two hand-sequenced calls inutils.tsandauth.ts.Behavior worth calling out
logoutreordered.auth.jsonis the only index of what the keyring holds, so removing the profile first would strand its entries. It now asserts the file version, clears the keyring, then removes the profile — the version refusal still comes before anything is deleted. Legacy fixed-name entries are still deleted too, so logout works for anyone who never re-keyed.loginWithToken()replaces the stored profile wholesale, so without this the previous user's keyring entries would be unreachable forever —logoutonly ever clears the active profile. Caught in review; three regression tests cover it.idhas no key to file the secret under. The secret is dropped and the next command asks for a re-login;auth.json.v1.bakstill holds it. No fallback read of the legacy key, and no network lookup of the user ID — the migration stays offline and non-blocking.getLocalUserInfo()'s "Stale credentials found without user metadata" branch is gone. It was the error for exactly the state above, and dropping the secret makes it unreachable. A danglingactiveProfilenow reports the missing profile by name whether or not a secret was found — previously it could only tell when a token happened to be readable.auth.jsonstill strands keyring entries. The keyring has no listing API, and reaching for fixed names on a machine with no account would touch the keyring on every command. Asserted in a test so the trade-off is on the record.Verification
pnpm run test:local— 648 passed, 4 skipped (63 files), up 14 from the base.pnpm run lint,pnpm run format,pnpm run build— clean.pnpm run update-docs— no change; no flag, arg, description or registration moved.pnpm run test:apinot run — no token in this environment.${service}:${account};com.apify.cli.token:uidandcom.apify.cli:tokenare distinct strings, so the new names do not collide with the legacy ones in that map.APIFY_DISABLE_KEYRINGtoggled between login and logout; logout leaving other profiles alone; two accounts holding their own entries; idempotency and single-flight.Not tested by hand: the macOS Keychain prompt. Creating an item under a new service may prompt, and this migration runs on the first command after the upgrade. Worth one manual check before release; if it prompts, re-key at next login instead of at first command.
Left out
--profile, noauth switch, noauth list.clearKeyringSecrets()iterates fromauth.jsonas the issue asks, but only ever sees one profile today — Stage-2 (Stage-2: Login - token multi account support #1386) is what puts a second one there.🤖 Generated with Claude Code