-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
73 lines (61 loc) · 2.37 KB
/
lib.rs
File metadata and controls
73 lines (61 loc) · 2.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use base64::Engine;
use hmac::{Hmac, Mac};
use openruntimes::{Context, Response};
use serde_json::json;
use sha1::Sha1;
use std::env;
use subtle::ConstantTimeEq;
type HmacSha1 = Hmac<Sha1>;
pub fn main(context: Context) -> Response {
if context.req.method != "POST" {
return context.res.text(
"POST an Appwrite webhook here. The function verifies `x-appwrite-webhook-signature` and logs the event.",
None,
None,
);
}
let signature_key = match env::var("WEBHOOK_SIGNATURE_KEY") {
Ok(k) if !k.is_empty() => k,
_ => return error(&context, 500, "WEBHOOK_SIGNATURE_KEY env variable is not set"),
};
let webhook_url = match env::var("WEBHOOK_URL") {
Ok(k) if !k.is_empty() => k,
_ => return error(&context, 500, "WEBHOOK_URL env variable is not set"),
};
let signature = match context.req.headers.get("x-appwrite-webhook-signature") {
Some(s) => s.clone(),
None => return error(&context, 401, "Missing webhook signature header"),
};
let payload = context.req.body_text().to_string();
if !verify_signature(&webhook_url, &payload, &signature_key, &signature) {
return error(&context, 401, "Invalid webhook signature");
}
let event = context
.req
.headers
.get("x-appwrite-webhook-events")
.cloned()
.unwrap_or_default();
// Webhook payloads can include PII like emails or IP addresses, so log only
// the event name and payload size by default. Add field-level logging as needed.
context.log(format!(
"Verified Appwrite webhook: {} ({} bytes)",
event,
payload.len()
));
context.res.json(json!({ "ok": true, "event": event }), None, None)
}
fn verify_signature(url: &str, payload: &str, signature_key: &str, signature: &str) -> bool {
let mut mac = match HmacSha1::new_from_slice(signature_key.as_bytes()) {
Ok(m) => m,
Err(_) => return false,
};
mac.update(url.as_bytes());
mac.update(payload.as_bytes());
let generated = base64::engine::general_purpose::STANDARD.encode(mac.finalize().into_bytes());
generated.as_bytes().ct_eq(signature.as_bytes()).into()
}
fn error(context: &Context, code: u16, message: &str) -> Response {
context.error(message);
context.res.json(json!({ "ok": false, "error": message }), Some(code), None)
}