///|
pub struct SeenEntry {
key : String
expires_at : Int
} derive(Eq, Debug)
///|
pub fn SeenEntry::new(key : String, expires_at : Int) -> SeenEntry {
{ key, expires_at }
}
///|
pub struct ReplayStore {
entries : Array[SeenEntry]
} derive(Eq, Debug)
///|
pub fn ReplayStore::empty() -> ReplayStore {
{ entries: [] }
}
///|
pub fn ReplayStore::contains(
self : ReplayStore,
key : String,
now : Int,
) -> Bool {
for i in 0..= now {
return true
}
}
false
}
///|
pub fn ReplayStore::remember(
self : ReplayStore,
key : String,
now : Int,
ttl_seconds : Int,
) -> ReplayStore {
let expires_at = now + ttl_seconds
let old = self.entries
let entry = SeenEntry::new(key, expires_at)
let entries = Array::makei(old.length() + 1, i => {
if i < old.length() {
old[i]
} else {
entry
}
})
{ entries, }
}
///|
pub fn ReplayStore::prune(self : ReplayStore, now : Int) -> ReplayStore {
let mut count = 0
for i in 0..= now {
count = count + 1
}
}
let mut offset = 0
let entries = Array::makei(count, _ => {
while offset < self.entries.length() &&
self.entries[offset].expires_at < now {
offset = offset + 1
}
let entry = self.entries[offset]
offset = offset + 1
entry
})
{ entries, }
}
///|
pub fn replay_key(
request : WebhookRequest,
policy : VerificationPolicy,
) -> String {
policy.provider.name +
":" +
provider_event_id(request, policy.provider) +
":" +
request.signature(policy.provider.signature_header)
}
///|
pub struct IdempotencyStore {
entries : Array[SeenEntry]
} derive(Eq, Debug)
///|
pub fn IdempotencyStore::empty() -> IdempotencyStore {
{ entries: [] }
}
///|
pub fn IdempotencyStore::contains(
self : IdempotencyStore,
key : String,
now : Int,
) -> Bool {
for i in 0..= now {
return true
}
}
false
}
///|
pub fn IdempotencyStore::remember(
self : IdempotencyStore,
key : String,
now : Int,
ttl_seconds : Int,
) -> IdempotencyStore {
let old = self.entries
let entry = SeenEntry::new(key, now + ttl_seconds)
let entries = Array::makei(old.length() + 1, i => {
if i < old.length() {
old[i]
} else {
entry
}
})
{ entries, }
}
///|
pub fn idempotency_key(
request : WebhookRequest,
policy : VerificationPolicy,
) -> String {
let event_id = provider_event_id(request, policy.provider)
if event_id == "" {
stable_sign(policy.secret, canonical_base(request, policy.provider)).value
} else {
policy.provider.name + ":" + event_id
}
}
///|
pub struct GuardState {
replay_store : ReplayStore
idempotency_store : IdempotencyStore
} derive(Eq, Debug)
///|
pub fn GuardState::empty() -> GuardState {
{
replay_store: ReplayStore::empty(),
idempotency_store: IdempotencyStore::empty(),
}
}
///|
pub fn GuardState::remember(
self : GuardState,
request : WebhookRequest,
policy : VerificationPolicy,
) -> GuardState {
{
replay_store: self.replay_store.remember(
replay_key(request, policy),
policy.now,
policy.provider.timestamp_tolerance_seconds,
),
idempotency_store: self.idempotency_store.remember(
idempotency_key(request, policy),
policy.now,
policy.provider.timestamp_tolerance_seconds,
),
}
}