link-assistant-router 1.7.0

Link.Assistant.Router — Claude MAX OAuth proxy and token gateway for Anthropic APIs
Documentation
//! In-process coverage for the administrative token handlers.

use axum::Json;
use axum::body::Body;
use axum::extract::State;
use axum::http::{HeaderMap, Request, StatusCode};
use axum::response::IntoResponse;
use http_body_util::BodyExt;
use tower::ServiceExt as _;

use crate::app_state::AppState;
use crate::token::{ADMIN_SCOPE, IssueRequest};
use crate::token_admin::{
    IssueClientTokenRequest, RotateClientTokenRequest, RotateTokenRequest, issue_client_token,
    rotate_admin_token, rotate_client_token,
};

fn state() -> (AppState, tempfile::TempDir) {
    let data = tempfile::tempdir().expect("temporary state directory");
    let mut state = AppState::for_tests(data.path());
    state.allow_anonymous_admin = true;
    (state, data)
}

fn client_request(client_kind: &str) -> IssueClientTokenRequest {
    IssueClientTokenRequest {
        client_kind: client_kind.into(),
        ttl_hours: None,
        sliding_expiry: None,
        label: None,
        max_requests: None,
        ephemeral: false,
    }
}

async fn json(response: axum::response::Response) -> serde_json::Value {
    let bytes = response
        .into_body()
        .collect()
        .await
        .expect("read response")
        .to_bytes();
    serde_json::from_slice(&bytes).expect("JSON response")
}

async fn issue_client_over_route(
    state: AppState,
    request: IssueClientTokenRequest,
) -> axum::response::Response {
    let path = crate::route_contract::route_template(crate::route_contract::RouteId::ClientTokens);
    axum::Router::new()
        .route(path, axum::routing::post(issue_client_token))
        .with_state(state)
        .oneshot(
            Request::builder()
                .method("POST")
                .uri(path)
                .header("content-type", "application/json")
                .body(Body::from(
                    serde_json::to_vec(&request).expect("serialize issuance request"),
                ))
                .expect("build issuance request"),
        )
        .await
        .expect("issue route response")
}

#[derive(Default)]
struct PanickingStore;

impl crate::storage::TokenStore for PanickingStore {
    fn list(&self) -> Result<Vec<crate::storage::TokenRecord>, crate::storage::StorageError> {
        Ok(Vec::new())
    }

    fn get(
        &self,
        _id: &str,
    ) -> Result<Option<crate::storage::TokenRecord>, crate::storage::StorageError> {
        Ok(None)
    }

    fn put(
        &self,
        _record: crate::storage::TokenRecord,
    ) -> Result<(), crate::storage::StorageError> {
        panic!("simulated doublets capacity panic")
    }

    fn delete(&self, _id: &str) -> Result<bool, crate::storage::StorageError> {
        Ok(false)
    }
}

/// A backend panic is a structured failed issuance, never a dropped HTTP
/// connection and never a token-shaped response (issue #557).
#[tokio::test]
async fn storage_capacity_panic_becomes_a_structured_non_success_response() {
    let (mut state, _data) = state();
    state.token_manager = crate::token::TokenManager::with_store(
        "capacity-test-secret",
        std::sync::Arc::new(PanickingStore),
    );

    let response = issue_client_over_route(state, client_request("claude")).await;
    assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
    let body = json(response).await;
    assert_eq!(body["error"]["type"], "api_error");
    assert!(
        body.pointer("/token").is_none(),
        "a failed write emits no token"
    );
}

/// A successful response from the real route names a credential whose record
/// is present when a second binary-store instance reopens the published file.
#[tokio::test]
async fn successful_client_route_response_is_present_after_binary_reopen() {
    let (mut state, data) = state();
    let path = data.path().join("tokens.bin");
    state.token_manager = crate::token::TokenManager::with_store(
        "capacity-test-secret",
        std::sync::Arc::new(
            crate::storage::BinaryTokenStore::open(&path).expect("open binary token store"),
        ),
    );
    let observer = state.clone();
    let mut request = client_request("claude");
    request.ephemeral = true;

    let response = issue_client_over_route(state, request).await;
    assert_eq!(response.status(), StatusCode::OK);
    let body = json(response).await;
    let token = body["token"].as_str().expect("issued token");
    let claims = observer
        .token_manager
        .validate_token(token)
        .expect("valid issued token");
    let reopened = crate::storage::BinaryTokenStore::open(&path).expect("reopen token store");
    let record = crate::storage::TokenStore::get(&reopened, &claims.sub)
        .expect("read reopened token store")
        .expect("issued record is durable");
    assert!(record.ephemeral);
    assert_eq!(record.client_kind.as_deref(), Some("claude"));
}

#[tokio::test]
async fn bound_client_issuance_checks_authority_kind_and_constraints() {
    let (mut state, _data) = state();
    state.allow_anonymous_admin = false;
    let response = issue_client_token(
        State(state.clone()),
        HeaderMap::new(),
        Json(client_request("codex")),
    )
    .await
    .into_response();
    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);

    state.allow_anonymous_admin = true;
    for client in ["unknown-client", "cursor"] {
        let response = issue_client_token(
            State(state.clone()),
            HeaderMap::new(),
            Json(client_request(client)),
        )
        .await
        .into_response();
        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
    }

    let mut invalid = client_request("claude");
    invalid.ttl_hours = Some(0);
    let response = issue_client_token(State(state.clone()), HeaderMap::new(), Json(invalid))
        .await
        .into_response();
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);

    let mut request = client_request("claude");
    request.ttl_hours = Some(2);
    request.sliding_expiry = Some(true);
    request.label = Some("bound claude".into());
    request.max_requests = Some(7);
    request.ephemeral = true;
    let response = issue_client_token(State(state.clone()), HeaderMap::new(), Json(request))
        .await
        .into_response();
    assert_eq!(response.status(), StatusCode::OK);
    let body = json(response).await;
    assert_eq!(body["client_kind"], "claude");
    assert_eq!(body["principal_id"], "primary");
    assert_eq!(body["ttl_hours"], 2);
    assert_eq!(body["label"], "bound claude");

    let claims = state
        .token_manager
        .validate_token(body["token"].as_str().expect("issued token"))
        .expect("valid issued token");
    let record = state
        .token_manager
        .store()
        .get(&claims.sub)
        .expect("read record")
        .expect("persisted record");
    assert_eq!(record.client_kind.as_deref(), Some("claude"));
    assert_eq!(record.principal_id.as_deref(), Some("primary"));
    assert_eq!(record.account.as_deref(), Some("primary"));
    assert_eq!(record.max_requests, Some(7));
    assert_eq!(record.sliding_window_seconds, Some(7_200));
    assert!(record.ephemeral);
}

fn request<'a>(label: &'a str, scope: &'a str) -> IssueRequest<'a> {
    IssueRequest {
        ttl_hours: 1,
        label,
        account: None,
        max_requests: None,
        max_tokens: None,
        rate_limit_per_minute: None,
        scope,
        github_repos: Vec::new(),
        sliding_window_seconds: None,
        client_kind: None,
        principal_id: None,
    }
}

fn rotate_request(id: String) -> RotateClientTokenRequest {
    RotateClientTokenRequest {
        id,
        label: None,
        ttl_hours: None,
        max_requests: None,
        max_tokens: None,
        rate_limit_per_minute: None,
        account: None,
    }
}

#[tokio::test]
async fn client_rotation_rejects_unknown_admin_and_invalid_tokens_then_rotates() {
    let (state, _data) = state();

    let response = rotate_client_token(
        State(state.clone()),
        HeaderMap::new(),
        Json(rotate_request("missing".into())),
    )
    .await
    .into_response();
    assert_eq!(response.status(), StatusCode::NOT_FOUND);

    let (_, admin_id) = state
        .token_manager
        .issue_with_id(&request("admin", ADMIN_SCOPE))
        .expect("issue admin token");
    let response = rotate_client_token(
        State(state.clone()),
        HeaderMap::new(),
        Json(rotate_request(admin_id)),
    )
    .await
    .into_response();
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);

    let (_, invalid_id) = state
        .token_manager
        .issue_with_id(&request("invalid replacement", ""))
        .expect("issue client token");
    let mut invalid = rotate_request(invalid_id);
    invalid.ttl_hours = Some(0);
    let response = rotate_client_token(State(state.clone()), HeaderMap::new(), Json(invalid))
        .await
        .into_response();
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);

    let (_, id) = state
        .token_manager
        .issue_with_id(&request("old", ""))
        .expect("issue client token");
    let mut replacement = rotate_request(id.clone());
    replacement.label = Some("new".into());
    replacement.ttl_hours = Some(2);
    replacement.max_requests = Some(3);
    let response = rotate_client_token(State(state.clone()), HeaderMap::new(), Json(replacement))
        .await
        .into_response();
    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(json(response).await["revoked"], id);
    assert!(
        state
            .token_manager
            .store()
            .get(&id)
            .expect("read old token")
            .expect("old token remains auditable")
            .revoked
    );
}

#[tokio::test]
async fn admin_rotation_requires_proof_of_possession_and_revokes_the_caller() {
    let (state, _data) = state();
    let response = rotate_admin_token(
        State(state.clone()),
        HeaderMap::new(),
        Json(RotateTokenRequest::default()),
    )
    .await
    .into_response();
    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);

    let (token, id) = state
        .token_manager
        .issue_with_id(&request("operator", ADMIN_SCOPE))
        .expect("issue admin token");
    let mut headers = HeaderMap::new();
    headers.insert("authorization", format!("Bearer {token}").parse().unwrap());
    let response = rotate_admin_token(
        State(state.clone()),
        headers,
        Json(RotateTokenRequest {
            ttl_hours: Some(2),
            label: Some("replacement operator".into()),
        }),
    )
    .await
    .into_response();
    assert_eq!(response.status(), StatusCode::OK);
    let body = json(response).await;
    assert_eq!(body["revoked"], id);
    assert_eq!(body["scope"], ADMIN_SCOPE);
    assert_eq!(body["label"], "replacement operator");
    assert!(
        state
            .token_manager
            .store()
            .get(&id)
            .expect("read old admin token")
            .expect("old token remains auditable")
            .revoked
    );
}