This is an example of accessing a REST API endpoint using a session.
The application requires a custom TLS certificate chain PEM file and the
Cuica hostname as input arguments. Placeholders are indicated by <>
.
Example Cargo.toml*
[dependencies]
clap = { version = "4.2" , features = [ "derive" ] }
rpassword = "5.1.0"
serde = { version = "1.0" , features = [ "derive" ] }
serde_json = "1.0"
reqwest = { version = "0.12" , features = [ "json" , "rustls-tls" , "cookies" ] }
tokio = { version = "1.29" , features = [ "rt-multi-thread" , "macros" ] }
*Versions may vary
Example Rust Application use clap :: Parser ;
use rpassword :: read_password;
use serde :: { Deserialize , Serialize };
use std :: fs;
use std :: io :: { self , Write };
use std :: path :: PathBuf ;
/// Command-line arguments
#[derive( Parser )]
struct Args {
/// The host of the Cuica device
#[arg(index = 1)]
cuica_host : String ,
/// Path to the Root CA certificate file
#[arg(index = 2)]
root_ca_path : PathBuf ,
}
/// Structure for authentication login data
#[derive( Debug , Serialize )]
pub struct AuthLogin {
pub username : String ,
pub password : String ,
}
/// Structure matching the expected JSON response schema
#[derive( Debug , Deserialize , Clone )]
#[serde(rename_all = "camelCase" )]
pub struct SystemInfoResponse {
pub version : String ,
pub build_version : String ,
pub last_commit_time : String ,
pub serial_number : String ,
}
/// Prompt the user for a username
fn prompt_username () -> String {
print! ( " \u{25e6} Username: " );
io :: stdout () . flush () . unwrap ();
let mut input = String :: new ();
if let Err ( e ) = io :: stdin () . read_line ( & mut input ) {
eprintln! ( "Error reading username: {}" , e );
std :: process :: exit ( 1 );
}
input . trim () . to_string ()
}
/// Prompt the user for a password without echoing input
fn prompt_password () -> String {
print! ( " \u{25e6} Password: " );
io :: stdout () . flush () . unwrap ();
match read_password () {
Ok ( pwd ) => pwd . trim () . to_string (),
Err ( e ) => {
eprintln! ( "Error reading password: {}" , e );
std :: process :: exit ( 1 );
}
}
}
#[tokio :: main]
async fn main () {
let args = Args :: parse ();
let username = prompt_username ();
let password = prompt_password ();
// Ensure the specified Root CA file exists to validate
// TLS connections
if ! args . root_ca_path . exists () {
eprintln! ( "Error: Root CA file was not found" );
std :: process :: exit ( 1 );
}
// Read and parse the Root CA certificate
let root_ca = match fs :: read ( & args . root_ca_path) {
Ok ( ca ) => ca ,
Err ( e ) => {
eprintln! ( "Error reading Root CA file: {}" , e );
std :: process :: exit ( 1 );
}
};
let cert = match reqwest :: Certificate :: from_pem ( & root_ca ) {
Ok ( cert ) => cert ,
Err ( e ) => {
eprintln! ( "Error parsing Root CA certificate: {}" , e );
std :: process :: exit ( 1 );
}
};
// Build the HTTP client with the custom Root CA certificate
let http_client = match reqwest :: Client :: builder ()
. cookie_store ( true )
. use_rustls_tls ()
. add_root_certificate ( cert )
. build ()
{
Ok ( client ) => client ,
Err ( e ) => {
eprintln! ( "Error building HTTP client: {}" , e );
std :: process :: exit ( 1 );
}
};
// Log into the Cuica device to get a valid session
let login_request = AuthLogin {
username : username . clone (),
password : password . clone (),
};
let login_response = match http_client
. post ( format! ( "https://{}/login" , args . cuica_host))
. json ( & login_request )
. header (reqwest :: header :: CONTENT_TYPE, "application/json" )
. send ()
. await
{
Ok ( response ) => response ,
Err ( e ) => {
eprintln! ( "Error sending login request: {}" , e );
std :: process :: exit ( 1 );
}
};
// Handle the login response
match login_response . status () {
reqwest :: StatusCode :: OK => {
println! ( "Logged in successfully." );
}
reqwest :: StatusCode :: UNAUTHORIZED => {
eprintln! ( "User login unsuccessful. \
Check username and password." );
std :: process :: exit ( 1 );
}
reqwest :: StatusCode :: FORBIDDEN => {
eprintln! (
"Access forbidden: Max tokens reached, \
system setup required, \
or non-admin user."
);
std :: process :: exit ( 1 );
}
_ => {
// Handle all other responses
let error_body = match login_response . text () . await {
Ok ( body ) => body ,
Err ( e ) => {
eprintln! ( "Error reading login error response: {}" , e );
std :: process :: exit ( 1 );
}
};
eprintln! ( "Login failed: {}" , error_body );
std :: process :: exit ( 1 );
}
}
// Perform a GET request to retrieve system information
let info_response = match http_client
. get ( format! ( "https://{}/v1/system/info" , args . cuica_host))
. header (reqwest :: header :: CONTENT_TYPE, "application/json" )
. send ()
. await
{
Ok ( response ) => response ,
Err ( e ) => {
eprintln! ( "Error sending system info request: {}" , e );
std :: process :: exit ( 1 );
}
};
// Deserialize the response into the SystemInfoResponse struct
let system_info =
match info_response . json :: < SystemInfoResponse >() . await {
Ok ( info ) => info ,
Err ( e ) => {
eprintln! ( "Error parsing system info response: {}" , e );
std :: process :: exit ( 1 );
}
};
// Print the retrieved system information
println! ( "Version: {}" , system_info . version);
println! ( "Build Version: {}" , system_info . build_version);
println! ( "Last Commit Time: {}" , system_info . last_commit_time);
println! ( "Serial Number: {}" , system_info . serial_number);
}