Files
crank/apps/admin-api/src/routes/machine_auth.rs
T
github-ops ba29ac7b94
Deploy / deploy (push) Successful in 2m44s
CI / Rust Checks (push) Successful in 5m31s
CI / UI Checks (push) Successful in 5s
CI / Deployment Manifests (push) Successful in 2s
CI / Frontend E2E (push) Successful in 4m24s
chore: publish clean community baseline
2026-06-19 16:45:51 +00:00

125 lines
4.3 KiB
Rust

use axum::{Json, extract::State};
use crank_core::{
IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, MachineAccessMode, MembershipRole,
TokenIssuerActor, TokenIssuerError, UserId, WorkspaceId,
};
use serde_json::json;
use crate::{error::ApiError, state::AppState};
pub async fn issue_agent_token(
State(state): State<AppState>,
Json(payload): Json<IssueAgentTokenRequest>,
) -> Result<Json<serde_json::Value>, ApiError> {
let edition = state.service.capability_profile().capabilities().edition;
let grant_type = payload.grant_type.clone();
let response = state
.service
.token_issuer()
.issue_short_lived(payload, &community_token_issuer_actor())
.await
.map_err(|error| {
map_token_issuer_error(
error,
edition,
MachineAccessMode::ShortLivedToken,
json!({
"grant_type": grant_type,
"upgrade_required": true,
}),
)
})?;
Ok(Json(serde_json::json!(response)))
}
pub async fn issue_one_time_agent_token(
State(state): State<AppState>,
Json(payload): Json<IssueOneTimeAgentTokenRequest>,
) -> Result<Json<serde_json::Value>, ApiError> {
let edition = state.service.capability_profile().capabilities().edition;
let operation_id = payload.operation_id.as_str().to_owned();
let response = state
.service
.token_issuer()
.issue_one_time(payload, &community_token_issuer_actor())
.await
.map_err(|error| {
map_token_issuer_error(
error,
edition,
MachineAccessMode::OneTimeToken,
json!({
"operation_id": operation_id,
"upgrade_required": true,
}),
)
})?;
Ok(Json(serde_json::json!(response)))
}
fn community_token_issuer_actor() -> TokenIssuerActor {
TokenIssuerActor {
user_id: UserId::new("user_community_public"),
workspace_id: WorkspaceId::new("ws_community_public"),
role: MembershipRole::Viewer,
}
}
fn map_token_issuer_error(
error: TokenIssuerError,
edition: crank_core::ProductEdition,
machine_access_mode: MachineAccessMode,
extra_context: serde_json::Value,
) -> ApiError {
match error {
TokenIssuerError::NotSupportedInEdition => ApiError::forbidden_with_context(
match machine_access_mode {
MachineAccessMode::ShortLivedToken => {
"short-lived machine access is not available in Community"
}
MachineAccessMode::OneTimeToken => {
"one-time machine access is not available in Community"
}
MachineAccessMode::StaticAgentKey => {
"static agent key machine access is not available for token issue"
}
},
merge_machine_auth_context(edition, machine_access_mode, extra_context),
),
TokenIssuerError::InvalidGrant(reason) => ApiError::validation_with_context(
"invalid machine token grant",
json!({ "reason": reason }),
),
TokenIssuerError::AgentKeyUnknown => ApiError::validation("agent key is unknown"),
TokenIssuerError::OperationNotStrict => ApiError::validation("operation is not strict"),
TokenIssuerError::OperationNotPublishedForAgent => {
ApiError::validation("operation is not published for agent")
}
TokenIssuerError::RegistryFailure(details) => {
ApiError::internal(format!("token issuer registry failure: {details}"))
}
TokenIssuerError::ReplayGuardFailure(details) => {
ApiError::internal(format!("token issuer replay guard failure: {details}"))
}
}
}
fn merge_machine_auth_context(
edition: crank_core::ProductEdition,
machine_access_mode: MachineAccessMode,
extra_context: serde_json::Value,
) -> serde_json::Value {
let mut context = json!({
"edition": edition,
"machine_access_mode": machine_access_mode,
});
if let (Some(base), Some(extra)) = (context.as_object_mut(), extra_context.as_object()) {
for (key, value) in extra {
base.insert(key.clone(), value.clone());
}
}
context
}