qubesync 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/LICENSE +21 -0
- package/README.md +324 -2
- package/client.js +660 -0
- package/index.d.ts +367 -0
- package/index.js +409 -0
- package/index.mjs +22 -0
- package/operations/account.d.ts +263 -0
- package/operations/account.js +50 -0
- package/operations/account_tax_line_info.d.ts +31 -0
- package/operations/account_tax_line_info.js +26 -0
- package/operations/aging_report.d.ts +100 -0
- package/operations/aging_report.js +26 -0
- package/operations/alert.d.ts +118 -0
- package/operations/alert.js +50 -0
- package/operations/ar_refund_credit_card.d.ts +201 -0
- package/operations/ar_refund_credit_card.js +38 -0
- package/operations/bar_code.d.ts +40 -0
- package/operations/bar_code.js +26 -0
- package/operations/bill.d.ts +612 -0
- package/operations/bill.js +50 -0
- package/operations/bill_payment_check.d.ts +276 -0
- package/operations/bill_payment_check.js +50 -0
- package/operations/bill_payment_credit_card.d.ts +178 -0
- package/operations/bill_payment_credit_card.js +38 -0
- package/operations/bill_to_pay.d.ts +54 -0
- package/operations/bill_to_pay.js +26 -0
- package/operations/billing_rate.d.ts +159 -0
- package/operations/billing_rate.js +38 -0
- package/operations/budget_summary_report.d.ts +71 -0
- package/operations/budget_summary_report.js +26 -0
- package/operations/build_assembly.d.ts +264 -0
- package/operations/build_assembly.js +50 -0
- package/operations/charge.d.ts +330 -0
- package/operations/charge.js +50 -0
- package/operations/check.d.ts +394 -0
- package/operations/check.js +50 -0
- package/operations/class.d.ts +170 -0
- package/operations/class.js +50 -0
- package/operations/cleared_status.d.ts +43 -0
- package/operations/cleared_status.js +26 -0
- package/operations/client.d.ts +254 -0
- package/operations/company.d.ts +36 -0
- package/operations/company.js +26 -0
- package/operations/company_activity.d.ts +32 -0
- package/operations/company_activity.js +26 -0
- package/operations/credit_card_charge.d.ts +280 -0
- package/operations/credit_card_charge.js +50 -0
- package/operations/credit_card_credit.d.ts +280 -0
- package/operations/credit_card_credit.js +50 -0
- package/operations/credit_memo.d.ts +642 -0
- package/operations/credit_memo.js +50 -0
- package/operations/currency.d.ts +195 -0
- package/operations/currency.js +50 -0
- package/operations/custom_detail_report.d.ts +108 -0
- package/operations/custom_detail_report.js +26 -0
- package/operations/custom_summary_report.d.ts +119 -0
- package/operations/custom_summary_report.js +26 -0
- package/operations/customer.d.ts +639 -0
- package/operations/customer.js +50 -0
- package/operations/customer_msg.d.ts +121 -0
- package/operations/customer_msg.js +38 -0
- package/operations/customer_type.d.ts +127 -0
- package/operations/customer_type.js +38 -0
- package/operations/data_event_recovery_info.d.ts +61 -0
- package/operations/data_event_recovery_info.js +38 -0
- package/operations/data_ext.d.ts +197 -0
- package/operations/data_ext.js +50 -0
- package/operations/data_ext_def.d.ts +173 -0
- package/operations/data_ext_def.js +62 -0
- package/operations/date_driven_terms.d.ts +141 -0
- package/operations/date_driven_terms.js +38 -0
- package/operations/deposit.d.ts +402 -0
- package/operations/deposit.js +50 -0
- package/operations/employee.d.ts +683 -0
- package/operations/employee.js +50 -0
- package/operations/employee_default.d.ts +133 -0
- package/operations/employee_default.js +38 -0
- package/operations/entity.d.ts +96 -0
- package/operations/entity.js +26 -0
- package/operations/estimate.d.ts +590 -0
- package/operations/estimate.js +50 -0
- package/operations/form1099_category_account_mapping.d.ts +89 -0
- package/operations/form1099_category_account_mapping.js +38 -0
- package/operations/general_detail_report.d.ts +108 -0
- package/operations/general_detail_report.js +26 -0
- package/operations/general_summary_report.d.ts +115 -0
- package/operations/general_summary_report.js +26 -0
- package/operations/host.d.ts +43 -0
- package/operations/host.js +26 -0
- package/operations/index.js +397 -0
- package/operations/inventory_adjustment.d.ts +389 -0
- package/operations/inventory_adjustment.js +50 -0
- package/operations/inventory_site.d.ts +247 -0
- package/operations/inventory_site.js +50 -0
- package/operations/invoice.d.ts +682 -0
- package/operations/invoice.js +50 -0
- package/operations/item.d.ts +100 -0
- package/operations/item.js +26 -0
- package/operations/item_assemblies_can_build.d.ts +41 -0
- package/operations/item_assemblies_can_build.js +26 -0
- package/operations/item_discount.d.ts +256 -0
- package/operations/item_discount.js +50 -0
- package/operations/item_fixed_asset.d.ts +382 -0
- package/operations/item_fixed_asset.js +50 -0
- package/operations/item_group.d.ts +236 -0
- package/operations/item_group.js +50 -0
- package/operations/item_inventory.d.ts +350 -0
- package/operations/item_inventory.js +50 -0
- package/operations/item_inventory_assembly.d.ts +368 -0
- package/operations/item_inventory_assembly.js +50 -0
- package/operations/item_non_inventory.d.ts +264 -0
- package/operations/item_non_inventory.js +50 -0
- package/operations/item_other_charge.d.ts +244 -0
- package/operations/item_other_charge.js +50 -0
- package/operations/item_payment.d.ts +220 -0
- package/operations/item_payment.js +50 -0
- package/operations/item_receipt.d.ts +362 -0
- package/operations/item_receipt.js +50 -0
- package/operations/item_sales_tax.d.ts +228 -0
- package/operations/item_sales_tax.js +50 -0
- package/operations/item_sales_tax_group.d.ts +192 -0
- package/operations/item_sales_tax_group.js +50 -0
- package/operations/item_service.d.ts +256 -0
- package/operations/item_service.js +50 -0
- package/operations/item_sites.d.ts +81 -0
- package/operations/item_sites.js +26 -0
- package/operations/item_subtotal.d.ts +188 -0
- package/operations/item_subtotal.js +50 -0
- package/operations/job_report.d.ts +99 -0
- package/operations/job_report.js +26 -0
- package/operations/job_type.d.ts +127 -0
- package/operations/job_type.js +38 -0
- package/operations/journal_entry.d.ts +339 -0
- package/operations/journal_entry.js +50 -0
- package/operations/lead.d.ts +291 -0
- package/operations/lead.js +50 -0
- package/operations/list.d.ts +39 -0
- package/operations/list.js +26 -0
- package/operations/list_deleted.d.ts +44 -0
- package/operations/list_deleted.js +26 -0
- package/operations/list_display.d.ts +65 -0
- package/operations/list_display.js +38 -0
- package/operations/list_merge.d.ts +58 -0
- package/operations/list_merge.js +26 -0
- package/operations/other_name.d.ts +280 -0
- package/operations/other_name.js +50 -0
- package/operations/paycheck.d.ts +82 -0
- package/operations/paycheck.js +26 -0
- package/operations/payment_method.d.ts +131 -0
- package/operations/payment_method.js +38 -0
- package/operations/payroll_detail_report.d.ts +97 -0
- package/operations/payroll_detail_report.js +26 -0
- package/operations/payroll_item.d.ts +83 -0
- package/operations/payroll_item.js +26 -0
- package/operations/payroll_item_addition.d.ts +95 -0
- package/operations/payroll_item_addition.js +38 -0
- package/operations/payroll_item_company_contribution.d.ts +105 -0
- package/operations/payroll_item_company_contribution.js +38 -0
- package/operations/payroll_item_deduction.d.ts +101 -0
- package/operations/payroll_item_deduction.js +38 -0
- package/operations/payroll_item_direct_deposit.d.ts +65 -0
- package/operations/payroll_item_direct_deposit.js +38 -0
- package/operations/payroll_item_non_wage.d.ts +92 -0
- package/operations/payroll_item_non_wage.js +26 -0
- package/operations/payroll_item_tax.d.ts +108 -0
- package/operations/payroll_item_tax.js +38 -0
- package/operations/payroll_item_wage.d.ts +169 -0
- package/operations/payroll_item_wage.js +50 -0
- package/operations/payroll_item_workers_compensation.d.ts +77 -0
- package/operations/payroll_item_workers_compensation.js +38 -0
- package/operations/payroll_last_period.d.ts +31 -0
- package/operations/payroll_last_period.js +26 -0
- package/operations/payroll_liability_adjustment.d.ts +137 -0
- package/operations/payroll_liability_adjustment.js +38 -0
- package/operations/payroll_liability_check.d.ts +82 -0
- package/operations/payroll_liability_check.js +26 -0
- package/operations/payroll_prior_payment.d.ts +126 -0
- package/operations/payroll_prior_payment.js +38 -0
- package/operations/payroll_summary_report.d.ts +107 -0
- package/operations/payroll_summary_report.js +26 -0
- package/operations/payroll_year_to_date_adjustment.d.ts +156 -0
- package/operations/payroll_year_to_date_adjustment.js +38 -0
- package/operations/preferences.d.ts +64 -0
- package/operations/preferences.js +38 -0
- package/operations/price_level.d.ts +234 -0
- package/operations/price_level.js +50 -0
- package/operations/purchase_order.d.ts +620 -0
- package/operations/purchase_order.js +50 -0
- package/operations/qb_command_exec.d.ts +49 -0
- package/operations/qb_command_exec.js +26 -0
- package/operations/receive_payment.d.ts +450 -0
- package/operations/receive_payment.js +50 -0
- package/operations/receive_payment_to_deposit.d.ts +36 -0
- package/operations/receive_payment_to_deposit.js +26 -0
- package/operations/sales_order.d.ts +612 -0
- package/operations/sales_order.js +50 -0
- package/operations/sales_receipt.d.ts +650 -0
- package/operations/sales_receipt.js +50 -0
- package/operations/sales_rep.d.ts +170 -0
- package/operations/sales_rep.js +50 -0
- package/operations/sales_tax_code.d.ts +194 -0
- package/operations/sales_tax_code.js +50 -0
- package/operations/sales_tax_payable.d.ts +42 -0
- package/operations/sales_tax_payable.js +26 -0
- package/operations/sales_tax_payment_check.d.ts +254 -0
- package/operations/sales_tax_payment_check.js +50 -0
- package/operations/sales_tax_return.d.ts +35 -0
- package/operations/sales_tax_return.js +26 -0
- package/operations/sales_tax_return_line.d.ts +36 -0
- package/operations/sales_tax_return_line.js +26 -0
- package/operations/ship_method.d.ts +121 -0
- package/operations/ship_method.js +38 -0
- package/operations/special_account.d.ts +40 -0
- package/operations/special_account.js +26 -0
- package/operations/special_item.d.ts +44 -0
- package/operations/special_item.js +26 -0
- package/operations/standard_terms.d.ts +133 -0
- package/operations/standard_terms.js +38 -0
- package/operations/template.d.ts +36 -0
- package/operations/template.js +26 -0
- package/operations/terms.d.ts +90 -0
- package/operations/terms.js +26 -0
- package/operations/time_report.d.ts +84 -0
- package/operations/time_report.js +26 -0
- package/operations/time_tracking.d.ts +214 -0
- package/operations/time_tracking.js +50 -0
- package/operations/to_do.d.ts +254 -0
- package/operations/to_do.js +50 -0
- package/operations/transaction.d.ts +244 -0
- package/operations/transaction.js +26 -0
- package/operations/transfer.d.ts +178 -0
- package/operations/transfer.js +50 -0
- package/operations/transfer_inventory.d.ts +290 -0
- package/operations/transfer_inventory.js +50 -0
- package/operations/txn.d.ts +39 -0
- package/operations/txn.js +26 -0
- package/operations/txn_deleted.d.ts +44 -0
- package/operations/txn_deleted.js +26 -0
- package/operations/txn_display.d.ts +69 -0
- package/operations/txn_display.js +38 -0
- package/operations/txn_void.d.ts +39 -0
- package/operations/txn_void.js +26 -0
- package/operations/types.d.ts +1259 -0
- package/operations/unit_of_measure_set.d.ts +168 -0
- package/operations/unit_of_measure_set.js +38 -0
- package/operations/vehicle.d.ts +168 -0
- package/operations/vehicle.js +50 -0
- package/operations/vehicle_mileage.d.ts +147 -0
- package/operations/vehicle_mileage.js +38 -0
- package/operations/vendor.d.ts +541 -0
- package/operations/vendor.js +50 -0
- package/operations/vendor_credit.d.ts +365 -0
- package/operations/vendor_credit.js +50 -0
- package/operations/vendor_type.d.ts +127 -0
- package/operations/vendor_type.js +38 -0
- package/operations/workers_comp_code.d.ts +199 -0
- package/operations/workers_comp_code.js +50 -0
- package/package.json +52 -4
package/index.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// QuBe Sync webhooks: verify a delivery's X-QUBE-Signature header, then parse
|
|
4
|
+
// the body into a typed event. Framework-free, zero dependencies.
|
|
5
|
+
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
|
|
8
|
+
const VERSION = "0.2.0";
|
|
9
|
+
const SIGNATURE_HEADER = "X-QUBE-Signature";
|
|
10
|
+
const DEFAULT_TOLERANCE = 300;
|
|
11
|
+
const REASONS = Object.freeze(["missing", "malformed", "stale", "mismatch"]);
|
|
12
|
+
|
|
13
|
+
const WORKFLOW_RUN_EVENTS = Object.freeze([
|
|
14
|
+
"workflow_run.awaiting_input",
|
|
15
|
+
"workflow_run.completed",
|
|
16
|
+
"workflow_run.failed",
|
|
17
|
+
"workflow_run.cancelled",
|
|
18
|
+
]);
|
|
19
|
+
const NOTIFICATION_EVENT = "workflow_run.notification";
|
|
20
|
+
|
|
21
|
+
class SignatureError extends Error {
|
|
22
|
+
constructor(reason, message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "SignatureError";
|
|
25
|
+
this.reason = reason;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Events
|
|
31
|
+
|
|
32
|
+
function own(object, key) {
|
|
33
|
+
if (object === null || typeof object !== "object" || Array.isArray(object)) return null;
|
|
34
|
+
if (!Object.prototype.hasOwnProperty.call(object, key)) return null;
|
|
35
|
+
const value = object[key];
|
|
36
|
+
return value === undefined ? null : value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class Event {
|
|
40
|
+
constructor(body) {
|
|
41
|
+
Object.defineProperty(this, "body", { value: body, enumerable: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get type() {
|
|
45
|
+
return "unknown";
|
|
46
|
+
}
|
|
47
|
+
get event() {
|
|
48
|
+
const event = own(this.body, "event");
|
|
49
|
+
return typeof event === "string" ? event : null;
|
|
50
|
+
}
|
|
51
|
+
get timestamp() {
|
|
52
|
+
return own(this.body, "timestamp");
|
|
53
|
+
}
|
|
54
|
+
get appId() {
|
|
55
|
+
return own(this.body, "app_id");
|
|
56
|
+
}
|
|
57
|
+
get sandbox() {
|
|
58
|
+
return own(this.body, "sandbox");
|
|
59
|
+
}
|
|
60
|
+
get revision() {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
get reference() {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
get dedupeKey() {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class UnknownEvent extends Event {}
|
|
72
|
+
|
|
73
|
+
class RequestEvent extends Event {
|
|
74
|
+
get type() {
|
|
75
|
+
return "request";
|
|
76
|
+
}
|
|
77
|
+
get id() {
|
|
78
|
+
return own(this.body, "id");
|
|
79
|
+
}
|
|
80
|
+
get pageRequestId() {
|
|
81
|
+
return own(this.body, "page_request_id");
|
|
82
|
+
}
|
|
83
|
+
get state() {
|
|
84
|
+
return own(this.body, "state");
|
|
85
|
+
}
|
|
86
|
+
get page() {
|
|
87
|
+
return own(this.body, "page");
|
|
88
|
+
}
|
|
89
|
+
get iterationState() {
|
|
90
|
+
return own(this.body, "iteration_state");
|
|
91
|
+
}
|
|
92
|
+
get responseJson() {
|
|
93
|
+
return own(this.body, "response_json");
|
|
94
|
+
}
|
|
95
|
+
get error() {
|
|
96
|
+
return own(this.body, "error");
|
|
97
|
+
}
|
|
98
|
+
get dedupeKey() {
|
|
99
|
+
const pageRequestId = this.pageRequestId;
|
|
100
|
+
const state = this.state;
|
|
101
|
+
if (pageRequestId === null || state === null) return null;
|
|
102
|
+
return `${pageRequestId}:${state}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function newerThan(revision, stored) {
|
|
107
|
+
if (stored === null || stored === undefined) return true;
|
|
108
|
+
return revision !== null && revision > stored;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class WorkflowRunEvent extends Event {
|
|
112
|
+
get type() {
|
|
113
|
+
return "workflow_run";
|
|
114
|
+
}
|
|
115
|
+
get runId() {
|
|
116
|
+
return own(this.body, "run_id");
|
|
117
|
+
}
|
|
118
|
+
get reference() {
|
|
119
|
+
return own(this.body, "reference");
|
|
120
|
+
}
|
|
121
|
+
get revision() {
|
|
122
|
+
return own(this.body, "revision");
|
|
123
|
+
}
|
|
124
|
+
get runState() {
|
|
125
|
+
return own(this.body, "run_state");
|
|
126
|
+
}
|
|
127
|
+
get workflow() {
|
|
128
|
+
return own(this.body, "workflow");
|
|
129
|
+
}
|
|
130
|
+
get connectionId() {
|
|
131
|
+
return own(this.body, "connection_id");
|
|
132
|
+
}
|
|
133
|
+
get prompt() {
|
|
134
|
+
return own(this.body, "prompt");
|
|
135
|
+
}
|
|
136
|
+
get outcome() {
|
|
137
|
+
return own(this.body, "outcome");
|
|
138
|
+
}
|
|
139
|
+
get output() {
|
|
140
|
+
return own(this.body, "output");
|
|
141
|
+
}
|
|
142
|
+
get error() {
|
|
143
|
+
return own(this.body, "error");
|
|
144
|
+
}
|
|
145
|
+
get summary() {
|
|
146
|
+
return own(this.body, "summary");
|
|
147
|
+
}
|
|
148
|
+
get cancelReason() {
|
|
149
|
+
return own(this.body, "cancel_reason");
|
|
150
|
+
}
|
|
151
|
+
get links() {
|
|
152
|
+
return own(this.body, "links");
|
|
153
|
+
}
|
|
154
|
+
newerThan(storedRevision) {
|
|
155
|
+
return newerThan(this.revision, storedRevision);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// A notification is data a run sends along the way: it carries a revision but
|
|
160
|
+
// is never skipped by it, so it has no newerThan. Dedupe on deliveryId.
|
|
161
|
+
class NotificationEvent extends Event {
|
|
162
|
+
get type() {
|
|
163
|
+
return "notification";
|
|
164
|
+
}
|
|
165
|
+
get runId() {
|
|
166
|
+
return own(this.body, "run_id");
|
|
167
|
+
}
|
|
168
|
+
get reference() {
|
|
169
|
+
return own(this.body, "reference");
|
|
170
|
+
}
|
|
171
|
+
get revision() {
|
|
172
|
+
return own(this.body, "revision");
|
|
173
|
+
}
|
|
174
|
+
get deliveryId() {
|
|
175
|
+
return own(this.body, "delivery_id");
|
|
176
|
+
}
|
|
177
|
+
get seq() {
|
|
178
|
+
return own(this.body, "seq");
|
|
179
|
+
}
|
|
180
|
+
get name() {
|
|
181
|
+
return own(this.body, "name");
|
|
182
|
+
}
|
|
183
|
+
get state() {
|
|
184
|
+
return own(this.body, "state");
|
|
185
|
+
}
|
|
186
|
+
get from() {
|
|
187
|
+
return own(this.body, "from");
|
|
188
|
+
}
|
|
189
|
+
get data() {
|
|
190
|
+
return own(this.body, "data");
|
|
191
|
+
}
|
|
192
|
+
get dedupeKey() {
|
|
193
|
+
return this.deliveryId;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
class ConnectionEvent extends Event {
|
|
198
|
+
get type() {
|
|
199
|
+
return "connection";
|
|
200
|
+
}
|
|
201
|
+
get deliveryId() {
|
|
202
|
+
return own(this.body, "delivery_id");
|
|
203
|
+
}
|
|
204
|
+
get revision() {
|
|
205
|
+
return own(this.body, "revision");
|
|
206
|
+
}
|
|
207
|
+
get connection() {
|
|
208
|
+
return own(this.body, "connection");
|
|
209
|
+
}
|
|
210
|
+
get connectionId() {
|
|
211
|
+
return own(this.connection, "id");
|
|
212
|
+
}
|
|
213
|
+
get stallReason() {
|
|
214
|
+
const fromConnection = own(this.connection, "stall_reason");
|
|
215
|
+
return fromConnection !== null ? fromConnection : own(this.body, "stall_reason");
|
|
216
|
+
}
|
|
217
|
+
get dedupeKey() {
|
|
218
|
+
return this.deliveryId;
|
|
219
|
+
}
|
|
220
|
+
newerThan(storedRevision) {
|
|
221
|
+
return newerThan(this.revision, storedRevision);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
class NameIndexEvent extends Event {
|
|
226
|
+
get type() {
|
|
227
|
+
return "name_index";
|
|
228
|
+
}
|
|
229
|
+
get connectionId() {
|
|
230
|
+
return own(this.body, "connection_id");
|
|
231
|
+
}
|
|
232
|
+
get nameIndex() {
|
|
233
|
+
return own(this.body, "name_index");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function eventClassFor(body) {
|
|
238
|
+
const event = own(body, "event");
|
|
239
|
+
if (event === null) {
|
|
240
|
+
return own(body, "page_request_id") !== null ? RequestEvent : UnknownEvent;
|
|
241
|
+
}
|
|
242
|
+
if (typeof event !== "string") return UnknownEvent;
|
|
243
|
+
if (WORKFLOW_RUN_EVENTS.includes(event)) return WorkflowRunEvent;
|
|
244
|
+
if (event === NOTIFICATION_EVENT) return NotificationEvent;
|
|
245
|
+
if (event.startsWith("connection.")) return ConnectionEvent;
|
|
246
|
+
if (event.startsWith("name_index.")) return NameIndexEvent;
|
|
247
|
+
return UnknownEvent;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Arguments
|
|
252
|
+
|
|
253
|
+
function bodyBytes(rawBody) {
|
|
254
|
+
if (Buffer.isBuffer(rawBody)) return rawBody;
|
|
255
|
+
if (typeof rawBody === "string") return Buffer.from(rawBody, "utf8");
|
|
256
|
+
if (rawBody instanceof Uint8Array) {
|
|
257
|
+
return Buffer.from(rawBody.buffer, rawBody.byteOffset, rawBody.byteLength);
|
|
258
|
+
}
|
|
259
|
+
if (rawBody !== null && typeof rawBody === "object") {
|
|
260
|
+
throw new TypeError(
|
|
261
|
+
"rawBody is a parsed object: pass the raw request body (a Buffer or string, exactly as received), " +
|
|
262
|
+
'for example from express.raw({ type: "application/json" })'
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
throw new TypeError(
|
|
266
|
+
`rawBody must be the raw request body as a Buffer or string, got ${rawBody === null ? "null" : typeof rawBody}`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function secretList(secrets) {
|
|
271
|
+
const list = typeof secrets === "string" || Buffer.isBuffer(secrets) ? [secrets] : secrets;
|
|
272
|
+
if (!Array.isArray(list)) {
|
|
273
|
+
throw new TypeError("secrets must be a webhook secret string or an array of them");
|
|
274
|
+
}
|
|
275
|
+
if (list.length === 0) {
|
|
276
|
+
throw new TypeError("secrets is empty: pass at least one webhook secret");
|
|
277
|
+
}
|
|
278
|
+
list.forEach((secret, index) => {
|
|
279
|
+
const ok = (typeof secret === "string" || Buffer.isBuffer(secret)) && secret.length > 0;
|
|
280
|
+
if (!ok) throw new TypeError(`secrets[${index}] must be a non-empty string`);
|
|
281
|
+
});
|
|
282
|
+
return list;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function nowSeconds(now) {
|
|
286
|
+
if (now === undefined || now === null) return Math.floor(Date.now() / 1000);
|
|
287
|
+
if (now instanceof Date) {
|
|
288
|
+
const ms = now.getTime();
|
|
289
|
+
if (Number.isNaN(ms)) throw new TypeError("now is an invalid Date");
|
|
290
|
+
return Math.floor(ms / 1000);
|
|
291
|
+
}
|
|
292
|
+
if (typeof now === "number" && Number.isFinite(now)) return Math.floor(now);
|
|
293
|
+
throw new TypeError("now must be unix seconds (a number) or a Date");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function toleranceSeconds(tolerance) {
|
|
297
|
+
if (tolerance === undefined || tolerance === null) return DEFAULT_TOLERANCE;
|
|
298
|
+
if (typeof tolerance === "number" && Number.isFinite(tolerance) && tolerance >= 0) return tolerance;
|
|
299
|
+
throw new TypeError("tolerance must be a non-negative number of seconds");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function headerString(header) {
|
|
303
|
+
if (Array.isArray(header)) return header.join(",");
|
|
304
|
+
if (header === undefined || header === null || typeof header === "string") return header;
|
|
305
|
+
throw new TypeError("header must be the X-QUBE-Signature header value (a string)");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Splits "t=...,v1=...,v1=..." into the first t and every non-empty v1.
|
|
309
|
+
function parseHeader(header) {
|
|
310
|
+
let t = null;
|
|
311
|
+
const signatures = [];
|
|
312
|
+
for (const rawPart of header.split(",")) {
|
|
313
|
+
const part = rawPart.trim();
|
|
314
|
+
const eq = part.indexOf("=");
|
|
315
|
+
if (eq === -1) continue;
|
|
316
|
+
const key = part.slice(0, eq);
|
|
317
|
+
const value = part.slice(eq + 1);
|
|
318
|
+
if (key === "t" && t === null) t = value;
|
|
319
|
+
else if (key === "v1" && value !== "") signatures.push(value);
|
|
320
|
+
}
|
|
321
|
+
return { t, signatures };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function constantTimeEqual(expected, candidate) {
|
|
325
|
+
const a = Buffer.from(expected, "utf8");
|
|
326
|
+
const b = Buffer.from(candidate, "utf8");
|
|
327
|
+
if (a.length !== b.length) return false;
|
|
328
|
+
return crypto.timingSafeEqual(a, b);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
// Public API
|
|
333
|
+
|
|
334
|
+
function parse(rawBody) {
|
|
335
|
+
const text = bodyBytes(rawBody).toString("utf8");
|
|
336
|
+
const body = JSON.parse(text);
|
|
337
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
338
|
+
throw new TypeError("webhook body is not a JSON object");
|
|
339
|
+
}
|
|
340
|
+
const EventClass = eventClassFor(body);
|
|
341
|
+
return new EventClass(body);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function verify(rawBody, header, options) {
|
|
345
|
+
const opts = options === undefined || options === null ? {} : options;
|
|
346
|
+
if (typeof opts !== "object") {
|
|
347
|
+
throw new TypeError("verify(rawBody, header, { secrets, tolerance, now }): options must be an object");
|
|
348
|
+
}
|
|
349
|
+
const bytes = bodyBytes(rawBody);
|
|
350
|
+
const secrets = secretList(opts.secrets);
|
|
351
|
+
const tolerance = toleranceSeconds(opts.tolerance);
|
|
352
|
+
const now = nowSeconds(opts.now);
|
|
353
|
+
const value = headerString(header);
|
|
354
|
+
|
|
355
|
+
if (value === undefined || value === null || value.trim() === "") {
|
|
356
|
+
throw new SignatureError("missing", `webhook signature missing: no ${SIGNATURE_HEADER} header`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const { t, signatures } = parseHeader(value);
|
|
360
|
+
if (t === null || !/^[0-9]+$/.test(t) || signatures.length === 0) {
|
|
361
|
+
throw new SignatureError(
|
|
362
|
+
"malformed",
|
|
363
|
+
`webhook signature malformed: ${SIGNATURE_HEADER} needs t=<unix seconds> and at least one v1=<hex>`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const age = now - Number(t);
|
|
368
|
+
if (Math.abs(age) > tolerance) {
|
|
369
|
+
const when = age > 0 ? `${age} seconds old` : `${-age} seconds in the future`;
|
|
370
|
+
throw new SignatureError(
|
|
371
|
+
"stale",
|
|
372
|
+
`webhook signature stale: its timestamp is ${when} (tolerance ${tolerance} seconds)`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let matched = false;
|
|
377
|
+
for (const secret of secrets) {
|
|
378
|
+
const expected = crypto.createHmac("sha256", secret).update(`${t}.`, "utf8").update(bytes).digest("hex");
|
|
379
|
+
for (const candidate of signatures) {
|
|
380
|
+
if (constantTimeEqual(expected, candidate)) matched = true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!matched) {
|
|
384
|
+
throw new SignatureError("mismatch", "webhook signature mismatch: no v1 matches any of the secrets");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return parse(bytes);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const { Client, APIError } = require("./client");
|
|
391
|
+
|
|
392
|
+
module.exports = {
|
|
393
|
+
VERSION,
|
|
394
|
+
SIGNATURE_HEADER,
|
|
395
|
+
DEFAULT_TOLERANCE,
|
|
396
|
+
REASONS,
|
|
397
|
+
SignatureError,
|
|
398
|
+
verify,
|
|
399
|
+
parse,
|
|
400
|
+
Event,
|
|
401
|
+
RequestEvent,
|
|
402
|
+
WorkflowRunEvent,
|
|
403
|
+
NotificationEvent,
|
|
404
|
+
ConnectionEvent,
|
|
405
|
+
NameIndexEvent,
|
|
406
|
+
UnknownEvent,
|
|
407
|
+
Client,
|
|
408
|
+
APIError,
|
|
409
|
+
};
|
package/index.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import qubesync from "./index.js";
|
|
2
|
+
|
|
3
|
+
export const {
|
|
4
|
+
VERSION,
|
|
5
|
+
SIGNATURE_HEADER,
|
|
6
|
+
DEFAULT_TOLERANCE,
|
|
7
|
+
REASONS,
|
|
8
|
+
SignatureError,
|
|
9
|
+
verify,
|
|
10
|
+
parse,
|
|
11
|
+
Event,
|
|
12
|
+
RequestEvent,
|
|
13
|
+
WorkflowRunEvent,
|
|
14
|
+
NotificationEvent,
|
|
15
|
+
ConnectionEvent,
|
|
16
|
+
NameIndexEvent,
|
|
17
|
+
UnknownEvent,
|
|
18
|
+
Client,
|
|
19
|
+
APIError,
|
|
20
|
+
} = qubesync;
|
|
21
|
+
|
|
22
|
+
export default qubesync;
|