feat: complete bitvora webhook
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
@ -1,9 +1,11 @@
|
||||
use crate::api::WEBHOOK_BRIDGE;
|
||||
use crate::api::{WebhookMessage, WEBHOOK_BRIDGE};
|
||||
use crate::json_api::JsonApi;
|
||||
use crate::lightning::{AddInvoiceRequest, AddInvoiceResult, InvoiceUpdate, LightningNode};
|
||||
use anyhow::bail;
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt};
|
||||
use hmac::{Hmac, Mac};
|
||||
use lnvps_db::async_trait;
|
||||
use log::{info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::pin::Pin;
|
||||
use tokio_stream::wrappers::BroadcastStream;
|
||||
@ -46,6 +48,7 @@ impl LightningNode for BitvoraNode {
|
||||
Ok(AddInvoiceResult {
|
||||
pr: rsp.data.payment_request,
|
||||
payment_hash: rsp.data.r_hash,
|
||||
external_id: Some(rsp.data.id),
|
||||
})
|
||||
}
|
||||
|
||||
@ -54,7 +57,45 @@ impl LightningNode for BitvoraNode {
|
||||
_from_payment_hash: Option<Vec<u8>>,
|
||||
) -> anyhow::Result<Pin<Box<dyn Stream<Item = InvoiceUpdate> + Send>>> {
|
||||
let rx = BroadcastStream::new(WEBHOOK_BRIDGE.listen());
|
||||
let mapped = rx.then(|r| async move { InvoiceUpdate::Unknown });
|
||||
let secret = self.webhook_secret.clone();
|
||||
let mapped = rx.then(move |r| {
|
||||
let secret = secret.clone();
|
||||
async move {
|
||||
match r {
|
||||
Ok(r) => {
|
||||
if r.endpoint != "/api/v1/webhook/bitvora" {
|
||||
return InvoiceUpdate::Unknown;
|
||||
}
|
||||
let body: BitvoraWebhookPayload<BitvoraWebhook> =
|
||||
match serde_json::from_slice(r.body.as_slice()) {
|
||||
Ok(b) => b,
|
||||
Err(e) => return InvoiceUpdate::Error(e.to_string()),
|
||||
};
|
||||
info!("Received webhook {:?}", body);
|
||||
let body = body.payload;
|
||||
if let Err(e) = verify_webhook(&secret, &r) {
|
||||
return InvoiceUpdate::Error(e.to_string());
|
||||
}
|
||||
|
||||
match body.event {
|
||||
BitvoraWebhookEvent::DepositLightningComplete => {
|
||||
InvoiceUpdate::Settled {
|
||||
payment_hash: None,
|
||||
external_id: Some(body.data.id),
|
||||
}
|
||||
}
|
||||
BitvoraWebhookEvent::DepositLightningFailed => {
|
||||
InvoiceUpdate::Error("Payment failed".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error handling webhook: {}", e);
|
||||
InvoiceUpdate::Error(e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(Box::pin(mapped))
|
||||
}
|
||||
}
|
||||
@ -80,3 +121,47 @@ struct CreateInvoiceResponse {
|
||||
pub r_hash: String,
|
||||
pub payment_request: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct BitvoraWebhookPayload<T> {
|
||||
pub payload: T,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct BitvoraWebhook {
|
||||
pub event: BitvoraWebhookEvent,
|
||||
pub data: BitvoraPayment,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
enum BitvoraWebhookEvent {
|
||||
#[serde(rename = "deposit.lightning.completed")]
|
||||
DepositLightningComplete,
|
||||
#[serde(rename = "deposit.lightning.failed")]
|
||||
DepositLightningFailed,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct BitvoraPayment {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
type HmacSha256 = Hmac<sha2::Sha256>;
|
||||
fn verify_webhook(secret: &str, msg: &WebhookMessage) -> anyhow::Result<()> {
|
||||
let sig = msg
|
||||
.headers
|
||||
.get("bitvora-signature")
|
||||
.ok_or_else(|| anyhow!("Missing bitvora-signature header"))?;
|
||||
|
||||
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())?;
|
||||
mac.update(msg.body.as_slice());
|
||||
let result = mac.finalize().into_bytes();
|
||||
|
||||
if hex::encode(result) == *sig {
|
||||
return Ok(());
|
||||
} else {
|
||||
warn!("Invalid signature found {} != {}", sig, hex::encode(result));
|
||||
}
|
||||
|
||||
bail!("No valid signature found!");
|
||||
}
|
||||
|
Reference in New Issue
Block a user