Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,29 @@ Check my stripe balance

The agent will use authsome to login into external services and perform the task.

## Proxy Policy

By default, `authsome run` injects credentials for connected providers and lets requests to unrecognised hosts pass through (`connected_allow`). You can tighten or loosen this behaviour with the proxy-mode setting.

```bash
# Read current mode
authsome config get proxy-mode

# Set mode
authsome config set proxy-mode connected_deny
```

| Mode | Scope | Unmatched requests |
|------|-------|--------------------|
| `connected_allow` | connected providers only | pass through (**default**) |
| `connected_deny` | connected providers only | blocked with 403 |
| `configured_allow` | all configured providers (including disconnected) | pass through |
| `configured_deny` | all configured providers | blocked with 403 |

The mode is caller-local — it lives in `~/.authsome/config.json` and applies per `authsome run` invocation. The daemon never sees or acts on it.

---

## Agent Integrations

Authsome ships with adapters for the most common agent frameworks and CLIs:
Expand Down
4 changes: 2 additions & 2 deletions src/authsome/cli/client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class ClientConfig(BaseModel):
The proxy_mode field lives here (not in ServerConfig) because the
mitmproxy addon runs inside the CLI process per `authsome run`
invocation. The daemon never acts on the mode itself; only the
caller-local proxy does. Users can change the mode by editing this
file directly — there is no CLI command for it today (YAGNI).
caller-local proxy does. Use `authsome config get/set proxy-mode`
to read or change the mode without hand-editing the file.
"""

version: str = __version__
Expand Down
56 changes: 56 additions & 0 deletions src/authsome/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,62 @@ async def init(ctx_obj: ContextObj) -> None:
ctx_obj.print_json(data)


_VALID_PROXY_MODES = ("connected_allow", "connected_deny", "configured_allow", "configured_deny")


@cli.group(name="config")
def config_group() -> None:
"""Read and write caller-local configuration."""


@config_group.command(name="get")
@click.argument("key")
@auth_command
def config_get(ctx_obj: ContextObj, key: str) -> None:
"""Read a caller-local configuration value.

Supported keys: proxy-mode
"""
from authsome.cli.client_config import load_client_config

if key != "proxy-mode":
ctx_obj.print_json({"error": "UnknownConfigKey", "message": f"Unknown key '{key}'. Valid keys: proxy-mode"})
sys.exit(1)

home = Path(os.environ.get("AUTHSOME_HOME", str(Path.home() / ".authsome")))
config = load_client_config(home)
ctx_obj.print_json({"proxy_mode": config.proxy_mode})


@config_group.command(name="set")
@click.argument("key")
@click.argument("value")
@auth_command
def config_set(ctx_obj: ContextObj, key: str, value: str) -> None:
"""Write a caller-local configuration value.

Supported keys: proxy-mode
Valid proxy-mode values: connected_allow, connected_deny, configured_allow, configured_deny
"""
from authsome.cli.client_config import load_client_config, save_client_config

if key != "proxy-mode":
ctx_obj.print_json({"error": "UnknownConfigKey", "message": f"Unknown key '{key}'. Valid keys: proxy-mode"})
sys.exit(1)

if value not in _VALID_PROXY_MODES:
modes_str = ", ".join(_VALID_PROXY_MODES)
ctx_obj.print_json(
{"error": "InvalidProxyMode", "message": f"Invalid proxy mode '{value}'. Valid modes: {modes_str}"}
)
sys.exit(1)

home = Path(os.environ.get("AUTHSOME_HOME", str(Path.home() / ".authsome")))
updated = load_client_config(home).model_copy(update={"proxy_mode": value})
save_client_config(home, updated)
ctx_obj.print_json({"proxy_mode": updated.proxy_mode, "status": "ok"})


@cli.group(name="profile")
def profile() -> None:
"""Manage local profiles backed by identity keys."""
Expand Down
75 changes: 75 additions & 0 deletions tests/cli/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Tests for `authsome config get` and `authsome config set`."""

import json

import pytest

from authsome.cli.main import cli


class TestConfigGet:
"""Tests for `authsome config get proxy-mode`."""

def test_get_returns_default_mode(self, runner) -> None:
result = runner.invoke(cli, ["--log-file", "", "config", "get", "proxy-mode"])
assert result.exit_code == 0, result.output
data = json.loads(result.output)
assert data["proxy_mode"] == "connected_allow"

def test_get_reflects_saved_mode(self, runner, tmp_path, monkeypatch) -> None:
monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path))
from authsome.cli.client_config import ClientConfig, save_client_config

save_client_config(tmp_path, ClientConfig(proxy_mode="configured_deny"))

result = runner.invoke(cli, ["--log-file", "", "config", "get", "proxy-mode"])
assert result.exit_code == 0, result.output
data = json.loads(result.output)
assert data["proxy_mode"] == "configured_deny"

def test_get_unknown_key_returns_error(self, runner) -> None:
result = runner.invoke(cli, ["--log-file", "", "config", "get", "unknown-key"])
assert result.exit_code != 0
data = json.loads(result.output)
assert data["error"] == "UnknownConfigKey"
assert "proxy-mode" in data["message"]


class TestConfigSet:
"""Tests for `authsome config set proxy-mode <value>`."""

@pytest.mark.parametrize(
"mode",
["connected_allow", "connected_deny", "configured_allow", "configured_deny"],
)
def test_set_all_valid_modes(self, runner, tmp_path, monkeypatch, mode) -> None:
monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path))

result = runner.invoke(cli, ["--log-file", "", "config", "set", "proxy-mode", mode])
assert result.exit_code == 0, result.output
data = json.loads(result.output)
assert data["status"] == "ok"
assert data["proxy_mode"] == mode

def test_set_persists_to_disk(self, runner, tmp_path, monkeypatch) -> None:
monkeypatch.setenv("AUTHSOME_HOME", str(tmp_path))
from authsome.cli.client_config import load_client_config

runner.invoke(cli, ["--log-file", "", "config", "set", "proxy-mode", "connected_deny"])
assert load_client_config(tmp_path).proxy_mode == "connected_deny"

def test_set_invalid_mode_returns_error(self, runner) -> None:
result = runner.invoke(cli, ["--log-file", "", "config", "set", "proxy-mode", "bad_value"])
assert result.exit_code != 0
data = json.loads(result.output)
assert data["error"] == "InvalidProxyMode"
assert "bad_value" in data["message"]
for valid in ("connected_allow", "connected_deny", "configured_allow", "configured_deny"):
assert valid in data["message"]

def test_set_unknown_key_returns_error(self, runner) -> None:
result = runner.invoke(cli, ["--log-file", "", "config", "set", "unknown-key", "value"])
assert result.exit_code != 0
data = json.loads(result.output)
assert data["error"] == "UnknownConfigKey"
assert "proxy-mode" in data["message"]
Loading