#[derive(Deserialize)] #[serde(deny_unknown_fields)] struct CreateApiToken { permission: String, #[serde(default)] comment: String, allowed_cidrs: Vec, } async fn list_api_tokens(State(state): State>) -> Result, ApiError> { let tokens = state.store.api_tokens().await.map_err(auth::internal)?; Ok(Json( json!({"tokens": tokens, "max_tokens": crate::db::API_TOKEN_CAPACITY}), )) } async fn create_api_token( State(state): State>, Extension(session): Extension, Json(input): Json, ) -> Result, ApiError> { if !matches!(input.permission.as_str(), "ro" | "rw") || input.comment.len() > 128 || input.comment.chars().any(char::is_control) || input.allowed_cidrs.is_empty() || input.allowed_cidrs.len() > 32 || input.allowed_cidrs.iter().any(|v| { v.parse::() .ok() .is_none_or(|net| net.prefix_len() == 0) }) { return Err(ApiError( StatusCode::UNPROCESSABLE_ENTITY, "invalid API permission, comment or CIDR allowlist".into(), )); } let secret = auth::random_secret(); let id = format!("api-{}", &secret[..12]); let inserted = state .store .create_api_token( &id, &auth::secret_hash(&secret), &input.permission, &input.comment, &input.allowed_cidrs, &session.username, ) .await .map_err(auth::internal)?; if !inserted { return Err(ApiError( StatusCode::CONFLICT, "API token already exists".into(), )); } Ok(Json( json!({"id": id, "token": secret, "permission": input.permission, "allowed_cidrs": input.allowed_cidrs}), )) } async fn revoke_api_token( State(state): State>, Extension(session): Extension, Path(id): Path, ) -> Result, ApiError> { if !crate::config::valid_id(&id) { return Err(ApiError(StatusCode::NOT_FOUND, "unknown API token".into())); } if !state .store .revoke_api_token(&id, &session.username) .await .map_err(auth::internal)? { return Err(ApiError(StatusCode::NOT_FOUND, "unknown API token".into())); } Ok(Json(json!({"revoked": id}))) } async fn prometheus_token_status(State(state): State>) -> Result, ApiError> { let created_at = state .store .prometheus_token_status() .await .map_err(auth::internal)?; Ok(Json( json!({"configured": created_at.is_some(), "created_at": created_at}), )) } async fn rotate_prometheus_token( State(state): State>, Extension(session): Extension, ) -> Result, ApiError> { let secret = auth::random_secret(); let hash = auth::secret_hash(&secret); state .store .rotate_prometheus_token(&hash, &session.username) .await .map_err(auth::internal)?; state.prometheus_token_hash.store(Some(Arc::new(hash))); Ok(Json(json!({"configured": true, "token": secret}))) } async fn revoke_prometheus_token( State(state): State>, Extension(session): Extension, ) -> Result, ApiError> { state .store .revoke_prometheus_token(&session.username) .await .map_err(auth::internal)?; state .prometheus_token_hash .store(Some(Arc::new("revoked".into()))); Ok(Json(json!({"configured": false}))) }