Merge pull request 'fix: report unauthenticated auth state honestly' (#66) from build/64-auth-state into main
Some checks failed
ci / test (push) Has been cancelled
Some checks failed
ci / test (push) Has been cancelled
Reviewed-on: #66 Reviewed-by: glm-bot-andresmgsl <andres+5@heavyduty.builders> Reviewed-by: claude-bot-andresmgsl <andres+1@heavyduty.builders> Reviewed-by: kimi-bot-andresmgsl <andres+4@heavyduty.builders>
This commit is contained in:
commit
c4110e7f7e
3 changed files with 59 additions and 3 deletions
1
changelog.d/64.md
Normal file
1
changelog.d/64.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
- Report supplied tokens that remain active after logout and make unauthenticated status machine-detectable. (#64).
|
||||||
10
src/cli.js
10
src/cli.js
|
|
@ -295,6 +295,8 @@ auth
|
||||||
} else {
|
} else {
|
||||||
console.log(`Skipping remote revocation (no password provided). Token ${config.tokenId} stays active on ${config.url}; revoke it from the web UI under Settings > Applications.`);
|
console.log(`Skipping remote revocation (no password provided). Token ${config.tokenId} stays active on ${config.url}; revoke it from the web UI under Settings > Applications.`);
|
||||||
}
|
}
|
||||||
|
} else if (!config.tokenId && !options.localOnly) {
|
||||||
|
console.log(`Removing local credentials. Stoke did not create this token and cannot revoke it. The token is still valid on ${config.url}; revoke it from the web UI under Settings > Applications.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearConfig();
|
clearConfig();
|
||||||
|
|
@ -313,8 +315,12 @@ auth
|
||||||
try {
|
try {
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
if (!config || !config.token) {
|
if (!config || !config.token) {
|
||||||
console.log('Not authenticated.');
|
if (options.json) {
|
||||||
return;
|
console.log('{"authenticated": false}');
|
||||||
|
} else {
|
||||||
|
console.log('Not authenticated.');
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = ForgejoClient.fromConfig(config);
|
const client = ForgejoClient.fromConfig(config);
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,59 @@ test('global --config flag overrides the config location', () => {
|
||||||
// "Not authenticated" instead of silently using the default config.
|
// "Not authenticated" instead of silently using the default config.
|
||||||
const missing = path.join(os.tmpdir(), `stoke-missing-${process.pid}.json`);
|
const missing = path.join(os.tmpdir(), `stoke-missing-${process.pid}.json`);
|
||||||
const res = run(['--config', missing, 'auth', 'status']);
|
const res = run(['--config', missing, 'auth', 'status']);
|
||||||
assert.equal(res.status, 0);
|
assert.equal(res.status, 1);
|
||||||
assert.match(res.stdout, /Not authenticated/);
|
assert.match(res.stdout, /Not authenticated/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('auth status reports an absent session in text and JSON with a failing status', () => {
|
||||||
|
const missing = path.join(os.tmpdir(), `stoke-missing-${process.pid}-auth-status.json`);
|
||||||
|
|
||||||
|
const text = run(['auth', 'status'], { STOKE_CONFIG_FILE: missing });
|
||||||
|
assert.equal(text.status, 1);
|
||||||
|
assert.equal(text.stdout, 'Not authenticated.\n');
|
||||||
|
assert.equal(text.stderr, '');
|
||||||
|
|
||||||
|
const json = run(['auth', 'status', '--json'], { STOKE_CONFIG_FILE: missing });
|
||||||
|
assert.equal(json.status, 1);
|
||||||
|
assert.equal(json.stdout, '{"authenticated": false}\n');
|
||||||
|
assert.equal(json.stderr, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auth logout identifies a supplied token that remains active without changing local-only output', () => {
|
||||||
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-auth-logout-'));
|
||||||
|
const cfg = path.join(dir, 'config.json');
|
||||||
|
const config = {
|
||||||
|
url: 'https://forge.test',
|
||||||
|
login: 'bot',
|
||||||
|
username: 'bot',
|
||||||
|
token: 'token-that-must-not-be-printed',
|
||||||
|
tokenId: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(cfg, JSON.stringify(config));
|
||||||
|
const logout = run(['auth', 'logout'], { STOKE_CONFIG_FILE: cfg });
|
||||||
|
assert.equal(logout.status, 0, logout.stderr);
|
||||||
|
assert.match(logout.stdout, /local credentials/i);
|
||||||
|
assert.match(logout.stdout, /did not create this token/i);
|
||||||
|
assert.match(logout.stdout, /cannot revoke it/i);
|
||||||
|
assert.match(logout.stdout, /still valid on https:\/\/forge\.test/i);
|
||||||
|
assert.match(logout.stdout, /Settings > Applications/);
|
||||||
|
assert.doesNotMatch(logout.stdout, /Revoked token/);
|
||||||
|
assert.doesNotMatch(logout.stdout, /Password for/);
|
||||||
|
assert.doesNotMatch(logout.stdout, /token-that-must-not-be-printed/);
|
||||||
|
assert.equal(fs.existsSync(cfg), false);
|
||||||
|
|
||||||
|
fs.writeFileSync(cfg, JSON.stringify(config));
|
||||||
|
const localOnly = run(['auth', 'logout', '--local-only'], { STOKE_CONFIG_FILE: cfg });
|
||||||
|
assert.equal(localOnly.status, 0, localOnly.stderr);
|
||||||
|
assert.equal(localOnly.stdout, 'Local credentials removed.\n');
|
||||||
|
assert.equal(fs.existsSync(cfg), false);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('invalid --limit is rejected before any network call', () => {
|
test('invalid --limit is rejected before any network call', () => {
|
||||||
const res = run(['repo', 'list', '-l', 'abc']);
|
const res = run(['repo', 'list', '-l', 'abc']);
|
||||||
assert.equal(res.status, 1);
|
assert.equal(res.status, 1);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue