Skip to content

Commit

Permalink
Add scanResultBlob on robonode-client side
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitrylavrenov committed May 28, 2024
1 parent c7089e4 commit ad03a17
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 63 deletions.
15 changes: 10 additions & 5 deletions crates/bioauth-flow-rpc/src/error_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@
use serde::Serialize;

/// The RPC error context we provide to trigger the face capture logic again,
/// effectively requesting a retry of the same request with a new liveness data.
/// The RPC error context we provide to handle possible scan result blob data.
#[derive(Debug)]
pub struct ShouldRetry;
pub struct ScanResultBlob(pub Option<String>);

impl Serialize for ShouldRetry {
impl Serialize for ScanResultBlob {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serde_json::json!({ "shouldRetry": true }).serialize(serializer)
match &self.0 {
Some(scan_result_blob) => {
serde_json::json!({ "scanResultBlob": scan_result_blob }).serialize(serializer)
}
// Needed to keep backward compatibility.
None => serde_json::json!({ "shouldRetry": true }).serialize(serializer),
}
}
}

Expand Down
16 changes: 11 additions & 5 deletions crates/bioauth-flow-rpc/src/errors/authenticate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@ where
}
Error::Sign(err) => rpc_error_response::simple(api_error_code::SIGN, err.to_string()),
Error::Robonode(
err @ robonode_client::Error::Call(
robonode_client::AuthenticateError::FaceScanRejected,
ref err @ robonode_client::Error::Call(
robonode_client::AuthenticateError::InvalidLivenessData(ref scan_result_blob)
| robonode_client::AuthenticateError::PersonNotFound(ref scan_result_blob)
| robonode_client::AuthenticateError::FaceScanRejected(ref scan_result_blob)
| robonode_client::AuthenticateError::SignatureInvalid(ref scan_result_blob)
| robonode_client::AuthenticateError::LogicInternal(ref scan_result_blob),
),
) => rpc_error_response::data(
api_error_code::ROBONODE,
err.to_string(),
error_data::ShouldRetry,
error_data::ScanResultBlob(scan_result_blob.clone()),
),
Error::Robonode(err) => {
rpc_error_response::simple(api_error_code::ROBONODE, err.to_string())
Expand Down Expand Up @@ -176,13 +180,15 @@ mod tests {
fn error_robonode_face_scan_rejected() {
let error: jsonrpsee::core::Error =
Error::<sc_transaction_pool_api::error::Error>::Robonode(robonode_client::Error::Call(
robonode_client::AuthenticateError::FaceScanRejected,
robonode_client::AuthenticateError::FaceScanRejected(Some(
"scan result blob".to_owned(),
)),
))
.into();
let error: ErrorObject = error.into();

let expected_error_message =
"{\"code\":200,\"message\":\"server error: face scan rejected\",\"data\":{\"shouldRetry\":true}}";
"{\"code\":200,\"message\":\"server error: face scan rejected\",\"data\":{\"scanResultBlob\":\"scan result blob\"}}";
assert_eq!(
expected_error_message,
serde_json::to_string(&error).unwrap()
Expand Down
15 changes: 11 additions & 4 deletions crates/bioauth-flow-rpc/src/errors/enroll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,18 @@ impl From<Error> for jsonrpsee::core::Error {
)
}
Error::Robonode(
err @ robonode_client::Error::Call(robonode_client::EnrollError::FaceScanRejected),
ref err @ robonode_client::Error::Call(
robonode_client::EnrollError::InvalidPublicKey(ref scan_result_blob)
| robonode_client::EnrollError::InvalidLivenessData(ref scan_result_blob)
| robonode_client::EnrollError::FaceScanRejected(ref scan_result_blob)
| robonode_client::EnrollError::PublicKeyAlreadyUsed(ref scan_result_blob)
| robonode_client::EnrollError::PersonAlreadyEnrolled(ref scan_result_blob)
| robonode_client::EnrollError::LogicInternal(ref scan_result_blob),
),
) => rpc_error_response::data(
api_error_code::ROBONODE,
err.to_string(),
error_data::ShouldRetry,
error_data::ScanResultBlob(scan_result_blob.clone()),
),
Error::Robonode(err) => {
rpc_error_response::simple(api_error_code::ROBONODE, err.to_string())
Expand Down Expand Up @@ -83,13 +90,13 @@ mod tests {
#[test]
fn error_robonode_face_scan_rejected() {
let error: jsonrpsee::core::Error = Error::Robonode(robonode_client::Error::Call(
robonode_client::EnrollError::FaceScanRejected,
robonode_client::EnrollError::FaceScanRejected(Some("scan result blob".to_owned())),
))
.into();
let error: ErrorObject = error.into();

let expected_error_message =
"{\"code\":200,\"message\":\"server error: face scan rejected\",\"data\":{\"shouldRetry\":true}}";
"{\"code\":200,\"message\":\"server error: face scan rejected\",\"data\":{\"scanResultBlob\":\"scan result blob\"}}";
assert_eq!(
expected_error_message,
serde_json::to_string(&error).unwrap()
Expand Down
44 changes: 24 additions & 20 deletions crates/robonode-client/src/authenticate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};

use crate::{error_response::ErrorResponse, Client, Error};
use crate::{error_response::ErrorResponse, Client, Error, ScanResultBlob};

impl Client {
/// Perform the authenticate call to the server.
Expand Down Expand Up @@ -45,28 +45,28 @@ pub struct AuthenticateResponse {
pub auth_ticket_signature: Box<[u8]>,
/// Scan result blob.
#[serde(skip_serializing_if = "Option::is_none")]
pub scan_result_blob: Option<String>,
pub scan_result_blob: Option<ScanResultBlob>,
}

/// The authenticate-specific error condition.
#[derive(Error, Debug, PartialEq)]
pub enum AuthenticateError {
/// The provided liveness data was invalid.
#[error("invalid liveness data")]
InvalidLivenessData,
InvalidLivenessData(Option<ScanResultBlob>),
/// The person was not found, it is likely because they haven't enrolled first.
#[error("person not found")]
PersonNotFound,
PersonNotFound(Option<ScanResultBlob>),
/// The face scan was rejected, this is likely due to a failed liveness check.
#[error("face scan rejected")]
FaceScanRejected,
FaceScanRejected(Option<ScanResultBlob>),
/// The signature was invalid, which means that the validator private key used for signing and
/// the public key that the person enrolled with don't match.
#[error("signature invalid")]
SignatureInvalid,
SignatureInvalid(Option<ScanResultBlob>),
/// A logic internal error occurred on the server end.
#[error("logic internal error")]
LogicInternal,
LogicInternal(Option<ScanResultBlob>),
/// An error with an unknown code occurred.
#[error("unknown error code: {0}")]
UnknownCode(String),
Expand All @@ -78,16 +78,19 @@ pub enum AuthenticateError {
impl AuthenticateError {
/// Parse the error response.
fn from_response(_status: StatusCode, body: String) -> Self {
let error_code = match body.try_into() {
Ok(ErrorResponse { error_code }) => error_code,
let (error_code, scan_result_blob) = match body.try_into() {
Ok(ErrorResponse {
error_code,
scan_result_blob,
}) => (error_code, scan_result_blob),
Err(body) => return Self::Unknown(body),
};
match error_code.as_str() {
"AUTHENTICATE_INVALID_LIVENESS_DATA" => Self::InvalidLivenessData,
"AUTHENTICATE_PERSON_NOT_FOUND" => Self::PersonNotFound,
"AUTHENTICATE_FACE_SCAN_REJECTED" => Self::FaceScanRejected,
"AUTHENTICATE_SIGNATURE_INVALID" => Self::SignatureInvalid,
"LOGIC_INTERNAL_ERROR" => Self::LogicInternal,
"AUTHENTICATE_INVALID_LIVENESS_DATA" => Self::InvalidLivenessData(scan_result_blob),
"AUTHENTICATE_PERSON_NOT_FOUND" => Self::PersonNotFound(scan_result_blob),
"AUTHENTICATE_FACE_SCAN_REJECTED" => Self::FaceScanRejected(scan_result_blob),
"AUTHENTICATE_SIGNATURE_INVALID" => Self::SignatureInvalid(scan_result_blob),
"LOGIC_INTERNAL_ERROR" => Self::LogicInternal(scan_result_blob),
_ => Self::UnknownCode(error_code),
}
}
Expand Down Expand Up @@ -173,27 +176,27 @@ mod tests {
(
StatusCode::BAD_REQUEST,
"AUTHENTICATE_INVALID_LIVENESS_DATA",
AuthenticateError::InvalidLivenessData,
AuthenticateError::InvalidLivenessData(Some("scan result blob".to_owned())),
),
(
StatusCode::NOT_FOUND,
"AUTHENTICATE_PERSON_NOT_FOUND",
AuthenticateError::PersonNotFound,
AuthenticateError::PersonNotFound(Some("scan result blob".to_owned())),
),
(
StatusCode::FORBIDDEN,
"AUTHENTICATE_FACE_SCAN_REJECTED",
AuthenticateError::FaceScanRejected,
AuthenticateError::FaceScanRejected(Some("scan result blob".to_owned())),
),
(
StatusCode::FORBIDDEN,
"AUTHENTICATE_SIGNATURE_INVALID",
AuthenticateError::SignatureInvalid,
AuthenticateError::SignatureInvalid(Some("scan result blob".to_owned())),
),
(
StatusCode::INTERNAL_SERVER_ERROR,
"LOGIC_INTERNAL_ERROR",
AuthenticateError::LogicInternal,
AuthenticateError::LogicInternal(Some("scan result blob".to_owned())),
),
(
StatusCode::BAD_REQUEST,
Expand All @@ -210,7 +213,8 @@ mod tests {
liveness_data_signature: b"123",
};

let response = ResponseTemplate::new(case.0).set_body_json(mkerr(case.1));
let response =
ResponseTemplate::new(case.0).set_body_json(mkerr(case.1, "scan result blob"));

Mock::given(matchers::method("POST"))
.and(matchers::path("/authenticate"))
Expand Down
50 changes: 27 additions & 23 deletions crates/robonode-client/src/enroll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};

use crate::{error_response::ErrorResponse, Client, Error};
use crate::{error_response::ErrorResponse, Client, Error, ScanResultBlob};

impl Client {
/// Perform the enroll call to the server.
Expand Down Expand Up @@ -43,7 +43,7 @@ pub struct EnrollRequest<'a> {
pub struct EnrollResponse {
/// Scan result blob.
#[serde(skip_serializing_if = "Option::is_none")]
pub scan_result_blob: Option<String>,
pub scan_result_blob: Option<ScanResultBlob>,
}

/// The enroll-specific error condition.
Expand All @@ -52,22 +52,22 @@ pub struct EnrollResponse {
pub enum EnrollError {
/// The public key is invalid.
#[error("invalid public key")]
InvalidPublicKey,
InvalidPublicKey(Option<ScanResultBlob>),
/// The liveness data is invalid.
#[error("invalid liveness data")]
InvalidLivenessData,
InvalidLivenessData(Option<ScanResultBlob>),
/// The face scan was rejeted.
#[error("face scan rejected")]
FaceScanRejected,
FaceScanRejected(Option<ScanResultBlob>),
/// The public key is already used.
#[error("public key already used")]
PublicKeyAlreadyUsed,
PublicKeyAlreadyUsed(Option<ScanResultBlob>),
/// The person is already enrolled.
#[error("person already enrolled")]
PersonAlreadyEnrolled,
PersonAlreadyEnrolled(Option<ScanResultBlob>),
/// A logic internal error occurred on the server end.
#[error("logic internal error")]
LogicInternal,
LogicInternal(Option<ScanResultBlob>),
/// An error with an unknown code occurred.
#[error("unknown error code: {0}")]
UnknownCode(String),
Expand All @@ -79,17 +79,20 @@ pub enum EnrollError {
impl EnrollError {
/// Parse the error response.
fn from_response(_status: StatusCode, body: String) -> Self {
let error_code = match body.try_into() {
Ok(ErrorResponse { error_code }) => error_code,
let (error_code, scan_result_blob) = match body.try_into() {
Ok(ErrorResponse {
error_code,
scan_result_blob,
}) => (error_code, scan_result_blob),
Err(body) => return Self::Unknown(body),
};
match error_code.as_str() {
"ENROLL_INVALID_PUBLIC_KEY" => Self::InvalidPublicKey,
"ENROLL_INVALID_LIVENESS_DATA" => Self::InvalidLivenessData,
"ENROLL_FACE_SCAN_REJECTED" => Self::FaceScanRejected,
"ENROLL_PUBLIC_KEY_ALREADY_USED" => Self::PublicKeyAlreadyUsed,
"ENROLL_PERSON_ALREADY_ENROLLED" => Self::PersonAlreadyEnrolled,
"LOGIC_INTERNAL_ERROR" => Self::LogicInternal,
"ENROLL_INVALID_PUBLIC_KEY" => Self::InvalidPublicKey(scan_result_blob),
"ENROLL_INVALID_LIVENESS_DATA" => Self::InvalidLivenessData(scan_result_blob),
"ENROLL_FACE_SCAN_REJECTED" => Self::FaceScanRejected(scan_result_blob),
"ENROLL_PUBLIC_KEY_ALREADY_USED" => Self::PublicKeyAlreadyUsed(scan_result_blob),
"ENROLL_PERSON_ALREADY_ENROLLED" => Self::PersonAlreadyEnrolled(scan_result_blob),
"LOGIC_INTERNAL_ERROR" => Self::LogicInternal(scan_result_blob),
_ => Self::UnknownCode(error_code),
}
}
Expand Down Expand Up @@ -159,32 +162,32 @@ mod tests {
(
StatusCode::BAD_REQUEST,
"ENROLL_INVALID_PUBLIC_KEY",
EnrollError::InvalidPublicKey,
EnrollError::InvalidPublicKey(Some("scan result blob".to_owned())),
),
(
StatusCode::BAD_REQUEST,
"ENROLL_INVALID_LIVENESS_DATA",
EnrollError::InvalidLivenessData,
EnrollError::InvalidLivenessData(Some("scan result blob".to_owned())),
),
(
StatusCode::FORBIDDEN,
"ENROLL_FACE_SCAN_REJECTED",
EnrollError::FaceScanRejected,
EnrollError::FaceScanRejected(Some("scan result blob".to_owned())),
),
(
StatusCode::CONFLICT,
"ENROLL_PUBLIC_KEY_ALREADY_USED",
EnrollError::PublicKeyAlreadyUsed,
EnrollError::PublicKeyAlreadyUsed(Some("scan result blob".to_owned())),
),
(
StatusCode::CONFLICT,
"ENROLL_PERSON_ALREADY_ENROLLED",
EnrollError::PersonAlreadyEnrolled,
EnrollError::PersonAlreadyEnrolled(Some("scan result blob".to_owned())),
),
(
StatusCode::INTERNAL_SERVER_ERROR,
"LOGIC_INTERNAL_ERROR",
EnrollError::LogicInternal,
EnrollError::LogicInternal(Some("scan result blob".to_owned())),
),
(
StatusCode::BAD_REQUEST,
Expand All @@ -202,7 +205,8 @@ mod tests {
public_key: b"123",
};

let response = ResponseTemplate::new(case.0).set_body_json(mkerr(case.1));
let response =
ResponseTemplate::new(case.0).set_body_json(mkerr(case.1, "scan result blob"));

Mock::given(matchers::method("POST"))
.and(matchers::path("/enroll"))
Expand Down
12 changes: 10 additions & 2 deletions crates/robonode-client/src/error_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
use serde::Deserialize;

use crate::ScanResultBlob;

/// A utility type assisting with decoding error response bodies.
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct ErrorResponse {
/// A machine-readable code identifying the error.
pub error_code: String,
/// Scan result blob.
pub scan_result_blob: Option<ScanResultBlob>,
}

impl TryFrom<String> for ErrorResponse {
Expand All @@ -25,8 +29,12 @@ pub mod tests {

#[test]
fn decodes() {
let err = mkerr("MY_ERR_CODE").to_string();
let ErrorResponse { error_code } = err.try_into().unwrap();
let err = mkerr("MY_ERR_CODE", "scan result blob").to_string();
let ErrorResponse {
error_code,
scan_result_blob,
} = err.try_into().unwrap();
assert_eq!(error_code, "MY_ERR_CODE");
assert_eq!(scan_result_blob, Some("scan result blob".to_owned()));
}
}
3 changes: 3 additions & 0 deletions crates/robonode-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub use enroll::*;
pub use get_facetec_device_sdk_params::*;
pub use get_facetec_session_token::*;

/// A type alias representing a scan result blob.
type ScanResultBlob = String;

/// The generic error type for the client calls.
#[derive(Error, Debug)]
pub enum Error<T: std::error::Error + 'static> {
Expand Down
Loading

0 comments on commit ad03a17

Please sign in to comment.