@adonis-agora/payments 0.2.0 → 0.4.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 +2375 -0
- package/dist/assets/spa/assets/index-B7rU8eIL.css +2 -0
- package/dist/assets/spa/assets/index-BcM2VH5k.js +10 -0
- package/dist/assets/spa/assets/index-BcM2VH5k.js.map +1 -0
- package/dist/assets/spa/index.html +20 -0
- package/dist/commands/main.d.ts +4 -3
- package/dist/commands/main.d.ts.map +1 -1
- package/dist/commands/main.js +9 -2
- package/dist/commands/main.js.map +1 -1
- package/dist/commands/make_billable.d.ts.map +1 -1
- package/dist/commands/make_billable.js +1 -1
- package/dist/commands/make_billable.js.map +1 -1
- package/dist/commands/make_webhook_handler.d.ts.map +1 -1
- package/dist/commands/make_webhook_handler.js +1 -1
- package/dist/commands/make_webhook_handler.js.map +1 -1
- package/dist/commands/payments_health.d.ts +31 -0
- package/dist/commands/payments_health.d.ts.map +1 -0
- package/dist/commands/payments_health.js +113 -0
- package/dist/commands/payments_health.js.map +1 -0
- package/dist/commands/payments_sync.d.ts +18 -4
- package/dist/commands/payments_sync.d.ts.map +1 -1
- package/dist/commands/payments_sync.js +136 -40
- package/dist/commands/payments_sync.js.map +1 -1
- package/dist/commands/payments_webhook.d.ts.map +1 -1
- package/dist/commands/payments_webhook.js.map +1 -1
- package/dist/configure.d.ts +12 -5
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +18 -5
- package/dist/configure.js.map +1 -1
- package/dist/providers/dashboard_provider.d.ts +97 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -0
- package/dist/providers/dashboard_provider.js +430 -0
- package/dist/providers/dashboard_provider.js.map +1 -0
- package/dist/providers/payments_client_provider.d.ts +41 -0
- package/dist/providers/payments_client_provider.d.ts.map +1 -0
- package/dist/providers/payments_client_provider.js +95 -0
- package/dist/providers/payments_client_provider.js.map +1 -0
- package/dist/providers/payments_provider.d.ts +35 -0
- package/dist/providers/payments_provider.d.ts.map +1 -1
- package/dist/providers/payments_provider.js +250 -36
- package/dist/providers/payments_provider.js.map +1 -1
- package/dist/src/billing/billing_health.d.ts +85 -0
- package/dist/src/billing/billing_health.d.ts.map +1 -0
- package/dist/src/billing/billing_health.js +155 -0
- package/dist/src/billing/billing_health.js.map +1 -0
- package/dist/src/billing/billing_overview.d.ts +4 -3
- package/dist/src/billing/billing_overview.d.ts.map +1 -1
- package/dist/src/billing/billing_overview.js +14 -5
- package/dist/src/billing/billing_overview.js.map +1 -1
- package/dist/src/billing/billing_store.d.ts +634 -5
- package/dist/src/billing/billing_store.d.ts.map +1 -1
- package/dist/src/billing/billing_store.js +18 -1
- package/dist/src/billing/billing_store.js.map +1 -1
- package/dist/src/billing/index.d.ts +4 -3
- package/dist/src/billing/index.d.ts.map +1 -1
- package/dist/src/billing/index.js +2 -1
- package/dist/src/billing/index.js.map +1 -1
- package/dist/src/billing/list_query.d.ts +16 -0
- package/dist/src/billing/list_query.d.ts.map +1 -0
- package/dist/src/billing/list_query.js +28 -0
- package/dist/src/billing/list_query.js.map +1 -0
- package/dist/src/billing/lucid_billing_store.d.ts +122 -4
- package/dist/src/billing/lucid_billing_store.d.ts.map +1 -1
- package/dist/src/billing/lucid_billing_store.js +811 -13
- package/dist/src/billing/lucid_billing_store.js.map +1 -1
- package/dist/src/billing/mixins/index.d.ts +5 -2
- package/dist/src/billing/mixins/index.d.ts.map +1 -1
- package/dist/src/billing/mixins/index.js +5 -2
- package/dist/src/billing/mixins/index.js.map +1 -1
- package/dist/src/billing/mixins/with_audit_event.d.ts +48 -0
- package/dist/src/billing/mixins/with_audit_event.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_audit_event.js +77 -0
- package/dist/src/billing/mixins/with_audit_event.js.map +1 -0
- package/dist/src/billing/mixins/with_billable.d.ts.map +1 -1
- package/dist/src/billing/mixins/with_customer.d.ts +39 -0
- package/dist/src/billing/mixins/with_customer.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_customer.js +68 -0
- package/dist/src/billing/mixins/with_customer.js.map +1 -0
- package/dist/src/billing/mixins/with_dispute.d.ts +68 -0
- package/dist/src/billing/mixins/with_dispute.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_dispute.js +83 -0
- package/dist/src/billing/mixins/with_dispute.js.map +1 -0
- package/dist/src/billing/mixins/with_payment.d.ts +42 -0
- package/dist/src/billing/mixins/with_payment.d.ts.map +1 -1
- package/dist/src/billing/mixins/with_payment.js +29 -3
- package/dist/src/billing/mixins/with_payment.js.map +1 -1
- package/dist/src/billing/mixins/with_subscription.d.ts +7 -0
- package/dist/src/billing/mixins/with_subscription.d.ts.map +1 -1
- package/dist/src/billing/mixins/with_subscription.js +15 -1
- package/dist/src/billing/mixins/with_subscription.js.map +1 -1
- package/dist/src/billing/mixins/with_usage_event.d.ts +7 -0
- package/dist/src/billing/mixins/with_usage_event.d.ts.map +1 -1
- package/dist/src/billing/mixins/with_usage_event.js +15 -1
- package/dist/src/billing/mixins/with_usage_event.js.map +1 -1
- package/dist/src/billing/mixins/with_webhook_event.d.ts +18 -0
- package/dist/src/billing/mixins/with_webhook_event.d.ts.map +1 -1
- package/dist/src/billing/mixins/with_webhook_event.js +18 -1
- package/dist/src/billing/mixins/with_webhook_event.js.map +1 -1
- package/dist/src/billing/resolve_dispatch.d.ts +22 -0
- package/dist/src/billing/resolve_dispatch.d.ts.map +1 -0
- package/dist/src/billing/resolve_dispatch.js +44 -0
- package/dist/src/billing/resolve_dispatch.js.map +1 -0
- package/dist/src/billing/resolve_store.d.ts +18 -0
- package/dist/src/billing/resolve_store.d.ts.map +1 -0
- package/dist/src/billing/resolve_store.js +35 -0
- package/dist/src/billing/resolve_store.js.map +1 -0
- package/dist/src/billing/schema.d.ts +108 -0
- package/dist/src/billing/schema.d.ts.map +1 -0
- package/dist/src/billing/schema.js +350 -0
- package/dist/src/billing/schema.js.map +1 -0
- package/dist/src/billing/webhook_dispatcher.d.ts +95 -16
- package/dist/src/billing/webhook_dispatcher.d.ts.map +1 -1
- package/dist/src/billing/webhook_dispatcher.js +186 -33
- package/dist/src/billing/webhook_dispatcher.js.map +1 -1
- package/dist/src/billing/webhook_events.d.ts +67 -1
- package/dist/src/billing/webhook_events.d.ts.map +1 -1
- package/dist/src/billing/webhook_events.js +15 -0
- package/dist/src/billing/webhook_events.js.map +1 -1
- package/dist/src/billing/webhook_processor.d.ts.map +1 -1
- package/dist/src/billing/webhook_processor.js +325 -5
- package/dist/src/billing/webhook_processor.js.map +1 -1
- package/dist/src/client/define_config.d.ts +150 -0
- package/dist/src/client/define_config.d.ts.map +1 -0
- package/dist/src/client/define_config.js +98 -0
- package/dist/src/client/define_config.js.map +1 -0
- package/dist/src/client/handlers.d.ts +71 -0
- package/dist/src/client/handlers.d.ts.map +1 -0
- package/dist/src/client/handlers.js +119 -0
- package/dist/src/client/handlers.js.map +1 -0
- package/dist/src/client/index.d.ts +15 -0
- package/dist/src/client/index.d.ts.map +1 -0
- package/dist/src/client/index.js +12 -0
- package/dist/src/client/index.js.map +1 -0
- package/dist/src/client/statuses.d.ts +38 -0
- package/dist/src/client/statuses.d.ts.map +1 -0
- package/dist/src/client/statuses.js +42 -0
- package/dist/src/client/statuses.js.map +1 -0
- package/dist/src/dashboard/actions.d.ts +143 -0
- package/dist/src/dashboard/actions.d.ts.map +1 -0
- package/dist/src/dashboard/actions.js +112 -0
- package/dist/src/dashboard/actions.js.map +1 -0
- package/dist/src/dashboard/auth.d.ts +136 -0
- package/dist/src/dashboard/auth.d.ts.map +1 -0
- package/dist/src/dashboard/auth.js +152 -0
- package/dist/src/dashboard/auth.js.map +1 -0
- package/dist/src/dashboard/define_config.d.ts +80 -0
- package/dist/src/dashboard/define_config.d.ts.map +1 -0
- package/dist/src/dashboard/define_config.js +83 -0
- package/dist/src/dashboard/define_config.js.map +1 -0
- package/dist/src/dashboard/handlers.d.ts +281 -0
- package/dist/src/dashboard/handlers.d.ts.map +1 -0
- package/dist/src/dashboard/handlers.js +960 -0
- package/dist/src/dashboard/handlers.js.map +1 -0
- package/dist/src/dashboard/index.d.ts +17 -0
- package/dist/src/dashboard/index.d.ts.map +1 -0
- package/dist/src/dashboard/index.js +13 -0
- package/dist/src/dashboard/index.js.map +1 -0
- package/dist/src/dashboard/login_page.d.ts +23 -0
- package/dist/src/dashboard/login_page.d.ts.map +1 -0
- package/dist/src/dashboard/login_page.js +170 -0
- package/dist/src/dashboard/login_page.js.map +1 -0
- package/dist/src/dashboard/period.d.ts +31 -0
- package/dist/src/dashboard/period.d.ts.map +1 -0
- package/dist/src/dashboard/period.js +44 -0
- package/dist/src/dashboard/period.js.map +1 -0
- package/dist/src/dashboard/session_cookie.d.ts +42 -0
- package/dist/src/dashboard/session_cookie.d.ts.map +1 -0
- package/dist/src/dashboard/session_cookie.js +97 -0
- package/dist/src/dashboard/session_cookie.js.map +1 -0
- package/dist/src/dashboard/spa.d.ts +24 -0
- package/dist/src/dashboard/spa.d.ts.map +1 -0
- package/dist/src/dashboard/spa.js +52 -0
- package/dist/src/dashboard/spa.js.map +1 -0
- package/dist/src/define_config.d.ts +245 -10
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +97 -1
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/diagnostics.d.ts +287 -7
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +317 -1
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/driver.d.ts +139 -5
- package/dist/src/driver.d.ts.map +1 -1
- package/dist/src/drivers/abacate.d.ts +13 -3
- package/dist/src/drivers/abacate.d.ts.map +1 -1
- package/dist/src/drivers/abacate.js +112 -19
- package/dist/src/drivers/abacate.js.map +1 -1
- package/dist/src/drivers/adyen.d.ts +164 -0
- package/dist/src/drivers/adyen.d.ts.map +1 -0
- package/dist/src/drivers/adyen.js +678 -0
- package/dist/src/drivers/adyen.js.map +1 -0
- package/dist/src/drivers/asaas.d.ts +53 -3
- package/dist/src/drivers/asaas.d.ts.map +1 -1
- package/dist/src/drivers/asaas.js +364 -29
- package/dist/src/drivers/asaas.js.map +1 -1
- package/dist/src/drivers/dodo.d.ts +135 -0
- package/dist/src/drivers/dodo.d.ts.map +1 -0
- package/dist/src/drivers/dodo.js +864 -0
- package/dist/src/drivers/dodo.js.map +1 -0
- package/dist/src/drivers/efi.d.ts +98 -0
- package/dist/src/drivers/efi.d.ts.map +1 -0
- package/dist/src/drivers/efi.js +628 -0
- package/dist/src/drivers/efi.js.map +1 -0
- package/dist/src/drivers/infinitepay.d.ts +156 -0
- package/dist/src/drivers/infinitepay.d.ts.map +1 -0
- package/dist/src/drivers/infinitepay.js +321 -0
- package/dist/src/drivers/infinitepay.js.map +1 -0
- package/dist/src/drivers/lemonsqueezy.d.ts +125 -0
- package/dist/src/drivers/lemonsqueezy.d.ts.map +1 -0
- package/dist/src/drivers/lemonsqueezy.js +632 -0
- package/dist/src/drivers/lemonsqueezy.js.map +1 -0
- package/dist/src/drivers/mercadopago.d.ts +97 -0
- package/dist/src/drivers/mercadopago.d.ts.map +1 -0
- package/dist/src/drivers/mercadopago.js +844 -0
- package/dist/src/drivers/mercadopago.js.map +1 -0
- package/dist/src/drivers/mollie.d.ts +125 -0
- package/dist/src/drivers/mollie.d.ts.map +1 -0
- package/dist/src/drivers/mollie.js +812 -0
- package/dist/src/drivers/mollie.js.map +1 -0
- package/dist/src/drivers/paddle.d.ts +148 -0
- package/dist/src/drivers/paddle.d.ts.map +1 -0
- package/dist/src/drivers/paddle.js +738 -0
- package/dist/src/drivers/paddle.js.map +1 -0
- package/dist/src/drivers/pagarme.d.ts +97 -0
- package/dist/src/drivers/pagarme.d.ts.map +1 -0
- package/dist/src/drivers/pagarme.js +829 -0
- package/dist/src/drivers/pagarme.js.map +1 -0
- package/dist/src/drivers/pagbank.d.ts +57 -0
- package/dist/src/drivers/pagbank.d.ts.map +1 -0
- package/dist/src/drivers/pagbank.js +499 -0
- package/dist/src/drivers/pagbank.js.map +1 -0
- package/dist/src/drivers/paypal.d.ts +120 -0
- package/dist/src/drivers/paypal.d.ts.map +1 -0
- package/dist/src/drivers/paypal.js +891 -0
- package/dist/src/drivers/paypal.js.map +1 -0
- package/dist/src/drivers/polar.d.ts +118 -0
- package/dist/src/drivers/polar.d.ts.map +1 -0
- package/dist/src/drivers/polar.js +604 -0
- package/dist/src/drivers/polar.js.map +1 -0
- package/dist/src/drivers/razorpay.d.ts +103 -0
- package/dist/src/drivers/razorpay.d.ts.map +1 -0
- package/dist/src/drivers/razorpay.js +867 -0
- package/dist/src/drivers/razorpay.js.map +1 -0
- package/dist/src/drivers/shared.d.ts +33 -0
- package/dist/src/drivers/shared.d.ts.map +1 -0
- package/dist/src/drivers/shared.js +37 -0
- package/dist/src/drivers/shared.js.map +1 -0
- package/dist/src/drivers/square.d.ts +133 -0
- package/dist/src/drivers/square.d.ts.map +1 -0
- package/dist/src/drivers/square.js +905 -0
- package/dist/src/drivers/square.js.map +1 -0
- package/dist/src/drivers/stripe.d.ts +41 -4
- package/dist/src/drivers/stripe.d.ts.map +1 -1
- package/dist/src/drivers/stripe.js +823 -26
- package/dist/src/drivers/stripe.js.map +1 -1
- package/dist/src/drivers/woovi.d.ts +11 -4
- package/dist/src/drivers/woovi.d.ts.map +1 -1
- package/dist/src/drivers/woovi.js +162 -29
- package/dist/src/drivers/woovi.js.map +1 -1
- package/dist/src/ensure_customer.d.ts +34 -1
- package/dist/src/ensure_customer.d.ts.map +1 -1
- package/dist/src/ensure_customer.js +29 -4
- package/dist/src/ensure_customer.js.map +1 -1
- package/dist/src/hooks/webhook_handlers.d.ts.map +1 -1
- package/dist/src/hooks/webhook_handlers.js.map +1 -1
- package/dist/src/http.d.ts +37 -3
- package/dist/src/http.d.ts.map +1 -1
- package/dist/src/http.js +69 -5
- package/dist/src/http.js.map +1 -1
- package/dist/src/index.d.ts +54 -26
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +29 -12
- package/dist/src/index.js.map +1 -1
- package/dist/src/invoice/drivers/asaas.d.ts.map +1 -1
- package/dist/src/invoice/drivers/asaas.js.map +1 -1
- package/dist/src/invoice/drivers/enotas.d.ts.map +1 -1
- package/dist/src/invoice/drivers/enotas.js.map +1 -1
- package/dist/src/invoice/drivers/focus.d.ts.map +1 -1
- package/dist/src/invoice/drivers/focus.js.map +1 -1
- package/dist/src/invoice/drivers/plugnotas.d.ts.map +1 -1
- package/dist/src/invoice/drivers/plugnotas.js.map +1 -1
- package/dist/src/invoice/drivers/tecnospeed.d.ts.map +1 -1
- package/dist/src/invoice/drivers/tecnospeed.js.map +1 -1
- package/dist/src/invoice/emit_invoice.d.ts.map +1 -1
- package/dist/src/invoice/emit_invoice.js +31 -11
- package/dist/src/invoice/emit_invoice.js.map +1 -1
- package/dist/src/invoice/invoice_manager.d.ts.map +1 -1
- package/dist/src/money.d.ts +23 -5
- package/dist/src/money.d.ts.map +1 -1
- package/dist/src/money.js +72 -7
- package/dist/src/money.js.map +1 -1
- package/dist/src/payments_manager.d.ts +8 -1
- package/dist/src/payments_manager.d.ts.map +1 -1
- package/dist/src/payments_manager.js +168 -6
- package/dist/src/payments_manager.js.map +1 -1
- package/dist/src/services/main.d.ts +38 -0
- package/dist/src/services/main.d.ts.map +1 -1
- package/dist/src/services/main.js +130 -0
- package/dist/src/services/main.js.map +1 -1
- package/dist/src/telescope/index.d.ts +2 -0
- package/dist/src/telescope/index.d.ts.map +1 -1
- package/dist/src/telescope/index.js +1 -0
- package/dist/src/telescope/index.js.map +1 -1
- package/dist/src/telescope/payments_watcher.d.ts +10 -4
- package/dist/src/telescope/payments_watcher.d.ts.map +1 -1
- package/dist/src/telescope/payments_watcher.js +66 -14
- package/dist/src/telescope/payments_watcher.js.map +1 -1
- package/dist/src/telescope/register.d.ts +62 -0
- package/dist/src/telescope/register.d.ts.map +1 -0
- package/dist/src/telescope/register.js +48 -0
- package/dist/src/telescope/register.js.map +1 -0
- package/dist/src/testing/fake_payments.d.ts +62 -0
- package/dist/src/testing/fake_payments.d.ts.map +1 -0
- package/dist/src/testing/fake_payments.js +82 -0
- package/dist/src/testing/fake_payments.js.map +1 -0
- package/dist/src/testing/fake_payments_driver.d.ts +1 -1
- package/dist/src/testing/fake_payments_driver.d.ts.map +1 -1
- package/dist/src/testing/fake_payments_driver.js.map +1 -1
- package/dist/src/testing/in_memory_billing_store.d.ts +151 -2
- package/dist/src/testing/in_memory_billing_store.d.ts.map +1 -1
- package/dist/src/testing/in_memory_billing_store.js +517 -4
- package/dist/src/testing/in_memory_billing_store.js.map +1 -1
- package/dist/src/testing/index.d.ts +3 -2
- package/dist/src/testing/index.d.ts.map +1 -1
- package/dist/src/testing/index.js +1 -0
- package/dist/src/testing/index.js.map +1 -1
- package/dist/src/testing/mutable_clock.d.ts.map +1 -1
- package/dist/src/types.d.ts +161 -5
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +30 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/webhook_handlers.d.ts +96 -2
- package/dist/src/webhook_handlers.d.ts.map +1 -1
- package/dist/src/webhook_handlers.js +91 -10
- package/dist/src/webhook_handlers.js.map +1 -1
- package/dist/src/webhook_security.d.ts +94 -3
- package/dist/src/webhook_security.d.ts.map +1 -1
- package/dist/src/webhook_security.js +173 -9
- package/dist/src/webhook_security.js.map +1 -1
- package/dist/stubs/config/payments.stub +13 -0
- package/dist/stubs/config/payments_client.stub +42 -0
- package/dist/stubs/config/payments_dashboard.stub +38 -0
- package/dist/stubs/database/migrations/create_billing_tables.stub +35 -77
- package/dist/stubs/main.js.map +1 -1
- package/dist/stubs/make/webhook_handler/main.stub +29 -11
- package/package.json +67 -32
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the refund action over a driver resolver (the app's `PaymentsManager.driver`).
|
|
3
|
+
*
|
|
4
|
+
* The capability check happens BEFORE the call, matching `PaymentsManager.assertCapability`: a
|
|
5
|
+
* driver that declares `refunds: false` implements `refund()` only to throw, and discovering that
|
|
6
|
+
* from a gateway round-trip reads to the operator like a network failure.
|
|
7
|
+
*/
|
|
8
|
+
export function createRefundAction(resolveDriver) {
|
|
9
|
+
return async ({ provider, gatewayId, amount }) => {
|
|
10
|
+
let driver;
|
|
11
|
+
try {
|
|
12
|
+
driver = await resolveDriver(provider);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
// The manager's own message names which drivers ARE configured, which is exactly the
|
|
16
|
+
// next thing the operator needs to know.
|
|
17
|
+
return { kind: 'unavailable', message: messageOf(error, `provider "${provider}" not found`) };
|
|
18
|
+
}
|
|
19
|
+
if (driver.capabilities?.refunds !== true) {
|
|
20
|
+
return {
|
|
21
|
+
kind: 'unsupported',
|
|
22
|
+
message: `The "${provider}" gateway has no refund API. Refund this payment from the gateway's own dashboard.`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const refund = await driver.refund(gatewayId, amount);
|
|
27
|
+
return {
|
|
28
|
+
kind: 'ok',
|
|
29
|
+
refund: {
|
|
30
|
+
gatewayId: refund.gatewayId,
|
|
31
|
+
amount: refund.amount.amount,
|
|
32
|
+
currency: refund.amount.currency,
|
|
33
|
+
status: refund.status,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return { kind: 'gateway-error', message: messageOf(error, 'the gateway refused the refund') };
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the retry action over the store plus the app's webhook processor.
|
|
44
|
+
*
|
|
45
|
+
* The ledger dance is the fiddly part and it lives here, once:
|
|
46
|
+
*
|
|
47
|
+
* 1. `recordWebhookEvent` re-claims a `failed` row and hands the ORIGINAL delivery back — both
|
|
48
|
+
* the raw payload and the NORMALIZED event recorded beside it. That is the only way to read
|
|
49
|
+
* a stored delivery through the `BillingStore` contract.
|
|
50
|
+
* 2. The event is rebuilt from those two columns. It is NEVER re-parsed: `parseWebhook`
|
|
51
|
+
* re-verifies a signature computed over headers the ledger does not keep, so rebuilding that
|
|
52
|
+
* way answered `422` on Stripe, Adyen and every other gateway that signs — a retry that
|
|
53
|
+
* worked for a minority of gateways, and only for the ones an attacker would not need to
|
|
54
|
+
* forge. A row with no `normalized` (written before the migration) is `undeliverable`,
|
|
55
|
+
* stated plainly, rather than half-replayed.
|
|
56
|
+
* 3. The row is put BACK to `failed` before `process` runs, because `WebhookProcessor.process`
|
|
57
|
+
* claims through `recordWebhookEvent` itself, and a row sitting at `received` reads to it as
|
|
58
|
+
* "in flight" — it would return `false` and run nothing at all. This is the one place that
|
|
59
|
+
* subtlety is written down.
|
|
60
|
+
* 4. `process` claims it properly and marks it `processed` or `failed` with the real reason.
|
|
61
|
+
*/
|
|
62
|
+
export function createReplayAction(deps) {
|
|
63
|
+
return async ({ gatewayEventId, provider, type, previousError }) => {
|
|
64
|
+
const claimed = await deps.store.recordWebhookEvent({
|
|
65
|
+
gatewayEventId,
|
|
66
|
+
provider,
|
|
67
|
+
type,
|
|
68
|
+
// Ignored on the re-claim path — the row keeps the payload and the normalized event it
|
|
69
|
+
// was recorded with, which is precisely what we need back.
|
|
70
|
+
payload: {},
|
|
71
|
+
});
|
|
72
|
+
if (claimed === null)
|
|
73
|
+
return { kind: 'conflict' };
|
|
74
|
+
const row = claimed;
|
|
75
|
+
const ledgerId = String(row.id);
|
|
76
|
+
const restore = () => deps.store.markWebhookFailed(ledgerId, previousError ?? '');
|
|
77
|
+
if (row.normalized === null || row.normalized === undefined) {
|
|
78
|
+
// Put the row back exactly as the operator found it: this refusal is about THIS retry, and
|
|
79
|
+
// stamping it over the handler's original message would destroy the only record of why the
|
|
80
|
+
// event failed in the first place.
|
|
81
|
+
await restore();
|
|
82
|
+
return {
|
|
83
|
+
kind: 'undeliverable',
|
|
84
|
+
message: 'This event was recorded before `billing_webhook_events.normalized` existed, so the normalized event it carried was never stored and cannot be rebuilt without re-verifying the gateway signature — which needs headers this table has never kept. Deliveries received since then replay normally; nothing can backfill this one.',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const event = {
|
|
88
|
+
id: gatewayEventId,
|
|
89
|
+
provider,
|
|
90
|
+
type,
|
|
91
|
+
data: row.normalized,
|
|
92
|
+
raw: row.payload ?? {},
|
|
93
|
+
};
|
|
94
|
+
await restore();
|
|
95
|
+
try {
|
|
96
|
+
await deps.process(event);
|
|
97
|
+
return { kind: 'processed' };
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return { kind: 'failed', message: messageOf(error, 'the handler threw again') };
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Every action reports a cause; an unreadable throw still gets a sentence rather than `{}`. */
|
|
105
|
+
function messageOf(error, fallback) {
|
|
106
|
+
if (error instanceof Error && error.message !== '')
|
|
107
|
+
return error.message;
|
|
108
|
+
if (typeof error === 'string' && error !== '')
|
|
109
|
+
return error;
|
|
110
|
+
return fallback;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../../src/dashboard/actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { type DashboardSession, type DashboardSessionUser } from './session_cookie.js';
|
|
2
|
+
/**
|
|
3
|
+
* Framework-light `dashboardAuth` mechanism for the payments dashboard — config resolution, the
|
|
4
|
+
* open-redirect guard, the login decision, and session verification. NO AdonisJS types leak in
|
|
5
|
+
* (mirroring {@link file://./handlers.ts handlers.ts}), so every branch is unit-testable against a
|
|
6
|
+
* plain object with no HTTP server. The provider (`providers/dashboard_provider.ts`) adapts an
|
|
7
|
+
* `HttpContext` to these functions and owns the cookie read/write via AdonisJS's native cookie API.
|
|
8
|
+
*
|
|
9
|
+
* When configured it gates BOTH the dashboard HTML shell (a full-page navigation redirected `302`
|
|
10
|
+
* to a server-rendered login page) and the JSON API (a plain `401`) behind a signed session cookie.
|
|
11
|
+
* When left unconfigured the dashboard behaves exactly as before (no login/logout routes, no
|
|
12
|
+
* session guard).
|
|
13
|
+
*
|
|
14
|
+
* Ported from `@adonis-agora/durable`'s `src/dashboard/auth.ts` with only the names changed, so the
|
|
15
|
+
* Agora consoles share ONE auth story rather than each inventing its own.
|
|
16
|
+
*/
|
|
17
|
+
/** Host hook validating submitted credentials from the built-in login page (Mode B — standalone). */
|
|
18
|
+
export type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
|
|
19
|
+
/**
|
|
20
|
+
* Host hook validating the HOST APP's own auth off the raw request (Mode A — SSO / "open from your
|
|
21
|
+
* app"). Receives the underlying platform request object (an AdonisJS `Request['request']`, i.e. the
|
|
22
|
+
* raw Node `IncomingMessage`) rather than an AdonisJS type, so this module stays framework-light and
|
|
23
|
+
* unit-testable with no `HttpContext` — the provider passes it through untouched. Mirrors
|
|
24
|
+
* `@adonis-agora/durable`'s `SessionHook` byte-for-byte (same two-mode split).
|
|
25
|
+
*/
|
|
26
|
+
export type SessionHook = (request: unknown) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
|
|
27
|
+
/**
|
|
28
|
+
* Author-facing `dashboardAuth` option on `config/payments_dashboard.ts`. Gates the dashboard behind
|
|
29
|
+
* a signed session cookie, minted by whichever mode(s) are configured:
|
|
30
|
+
*
|
|
31
|
+
* - Mode B (`login`) — the built-in server-rendered login page (`GET <path>/login`), for a
|
|
32
|
+
* standalone console with its own credentials.
|
|
33
|
+
* - Mode A (`session`) — `POST <path>/session`, for a host app that's already authenticated the
|
|
34
|
+
* operator itself and wants a one-shot "open the console" button (a one-shot "open the console" button).
|
|
35
|
+
*
|
|
36
|
+
* Both may be configured together; at least one is required. The bundled dashboard SPA is unaware of
|
|
37
|
+
* which mode(s) are active — it only reacts to a 401's `{ auth: { modes } }` body (see
|
|
38
|
+
* `payments-client.ts`).
|
|
39
|
+
*
|
|
40
|
+
* Each hook is just a plain function that may be `async` and close over
|
|
41
|
+
* `app.container`/services (import them at the top of `config/payments_dashboard.ts`), so there is
|
|
42
|
+
* nothing extra to build for the async case.
|
|
43
|
+
*/
|
|
44
|
+
export interface DashboardAuthOptions {
|
|
45
|
+
/** REQUIRED HMAC-SHA256 signing key. Missing/empty => boot error (fail closed). */
|
|
46
|
+
secret: string;
|
|
47
|
+
/** Cookie TTL as a duration string (`'8h'`, `'30m'`, `'7d'`). Default `'8h'`. */
|
|
48
|
+
ttl?: string;
|
|
49
|
+
/** Validates submitted username/password; return the session user, or `null` to deny. Thrown
|
|
50
|
+
* errors are treated as a denial (logged once, never surfaced to the client). May be async. */
|
|
51
|
+
login?: LoginHook;
|
|
52
|
+
/** Validates the host app's own auth off the raw request; return the session user, or `null` to
|
|
53
|
+
* deny. Thrown errors are treated as a denial. May be async. */
|
|
54
|
+
session?: SessionHook;
|
|
55
|
+
}
|
|
56
|
+
/** Which auth surface(s) are active — carried on an API 401's `{ auth: { modes } }` body so the SPA
|
|
57
|
+
* knows whether to offer the login page or just report "not authenticated". */
|
|
58
|
+
export type AuthMode = 'session' | 'login';
|
|
59
|
+
/** Resolved, validated `dashboardAuth` config shared by the guard, the login/session handlers, and
|
|
60
|
+
* the page. */
|
|
61
|
+
export interface ResolvedDashboardAuth {
|
|
62
|
+
secret: string;
|
|
63
|
+
ttlMs: number;
|
|
64
|
+
modes: AuthMode[];
|
|
65
|
+
login?: LoginHook;
|
|
66
|
+
session?: SessionHook;
|
|
67
|
+
}
|
|
68
|
+
/** Cookie name carrying the signed dashboard session. */
|
|
69
|
+
export declare const SESSION_COOKIE_NAME = "payments_dashboard_session";
|
|
70
|
+
/**
|
|
71
|
+
* Validate + resolve the `dashboardAuth` option. Returns `null` when unconfigured (today's
|
|
72
|
+
* unauthenticated behavior, unchanged). Throws at boot (fail closed) when configured but missing a
|
|
73
|
+
* secret, or neither `login` nor `session` is a function — the host learns immediately rather than
|
|
74
|
+
* shipping an un-mintable gate.
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveDashboardAuth(options: DashboardAuthOptions | undefined): ResolvedDashboardAuth | null;
|
|
77
|
+
/**
|
|
78
|
+
* Guard a client-supplied redirect target against an open redirect: it must be a same-origin,
|
|
79
|
+
* root-relative path (`/payments/foo`), never a protocol-relative (`//evil.com`) or absolute
|
|
80
|
+
* (`https://evil.com`) URL. Falls back to `fallback` (the dashboard's own `basePath`) for anything
|
|
81
|
+
* else, including a missing/non-string value.
|
|
82
|
+
*/
|
|
83
|
+
export declare function sanitizeReturnTo(candidate: unknown, fallback: string): string;
|
|
84
|
+
/**
|
|
85
|
+
* The framework-light outcome of a login attempt, mapped to HTTP by the provider:
|
|
86
|
+
* - `ok` → set `cookieValue` as the session cookie, reply `200 { redirectTo }`.
|
|
87
|
+
* - `bad-request` → `400` (body missing string `username`/`password`).
|
|
88
|
+
* - `unauthorized` → uniform `401 { error }` for EVERY credential failure (unknown user, wrong
|
|
89
|
+
* password, or a throwing hook) — no user-enumeration. `hookError` is set only when the hook
|
|
90
|
+
* threw, so the provider can warn-log it once without changing the client-visible response.
|
|
91
|
+
*/
|
|
92
|
+
export type LoginOutcome = {
|
|
93
|
+
kind: 'ok';
|
|
94
|
+
cookieValue: string;
|
|
95
|
+
redirectTo: string;
|
|
96
|
+
} | {
|
|
97
|
+
kind: 'bad-request';
|
|
98
|
+
message: string;
|
|
99
|
+
} | {
|
|
100
|
+
kind: 'unauthorized';
|
|
101
|
+
message: string;
|
|
102
|
+
hookError?: unknown;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Decide a login attempt end-to-end (validate body → run hook → mint cookie) with no HTTP types.
|
|
106
|
+
* On success returns the signed cookie value to set plus the sanitized `redirectTo`; the password
|
|
107
|
+
* is forwarded to the hook AS-IS (empty string when blank), so the hook alone decides whether a
|
|
108
|
+
* password is required. `404`-worthy (Mode B not configured) is the provider's call, not this
|
|
109
|
+
* function's — it only runs once the provider has confirmed `auth.login` exists.
|
|
110
|
+
*/
|
|
111
|
+
export declare function performLogin(auth: ResolvedDashboardAuth, body: unknown, basePath: string, now?: number): Promise<LoginOutcome>;
|
|
112
|
+
/**
|
|
113
|
+
* The framework-light outcome of a Mode A session-mint attempt (`POST <path>/session`), mapped to
|
|
114
|
+
* HTTP by the provider: `ok` → set the cookie, reply `204`; `unauthorized` → uniform `401` (the
|
|
115
|
+
* `session` hook returned/threw a denial).
|
|
116
|
+
*/
|
|
117
|
+
export type SessionOutcome = {
|
|
118
|
+
kind: 'ok';
|
|
119
|
+
cookieValue: string;
|
|
120
|
+
} | {
|
|
121
|
+
kind: 'unauthorized';
|
|
122
|
+
hookError?: unknown;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Decide a Mode A session-mint attempt end-to-end: run the host's `session` hook against the raw
|
|
126
|
+
* platform request, and mint the cookie on success. No body to validate — the hook reads whatever it
|
|
127
|
+
* needs directly off `request` (its own cookie/JWT/header).
|
|
128
|
+
*/
|
|
129
|
+
export declare function performSession(auth: ResolvedDashboardAuth, request: unknown, now?: number): Promise<SessionOutcome>;
|
|
130
|
+
/**
|
|
131
|
+
* Read + verify a session cookie value, returning the session or `null` when absent, tampered, or
|
|
132
|
+
* expired. Thin wrapper over {@link verifySessionCookie} so the provider never touches crypto.
|
|
133
|
+
*/
|
|
134
|
+
export declare function readSession(auth: ResolvedDashboardAuth, cookieValue: string | undefined, now?: number): DashboardSession | null;
|
|
135
|
+
export type { DashboardSession, DashboardSessionUser } from './session_cookie.js';
|
|
136
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/dashboard/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAG1B,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;GAcG;AAEH,qGAAqG;AACrG,MAAM,MAAM,SAAS,GAAG,CACtB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,GAAG,oBAAoB,GAAG,IAAI,CAAC;AAExE;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,GAAG,oBAAoB,GAAG,IAAI,CAAC;AAExE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;oGACgG;IAChG,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;qEACiE;IACjE,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED;gFACgF;AAChF,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAE3C;gBACgB;AAChB,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,+BAA+B,CAAC;AAoBhE;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,oBAAoB,GAAG,SAAS,GACxC,qBAAqB,GAAG,IAAI,CA0B9B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM7E;AAaD;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAoBnE;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,qBAAqB,EAC3B,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,CAuBvB;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAElD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,OAAO,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,cAAc,CAAC,CAezB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,qBAAqB,EAC3B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,GAAG,CAAC,EAAE,MAAM,GACX,gBAAgB,GAAG,IAAI,CAMzB;AAED,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { signSessionCookie, verifySessionCookie, } from './session_cookie.js';
|
|
2
|
+
/** Cookie name carrying the signed dashboard session. */
|
|
3
|
+
export const SESSION_COOKIE_NAME = 'payments_dashboard_session';
|
|
4
|
+
const DEFAULT_TTL = '8h';
|
|
5
|
+
const DEFAULT_TTL_MS = 8 * 60 * 60 * 1000;
|
|
6
|
+
const DURATION_UNITS = {
|
|
7
|
+
s: 1000,
|
|
8
|
+
m: 60 * 1000,
|
|
9
|
+
h: 60 * 60 * 1000,
|
|
10
|
+
d: 24 * 60 * 60 * 1000,
|
|
11
|
+
};
|
|
12
|
+
/** Parse a `'<number><s|m|h|d>'` duration to ms; falls back to the 8h default on a bad value. */
|
|
13
|
+
function durationToMs(ttl) {
|
|
14
|
+
const match = /^(\d+)([smhd])$/.exec(ttl.trim());
|
|
15
|
+
if (!match)
|
|
16
|
+
return DEFAULT_TTL_MS;
|
|
17
|
+
const unit = DURATION_UNITS[match[2] ?? ''];
|
|
18
|
+
if (unit === undefined)
|
|
19
|
+
return DEFAULT_TTL_MS;
|
|
20
|
+
return Number(match[1]) * unit;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validate + resolve the `dashboardAuth` option. Returns `null` when unconfigured (today's
|
|
24
|
+
* unauthenticated behavior, unchanged). Throws at boot (fail closed) when configured but missing a
|
|
25
|
+
* secret, or neither `login` nor `session` is a function — the host learns immediately rather than
|
|
26
|
+
* shipping an un-mintable gate.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveDashboardAuth(options) {
|
|
29
|
+
if (options === undefined)
|
|
30
|
+
return null;
|
|
31
|
+
if (typeof options.secret !== 'string' || options.secret === '') {
|
|
32
|
+
throw new Error('payments_dashboard: dashboardAuth.secret is required and must be a non-empty string ' +
|
|
33
|
+
'(HMAC-SHA256 signing key, 32+ bytes recommended). Failing closed.');
|
|
34
|
+
}
|
|
35
|
+
const hasLogin = typeof options.login === 'function';
|
|
36
|
+
const hasSession = typeof options.session === 'function';
|
|
37
|
+
if (!hasLogin && !hasSession) {
|
|
38
|
+
throw new Error('payments_dashboard: dashboardAuth needs at least one of `login` or `session` (a login hook or ' +
|
|
39
|
+
'a host-session hook).');
|
|
40
|
+
}
|
|
41
|
+
const modes = [];
|
|
42
|
+
if (hasSession)
|
|
43
|
+
modes.push('session');
|
|
44
|
+
if (hasLogin)
|
|
45
|
+
modes.push('login');
|
|
46
|
+
return {
|
|
47
|
+
secret: options.secret,
|
|
48
|
+
ttlMs: durationToMs(options.ttl ?? DEFAULT_TTL),
|
|
49
|
+
modes,
|
|
50
|
+
...(hasLogin ? { login: options.login } : {}),
|
|
51
|
+
...(hasSession ? { session: options.session } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Guard a client-supplied redirect target against an open redirect: it must be a same-origin,
|
|
56
|
+
* root-relative path (`/payments/foo`), never a protocol-relative (`//evil.com`) or absolute
|
|
57
|
+
* (`https://evil.com`) URL. Falls back to `fallback` (the dashboard's own `basePath`) for anything
|
|
58
|
+
* else, including a missing/non-string value.
|
|
59
|
+
*/
|
|
60
|
+
export function sanitizeReturnTo(candidate, fallback) {
|
|
61
|
+
if (typeof candidate !== 'string')
|
|
62
|
+
return fallback;
|
|
63
|
+
if (!candidate.startsWith('/'))
|
|
64
|
+
return fallback;
|
|
65
|
+
if (candidate.startsWith('//'))
|
|
66
|
+
return fallback;
|
|
67
|
+
if (candidate.includes('://'))
|
|
68
|
+
return fallback;
|
|
69
|
+
return candidate;
|
|
70
|
+
}
|
|
71
|
+
function isString(value) {
|
|
72
|
+
return typeof value === 'string';
|
|
73
|
+
}
|
|
74
|
+
const UNAUTHORIZED_MESSAGE = 'Invalid username or password.';
|
|
75
|
+
/**
|
|
76
|
+
* Run a host hook (`login` or `session`) defensively: a throw is treated as a denial (uniform
|
|
77
|
+
* failure) so a buggy hook never 500s the endpoint into a stack-trace leak. The thrown error is
|
|
78
|
+
* surfaced on the outcome (`hookError`) so the provider can warn-log it once, but it never reaches
|
|
79
|
+
* the client.
|
|
80
|
+
*/
|
|
81
|
+
async function runHook(hook) {
|
|
82
|
+
try {
|
|
83
|
+
return { user: (await hook()) ?? null };
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return { user: null, error };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Decide a login attempt end-to-end (validate body → run hook → mint cookie) with no HTTP types.
|
|
91
|
+
* On success returns the signed cookie value to set plus the sanitized `redirectTo`; the password
|
|
92
|
+
* is forwarded to the hook AS-IS (empty string when blank), so the hook alone decides whether a
|
|
93
|
+
* password is required. `404`-worthy (Mode B not configured) is the provider's call, not this
|
|
94
|
+
* function's — it only runs once the provider has confirmed `auth.login` exists.
|
|
95
|
+
*/
|
|
96
|
+
export async function performLogin(auth, body, basePath, now) {
|
|
97
|
+
const parsed = (body ?? {});
|
|
98
|
+
if (!isString(parsed.username) || !isString(parsed.password)) {
|
|
99
|
+
return { kind: 'bad-request', message: 'Body must include string `username` and `password`.' };
|
|
100
|
+
}
|
|
101
|
+
const login = auth.login;
|
|
102
|
+
if (!login) {
|
|
103
|
+
return { kind: 'unauthorized', message: UNAUTHORIZED_MESSAGE };
|
|
104
|
+
}
|
|
105
|
+
const { user, error } = await runHook(() => login(parsed.username, parsed.password));
|
|
106
|
+
if (!user) {
|
|
107
|
+
return error !== undefined
|
|
108
|
+
? { kind: 'unauthorized', message: UNAUTHORIZED_MESSAGE, hookError: error }
|
|
109
|
+
: { kind: 'unauthorized', message: UNAUTHORIZED_MESSAGE };
|
|
110
|
+
}
|
|
111
|
+
const cookieValue = signSessionCookie(user, {
|
|
112
|
+
secret: auth.secret,
|
|
113
|
+
ttlMs: auth.ttlMs,
|
|
114
|
+
...(now !== undefined ? { now } : {}),
|
|
115
|
+
});
|
|
116
|
+
return { kind: 'ok', cookieValue, redirectTo: sanitizeReturnTo(parsed.returnTo, basePath) };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Decide a Mode A session-mint attempt end-to-end: run the host's `session` hook against the raw
|
|
120
|
+
* platform request, and mint the cookie on success. No body to validate — the hook reads whatever it
|
|
121
|
+
* needs directly off `request` (its own cookie/JWT/header).
|
|
122
|
+
*/
|
|
123
|
+
export async function performSession(auth, request, now) {
|
|
124
|
+
const session = auth.session;
|
|
125
|
+
if (!session)
|
|
126
|
+
return { kind: 'unauthorized' };
|
|
127
|
+
const { user, error } = await runHook(() => session(request));
|
|
128
|
+
if (!user) {
|
|
129
|
+
return error !== undefined
|
|
130
|
+
? { kind: 'unauthorized', hookError: error }
|
|
131
|
+
: { kind: 'unauthorized' };
|
|
132
|
+
}
|
|
133
|
+
const cookieValue = signSessionCookie(user, {
|
|
134
|
+
secret: auth.secret,
|
|
135
|
+
ttlMs: auth.ttlMs,
|
|
136
|
+
...(now !== undefined ? { now } : {}),
|
|
137
|
+
});
|
|
138
|
+
return { kind: 'ok', cookieValue };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Read + verify a session cookie value, returning the session or `null` when absent, tampered, or
|
|
142
|
+
* expired. Thin wrapper over {@link verifySessionCookie} so the provider never touches crypto.
|
|
143
|
+
*/
|
|
144
|
+
export function readSession(auth, cookieValue, now) {
|
|
145
|
+
if (cookieValue === undefined || cookieValue === '')
|
|
146
|
+
return null;
|
|
147
|
+
return verifySessionCookie(cookieValue, {
|
|
148
|
+
secret: auth.secret,
|
|
149
|
+
...(now !== undefined ? { now } : {}),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/dashboard/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AA+E7B,yDAAyD;AACzD,MAAM,CAAC,MAAM,mBAAmB,GAAG,4BAA4B,CAAC;AAEhE,MAAM,WAAW,GAAG,IAAI,CAAC;AACzB,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAC1C,MAAM,cAAc,GAA2B;IAC7C,CAAC,EAAE,IAAI;IACP,CAAC,EAAE,EAAE,GAAG,IAAI;IACZ,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACjB,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;CACvB,CAAC;AAEF,iGAAiG;AACjG,SAAS,YAAY,CAAC,GAAW;IAC/B,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC,KAAK;QAAE,OAAO,cAAc,CAAC;IAClC,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,cAAc,CAAC;IAC9C,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAyC;IAEzC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CACb,sFAAsF;YACpF,mEAAmE,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC;IACrD,MAAM,UAAU,GAAG,OAAO,OAAO,CAAC,OAAO,KAAK,UAAU,CAAC;IACzD,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,gGAAgG;YAC9F,uBAAuB,CAC1B,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,IAAI,UAAU;QAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC;QAC/C,KAAK;QACL,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAkB,EAAE,QAAgB;IACnE,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IACnD,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChD,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChD,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC/C,OAAO,SAAS,CAAC;AACnB,CAAC;AASD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC;AACnC,CAAC;AAeD,MAAM,oBAAoB,GAAG,+BAA+B,CAAC;AAE7D;;;;;GAKG;AACH,KAAK,UAAU,OAAO,CACpB,IAA8E;IAE9E,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC/B,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAA2B,EAC3B,IAAa,EACb,QAAgB,EAChB,GAAY;IAEZ,MAAM,MAAM,GAAG,CAAC,IAAI,IAAI,EAAE,CAAc,CAAC;IACzC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,qDAAqD,EAAE,CAAC;IACjG,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;IACjE,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CACzC,KAAK,CAAC,MAAM,CAAC,QAAkB,EAAE,MAAM,CAAC,QAAkB,CAAC,CAC5D,CAAC;IACF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,KAAK,KAAK,SAAS;YACxB,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,KAAK,EAAE;YAC3E,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAC9D,CAAC;IACD,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,EAAE;QAC1C,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtC,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;AAC9F,CAAC;AAWD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAA2B,EAC3B,OAAgB,EAChB,GAAY;IAEZ,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAC9C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,KAAK,KAAK,SAAS;YACxB,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE;YAC5C,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAC/B,CAAC;IACD,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,EAAE;QAC1C,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtC,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,IAA2B,EAC3B,WAA+B,EAC/B,GAAY;IAEZ,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACjE,OAAO,mBAAmB,CAAC,WAAW,EAAE;QACtC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import { type DashboardAuthOptions, type ResolvedDashboardAuth } from './auth.js';
|
|
3
|
+
/**
|
|
4
|
+
* Authorization guard for the dashboard. Runs before every dashboard route
|
|
5
|
+
* (API + HTML). Return `true` to allow the request, `false` to deny it (the
|
|
6
|
+
* provider replies `403`). May be async (e.g. an auth lookup).
|
|
7
|
+
*
|
|
8
|
+
* It receives the AdonisJS {@link HttpContext}, so it can read the session,
|
|
9
|
+
* a bearer token, an IP allow-list, etc.
|
|
10
|
+
*/
|
|
11
|
+
export type AuthorizeHook = (ctx: HttpContext) => boolean | Promise<boolean>;
|
|
12
|
+
/** Shape of `config/payments_dashboard.ts`. */
|
|
13
|
+
export interface PaymentsDashboardConfig {
|
|
14
|
+
/**
|
|
15
|
+
* Master switch. When `false`, the provider registers no routes at all — the
|
|
16
|
+
* dashboard is completely absent. Defaults to `true`.
|
|
17
|
+
*/
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* URL prefix the dashboard + its API mount under. Defaults to `/payments`. The HTML is
|
|
21
|
+
* served at the prefix root; the JSON API lives under `<path>/api`.
|
|
22
|
+
*
|
|
23
|
+
* It shares that prefix with the machine endpoints — `POST /payments/webhook/:provider`
|
|
24
|
+
* and `GET /payments/client/status` — and does so safely, because every route this
|
|
25
|
+
* provider registers is an EXACT path (`/payments`, `/payments/assets/:file`,
|
|
26
|
+
* `/payments/api/…`, login/session/logout). There is no SPA catch-all, so the dashboard's
|
|
27
|
+
* `enforce` guard cannot reach a delivery it was never routed. If you ever add one, the
|
|
28
|
+
* webhook route is what it would swallow, and a 403'd webhook looks exactly like a gateway
|
|
29
|
+
* outage from the outside.
|
|
30
|
+
*/
|
|
31
|
+
path?: string;
|
|
32
|
+
/**
|
|
33
|
+
* ISO 4217 code the SPA formats the money columns with. Cents stay integer cents on the
|
|
34
|
+
* wire either way — this only decides how the edge renders them. Defaults to `'BRL'`.
|
|
35
|
+
*/
|
|
36
|
+
currency?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Per-request authorization guard. Defaults to {@link defaultAuthorize}:
|
|
39
|
+
* allow everything OUTSIDE production, and in production require a bearer
|
|
40
|
+
* token matching the `PAYMENTS_DASHBOARD_TOKEN` env var (deny if it is unset).
|
|
41
|
+
*/
|
|
42
|
+
authorize?: AuthorizeHook;
|
|
43
|
+
/**
|
|
44
|
+
* Optional built-in login screen. When set, the provider mounts a
|
|
45
|
+
* server-rendered `GET <path>/login` page plus `POST <path>/login` /
|
|
46
|
+
* `GET <path>/logout`, and stamps a session guard on the dashboard: an
|
|
47
|
+
* unauthenticated page navigation is redirected (`302`) to the login page and
|
|
48
|
+
* an unauthenticated API request gets `401`. The signed session cookie is
|
|
49
|
+
* minted only by the host's {@link DashboardAuthOptions.login} hook.
|
|
50
|
+
*
|
|
51
|
+
* This is ADDITIVE and composes WITH {@link authorize} (both must pass) — it
|
|
52
|
+
* does not replace it. Omit it entirely for no login/logout routes and no
|
|
53
|
+
* session guard. Missing `secret`/`login` fails closed at boot.
|
|
54
|
+
*/
|
|
55
|
+
dashboardAuth?: DashboardAuthOptions;
|
|
56
|
+
}
|
|
57
|
+
/** A fully-resolved config — every field present (defaults applied). */
|
|
58
|
+
export interface ResolvedPaymentsDashboardConfig {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
path: string;
|
|
61
|
+
currency: string;
|
|
62
|
+
authorize: AuthorizeHook;
|
|
63
|
+
/** Resolved built-in login config, or `null` when `dashboardAuth` is unconfigured. */
|
|
64
|
+
dashboardAuth: ResolvedDashboardAuth | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The default guard: open outside production; in production it requires a
|
|
68
|
+
* bearer token equal to `PAYMENTS_DASHBOARD_TOKEN`. If that env var is unset in
|
|
69
|
+
* production the dashboard is denied entirely (fail-closed) — you must opt in
|
|
70
|
+
* by setting a token or supplying your own {@link AuthorizeHook}.
|
|
71
|
+
*
|
|
72
|
+
* Identical posture to `@adonis-agora/durable`'s `defaultAuthorize`, down to the
|
|
73
|
+
* constant-time compare; only the env var name differs.
|
|
74
|
+
*/
|
|
75
|
+
export declare function defaultAuthorize(ctx: HttpContext): boolean;
|
|
76
|
+
/** Apply defaults to a partial config, producing a fully-resolved one. */
|
|
77
|
+
export declare function resolveConfig(config?: PaymentsDashboardConfig): ResolvedPaymentsDashboardConfig;
|
|
78
|
+
/** Identity helper giving `config/payments_dashboard.ts` full type-checking. */
|
|
79
|
+
export declare function defineConfig(config?: PaymentsDashboardConfig): PaymentsDashboardConfig;
|
|
80
|
+
//# sourceMappingURL=define_config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define_config.d.ts","sourceRoot":"","sources":["../../../src/dashboard/define_config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAE3B,MAAM,WAAW,CAAC;AAEnB;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE7E,+CAA+C;AAC/C,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,wEAAwE;AACxE,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,aAAa,CAAC;IACzB,sFAAsF;IACtF,aAAa,EAAE,qBAAqB,GAAG,IAAI,CAAC;CAC7C;AAyCD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAQ1D;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAC3B,MAAM,GAAE,uBAA4B,GACnC,+BAA+B,CAajC;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,MAAM,GAAE,uBAA4B,GAAG,uBAAuB,CAE1F"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { resolveDashboardAuth, } from './auth.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether the process is running in production. Mirrors how AdonisJS reads the
|
|
5
|
+
* environment without taking a hard dependency on its env service.
|
|
6
|
+
*/
|
|
7
|
+
function isProduction() {
|
|
8
|
+
return (process.env.NODE_ENV ?? '').toLowerCase() === 'production';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Extract a bearer token from an `Authorization: Bearer <token>` header, a
|
|
12
|
+
* `token` query-string param, or an `x-payments-token` header — whichever is
|
|
13
|
+
* present. Returns `undefined` when none is supplied.
|
|
14
|
+
*/
|
|
15
|
+
function readToken(ctx) {
|
|
16
|
+
const header = ctx.request.header('authorization');
|
|
17
|
+
if (header) {
|
|
18
|
+
const match = /^Bearer\s+(.+)$/i.exec(header);
|
|
19
|
+
if (match?.[1])
|
|
20
|
+
return match[1].trim();
|
|
21
|
+
}
|
|
22
|
+
const xHeader = ctx.request.header('x-payments-token');
|
|
23
|
+
if (xHeader)
|
|
24
|
+
return xHeader.trim();
|
|
25
|
+
const qs = ctx.request.qs().token;
|
|
26
|
+
if (typeof qs === 'string' && qs.length > 0)
|
|
27
|
+
return qs;
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Compare two secrets in constant time (guarding for equal byte-length first,
|
|
32
|
+
* since {@link timingSafeEqual} throws on a length mismatch). Returns `false`
|
|
33
|
+
* for any length difference, otherwise the timing-safe equality — so the token
|
|
34
|
+
* check leaks neither a match nor the token's length via response time.
|
|
35
|
+
*/
|
|
36
|
+
function secretsMatch(a, b) {
|
|
37
|
+
const bufA = Buffer.from(a);
|
|
38
|
+
const bufB = Buffer.from(b);
|
|
39
|
+
if (bufA.length !== bufB.length)
|
|
40
|
+
return false;
|
|
41
|
+
return timingSafeEqual(bufA, bufB);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The default guard: open outside production; in production it requires a
|
|
45
|
+
* bearer token equal to `PAYMENTS_DASHBOARD_TOKEN`. If that env var is unset in
|
|
46
|
+
* production the dashboard is denied entirely (fail-closed) — you must opt in
|
|
47
|
+
* by setting a token or supplying your own {@link AuthorizeHook}.
|
|
48
|
+
*
|
|
49
|
+
* Identical posture to `@adonis-agora/durable`'s `defaultAuthorize`, down to the
|
|
50
|
+
* constant-time compare; only the env var name differs.
|
|
51
|
+
*/
|
|
52
|
+
export function defaultAuthorize(ctx) {
|
|
53
|
+
if (!isProduction())
|
|
54
|
+
return true;
|
|
55
|
+
const expected = process.env.PAYMENTS_DASHBOARD_TOKEN;
|
|
56
|
+
if (!expected)
|
|
57
|
+
return false;
|
|
58
|
+
const provided = readToken(ctx);
|
|
59
|
+
if (provided === undefined)
|
|
60
|
+
return false;
|
|
61
|
+
// Constant-time compare to remove the timing side-channel from the token check.
|
|
62
|
+
return secretsMatch(provided, expected);
|
|
63
|
+
}
|
|
64
|
+
/** Apply defaults to a partial config, producing a fully-resolved one. */
|
|
65
|
+
export function resolveConfig(config = {}) {
|
|
66
|
+
const rawPath = config.path ?? '/payments';
|
|
67
|
+
// Normalize: ensure a single leading slash and no trailing slash (root stays '/').
|
|
68
|
+
const trimmed = `/${rawPath.replace(/^\/+/, '').replace(/\/+$/, '')}`;
|
|
69
|
+
return {
|
|
70
|
+
enabled: config.enabled ?? true,
|
|
71
|
+
path: trimmed === '/' ? '' : trimmed,
|
|
72
|
+
currency: config.currency ?? 'BRL',
|
|
73
|
+
authorize: config.authorize ?? defaultAuthorize,
|
|
74
|
+
// Validate + resolve now so a misconfigured secret/login fails closed at boot,
|
|
75
|
+
// not on the first login attempt. `null` when `dashboardAuth` is omitted.
|
|
76
|
+
dashboardAuth: resolveDashboardAuth(config.dashboardAuth),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Identity helper giving `config/payments_dashboard.ts` full type-checking. */
|
|
80
|
+
export function defineConfig(config = {}) {
|
|
81
|
+
return config;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=define_config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define_config.js","sourceRoot":"","sources":["../../../src/dashboard/define_config.ts"],"names":[],"mappings":"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"}
|