Skip to content

test: pin auth behavior before the multi-account refactor - #1417

Merged
l2ysho merged 7 commits into
masterfrom
claude/github-issue-1387-d39d52
Sep 22, 2026
Merged

l2ysho merged 7 commits into
masterfrom
claude/github-issue-1387-d39d52

Conversation

@l2ysho

@l2ysho l2ysho commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Note

Stage-0 of #1297. Test-only, main reason is to have better coverage of auth flow before we start with #1297 .

Why

  • useAuthSetup and run-cli both pin APIFY_DISABLE_KEYRING=1, so no test ever hit the keyring path.
  • src/commands/auth/* ran only under test:api / test:e2e, which need TEST_USER_TOKEN.
  • getLoggedClient is a second auth.json writer, separate from credentials.ts. Its keyring branch — strip token, strip proxy.password, drop proxy when empty — had no test.
  • 6 tests in Stage-0: Verify tests and coverage #1387 were mutation-verified as unable to fail.

What

  • test/__setup__/keyring-mock.ts — shared @napi-rs/keyring fake. Exposes keyringStore, keyringFailures, keyringSetKeys.
  • useKeyringBackend() in useAuthSetup.ts — describe-scoped, so one file covers both backends. Throws if the file forgot the mock, rather than writing to the developer's real OS keyring.
  • test/local/commands/auth.test.ts — 11 tests. Stubs apify-client, so login / logout / auth token run in test:local on both backends.
  • credentials.test.ts — 23 → 42 tests: writeFileSync spy (F1/F2 could not fail before), 0o600, keyring write failures for setToken and setProxyPassword, the stale-credentials throw, resolveToken via getApifyClientOptions, the persisted secretsBackend marker branch.
  • e2e auth token compares stdout to the token, not to length > 0 (F5).
  • CONTRIBUTING documents the new hook.

All 8 exit criteria in #1387 are met, plus F3 and the account-switch merge that Stage-1 rewrites.

Notes

  • The issue says resolveToken has an APIFY_TOKEN branch. It does not — src/lib/utils.ts:132 is existingToken ?? (ensureMigrated(), getToken()). That variable is read in actor.ts, mcp/install.ts and actor/charge.ts instead.
  • Install size unchanged — no dependency added, and the shared mock is a test file.
  • auth.test.ts:116 asserts that fields from the old account survive a re-login. That is current behavior, pinned deliberately, and flagged in a comment.

Verification

test:local 585 passed / 4 skipped. Lint, format, build, test typecheck clean. test:api not run locally — no token here; the useAuthSetup.ts change is one added export.

🤖 Generated with Claude Code

l2ysho and others added 3 commits September 10, 2026 13:44
Stage-0 of #1297. The keyring backend — the default for real users — was
never reached through a command: useAuthSetup and run-cli both pin
APIFY_DISABLE_KEYRING=1, and the auth commands only ran under test:api.

- Shared @napi-rs/keyring fake in test/__setup__/keyring-mock.ts, plus a
  useKeyringBackend() hook so one file can cover both backends
- New test/local/commands/auth.test.ts stubs apify-client, so login,
  logout and auth token now run in test:local on both backends
- credentials.test.ts: writeFileSync spy so the skipIfUnchanged tests can
  fail, 0o600 assertions, keyring write failures for setToken and
  setProxyPassword, the stale-credentials throw, resolveToken happy path
- e2e auth token now compares stdout to the token, not to length > 0

Every new test was mutation-verified against the branch it covers.

Closes #1387

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
From the second staff review:
- getBackend() reading a persisted secretsBackend marker is what makes a
  keyring downgrade survive across processes; it had no test
- every login in auth.test.ts used one identity, so the merge in
  getLoggedClient was never given a differing user. Stage-1 rewrites
  exactly that merge

Also imports process in credentials.test.ts and notes the nesting
constraint on useKeyringBackend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the 149th sprint - Tooling team milestone Sep 10, 2026
@github-actions github-actions Bot added t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics. labels Sep 10, 2026
@l2ysho
l2ysho marked this pull request as ready for review September 10, 2026 12:40
@l2ysho
l2ysho requested a review from DaveHanns as a code owner September 10, 2026 12:40
@l2ysho l2ysho added the adhoc Ad-hoc unplanned task added during the sprint. label Sep 22, 2026
@l2ysho
l2ysho merged commit 92a55c0 into master Sep 22, 2026
22 of 23 checks passed
@l2ysho
l2ysho deleted the claude/github-issue-1387-d39d52 branch September 22, 2026 13:27
l2ysho added a commit that referenced this pull request Sep 22, 2026
Resolves conflicts in the three auth test files. #1417 was squash-merged
into master, so master's copies are content-identical to this branch's
ancestor; the branch versions supersede them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
l2ysho added a commit that referenced this pull request Sep 23, 2026
> [!NOTE]
> **TL;DR** 
> - `APIFY_TOKEN` now has max priority if set, check comparison tables
below for more info
> - `resolveAuth()` replaces `getApifyTokenFromEnvOrAuthFile` and
`resolveApifyToken` -> solving issues with rewriting auth.json when not
needed
> - `--token` available for `auth login` and `mcp install`


Stacked on #1417 (Stage-0). Base branch is
`claude/github-issue-1387-d39d52`, not `master`.

Closes [#1418](#1418) and
[#720](#720).

## Checklist

- [x] Check `skills/apify/SKILL.md` after the implementation lands

## Behavior, before and after

Run against two real accounts, **account-a** (logged in) and
**account-b** (`$B`), with 1.10.0 side by side.

### `APIFY_TOKEN` no longer ignored

| Command | Before | Now |
|---|---|---|
| `APIFY_TOKEN=$B apify actors ls` | lists **account-a**'s Actors |
lists **account-b**'s Actors |
| `APIFY_TOKEN=$B apify actors ls` *(logged out)* | `Error: You are not
logged in` | lists **account-b**'s Actors |
| `APIFY_TOKEN=$B apify info` | `username: account-a` | `username:
account-b` + `token source: APIFY_TOKEN environment variable` |
| `APIFY_TOKEN=$B apify auth token` | prints **account-a**'s token |
prints **account-b**'s token |
| `APIFY_TOKEN=$B apify actors info <name>` | resolves
`account-a/<name>` → not found | resolves `account-b/<name>` → found |

### `apify run` handed the child the wrong identity

| Command | Before | Now |
|---|---|---|
| `APIFY_TOKEN=$B apify run` | child gets **account-a**'s token, user id
and proxy password | child gets **account-b**'s |

This is the "Insufficient permissions for the Actor run" report, and why
it started working after `apify logout`.

### Reads overwrote the stored login

| Command | Before | Now |
|---|---|---|
| `APIFY_IS_AT_HOME=1 APIFY_TOKEN=$B apify actor charge result-item` |
errors on the missing run id — **and `auth.json` now holds account-b**.
Unsetting `APIFY_TOKEN` does not restore account-a | same error,
`auth.json` untouched |

### A misconfigured `APIFY_TOKEN` fails instead of guessing

| Command | Before | Now |
|---|---|---|
| `APIFY_TOKEN=undefined apify actors ls` | silently uses the stored
login | `Error: APIFY_TOKEN is set to "undefined", which is not an API
token.` exit 5 |
| `APIFY_TOKEN= apify actors ls` | silently uses the stored login |
unchanged — an unset variable is not worth a message |
| `APIFY_TOKEN=<revoked> apify actors ls` | silently succeeds as
**account-a** | `Error: The API token in APIFY_TOKEN was rejected.` |
| `APIFY_TOKEN=$B apify login --token $A` | `Success: logged in as
account-a`, while every later command ran as account-b | `Error:` exit
5. Same token stays silent — that is the CI idiom |
| `APIFY_TOKEN=undefined apify login --token $A` | success, then every
later command failed | warns and proceeds: login is the only way to fix
the shell |
| `apify login --token <bad>` | exit **0** — `login && push` ran on |
exit **1** |

## What changed

- One `resolveAuth()` in the new `src/lib/auth.ts`.
`getApifyTokenFromEnvOrAuthFile` and `resolveApifyToken` are gone. No
special case for the `actor` entrypoint: inside a platform run there is
no stored login, so `APIFY_TOKEN` wins on its own.
- Resolve is split from persist. `loginWithToken()` is the only
credential writer, and it replaces the stored account rather than
merging into it — including the keyring proxy password, which used to
survive a re-login and leak the wrong account's credential into `apify
run`'s child.
- `resolveAuth` is single-flighted. It ran 2–4× per command, each an
uncached OS keyring read.
- One reader for `APIFY_TOKEN`, returning unset / invalid / token, so
`login` and `logout` see a value every other command rejects.
- Commands that address resources by `username` moved to
`getCurrentUserInfo()`, which reads the resolved account rather than
`auth.json`.
- `--token` stays on `login` and `mcp install` only. No global flag —
`APIFY_TOKEN` already does that job, and two mechanisms for one thing is
worse.

Closes #1418. Closes #720.

## Verification

- `pnpm run test:local` — 621 passed, 4 skipped.
- Same with `APIFY_TOKEN` set to a bogus value, which is deliberate: a
host token must not decide which account the tests run as.
- Lint, format, build, `update-docs` clean.
- `pnpm run test:api` not run — no token in this environment.
- The before/after table above was run by hand against two real
accounts.

## Upgrade note

A stale `APIFY_TOKEN` in a shell profile or CI job now decides which
account commands run as. If it holds a placeholder, commands fail rather
than falling back. Worth a release note.


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Edyta <142720610+szaganek@users.noreply.github.com>
@l2ysho l2ysho mentioned this pull request Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants