(
state: &Arc<ServerState>,
request: Request<CreateSshSessionRequest>,
)
| 1328 | // --------------------------------------------------------------------------- |
| 1329 | |
| 1330 | pub(super) async fn handle_create_ssh_session( |
| 1331 | state: &Arc<ServerState>, |
| 1332 | request: Request<CreateSshSessionRequest>, |
| 1333 | ) -> Result<Response<CreateSshSessionResponse>, Status> { |
| 1334 | let req = request.into_inner(); |
| 1335 | if req.sandbox_id.is_empty() { |
| 1336 | return Err(Status::invalid_argument("sandbox_id is required")); |
| 1337 | } |
| 1338 | |
| 1339 | let sandbox = state |
| 1340 | .store |
| 1341 | .get_message::<Sandbox>(&req.sandbox_id) |
| 1342 | .await |
| 1343 | .map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))? |
| 1344 | .ok_or_else(|| Status::not_found("sandbox not found"))?; |
| 1345 | |
| 1346 | if SandboxPhase::try_from(sandbox.phase()).ok() != Some(SandboxPhase::Ready) { |
| 1347 | return Err(Status::failed_precondition("sandbox is not ready")); |
| 1348 | } |
| 1349 | |
| 1350 | let token = uuid::Uuid::new_v4().to_string(); |
| 1351 | let now_ms = current_time_ms(); |
| 1352 | let expires_at_ms = if state.config.ssh_session_ttl_secs > 0 { |
| 1353 | now_ms + (state.config.ssh_session_ttl_secs as i64 * 1000) |
| 1354 | } else { |
| 1355 | 0 |
| 1356 | }; |
| 1357 | let session = SshSession { |
| 1358 | metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta { |
| 1359 | id: token.clone(), |
| 1360 | name: generate_name(), |
| 1361 | created_at_ms: now_ms, |
| 1362 | labels: std::collections::HashMap::new(), |
| 1363 | resource_version: 0, |
| 1364 | }), |
| 1365 | sandbox_id: req.sandbox_id.clone(), |
| 1366 | token: token.clone(), |
| 1367 | revoked: false, |
| 1368 | expires_at_ms, |
| 1369 | }; |
| 1370 | |
| 1371 | // Ensure metadata is valid (defense in depth - should always be true for server-constructed metadata) |
| 1372 | super::validation::validate_object_metadata(session.metadata.as_ref(), "ssh_session")?; |
| 1373 | |
| 1374 | // Use MustCreate to atomically ensure the session token is unique |
| 1375 | state |
| 1376 | .store |
| 1377 | .put_if( |
| 1378 | SshSession::object_type(), |
| 1379 | &token, |
| 1380 | session.object_name(), |
| 1381 | &session.encode_to_vec(), |
| 1382 | None, |
| 1383 | WriteCondition::MustCreate, |
| 1384 | ) |
| 1385 | .await |
| 1386 | .map_err(|e| Status::internal(format!("persist ssh session failed: {e}")))?; |
| 1387 |
no test coverage detected