open-swe/agent/dashboard/repo_access.py
Johannes du Plessis 6895ddcedc
feat: add scheduled web agents (#1422)
* feat: add scheduled web agents

Co-authored-by: open-swe[bot] <215916821+open-swe[bot]@users.noreply.github.com>

* fix: secure scheduled agent repositories

Co-authored-by: open-swe[bot] <215916821+open-swe[bot]@users.noreply.github.com>

* feat: rebuild scheduled agents as Automations tab

Scrap the inline ScheduledAgentsPanel and replace it with a dedicated
Automations tab: sidebar nav entry, list view with stat cards + empty
state, and a full editor (name, Active toggle, repo, scheduled trigger
picker, agent instructions + model).

* fix: clear collapsed-sidebar button on mobile in Automations

* fix: allow clearing automation repo on update

---------

Co-authored-by: open-swe[bot] <215916821+open-swe[bot]@users.noreply.github.com>
2026-06-05 02:20:24 +00:00

65 lines
2.3 KiB
Python

"""GitHub repository access checks for dashboard actions."""
from __future__ import annotations
import httpx
from fastapi import HTTPException
from .profiles import get_valid_access_token
from .review_styles import normalize_repo_full_name
def _raise_for_github_repo_status(status_code: int) -> None:
if status_code == 401:
raise HTTPException(401, "github token expired, re-login required")
if status_code == 404:
raise HTTPException(404, "repository not found")
if status_code == 403:
raise HTTPException(403, "no access to this private repository")
if status_code != 200:
raise HTTPException(502, f"github API error ({status_code})")
async def assert_repo_access(full_name: str, token: str) -> str:
full_name = normalize_repo_full_name(full_name)
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
}
owner, name = full_name.split("/", 1)
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.github.com/repos/{owner}/{name}",
headers=headers,
)
_raise_for_github_repo_status(response.status_code)
return full_name
async def require_repo_access_for_user(login: str, full_name: str) -> str:
token = await get_valid_access_token(login)
if not token:
raise HTTPException(401, "github token unavailable, re-login required")
try:
await assert_repo_access(full_name, token)
except HTTPException as exc:
if exc.status_code != 401:
raise
token = await get_valid_access_token(login, force_refresh=True)
if not token:
raise HTTPException(401, "github token expired, re-login required") from exc
await assert_repo_access(full_name, token)
return token
async def repo_config_for_user(login: str, full_name: str | None) -> dict[str, str] | None:
if not isinstance(full_name, str) or not full_name.strip():
return None
try:
normalized = normalize_repo_full_name(full_name)
except ValueError as exc:
raise HTTPException(422, str(exc)) from exc
await require_repo_access_for_user(login, normalized)
owner, name = normalized.split("/", 1)
return {"owner": owner, "name": name}