diff --git a/src/commands/run.ts b/src/commands/run.ts index 050e3e3c6..2e9f5c8a3 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -328,7 +328,8 @@ export class RunCommand extends ApifyCommand { if (proxy && proxy.password) localEnvVars[APIFY_ENV_VARS.PROXY_PASSWORD] = proxy.password; if (userId) localEnvVars[APIFY_ENV_VARS.USER_ID] = userId; - if (token) localEnvVars[APIFY_ENV_VARS.TOKEN] = token; + // Don't clobber an explicitly-set APIFY_TOKEN inherited from the environment — it must win over the stored login. + if (token && !process.env[APIFY_ENV_VARS.TOKEN]) localEnvVars[APIFY_ENV_VARS.TOKEN] = token; if (localConfig!.environmentVariables) { const updatedEnv = replaceSecretsValue(localConfig!.environmentVariables as Record, undefined, { allowMissing: this.flags.allowMissingSecrets, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 99d8784e8..ae00a50b1 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -131,6 +131,7 @@ export async function getLoggedClientOrThrow() { const resolveToken = async (existingToken?: string): Promise => { if (existingToken) return existingToken; + if (process.env[APIFY_ENV_VARS.TOKEN]) return process.env[APIFY_ENV_VARS.TOKEN]; await ensureMigrated(); return getToken(); }; diff --git a/test/local/commands/run.test.ts b/test/local/commands/run.test.ts index bc4e26323..bd438ab92 100644 --- a/test/local/commands/run.test.ts +++ b/test/local/commands/run.test.ts @@ -216,6 +216,33 @@ describe('apify run', () => { expect(actOutput2).toStrictEqual('can play'); }); + // Regression: an inherited APIFY_TOKEN must reach the Actor unchanged, not be clobbered by the stored login token. + it(`[api] does not override an inherited ${APIFY_ENV_VARS.TOKEN} with the stored token`, async () => { + await safeLogin(); + + const auth = JSON.parse(readFileSync(AUTH_FILE_PATH(), 'utf8')); + const inheritedToken = `${auth.token}_inherited`; + vitest.stubEnv(APIFY_ENV_VARS.TOKEN, inheritedToken); + + const actCode = ` + import { Actor } from 'apify'; + + Actor.main(async () => { + await Actor.setValue('OUTPUT', process.env); + console.log('Done.'); + }); + `; + writeFileSync(joinPath('src/main.js'), actCode, { flag: 'w' }); + + await testRunCommand(RunCommand, {}); + + const actOutputPath = joinPath(getLocalKeyValueStorePath(), 'OUTPUT.json'); + const localEnvVars = JSON.parse(readFileSync(actOutputPath, 'utf8')); + + expect(localEnvVars[APIFY_ENV_VARS.TOKEN]).toStrictEqual(inheritedToken); + expect(localEnvVars[APIFY_ENV_VARS.TOKEN]).not.toStrictEqual(auth.token); + }); + it('run purge stores', async () => { const input = { myInput: 'value', diff --git a/test/local/lib/credentials.test.ts b/test/local/lib/credentials.test.ts index 4a0566ed8..4b78727fa 100644 --- a/test/local/lib/credentials.test.ts +++ b/test/local/lib/credentials.test.ts @@ -14,7 +14,7 @@ import { setProxyPassword, setToken, } from '../../../src/lib/credentials.js'; -import { getLocalUserInfo } from '../../../src/lib/utils.js'; +import { getApifyClientOptions, getLocalUserInfo } from '../../../src/lib/utils.js'; const keyringStore = new Map(); const keyringFailures = new Set(); @@ -278,4 +278,30 @@ describe('credentials', () => { expect(info.proxy?.password).toBe('pw_kr'); }); }); + + // Precedence: explicit token arg (e.g. --token) > APIFY_TOKEN env var > stored login token. + // Regression guard for the env var being ignored in favour of the stored token. + describe('token resolution precedence (getApifyClientOptions)', () => { + it('prefers the APIFY_TOKEN env var over the stored token', async () => { + vitest.stubEnv('APIFY_DISABLE_KEYRING', '1'); + await setToken('stored_tok'); + vitest.stubEnv('APIFY_TOKEN', 'env_tok'); + const { token } = await getApifyClientOptions(); + expect(token).toBe('env_tok'); + }); + + it('prefers an explicitly passed token over the APIFY_TOKEN env var', async () => { + vitest.stubEnv('APIFY_TOKEN', 'env_tok'); + const { token } = await getApifyClientOptions('explicit_tok'); + expect(token).toBe('explicit_tok'); + }); + + it('falls back to the stored token when APIFY_TOKEN is not set', async () => { + vitest.stubEnv('APIFY_DISABLE_KEYRING', '1'); + vitest.stubEnv('APIFY_TOKEN', ''); + await setToken('stored_tok'); + const { token } = await getApifyClientOptions(); + expect(token).toBe('stored_tok'); + }); + }); });