Files
RustProxy/src/admin/api_tokens.rs
T

118 lines
3.6 KiB
Rust

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct CreateApiToken {
permission: String,
#[serde(default)]
comment: String,
allowed_cidrs: Vec<String>,
}
async fn list_api_tokens(State(state): State<Arc<App>>) -> Result<Json<Value>, 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<Arc<App>>,
Extension(session): Extension<Session>,
Json(input): Json<CreateApiToken>,
) -> Result<Json<Value>, 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::<ipnet::IpNet>()
.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<Arc<App>>,
Extension(session): Extension<Session>,
Path(id): Path<String>,
) -> Result<Json<Value>, 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<Arc<App>>) -> Result<Json<Value>, 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<Arc<App>>,
Extension(session): Extension<Session>,
) -> Result<Json<Value>, 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<Arc<App>>,
Extension(session): Extension<Session>,
) -> Result<Json<Value>, 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})))
}