diff --git a/routes/api_token_routes.py b/routes/api_token_routes.py index 05806e420..6f8ac2fc9 100644 --- a/routes/api_token_routes.py +++ b/routes/api_token_routes.py @@ -67,6 +67,7 @@ def _normalize_scopes(scopes: str | list[str] | None = None, profile: str | None ensure_before("calendar:write", "calendar:read") ensure_before("memory:write", "memory:read") ensure_before("email:draft", "email:read") + ensure_before("cookbook:launch", "cookbook:read") return normalized or [DEFAULT_SCOPES] diff --git a/tests/test_api_token_routes.py b/tests/test_api_token_routes.py index 8c9aaab51..8443fdafe 100644 --- a/tests/test_api_token_routes.py +++ b/tests/test_api_token_routes.py @@ -192,6 +192,36 @@ def test_create_token_attributes_owner_hashes_secret_and_returns_raw_once(monkey invalidator.assert_called_once() +def test_create_token_accepts_cookbook_read_scope(monkeypatch, token_routes_mod): + monkeypatch.setenv("AUTH_ENABLED", "true") + mod = token_routes_mod + + fake_session = MagicMock() + monkeypatch.setattr(mod, "get_db_session", lambda: _db_ctx(fake_session)) + monkeypatch.setattr(mod, "get_current_user", lambda req: req.state.current_user) + + req = _req("alice", is_admin=True) + create_token = _get_handler(mod, "POST", "/tokens") + resp = create_token(request=req, name="cookbook-reader", scopes="cookbook:read") + + assert resp["scopes"] == ["cookbook:read"] + + +def test_cookbook_launch_scope_implies_read(monkeypatch, token_routes_mod): + monkeypatch.setenv("AUTH_ENABLED", "true") + mod = token_routes_mod + + fake_session = MagicMock() + monkeypatch.setattr(mod, "get_db_session", lambda: _db_ctx(fake_session)) + monkeypatch.setattr(mod, "get_current_user", lambda req: req.state.current_user) + + req = _req("alice", is_admin=True) + create_token = _get_handler(mod, "POST", "/tokens") + resp = create_token(request=req, name="cookbook-launcher", scopes="cookbook:launch") + + assert resp["scopes"] == ["cookbook:read", "cookbook:launch"] + + # --------------------------------------------------------------------------- # 3. GET /api/tokens — safe display fields only, no hash or raw token # ---------------------------------------------------------------------------