Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,68 @@ use subxt::{
use crate::tx::KiltConfig;

#[derive(Deserialize, Debug, Clone, Parser)]
/// Represents the configuration settings required for running the attestation service.
pub struct Configuration {
// Seed for attester
/// Seed for generating the attester's DID (Decentralized Identifier).
#[clap(env)]
attester_did_seed: String,
// Seed for payer

/// Seed for the payer, responsible for transaction fees.
#[clap(env)]
payer_seed: String,
// Seed for attestation key

/// Seed for generating the attester's attestation key.
#[clap(env)]
attester_attestation_seed: String,
// Websocket address of network

/// Websocket address of the blockchain network.
#[clap(env)]
pub wss_address: String,

/// Hostname for serving the attestation service.
#[clap(env)]
pub host_name: String,

/// URL for the PostgreSQL database.
#[clap(env)]
pub database_url: String,

/// Port for serving the attestation service.
#[clap(env)]
pub port: u16,

/// Secret key for JWT (JSON Web Tokens) generation and validation.
#[clap(env)]
pub jwt_secret: String,

/// Path to the front-end web application.
#[clap(env)]
pub front_end_path: String,
}

impl Configuration {
/// Get the credential signer based on the attester's attestation key seed.
/// Returns a `Result` containing the `PairSigner` or an error if the seed is invalid.
pub fn get_credential_signer(&self) -> Result<PairSigner<KiltConfig, Pair>, SecretStringError> {
let pair = Pair::from_string_with_seed(&self.attester_attestation_seed, None)?.0;
Ok(PairSigner::new(pair))
}

/// Get the payer signer based on the payer's seed.
/// Returns a `Result` containing the `PairSigner` or an error if the seed is invalid.
pub fn get_payer_signer(&self) -> Result<PairSigner<KiltConfig, Pair>, SecretStringError> {
let pair = Pair::from_string_with_seed(&self.payer_seed, None)?.0;
Ok(PairSigner::new(pair))
}

/// Get an online client for interacting with the blockchain network based on the provided WebSocket address.
/// Returns a `Result` containing the `OnlineClient` or an error if the connection fails.
pub async fn get_client(&self) -> Result<OnlineClient<KiltConfig>, subxt::Error> {
Ok(OnlineClient::<KiltConfig>::from_url(&self.wss_address).await?)
}

/// Get the DID (Decentralized Identifier) for the attester based on the attester's DID seed.
/// Returns a `Result` containing the `AccountId32` representing the DID or an error if the seed is invalid.
pub fn get_did(&self) -> Result<AccountId32, SecretStringError> {
let pair = Pair::from_string_with_seed(&self.attester_did_seed, None)?.0;
Ok(pair.public().into())
Expand Down
26 changes: 21 additions & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,55 @@ use serde_json::error::Category;
use subxt::ext::sp_core::crypto::SecretStringError;
use thiserror::Error;

/// The `AppError` enum represents various error types that can occur within the application.
/// It is used to handle and report different error conditions with user-friendly error messages.
#[derive(Debug, Error)]
pub enum AppError {
/// Represents a database-related error with an associated `sqlx::Error`.
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),

/// Represents a blockchain-related error with an associated `subxt::Error`.
#[error("Blockchain error: {0}")]
Subxt(#[from] subxt::Error),

/// Represents a server-related error with an associated `actix_web::Error`.
#[error("Server error: {0}")]
ActixWeb(#[from] actix_web::Error),

/// Represents a signature-related error with an associated `SecretStringError`.
#[error("Signature error: {0}")]
Secret(#[from] SecretStringError),

/// Represents a JSON-related error with an associated `serde_json::Error`.
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),

/// Represents a hexadecimal encoding/decoding error with an associated `hex::FromHexError`.
#[error("Hex error: {0}")]
Hex(#[from] hex::FromHexError),
}

// Is thread safe. No data races or similar can happen.
/// The `AppError` is designed to be thread-safe. It ensures there are no data races or similar issues.
unsafe impl Send for AppError {}
unsafe impl Sync for AppError {}

/// The `AppError` implements the `actix_web::error::ResponseError` trait, allowing it to provide customized error responses.
impl actix_web::error::ResponseError for AppError {
/// Generates an HTTP response body with the error message.
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code()).body(self.to_string())
}

/// Determines the HTTP status code based on the specific error type.
fn status_code(&self) -> StatusCode {
match self {
AppError::Database(sqlx::Error::RowNotFound) => StatusCode::NOT_FOUND,
AppError::Hex(hex::FromHexError::InvalidHexCharacter { .. }) => StatusCode::BAD_REQUEST,
AppError::Hex(hex::FromHexError::InvalidStringLength) => StatusCode::BAD_REQUEST,
AppError::Hex(hex::FromHexError::InvalidHexCharacter { .. })
| AppError::Hex(hex::FromHexError::InvalidStringLength) => StatusCode::BAD_REQUEST,

AppError::Json(e) => match e.classify() {
Category::Data => StatusCode::BAD_REQUEST,
Category::Syntax => StatusCode::BAD_REQUEST,
Category::Data | Category::Syntax => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
},
_ => StatusCode::INTERNAL_SERVER_ERROR,
Expand Down
12 changes: 12 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ use sqlx::{Pool, Postgres};

use crate::routes::get_attestation_request_scope;

/// The `AppState` struct represents the application state and holds configuration and database-related information.
#[derive(Clone)]
pub struct AppState {
pub config: Configuration,
pub db_executor: Pool<Postgres>,
}

/// The `JWTPayload` struct represents the payload of a JSON Web Token (JWT).
/// It is deserialized from a JWT and contains various claims like subject (`sub`), expiration (`exp`), etc.
/// This struct is used during JWT validation.
#[allow(dead_code)]
#[derive(serde::Deserialize)]
struct JWTPayload {
Expand All @@ -36,12 +40,17 @@ struct JWTPayload {
nonce: String,
}

/// The `User` struct represents a user in the application.
/// It holds user-specific information such as the user DID and whether the user is an admin.
#[derive(Clone)]
pub struct User {
pub id: String,
pub is_admin: bool,
}

/// The `jwt_validator` function is used as a middleware for JWT (JSON Web Token) authentication.
/// It validates JWTs provided in the request's `Authorization` header, extracts user information, and sets it in the request's extensions.
/// This function is used to authenticate and authorize users.
async fn jwt_validator(
req: ServiceRequest,
credentials: BearerAuth,
Expand Down Expand Up @@ -88,6 +97,9 @@ async fn jwt_validator(
Ok(request)
}

/// The `main` function is the entry point for the application.
/// It initializes the application, sets up routes, middleware, and runs the HTTP server.
/// The application's configuration, database connection, and other settings are configured here.
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
Expand Down
91 changes: 91 additions & 0 deletions src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ use crate::{
AppState, User,
};

/// Get attestation information by ID.
/// This endpoint allows users to retrieve attestation information by its unique ID.
///
/// # Parameters
/// - `attestation_request_id`: A unique UUID identifier for the attestation request.
/// - `user`: Information about the requesting user.
/// - `state`: Application state data.
///
/// # Returns
/// - If the user is authorized to view the attestation, the endpoint responds with a JSON representation of the attestation.
/// - If the user is not authorized, it returns an error response.
#[get("/{attestation_request_id}")]
async fn get_attestation(
path: web::Path<Uuid>,
Expand All @@ -34,6 +45,17 @@ async fn get_attestation(
Ok(HttpResponse::Ok().json(serde_json::to_value(&attestation)?))
}

/// Get a list of attestation requests.
/// This endpoint allows users to retrieve a list of attestation requests based on pagination parameters.
///
/// # Parameters
/// - `state`: Application state data.
/// - `user`: Information about the requesting user.
/// - `pagination_query`: Query parameters for pagination.
///
/// # Returns
/// - If the user is authorized to view the attestation requests, the endpoint responds with a JSON list of attestation requests and includes a `Content-Range` header.
/// - If the user is not authorized, it returns an error response.
#[get("")]
async fn get_attestations(
state: web::Data<AppState>,
Expand All @@ -53,6 +75,17 @@ async fn get_attestations(
.json(response))
}

/// Delete an attestation request by ID.
/// This endpoint allows users to delete an attestation request by its unique ID.
///
/// # Parameters
/// - `attestation_request_id`: A unique UUID identifier for the attestation request.
/// - `user`: Information about the requesting user.
/// - `state`: Application state data.
///
/// # Returns
/// - If the user is authorized to delete the attestation, the endpoint responds with a success message.
/// - If the user is not authorized, it returns an error response 401.
#[delete("/{attestation_request_id}")]
async fn delete_attestation(
path: web::Path<Uuid>,
Expand All @@ -66,6 +99,15 @@ async fn delete_attestation(
Ok(HttpResponse::Ok().json("ok"))
}

/// Create a new attestation request.
/// This endpoint allows users to create a new attestation request.
///
/// # Parameters
/// - `body`: JSON data representing the credential for the attestation request.
/// - `state`: Application state data.
///
/// # Returns
/// - If the attestation request is successfully created, the endpoint responds with a JSON representation of the attestation request.
#[post("")]
async fn post_attestation(
body: web::Json<Credential>,
Expand All @@ -77,6 +119,17 @@ async fn post_attestation(
Ok(HttpResponse::Ok().json(attestation))
}

/// Approve an attestation request.
/// This endpoint allows administrators to approve an attestation request, which triggers the creation of a new claim.
///
/// # Parameters
/// - `attestation_request_id`: A unique UUID identifier for the attestation request.
/// - `user`: Information about the requesting user.
/// - `state`: Application state data.
///
/// # Returns
/// - If the user is an administrator and the approval process is successful, the endpoint responds with a success message.
/// - If the user is not authorized or if there are errors in the approval process, it returns an error response.
#[put("/{attestation_request_id}/approve")]
async fn approve_attestation(
path: web::Path<Uuid>,
Expand Down Expand Up @@ -139,6 +192,17 @@ async fn approve_attestation(
Ok(HttpResponse::Ok().json("ok"))
}

/// Revoke an attestation request.
/// This endpoint allows users to revoke a previously approved attestation request.
///
/// # Parameters
/// - `attestation_request_id`: A unique UUID identifier for the attestation request.
/// - `user`: Information about the requesting user.
/// - `state`: Application state data.
///
/// # Returns
/// - If the user is authorized and the revocation process is successful, the endpoint responds with a success message.
/// - If the user is not authorized or if there are errors in the revocation process, it returns an error response.
#[put("/{attestation_request_id}/revoke")]
async fn revoke_attestation(
path: web::Path<Uuid>,
Expand Down Expand Up @@ -193,6 +257,18 @@ async fn revoke_attestation(
Ok(HttpResponse::Ok().json("ok"))
}

/// Update an attestation request.
/// This endpoint allows users to update an existing attestation request.
///
/// # Parameters
/// - `attestation_request_id`: A unique UUID identifier for the attestation request to be updated.
/// - `state`: Application state data.
/// - `user`: Information about the requesting user.
/// - `body`: JSON data representing the updated credential for the attestation request.
///
/// # Returns
/// - If the user is authorized to update the attestation and the update process is successful, the endpoint responds with a JSON representation of the updated attestation request.
/// - If the user is not authorized or if there are errors in the update process, it returns an error response.
#[put("/{attestation_request_id}")]
async fn update_attestation(
path: web::Path<Uuid>,
Expand All @@ -212,12 +288,27 @@ async fn update_attestation(
Ok(HttpResponse::Ok().json(serde_json::to_value(&attestation)?))
}

/// Get attestation key performance indicators (KPIs).
/// This endpoint allows users to retrieve key performance indicators related to attestation requests.
///
/// # Parameters
/// - `state`: Application state data.
///
/// # Returns
/// - If the request is successful, the endpoint responds with a JSON representation of attestation-related KPIs.
/// - If there are errors in the retrieval process, it returns an error response.
#[get("/metric/kpis")]
async fn get_attestation_kpis(state: web::Data<AppState>) -> Result<HttpResponse, AppError> {
let kpis = attestation_requests_kpis(&state.db_executor).await?;
Ok(HttpResponse::Ok().json(serde_json::to_value(kpis)?))
}

// Create a scope for attestation request-related endpoints.
/// This function sets up a scope for handling attestation request-related endpoints, such as approving, retrieving, creating,
/// deleting, updating, revoking attestation requests, and fetching key performance indicators (KPIs).
///
/// # Returns
/// A scope that contains various attestation request-related endpoints for handling attestation requests and related actions.
pub(crate) fn get_attestation_request_scope() -> Scope {
web::scope("/api/v1/attestation_request")
.service(approve_attestation)
Expand Down
Loading