125 lines
4.3 KiB
Rust
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
|
|
}
|