Skip to content

Commit de92550

Browse files
authored
Read ASTA_TOKEN in auth status command (#88)
1 parent 1f9d837 commit de92550

9 files changed

Lines changed: 117 additions & 21 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"name": "asta-tools",
1212
"source": "./plugins/asta-tools",
1313
"description": "Paper search, metadata, document management, literature reports, experiments, and theory generation for scientific research",
14-
"version": "0.101.0",
14+
"version": "0.101.1",
1515
"author": {
1616
"name": "Ai2 Asta Team"
1717
},
@@ -30,7 +30,7 @@
3030
"name": "asta-flows",
3131
"source": "./plugins/asta-flows",
3232
"description": "Executes named, typed chains of research steps using the skills in asta-tools. Requires installation of asta-tools",
33-
"version": "0.101.0",
33+
"version": "0.101.1",
3434
"author": {
3535
"name": "Ai2 Asta Team"
3636
},
@@ -47,7 +47,7 @@
4747
"name": "asta-assistant",
4848
"source": "./plugins/asta-assistant",
4949
"description": "Autonomous collaborative agent able to plan and execute a long-range research agenda using asta-tools. Requires installation of asta-tools",
50-
"version": "0.101.0",
50+
"version": "0.101.1",
5151
"author": {
5252
"name": "Ai2 Asta Team"
5353
},
@@ -64,7 +64,7 @@
6464
"name": "asta-dev",
6565
"source": "./plugins/asta-dev",
6666
"description": "Developer skills for working on Asta itself (improve-skills, research-challenge). Requires the asta-tools plugin to be installed.",
67-
"version": "0.101.0",
67+
"version": "0.101.1",
6868
"author": {
6969
"name": "Ai2 Asta Team"
7070
},

plugins/asta-tools/hooks/sync-cli-version.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/bash
22
# Check if asta CLI version matches the plugin version and auto-install/update if needed
33

4-
PLUGIN_VERSION=0.101.0
4+
PLUGIN_VERSION=0.101.1
55
INSTALL_URL="git+https://github.com/allenai/asta-plugins.git@v$PLUGIN_VERSION"
66

77
# Check if asta is installed

plugins/asta-tools/skills/asta-cli/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Installs the `asta` CLI for agents that don't install it automatically via a plu
1818
**Prerequisites:** Python 3.11+ and [uv](https://docs.astral.sh/uv/).
1919

2020
```bash
21-
PLUGIN_VERSION=0.101.0
21+
PLUGIN_VERSION=0.101.1
2222
INSTALL_URL="git+https://github.com/allenai/asta-plugins.git@v$PLUGIN_VERSION"
2323

2424
if ! command -v asta &> /dev/null; then

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "asta"
7-
version = "0.101.0"
7+
version = "0.101.1"
88
description = "Asta CLI for scientific literature review"
99
readme = "README.md"
1010
requires-python = ">=3.11"

src/asta/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Asta - Science literature research tools"""
22

3-
__version__ = "0.101.0"
3+
__version__ = "0.101.1"

src/asta/auth/token_manager.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,15 @@ def get_user_info(self) -> dict | None:
215215
except Exception:
216216
return None
217217

218-
def verify_token_with_gateway(self) -> dict[str, any]:
218+
def verify_token_with_gateway(
219+
self, access_token: str | None = None
220+
) -> dict[str, any]:
219221
"""
220222
Verify the access token with the gateway server.
221223
224+
Args:
225+
access_token: Token to verify. If None, loads from storage.
226+
222227
Returns:
223228
Dictionary with verification result:
224229
- valid (bool): Whether the token is valid on the server
@@ -231,11 +236,12 @@ def verify_token_with_gateway(self) -> dict[str, any]:
231236
if not self.gateway_url:
232237
raise AuthenticationError("Gateway URL not configured")
233238

234-
tokens = self.storage.load_tokens()
235-
if not tokens or not tokens.get("access_token"):
236-
return {"valid": False, "error": "No access token found"}
239+
if access_token is None:
240+
tokens = self.storage.load_tokens()
241+
if not tokens or not tokens.get("access_token"):
242+
return {"valid": False, "error": "No access token found"}
243+
access_token = tokens["access_token"]
237244

238-
access_token = tokens["access_token"]
239245
verify_url = f"{self.gateway_url}/auth/verify"
240246

241247
try:

src/asta/commands/auth.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import base64
77
import datetime
88
import json
9+
import os
910

1011
import click
1112
from rich.console import Console
@@ -85,6 +86,42 @@ def status():
8586
gateway_url=settings.gateway_url,
8687
)
8788

89+
# ASTA_TOKEN overrides stored credentials (see asta.utils.auth_helper).
90+
env_token = os.environ.get("ASTA_TOKEN")
91+
if env_token:
92+
table = Table(title="Authentication Status")
93+
table.add_column("Property", style="cyan")
94+
table.add_column("Value")
95+
table.add_row("Token Source", "[cyan]ASTA_TOKEN environment variable[/cyan]")
96+
97+
verification_result = token_manager.verify_token_with_gateway(
98+
access_token=env_token
99+
)
100+
if verification_result["valid"]:
101+
table.add_row("Server Verification", "✅ [green]Valid[/green]")
102+
server_user = verification_result.get("user_info") or {}
103+
if email := server_user.get("email"):
104+
table.add_row("Email", email)
105+
if name := server_user.get("name"):
106+
table.add_row("Name", name)
107+
else:
108+
error_msg = verification_result.get("error", "Unknown error")
109+
table.add_row(
110+
"Server Verification", f"❌ [red]Invalid[/red]\n {error_msg}"
111+
)
112+
113+
console.print(table)
114+
115+
if not verification_result["valid"]:
116+
console.print()
117+
console.print(
118+
"⚠️ [yellow]Token verification failed with gateway server.[/yellow]"
119+
)
120+
console.print(
121+
" Check that [cyan]ASTA_TOKEN[/cyan] is set to a valid token."
122+
)
123+
return
124+
88125
user_info = token_manager.get_user_info()
89126

90127
if not user_info:

tests/test_auth.py

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,18 @@ def test_load_tokens_when_none_exist(self, tmp_path):
6464
class TestAuthCommands:
6565
"""Tests for auth CLI commands."""
6666

67+
@staticmethod
68+
def _invoke(runner, args):
69+
"""Invoke CLI with ASTA_TOKEN cleared from env for storage-based tests."""
70+
return runner.invoke(cli, args, env={"ASTA_TOKEN": ""})
71+
6772
def test_auth_status_not_authenticated(self):
6873
"""Test auth status when not authenticated."""
6974
runner = CliRunner()
7075

7176
# Mock TokenStorage.load_tokens to return None (no tokens)
7277
with patch.object(TokenStorage, "load_tokens", return_value=None):
73-
result = runner.invoke(cli, ["auth", "status"])
78+
result = self._invoke(runner, ["auth", "status"])
7479
assert result.exit_code == 0
7580
assert "Not authenticated" in result.output
7681

@@ -100,7 +105,7 @@ def test_auth_status_with_server_verification(self):
100105
return_value=mock_verification,
101106
),
102107
):
103-
result = runner.invoke(cli, ["auth", "status"])
108+
result = self._invoke(runner, ["auth", "status"])
104109
assert result.exit_code == 0
105110
assert "Server Verification" in result.output
106111
assert "Valid" in result.output
@@ -128,16 +133,64 @@ def test_auth_status_with_server_verification_failure(self):
128133
return_value=mock_verification,
129134
),
130135
):
131-
result = runner.invoke(cli, ["auth", "status"])
136+
result = self._invoke(runner, ["auth", "status"])
132137
assert result.exit_code == 0
133138
assert "Server Verification" in result.output
134139
assert "Invalid" in result.output
135140
assert "Token verification failed" in result.output
136141

142+
def test_auth_status_honors_asta_token_env_var(self):
143+
"""auth status verifies ASTA_TOKEN from env, ignoring stored tokens."""
144+
runner = CliRunner()
145+
146+
mock_verification = {
147+
"valid": True,
148+
"user_info": {"email": "env@example.com", "name": "Env User"},
149+
}
150+
151+
# No stored tokens — status should still succeed via env var.
152+
with (
153+
patch.object(TokenStorage, "load_tokens", return_value=None),
154+
patch.object(
155+
TokenManager,
156+
"verify_token_with_gateway",
157+
return_value=mock_verification,
158+
) as verify_mock,
159+
):
160+
result = runner.invoke(
161+
cli, ["auth", "status"], env={"ASTA_TOKEN": "env-token-value"}
162+
)
163+
assert result.exit_code == 0
164+
assert "ASTA_TOKEN" in result.output
165+
assert "Valid" in result.output
166+
assert "env@example.com" in result.output
167+
# Verified the token from the env var, not from storage.
168+
verify_mock.assert_called_once()
169+
_, kwargs = verify_mock.call_args
170+
assert kwargs.get("access_token") == "env-token-value"
171+
172+
def test_auth_status_asta_token_invalid(self):
173+
"""auth status surfaces gateway rejection of ASTA_TOKEN."""
174+
runner = CliRunner()
175+
176+
mock_verification = {"valid": False, "error": "Invalid signature"}
177+
178+
with patch.object(
179+
TokenManager,
180+
"verify_token_with_gateway",
181+
return_value=mock_verification,
182+
):
183+
result = runner.invoke(
184+
cli, ["auth", "status"], env={"ASTA_TOKEN": "bad-token"}
185+
)
186+
assert result.exit_code == 0
187+
assert "Invalid" in result.output
188+
assert "Invalid signature" in result.output
189+
137190
def test_auth_logout(self):
138191
"""Test auth logout command."""
139192
runner = CliRunner()
140-
result = runner.invoke(cli, ["auth", "logout"])
193+
result = self._invoke(runner, ["auth", "logout"])
141194
assert result.exit_code == 0
142195
assert "Logged out successfully" in result.output
143196

@@ -147,7 +200,7 @@ def test_auth_print_token_no_token(self):
147200

148201
# Mock TokenStorage.load_tokens to return None
149202
with patch.object(TokenStorage, "load_tokens", return_value=None):
150-
result = runner.invoke(cli, ["auth", "print-token"])
203+
result = self._invoke(runner, ["auth", "print-token"])
151204
assert result.exit_code == 1
152205
assert "No token found" in result.output
153206

@@ -160,7 +213,7 @@ def test_auth_print_token_raw(self):
160213
mock_tokens = {"access_token": test_token}
161214

162215
with patch.object(TokenStorage, "load_tokens", return_value=mock_tokens):
163-
result = runner.invoke(cli, ["auth", "print-token", "--raw"])
216+
result = self._invoke(runner, ["auth", "print-token", "--raw"])
164217
assert result.exit_code == 0
165218
assert test_token in result.output
166219

@@ -175,7 +228,7 @@ def test_auth_print_token_decoded(self):
175228
mock_tokens = {"access_token": test_token}
176229

177230
with patch.object(TokenStorage, "load_tokens", return_value=mock_tokens):
178-
result = runner.invoke(cli, ["auth", "print-token"])
231+
result = self._invoke(runner, ["auth", "print-token"])
179232
assert result.exit_code == 0
180233
assert "JWT Header:" in result.output
181234
assert "JWT Payload:" in result.output

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)