@aglyn/plugins-commerce 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +10 -0
- package/package.json +57 -0
- package/src/index.d.ts +38 -0
- package/src/index.js +38 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/account.d.ts +31 -0
- package/src/lib/components/account.js +645 -0
- package/src/lib/components/account.js.map +1 -0
- package/src/lib/components/cart.d.ts +38 -0
- package/src/lib/components/cart.js +851 -0
- package/src/lib/components/cart.js.map +1 -0
- package/src/lib/components/commerce-console-page.d.ts +32 -0
- package/src/lib/components/commerce-console-page.js +296 -0
- package/src/lib/components/commerce-console-page.js.map +1 -0
- package/src/lib/components/commerce-console-sections.d.ts +34 -0
- package/src/lib/components/commerce-console-sections.js +62 -0
- package/src/lib/components/commerce-console-sections.js.map +1 -0
- package/src/lib/components/console/catalog-organization-card.component.d.ts +14 -0
- package/src/lib/components/console/catalog-organization-card.component.js +1087 -0
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-analytics-card.component.d.ts +27 -0
- package/src/lib/components/console/commerce-analytics-card.component.js +310 -0
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-glance-card.component.d.ts +20 -0
- package/src/lib/components/console/commerce-glance-card.component.js +340 -0
- package/src/lib/components/console/commerce-glance-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-stat-tile.component.d.ts +37 -0
- package/src/lib/components/console/commerce-stat-tile.component.js +72 -0
- package/src/lib/components/console/commerce-stat-tile.component.js.map +1 -0
- package/src/lib/components/console/discounts-card.component.d.ts +14 -0
- package/src/lib/components/console/discounts-card.component.js +443 -0
- package/src/lib/components/console/discounts-card.component.js.map +1 -0
- package/src/lib/components/console/entitlement-gate.component.d.ts +76 -0
- package/src/lib/components/console/entitlement-gate.component.js +115 -0
- package/src/lib/components/console/entitlement-gate.component.js.map +1 -0
- package/src/lib/components/console/gift-cards-card.component.d.ts +29 -0
- package/src/lib/components/console/gift-cards-card.component.js +379 -0
- package/src/lib/components/console/gift-cards-card.component.js.map +1 -0
- package/src/lib/components/console/host-coupons-card.component.d.ts +14 -0
- package/src/lib/components/console/host-coupons-card.component.js +252 -0
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -0
- package/src/lib/components/console/host-orders-card.component.d.ts +13 -0
- package/src/lib/components/console/host-orders-card.component.js +861 -0
- package/src/lib/components/console/host-orders-card.component.js.map +1 -0
- package/src/lib/components/console/locations-card.component.d.ts +14 -0
- package/src/lib/components/console/locations-card.component.js +256 -0
- package/src/lib/components/console/locations-card.component.js.map +1 -0
- package/src/lib/components/console/member-posts-card.component.d.ts +8 -0
- package/src/lib/components/console/member-posts-card.component.js +283 -0
- package/src/lib/components/console/member-posts-card.component.js.map +1 -0
- package/src/lib/components/console/newest-site-users-card.component.d.ts +36 -0
- package/src/lib/components/console/newest-site-users-card.component.js +126 -0
- package/src/lib/components/console/newest-site-users-card.component.js.map +1 -0
- package/src/lib/components/console/order-detail-dialog.component.d.ts +28 -0
- package/src/lib/components/console/order-detail-dialog.component.js +976 -0
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -0
- package/src/lib/components/console/paid-media.d.ts +127 -0
- package/src/lib/components/console/paid-media.js +397 -0
- package/src/lib/components/console/paid-media.js.map +1 -0
- package/src/lib/components/console/payments-settings-card.component.d.ts +13 -0
- package/src/lib/components/console/payments-settings-card.component.js +295 -0
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -0
- package/src/lib/components/console/pos-page.component.d.ts +13 -0
- package/src/lib/components/console/pos-page.component.js +916 -0
- package/src/lib/components/console/pos-page.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.d.ts +32 -0
- package/src/lib/components/console/product-editor-dialog.component.js +1374 -0
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -0
- package/src/lib/components/console/products-hub-card.component.d.ts +16 -0
- package/src/lib/components/console/products-hub-card.component.js +1280 -0
- package/src/lib/components/console/products-hub-card.component.js.map +1 -0
- package/src/lib/components/console/products-hub-zone.component.d.ts +50 -0
- package/src/lib/components/console/products-hub-zone.component.js +200 -0
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -0
- package/src/lib/components/console/recovery-queue-card.component.d.ts +39 -0
- package/src/lib/components/console/recovery-queue-card.component.js +262 -0
- package/src/lib/components/console/recovery-queue-card.component.js.map +1 -0
- package/src/lib/components/console/registers-card.component.d.ts +21 -0
- package/src/lib/components/console/registers-card.component.js +296 -0
- package/src/lib/components/console/registers-card.component.js.map +1 -0
- package/src/lib/components/console/reservations-card.component.d.ts +14 -0
- package/src/lib/components/console/reservations-card.component.js +650 -0
- package/src/lib/components/console/reservations-card.component.js.map +1 -0
- package/src/lib/components/console/reviews-moderation-card.component.d.ts +8 -0
- package/src/lib/components/console/reviews-moderation-card.component.js +235 -0
- package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -0
- package/src/lib/components/console/shipping-settings-card.component.d.ts +38 -0
- package/src/lib/components/console/shipping-settings-card.component.js +560 -0
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -0
- package/src/lib/components/console/stock-movements-card.component.d.ts +31 -0
- package/src/lib/components/console/stock-movements-card.component.js +333 -0
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -0
- package/src/lib/components/console/store-settings-card.component.d.ts +14 -0
- package/src/lib/components/console/store-settings-card.component.js +300 -0
- package/src/lib/components/console/store-settings-card.component.js.map +1 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.d.ts +73 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js +408 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -0
- package/src/lib/components/console/suppliers-card.component.d.ts +8 -0
- package/src/lib/components/console/suppliers-card.component.js +328 -0
- package/src/lib/components/console/suppliers-card.component.js.map +1 -0
- package/src/lib/components/console/tax-settings-card.component.d.ts +14 -0
- package/src/lib/components/console/tax-settings-card.component.js +473 -0
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -0
- package/src/lib/components/gate.d.ts +39 -0
- package/src/lib/components/gate.js +202 -0
- package/src/lib/components/gate.js.map +1 -0
- package/src/lib/components/gated-video.d.ts +40 -0
- package/src/lib/components/gated-video.js +366 -0
- package/src/lib/components/gated-video.js.map +1 -0
- package/src/lib/components/member-feed.d.ts +32 -0
- package/src/lib/components/member-feed.js +198 -0
- package/src/lib/components/member-feed.js.map +1 -0
- package/src/lib/components/member-recovery.d.ts +35 -0
- package/src/lib/components/member-recovery.js +313 -0
- package/src/lib/components/member-recovery.js.map +1 -0
- package/src/lib/components/member-signin.d.ts +35 -0
- package/src/lib/components/member-signin.js +257 -0
- package/src/lib/components/member-signin.js.map +1 -0
- package/src/lib/components/member-signup.d.ts +34 -0
- package/src/lib/components/member-signup.js +287 -0
- package/src/lib/components/member-signup.js.map +1 -0
- package/src/lib/components/newsletter-signup.d.ts +39 -0
- package/src/lib/components/newsletter-signup.js +229 -0
- package/src/lib/components/newsletter-signup.js.map +1 -0
- package/src/lib/components/product-detail.d.ts +38 -0
- package/src/lib/components/product-detail.js +1022 -0
- package/src/lib/components/product-detail.js.map +1 -0
- package/src/lib/components/product-grid.d.ts +67 -0
- package/src/lib/components/product-grid.js +909 -0
- package/src/lib/components/product-grid.js.map +1 -0
- package/src/lib/components/product-reviews.d.ts +32 -0
- package/src/lib/components/product-reviews.js +349 -0
- package/src/lib/components/product-reviews.js.map +1 -0
- package/src/lib/components/related-products.d.ts +33 -0
- package/src/lib/components/related-products.js +232 -0
- package/src/lib/components/related-products.js.map +1 -0
- package/src/lib/components/reservation-widget.d.ts +33 -0
- package/src/lib/components/reservation-widget.js +418 -0
- package/src/lib/components/reservation-widget.js.map +1 -0
- package/src/lib/components/storefront-payment-element-fallback.d.ts +18 -0
- package/src/lib/components/storefront-payment-element-fallback.js +51 -0
- package/src/lib/components/storefront-payment-element-fallback.js.map +1 -0
- package/src/lib/components/storefront-payment-element.d.ts +78 -0
- package/src/lib/components/storefront-payment-element.js +156 -0
- package/src/lib/components/storefront-payment-element.js.map +1 -0
- package/src/lib/components/wishlist.d.ts +46 -0
- package/src/lib/components/wishlist.js +304 -0
- package/src/lib/components/wishlist.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +18 -0
- package/src/lib/constants/bundle-common.js +18 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/product-filters.d.ts +35 -0
- package/src/lib/constants/product-filters.js +109 -0
- package/src/lib/constants/product-filters.js.map +1 -0
- package/src/lib/model/commerce-cart.d.ts +52 -0
- package/src/lib/model/commerce-cart.js +102 -0
- package/src/lib/model/commerce-cart.js.map +1 -0
- package/src/lib/model/commerce-connect-transfer.d.ts +243 -0
- package/src/lib/model/commerce-connect-transfer.js +231 -0
- package/src/lib/model/commerce-connect-transfer.js.map +1 -0
- package/src/lib/model/commerce-discounts.d.ts +164 -0
- package/src/lib/model/commerce-discounts.js +231 -0
- package/src/lib/model/commerce-discounts.js.map +1 -0
- package/src/lib/model/commerce-dispute.d.ts +124 -0
- package/src/lib/model/commerce-dispute.js +225 -0
- package/src/lib/model/commerce-dispute.js.map +1 -0
- package/src/lib/model/commerce-gift-cards.d.ts +107 -0
- package/src/lib/model/commerce-gift-cards.js +121 -0
- package/src/lib/model/commerce-gift-cards.js.map +1 -0
- package/src/lib/model/commerce-io.d.ts +36 -0
- package/src/lib/model/commerce-io.js +245 -0
- package/src/lib/model/commerce-io.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +1040 -0
- package/src/lib/model/commerce-orders.js +1022 -0
- package/src/lib/model/commerce-orders.js.map +1 -0
- package/src/lib/model/commerce-promotions.d.ts +188 -0
- package/src/lib/model/commerce-promotions.js +199 -0
- package/src/lib/model/commerce-promotions.js.map +1 -0
- package/src/lib/model/commerce-reservations.d.ts +175 -0
- package/src/lib/model/commerce-reservations.js +157 -0
- package/src/lib/model/commerce-reservations.js.map +1 -0
- package/src/lib/model/commerce-shipping.d.ts +316 -0
- package/src/lib/model/commerce-shipping.js +484 -0
- package/src/lib/model/commerce-shipping.js.map +1 -0
- package/src/lib/model/commerce-stock-holds.d.ts +199 -0
- package/src/lib/model/commerce-stock-holds.js +190 -0
- package/src/lib/model/commerce-stock-holds.js.map +1 -0
- package/src/lib/model/commerce-subscription-status.d.ts +67 -0
- package/src/lib/model/commerce-subscription-status.js +76 -0
- package/src/lib/model/commerce-subscription-status.js.map +1 -0
- package/src/lib/model/commerce-tax-decision.d.ts +222 -0
- package/src/lib/model/commerce-tax-decision.js +165 -0
- package/src/lib/model/commerce-tax-decision.js.map +1 -0
- package/src/lib/model/commerce-tax.d.ts +176 -0
- package/src/lib/model/commerce-tax.js +92 -0
- package/src/lib/model/commerce-tax.js.map +1 -0
- package/src/lib/model/commerce.d.ts +551 -0
- package/src/lib/model/commerce.js +739 -0
- package/src/lib/model/commerce.js.map +1 -0
- package/src/lib/model/index.d.ts +40 -0
- package/src/lib/model/index.js +40 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/order-figures.d.ts +48 -0
- package/src/lib/model/order-figures.js +74 -0
- package/src/lib/model/order-figures.js.map +1 -0
- package/src/lib/model/plugin-permissions.d.ts +23 -0
- package/src/lib/model/plugin-permissions.js +34 -0
- package/src/lib/model/plugin-permissions.js.map +1 -0
- package/src/lib/model/purchase-analytics.d.ts +186 -0
- package/src/lib/model/purchase-analytics.js +216 -0
- package/src/lib/model/purchase-analytics.js.map +1 -0
- package/src/lib/plugin-config.d.ts +62 -0
- package/src/lib/plugin-config.js +78 -0
- package/src/lib/plugin-config.js.map +1 -0
- package/src/lib/plugin.d.ts +28 -0
- package/src/lib/plugin.js +147 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/billing-webhook.d.ts +26 -0
- package/src/lib/server/billing-webhook.js +4011 -0
- package/src/lib/server/billing-webhook.js.map +1 -0
- package/src/lib/server/cancel-order.d.ts +87 -0
- package/src/lib/server/cancel-order.js +368 -0
- package/src/lib/server/cancel-order.js.map +1 -0
- package/src/lib/server/cart-checkout.d.ts +25 -0
- package/src/lib/server/cart-checkout.js +976 -0
- package/src/lib/server/cart-checkout.js.map +1 -0
- package/src/lib/server/cart-cookie.d.ts +32 -0
- package/src/lib/server/cart-cookie.js +77 -0
- package/src/lib/server/cart-cookie.js.map +1 -0
- package/src/lib/server/cart.d.ts +34 -0
- package/src/lib/server/cart.js +181 -0
- package/src/lib/server/cart.js.map +1 -0
- package/src/lib/server/catalog.d.ts +127 -0
- package/src/lib/server/catalog.js +277 -0
- package/src/lib/server/catalog.js.map +1 -0
- package/src/lib/server/checkout.d.ts +36 -0
- package/src/lib/server/checkout.js +915 -0
- package/src/lib/server/checkout.js.map +1 -0
- package/src/lib/server/connect.d.ts +26 -0
- package/src/lib/server/connect.js +238 -0
- package/src/lib/server/connect.js.map +1 -0
- package/src/lib/server/contact-refund.d.ts +117 -0
- package/src/lib/server/contact-refund.js +196 -0
- package/src/lib/server/contact-refund.js.map +1 -0
- package/src/lib/server/download.d.ts +39 -0
- package/src/lib/server/download.js +206 -0
- package/src/lib/server/download.js.map +1 -0
- package/src/lib/server/draft-order.d.ts +26 -0
- package/src/lib/server/draft-order.js +706 -0
- package/src/lib/server/draft-order.js.map +1 -0
- package/src/lib/server/feed.d.ts +24 -0
- package/src/lib/server/feed.js +59 -0
- package/src/lib/server/feed.js.map +1 -0
- package/src/lib/server/fulfill-order.d.ts +97 -0
- package/src/lib/server/fulfill-order.js +235 -0
- package/src/lib/server/fulfill-order.js.map +1 -0
- package/src/lib/server/gate.d.ts +36 -0
- package/src/lib/server/gate.js +112 -0
- package/src/lib/server/gate.js.map +1 -0
- package/src/lib/server/gift-cards.d.ts +42 -0
- package/src/lib/server/gift-cards.js +219 -0
- package/src/lib/server/gift-cards.js.map +1 -0
- package/src/lib/server/low-stock.d.ts +49 -0
- package/src/lib/server/low-stock.js +60 -0
- package/src/lib/server/low-stock.js.map +1 -0
- package/src/lib/server/manual-tax-rate.d.ts +82 -0
- package/src/lib/server/manual-tax-rate.js +113 -0
- package/src/lib/server/manual-tax-rate.js.map +1 -0
- package/src/lib/server/member-feed.d.ts +24 -0
- package/src/lib/server/member-feed.js +72 -0
- package/src/lib/server/member-feed.js.map +1 -0
- package/src/lib/server/member-name-search.d.ts +37 -0
- package/src/lib/server/member-name-search.js +42 -0
- package/src/lib/server/member-name-search.js.map +1 -0
- package/src/lib/server/member-post.d.ts +23 -0
- package/src/lib/server/member-post.js +166 -0
- package/src/lib/server/member-post.js.map +1 -0
- package/src/lib/server/membership-account.d.ts +37 -0
- package/src/lib/server/membership-account.js +148 -0
- package/src/lib/server/membership-account.js.map +1 -0
- package/src/lib/server/membership-admin-password.d.ts +33 -0
- package/src/lib/server/membership-admin-password.js +202 -0
- package/src/lib/server/membership-admin-password.js.map +1 -0
- package/src/lib/server/membership-content.d.ts +23 -0
- package/src/lib/server/membership-content.js +105 -0
- package/src/lib/server/membership-content.js.map +1 -0
- package/src/lib/server/membership-login.d.ts +19 -0
- package/src/lib/server/membership-login.js +137 -0
- package/src/lib/server/membership-login.js.map +1 -0
- package/src/lib/server/membership-logout.d.ts +19 -0
- package/src/lib/server/membership-logout.js +35 -0
- package/src/lib/server/membership-logout.js.map +1 -0
- package/src/lib/server/membership-recover.d.ts +60 -0
- package/src/lib/server/membership-recover.js +244 -0
- package/src/lib/server/membership-recover.js.map +1 -0
- package/src/lib/server/membership-register.d.ts +22 -0
- package/src/lib/server/membership-register.js +241 -0
- package/src/lib/server/membership-register.js.map +1 -0
- package/src/lib/server/membership-reset.d.ts +26 -0
- package/src/lib/server/membership-reset.js +88 -0
- package/src/lib/server/membership-reset.js.map +1 -0
- package/src/lib/server/membership-wishlist.d.ts +23 -0
- package/src/lib/server/membership-wishlist.js +89 -0
- package/src/lib/server/membership-wishlist.js.map +1 -0
- package/src/lib/server/membership.d.ts +96 -0
- package/src/lib/server/membership.js +222 -0
- package/src/lib/server/membership.js.map +1 -0
- package/src/lib/server/native-checkout.d.ts +170 -0
- package/src/lib/server/native-checkout.js +193 -0
- package/src/lib/server/native-checkout.js.map +1 -0
- package/src/lib/server/newsletter.d.ts +23 -0
- package/src/lib/server/newsletter.js +323 -0
- package/src/lib/server/newsletter.js.map +1 -0
- package/src/lib/server/notify-restock.d.ts +22 -0
- package/src/lib/server/notify-restock.js +84 -0
- package/src/lib/server/notify-restock.js.map +1 -0
- package/src/lib/server/order-analytics.d.ts +134 -0
- package/src/lib/server/order-analytics.js +263 -0
- package/src/lib/server/order-analytics.js.map +1 -0
- package/src/lib/server/order-figures.d.ts +33 -0
- package/src/lib/server/order-figures.js +220 -0
- package/src/lib/server/order-figures.js.map +1 -0
- package/src/lib/server/paid-media-delivery.d.ts +173 -0
- package/src/lib/server/paid-media-delivery.js +212 -0
- package/src/lib/server/paid-media-delivery.js.map +1 -0
- package/src/lib/server/pos-order.d.ts +54 -0
- package/src/lib/server/pos-order.js +1137 -0
- package/src/lib/server/pos-order.js.map +1 -0
- package/src/lib/server/process-abandoned.d.ts +54 -0
- package/src/lib/server/process-abandoned.js +222 -0
- package/src/lib/server/process-abandoned.js.map +1 -0
- package/src/lib/server/process-restock.d.ts +42 -0
- package/src/lib/server/process-restock.js +219 -0
- package/src/lib/server/process-restock.js.map +1 -0
- package/src/lib/server/product-card.d.ts +29 -0
- package/src/lib/server/product-card.js +54 -0
- package/src/lib/server/product-card.js.map +1 -0
- package/src/lib/server/product.d.ts +70 -0
- package/src/lib/server/product.js +105 -0
- package/src/lib/server/product.js.map +1 -0
- package/src/lib/server/promotion-hold.d.ts +144 -0
- package/src/lib/server/promotion-hold.js +174 -0
- package/src/lib/server/promotion-hold.js.map +1 -0
- package/src/lib/server/reconcile-stock.d.ts +106 -0
- package/src/lib/server/reconcile-stock.js +349 -0
- package/src/lib/server/reconcile-stock.js.map +1 -0
- package/src/lib/server/refund.d.ts +45 -0
- package/src/lib/server/refund.js +607 -0
- package/src/lib/server/refund.js.map +1 -0
- package/src/lib/server/related.d.ts +24 -0
- package/src/lib/server/related.js +104 -0
- package/src/lib/server/related.js.map +1 -0
- package/src/lib/server/reservation-availability.d.ts +23 -0
- package/src/lib/server/reservation-availability.js +111 -0
- package/src/lib/server/reservation-availability.js.map +1 -0
- package/src/lib/server/reserve-stock.d.ts +156 -0
- package/src/lib/server/reserve-stock.js +146 -0
- package/src/lib/server/reserve-stock.js.map +1 -0
- package/src/lib/server/reserve.d.ts +25 -0
- package/src/lib/server/reserve.js +426 -0
- package/src/lib/server/reserve.js.map +1 -0
- package/src/lib/server/restock-flag.d.ts +202 -0
- package/src/lib/server/restock-flag.js +449 -0
- package/src/lib/server/restock-flag.js.map +1 -0
- package/src/lib/server/reviews.d.ts +51 -0
- package/src/lib/server/reviews.js +132 -0
- package/src/lib/server/reviews.js.map +1 -0
- package/src/lib/server/site-page-enricher.d.ts +42 -0
- package/src/lib/server/site-page-enricher.js +149 -0
- package/src/lib/server/site-page-enricher.js.map +1 -0
- package/src/lib/server/site-page-resolver.d.ts +26 -0
- package/src/lib/server/site-page-resolver.js +223 -0
- package/src/lib/server/site-page-resolver.js.map +1 -0
- package/src/lib/server/stock-hold.d.ts +127 -0
- package/src/lib/server/stock-hold.js +258 -0
- package/src/lib/server/stock-hold.js.map +1 -0
- package/src/lib/server/storefront-tax-record.d.ts +22 -0
- package/src/lib/server/storefront-tax-record.js +218 -0
- package/src/lib/server/storefront-tax-record.js.map +1 -0
- package/src/lib/server/storefront-tax.d.ts +197 -0
- package/src/lib/server/storefront-tax.js +245 -0
- package/src/lib/server/storefront-tax.js.map +1 -0
- package/src/lib/server/stream.d.ts +25 -0
- package/src/lib/server/stream.js +191 -0
- package/src/lib/server/stream.js.map +1 -0
- package/src/lib/server/subscription-portal.d.ts +24 -0
- package/src/lib/server/subscription-portal.js +91 -0
- package/src/lib/server/subscription-portal.js.map +1 -0
- package/src/lib/server/supplier-outbox.d.ts +190 -0
- package/src/lib/server/supplier-outbox.js +342 -0
- package/src/lib/server/supplier-outbox.js.map +1 -0
- package/src/lib/server/supplier-update.d.ts +23 -0
- package/src/lib/server/supplier-update.js +289 -0
- package/src/lib/server/supplier-update.js.map +1 -0
- package/src/lib/server/tax-profile.d.ts +29 -0
- package/src/lib/server/tax-profile.js +40 -0
- package/src/lib/server/tax-profile.js.map +1 -0
- package/src/lib/server.d.ts +32 -0
- package/src/lib/server.js +311 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +27 -0
- package/src/lib/site.js +134 -0
- package/src/lib/site.js.map +1 -0
- package/src/lib/utils/escape-html.d.ts +41 -0
- package/src/lib/utils/escape-html.js +41 -0
- package/src/lib/utils/escape-html.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
- package/src/lib/utils/member-continue.d.ts +45 -0
- package/src/lib/utils/member-continue.js +49 -0
- package/src/lib/utils/member-continue.js.map +1 -0
- package/src/lib/utils/use-storefront-purchase-event.d.ts +60 -0
- package/src/lib/utils/use-storefront-purchase-event.js +142 -0
- package/src/lib/utils/use-storefront-purchase-event.js.map +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/** Site member sign-in (AGL-109); sets the session cookie on success. */
|
|
19
|
+
export declare const membershipLoginHandler: PluginApiHandler;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
17
|
+
import { emitHostEvent } from "@aglyn/tenant-runtime";
|
|
18
|
+
import { readCartId } from "./cart-cookie.js";
|
|
19
|
+
import { MEMBER_SUSPENDED_ERROR, mintMemberSession, setMemberCookie, verifyMemberPassword } from "./membership.js";
|
|
20
|
+
import { NO_CLIENT_ADDRESS_BUCKET, readClientIp } from "@aglyn/aglyn/app-utils/request-ip";
|
|
21
|
+
// Best-effort per-instance brute-force damper (mirrors AGL-87 unlock).
|
|
22
|
+
const attemptsByIp = new Map();
|
|
23
|
+
const WINDOW_MS = 60000;
|
|
24
|
+
const MAX_ATTEMPTS = 10;
|
|
25
|
+
/** Site member sign-in (AGL-109); sets the session cookie on success. */ export const membershipLoginHandler = async (req, res)=>{
|
|
26
|
+
var _ref, _ref1, _ref2, _readClientIp, _attemptsByIp_get;
|
|
27
|
+
var _req_body, _req_body1, _req_body2, _req_socket;
|
|
28
|
+
if (req.method !== 'POST') {
|
|
29
|
+
return res.status(405).json({
|
|
30
|
+
error: 'Method not allowed'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
34
|
+
const email = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.email) != null ? _ref1 : '').trim().toLowerCase();
|
|
35
|
+
const password = String((_ref2 = (_req_body2 = req.body) == null ? void 0 : _req_body2.password) != null ? _ref2 : '');
|
|
36
|
+
if (!hostId || !email || !password) {
|
|
37
|
+
return res.status(400).json({
|
|
38
|
+
error: 'Invalid request'
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Keeps counting under the no-address bucket rather than being skipped: this
|
|
42
|
+
// damper guards a PASSWORD, and one that stops counting is a password anyone
|
|
43
|
+
// can grind. On a deployment that cannot name its callers the whole site
|
|
44
|
+
// shares one attempt budget, which is the direction to be wrong in here.
|
|
45
|
+
const ip = (_readClientIp = readClientIp(req.headers, {
|
|
46
|
+
remoteAddress: (_req_socket = req.socket) == null ? void 0 : _req_socket.remoteAddress
|
|
47
|
+
})) != null ? _readClientIp : NO_CLIENT_ADDRESS_BUCKET;
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const attempts = ((_attemptsByIp_get = attemptsByIp.get(ip)) != null ? _attemptsByIp_get : []).filter((at)=>now - at < WINDOW_MS);
|
|
50
|
+
attempts.push(now);
|
|
51
|
+
attemptsByIp.set(ip, attempts);
|
|
52
|
+
if (attempts.length > MAX_ATTEMPTS) {
|
|
53
|
+
return res.status(429).json({
|
|
54
|
+
error: 'Too many attempts'
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
59
|
+
const membersQuery = await firestore.collection('hosts').doc(hostId).collection('siteMembers').where('email', '==', email).limit(1).get();
|
|
60
|
+
const memberDoc = membersQuery.docs[0];
|
|
61
|
+
if (!memberDoc || !verifyMemberPassword(password, memberDoc.get('passwordScrypt'))) {
|
|
62
|
+
return res.status(401).json({
|
|
63
|
+
error: 'Wrong email or password'
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// Suspension gate (AGL-546): console-suspended members cannot sign in.
|
|
67
|
+
// Checked after the password so the message never leaks account
|
|
68
|
+
// existence to guessers who don't hold the credentials.
|
|
69
|
+
if (memberDoc.get('suspended') === true) {
|
|
70
|
+
return res.status(401).json({
|
|
71
|
+
error: MEMBER_SUSPENDED_ERROR
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Event trigger (AGL-128/148).
|
|
75
|
+
await emitHostEvent(hostId, 'memberSignIn', {
|
|
76
|
+
email
|
|
77
|
+
});
|
|
78
|
+
// Cart linkage (AGL-294): stamp the guest cart with the member so
|
|
79
|
+
// abandoned-cart and analytics can attribute it.
|
|
80
|
+
//
|
|
81
|
+
// AGL-1763: `update()`, never `set(..., { merge: true })`. `cartId` is the
|
|
82
|
+
// `aglyn_cart_{hostId}` cookie verbatim — the one value on this request the
|
|
83
|
+
// client chooses — and a merge-set against a missing path CREATES the
|
|
84
|
+
// document. So a member could mint `carts/{anything}` at will, one document
|
|
85
|
+
// per sign-in, each holding a `customerId` and nothing else: no `lines`, no
|
|
86
|
+
// `createdAtMs`, no `updatedAtMs`. Inert to read — `cart.ts` treats a
|
|
87
|
+
// missing `lines` as `[]` — but real storage on the merchant's Firestore
|
|
88
|
+
// and real weight in the per-host document count, growing without bound and
|
|
89
|
+
// belonging to no basket anyone ever filled.
|
|
90
|
+
//
|
|
91
|
+
// Nor is the cookie confined to one document. `CollectionReference.doc()`
|
|
92
|
+
// appends a SLASH-SEPARATED path and refuses it only when the component
|
|
93
|
+
// count comes out odd, so a cookie of `a/b/c` wrote to `carts/a/b/c` — a
|
|
94
|
+
// document at a nesting of the caller's choosing.
|
|
95
|
+
//
|
|
96
|
+
// Refusing loses nothing, which is why this is a plain refusal rather than
|
|
97
|
+
// AGL-1760's refuse-and-record: a cart that does not exist holds no lines,
|
|
98
|
+
// so there is no basket to attribute and no work to strand. `cart.ts` is
|
|
99
|
+
// what creates a cart — on the first POST that puts something in it, with
|
|
100
|
+
// the whole document written. This only ever stamps one.
|
|
101
|
+
//
|
|
102
|
+
// AGL-1769 moved the cookie's name and its validity rule into
|
|
103
|
+
// `cart-cookie.ts`, so a slash-bearing value never reaches `.doc()` from
|
|
104
|
+
// here at all. `update()` stays: the rule bounds the value to ONE document
|
|
105
|
+
// id, and whether THAT document exists is a separate question only the
|
|
106
|
+
// write can answer.
|
|
107
|
+
const cartId = readCartId(req.cookies, hostId);
|
|
108
|
+
if (cartId) {
|
|
109
|
+
try {
|
|
110
|
+
await firestore.collection('hosts').doc(hostId).collection('carts').doc(cartId).update({
|
|
111
|
+
customerId: memberDoc.id
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// Absent cart, malformed cookie, or a Firestore failure — none of them
|
|
115
|
+
// may fail a sign-in that has already succeeded, so this stays
|
|
116
|
+
// swallowed as it was. It is no longer SILENT, though: the old
|
|
117
|
+
// `.catch(() => undefined)` could not tell a missing cart from an
|
|
118
|
+
// outage. And the guard is a `try` rather than a `.catch()` because
|
|
119
|
+
// `.doc()` throws SYNCHRONOUSLY on an odd-component path, outside the
|
|
120
|
+
// promise the old handler covered — a mangled cookie used to 500 the
|
|
121
|
+
// member's own sign-in.
|
|
122
|
+
console.error('Cart linkage failed', hostId, error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
setMemberCookie(res, hostId, mintMemberSession(hostId, memberDoc.id));
|
|
126
|
+
return res.status(200).json({
|
|
127
|
+
ok: true
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(error);
|
|
131
|
+
return res.status(500).json({
|
|
132
|
+
error: 'Sign-in failed'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=membership-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/membership-login.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { emitHostEvent } from '@aglyn/tenant-runtime'\nimport { readCartId } from './cart-cookie'\nimport {\n MEMBER_SUSPENDED_ERROR,\n mintMemberSession,\n setMemberCookie,\n verifyMemberPassword,\n} from './membership'\nimport {\n NO_CLIENT_ADDRESS_BUCKET,\n readClientIp,\n} from '@aglyn/aglyn/app-utils/request-ip'\n\n// Best-effort per-instance brute-force damper (mirrors AGL-87 unlock).\nconst attemptsByIp = new Map<string, number[]>()\nconst WINDOW_MS = 60_000\nconst MAX_ATTEMPTS = 10\n\n/** Site member sign-in (AGL-109); sets the session cookie on success. */\nexport const membershipLoginHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.body?.hostId ?? '')\n const email = String(req.body?.email ?? '')\n .trim()\n .toLowerCase()\n const password = String(req.body?.password ?? '')\n if (!hostId || !email || !password) {\n return res.status(400).json({ error: 'Invalid request' })\n }\n // Keeps counting under the no-address bucket rather than being skipped: this\n // damper guards a PASSWORD, and one that stops counting is a password anyone\n // can grind. On a deployment that cannot name its callers the whole site\n // shares one attempt budget, which is the direction to be wrong in here.\n const ip =\n readClientIp(req.headers, { remoteAddress: req.socket?.remoteAddress }) ??\n NO_CLIENT_ADDRESS_BUCKET\n const now = Date.now()\n const attempts = (attemptsByIp.get(ip) ?? []).filter(\n (at) => now - at < WINDOW_MS,\n )\n attempts.push(now)\n attemptsByIp.set(ip, attempts)\n if (attempts.length > MAX_ATTEMPTS) {\n return res.status(429).json({ error: 'Too many attempts' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const membersQuery = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('siteMembers')\n .where('email', '==', email)\n .limit(1)\n .get()\n const memberDoc = membersQuery.docs[0]\n if (\n !memberDoc ||\n !verifyMemberPassword(password, memberDoc.get('passwordScrypt'))\n ) {\n return res.status(401).json({ error: 'Wrong email or password' })\n }\n // Suspension gate (AGL-546): console-suspended members cannot sign in.\n // Checked after the password so the message never leaks account\n // existence to guessers who don't hold the credentials.\n if (memberDoc.get('suspended') === true) {\n return res.status(401).json({ error: MEMBER_SUSPENDED_ERROR })\n }\n // Event trigger (AGL-128/148).\n await emitHostEvent(hostId, 'memberSignIn', { email })\n // Cart linkage (AGL-294): stamp the guest cart with the member so\n // abandoned-cart and analytics can attribute it.\n //\n // AGL-1763: `update()`, never `set(..., { merge: true })`. `cartId` is the\n // `aglyn_cart_{hostId}` cookie verbatim — the one value on this request the\n // client chooses — and a merge-set against a missing path CREATES the\n // document. So a member could mint `carts/{anything}` at will, one document\n // per sign-in, each holding a `customerId` and nothing else: no `lines`, no\n // `createdAtMs`, no `updatedAtMs`. Inert to read — `cart.ts` treats a\n // missing `lines` as `[]` — but real storage on the merchant's Firestore\n // and real weight in the per-host document count, growing without bound and\n // belonging to no basket anyone ever filled.\n //\n // Nor is the cookie confined to one document. `CollectionReference.doc()`\n // appends a SLASH-SEPARATED path and refuses it only when the component\n // count comes out odd, so a cookie of `a/b/c` wrote to `carts/a/b/c` — a\n // document at a nesting of the caller's choosing.\n //\n // Refusing loses nothing, which is why this is a plain refusal rather than\n // AGL-1760's refuse-and-record: a cart that does not exist holds no lines,\n // so there is no basket to attribute and no work to strand. `cart.ts` is\n // what creates a cart — on the first POST that puts something in it, with\n // the whole document written. This only ever stamps one.\n //\n // AGL-1769 moved the cookie's name and its validity rule into\n // `cart-cookie.ts`, so a slash-bearing value never reaches `.doc()` from\n // here at all. `update()` stays: the rule bounds the value to ONE document\n // id, and whether THAT document exists is a separate question only the\n // write can answer.\n const cartId = readCartId(req.cookies, hostId)\n if (cartId) {\n try {\n await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('carts')\n .doc(cartId)\n .update({ customerId: memberDoc.id })\n } catch (error) {\n // Absent cart, malformed cookie, or a Firestore failure — none of them\n // may fail a sign-in that has already succeeded, so this stays\n // swallowed as it was. It is no longer SILENT, though: the old\n // `.catch(() => undefined)` could not tell a missing cart from an\n // outage. And the guard is a `try` rather than a `.catch()` because\n // `.doc()` throws SYNCHRONOUSLY on an odd-component path, outside the\n // promise the old handler covered — a mangled cookie used to 500 the\n // member's own sign-in.\n console.error('Cart linkage failed', hostId, error)\n }\n }\n setMemberCookie(res, hostId, mintMemberSession(hostId, memberDoc.id))\n return res.status(200).json({ ok: true })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Sign-in failed' })\n }\n}\n"],"names":["firebaseAdmin","emitHostEvent","readCartId","MEMBER_SUSPENDED_ERROR","mintMemberSession","setMemberCookie","verifyMemberPassword","NO_CLIENT_ADDRESS_BUCKET","readClientIp","attemptsByIp","Map","WINDOW_MS","MAX_ATTEMPTS","membershipLoginHandler","req","res","method","status","json","error","hostId","String","body","email","trim","toLowerCase","password","ip","headers","remoteAddress","socket","now","Date","attempts","get","filter","at","push","set","length","firestore","app","membersQuery","collection","doc","where","limit","memberDoc","docs","cartId","cookies","update","customerId","id","console","ok"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/** Member sign-out (AGL-294): clears the session cookie. */
|
|
19
|
+
export declare const membershipLogoutHandler: PluginApiHandler;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { setMemberCookie } from "./membership.js";
|
|
17
|
+
/** Member sign-out (AGL-294): clears the session cookie. */ export const membershipLogoutHandler = (req, res)=>{
|
|
18
|
+
var _ref;
|
|
19
|
+
var _req_body;
|
|
20
|
+
if (req.method !== 'POST') {
|
|
21
|
+
return res.status(405).json({
|
|
22
|
+
error: 'Method not allowed'
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
26
|
+
if (!hostId) return res.status(400).json({
|
|
27
|
+
error: 'Missing hostId'
|
|
28
|
+
});
|
|
29
|
+
setMemberCookie(res, hostId, null);
|
|
30
|
+
return res.status(200).json({
|
|
31
|
+
ok: true
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=membership-logout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/membership-logout.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport { setMemberCookie } from './membership'\n\n/** Member sign-out (AGL-294): clears the session cookie. */\nexport const membershipLogoutHandler: PluginApiHandler = (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.body?.hostId ?? '')\n if (!hostId) return res.status(400).json({ error: 'Missing hostId' })\n setMemberCookie(res, hostId, null)\n return res.status(200).json({ ok: true })\n}\n"],"names":["setMemberCookie","membershipLogoutHandler","req","res","method","status","json","error","hostId","String","body","ok"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,eAAe,QAAQ,kBAAc;AAE9C,0DAA0D,GAC1D,OAAO,MAAMC,0BAA4C,CAACC,KAAKC;;QAIvCD;IAHtB,IAAIA,IAAIE,MAAM,KAAK,QAAQ;QACzB,OAAOD,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAqB;IAC5D;IACA,MAAMC,SAASC,gBAAOP,YAAAA,IAAIQ,IAAI,qBAARR,UAAUM,MAAM,mBAAI;IAC1C,IAAI,CAACA,QAAQ,OAAOL,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;QAAEC,OAAO;IAAiB;IACnEP,gBAAgBG,KAAKK,QAAQ;IAC7B,OAAOL,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;QAAEK,IAAI;IAAK;AACzC,EAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/**
|
|
19
|
+
* Password-recovery request (AGL-552). ALWAYS answers `200 {ok:true}` once
|
|
20
|
+
* the input validates and the caller is inside its rate budget — whether or
|
|
21
|
+
* not the email belongs to a member — so the endpoint can't be used to probe
|
|
22
|
+
* account existence. When a member matches, a single-use one-hour token
|
|
23
|
+
* (bound to the current password hash, see membership.ts) is mailed via
|
|
24
|
+
* Resend with a link to the site's `/recover` route.
|
|
25
|
+
*
|
|
26
|
+
* ## Abuse controls (AGL-1966)
|
|
27
|
+
*
|
|
28
|
+
* This is an unauthenticated endpoint that makes us send mail from
|
|
29
|
+
* `noreply@aglyn.com` to an address the caller chose, and it composes with
|
|
30
|
+
* `membership/register` — which admits arbitrary addresses because member
|
|
31
|
+
* accounts are unlimited on every plan (AGL-889) — into a mail relay. The
|
|
32
|
+
* bounces from that land on the sending reputation shared by every customer's
|
|
33
|
+
* transactional mail.
|
|
34
|
+
*
|
|
35
|
+
* Four controls, in the order they run, and the ORDER is part of each:
|
|
36
|
+
*
|
|
37
|
+
* 1. **Attempt caps** — per recipient and per IP, durable
|
|
38
|
+
* (`membership-recover-throttle.ts`). Consumed BEFORE the member lookup so
|
|
39
|
+
* a member and a non-member spend the identical budget and receive the
|
|
40
|
+
* identical answer; a visible 429 from here therefore leaks nothing. The
|
|
41
|
+
* per-instance `Map` this replaced reset on every cold start and was kept
|
|
42
|
+
* per instance, which on Vercel is close to no limit at all (AGL-794).
|
|
43
|
+
* 2. **The young-member guard** — a member row created inside
|
|
44
|
+
* `RECOVER_MIN_MEMBER_AGE_MS` is not worth a reset mail. Register-then-
|
|
45
|
+
* recover is the whole attack and nobody forgets a password ten minutes
|
|
46
|
+
* after choosing it. Silent-success exit.
|
|
47
|
+
* 3. **The platform suppression list** — see the note on the call below.
|
|
48
|
+
* Silent-success exit.
|
|
49
|
+
* 4. **The per-site daily send ceiling** — consumed only when a message is
|
|
50
|
+
* actually about to go out. Because that branch is reachable only for real
|
|
51
|
+
* members, its refusal MUST be the silent-success exit rather than an
|
|
52
|
+
* error, or the ceiling becomes the oracle everything else avoids.
|
|
53
|
+
*
|
|
54
|
+
* What remains unbounded is stated rather than papered over: a genuine botnet
|
|
55
|
+
* still gets `RECOVER_ATTEMPTS_PER_IP` per source, and an attacker who can
|
|
56
|
+
* create many sites gets the per-site ceiling once per site. Host creation is
|
|
57
|
+
* itself rate-limited, and bounding the botnet case needs an edge WAF, not an
|
|
58
|
+
* application limiter — the same conclusion `plugin-api-rate-limit.ts` reaches.
|
|
59
|
+
*/
|
|
60
|
+
export declare const membershipRecoverHandler: PluginApiHandler;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { resolveBrandingProfile } from "@aglyn/aglyn/server";
|
|
17
|
+
import { hostPublicOrigin } from "@aglyn/aglyn/server";
|
|
18
|
+
import { consumeMembershipRecoverAttempt, consumeMembershipRecoverSend, firebaseAdmin, getOrgForHost, hostSendingIdentity, isEmailSuppressed, meterHostEmail, RECOVER_MIN_MEMBER_AGE_MS } from "@aglyn/tenant-data-admin";
|
|
19
|
+
import { isEmailConfigured, sendEmail } from "@aglyn/shared-util-email";
|
|
20
|
+
import { mintPasswordResetToken } from "./membership.js";
|
|
21
|
+
import { readClientIp } from "@aglyn/aglyn/app-utils/request-ip";
|
|
22
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
23
|
+
/**
|
|
24
|
+
* Password-recovery request (AGL-552). ALWAYS answers `200 {ok:true}` once
|
|
25
|
+
* the input validates and the caller is inside its rate budget — whether or
|
|
26
|
+
* not the email belongs to a member — so the endpoint can't be used to probe
|
|
27
|
+
* account existence. When a member matches, a single-use one-hour token
|
|
28
|
+
* (bound to the current password hash, see membership.ts) is mailed via
|
|
29
|
+
* Resend with a link to the site's `/recover` route.
|
|
30
|
+
*
|
|
31
|
+
* ## Abuse controls (AGL-1966)
|
|
32
|
+
*
|
|
33
|
+
* This is an unauthenticated endpoint that makes us send mail from
|
|
34
|
+
* `noreply@aglyn.com` to an address the caller chose, and it composes with
|
|
35
|
+
* `membership/register` — which admits arbitrary addresses because member
|
|
36
|
+
* accounts are unlimited on every plan (AGL-889) — into a mail relay. The
|
|
37
|
+
* bounces from that land on the sending reputation shared by every customer's
|
|
38
|
+
* transactional mail.
|
|
39
|
+
*
|
|
40
|
+
* Four controls, in the order they run, and the ORDER is part of each:
|
|
41
|
+
*
|
|
42
|
+
* 1. **Attempt caps** — per recipient and per IP, durable
|
|
43
|
+
* (`membership-recover-throttle.ts`). Consumed BEFORE the member lookup so
|
|
44
|
+
* a member and a non-member spend the identical budget and receive the
|
|
45
|
+
* identical answer; a visible 429 from here therefore leaks nothing. The
|
|
46
|
+
* per-instance `Map` this replaced reset on every cold start and was kept
|
|
47
|
+
* per instance, which on Vercel is close to no limit at all (AGL-794).
|
|
48
|
+
* 2. **The young-member guard** — a member row created inside
|
|
49
|
+
* `RECOVER_MIN_MEMBER_AGE_MS` is not worth a reset mail. Register-then-
|
|
50
|
+
* recover is the whole attack and nobody forgets a password ten minutes
|
|
51
|
+
* after choosing it. Silent-success exit.
|
|
52
|
+
* 3. **The platform suppression list** — see the note on the call below.
|
|
53
|
+
* Silent-success exit.
|
|
54
|
+
* 4. **The per-site daily send ceiling** — consumed only when a message is
|
|
55
|
+
* actually about to go out. Because that branch is reachable only for real
|
|
56
|
+
* members, its refusal MUST be the silent-success exit rather than an
|
|
57
|
+
* error, or the ceiling becomes the oracle everything else avoids.
|
|
58
|
+
*
|
|
59
|
+
* What remains unbounded is stated rather than papered over: a genuine botnet
|
|
60
|
+
* still gets `RECOVER_ATTEMPTS_PER_IP` per source, and an attacker who can
|
|
61
|
+
* create many sites gets the per-site ceiling once per site. Host creation is
|
|
62
|
+
* itself rate-limited, and bounding the botnet case needs an edge WAF, not an
|
|
63
|
+
* application limiter — the same conclusion `plugin-api-rate-limit.ts` reaches.
|
|
64
|
+
*/ export const membershipRecoverHandler = async (req, res)=>{
|
|
65
|
+
var _ref, _ref1;
|
|
66
|
+
var _req_body, _req_body1, _req_socket;
|
|
67
|
+
if (req.method !== 'POST') {
|
|
68
|
+
return res.status(405).json({
|
|
69
|
+
error: 'Method not allowed'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
73
|
+
const email = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.email) != null ? _ref1 : '').trim().toLowerCase();
|
|
74
|
+
if (!hostId || !EMAIL_PATTERN.test(email)) {
|
|
75
|
+
return res.status(400).json({
|
|
76
|
+
error: 'Invalid request'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Null when nothing readable names the caller; the throttle then skips its
|
|
80
|
+
// per-source cap and the per-recipient one carries the control alone.
|
|
81
|
+
const ip = readClientIp(req.headers, {
|
|
82
|
+
remoteAddress: (_req_socket = req.socket) == null ? void 0 : _req_socket.remoteAddress
|
|
83
|
+
});
|
|
84
|
+
// Control 1. Before the lookup, so both branches spend the same budget.
|
|
85
|
+
// A store failure degrades to the in-process cap rather than refusing
|
|
86
|
+
// (`consumeRateLimit` fails soft on an unreachable store, closed on
|
|
87
|
+
// contention) — a Firestore blip must not lock every site's members out of
|
|
88
|
+
// their own password reset.
|
|
89
|
+
const attempt = await consumeMembershipRecoverAttempt({
|
|
90
|
+
email,
|
|
91
|
+
ip
|
|
92
|
+
});
|
|
93
|
+
if (!attempt.allowed) {
|
|
94
|
+
res.setHeader('Retry-After', String(attempt.retryAfterSeconds));
|
|
95
|
+
return res.status(429).json({
|
|
96
|
+
error: 'Too many attempts'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
var _hostPublicOrigin, _ref2, _hostSnapshot_get;
|
|
101
|
+
var _this;
|
|
102
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
103
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
104
|
+
const [hostSnapshot, membersQuery] = await Promise.all([
|
|
105
|
+
hostRef.get(),
|
|
106
|
+
hostRef.collection('siteMembers').where('email', '==', email).limit(1).get()
|
|
107
|
+
]);
|
|
108
|
+
const memberDoc = membersQuery.docs[0];
|
|
109
|
+
// Unknown site, unknown email, suspended account (AGL-546): all take
|
|
110
|
+
// the same silent-success exit — the response never distinguishes.
|
|
111
|
+
if (!hostSnapshot.exists || !memberDoc || memberDoc.get('suspended') === true) {
|
|
112
|
+
return res.status(200).json({
|
|
113
|
+
ok: true
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Control 2 — the register-then-recover composition (AGL-1966).
|
|
117
|
+
//
|
|
118
|
+
// `createdAt` is a server timestamp written by `membership-register.ts`.
|
|
119
|
+
// A row that has none is a legacy member, not a fresh one: rows predating
|
|
120
|
+
// the field must NOT be refused, so an unreadable or absent value reads as
|
|
121
|
+
// "old enough". This guard exists to refuse a member created seconds ago,
|
|
122
|
+
// which is a case where the field is always present by construction.
|
|
123
|
+
const createdAtMs = (()=>{
|
|
124
|
+
const raw = memberDoc.get('createdAt');
|
|
125
|
+
try {
|
|
126
|
+
return typeof (raw == null ? void 0 : raw.toMillis) === 'function' ? raw.toMillis() : null;
|
|
127
|
+
} catch (unused) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
if (createdAtMs !== null && Date.now() - createdAtMs < RECOVER_MIN_MEMBER_AGE_MS) {
|
|
132
|
+
return res.status(200).json({
|
|
133
|
+
ok: true
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Control 3 — the platform suppression list (AGL-1918 / AGL-2407).
|
|
137
|
+
//
|
|
138
|
+
// `email-suppression.ts` argues that TRANSACTIONAL mail deliberately does
|
|
139
|
+
// not consult this list, because refusing a password reset over an
|
|
140
|
+
// unrelated old bounce locks a real customer out of their own account.
|
|
141
|
+
// That argument is about mail a signed-in human just asked us for. It does
|
|
142
|
+
// not reach here, and this is the exception it does not cover:
|
|
143
|
+
//
|
|
144
|
+
// - The address is chosen by an ANONYMOUS caller, not by the account
|
|
145
|
+
// holder. "The human just did something" is exactly the premise that is
|
|
146
|
+
// missing on an unauthenticated endpoint.
|
|
147
|
+
// - The list holds PERMANENT bounces and spam complaints only — transient
|
|
148
|
+
// bounces are excluded at the webhook. A permanent bounce means the
|
|
149
|
+
// mailbox does not exist, so the send cannot reach anyone; all it can do
|
|
150
|
+
// is tell a provider that `aglyn.com` re-mails dead addresses. There is
|
|
151
|
+
// no locked-out customer on the other side of it to protect.
|
|
152
|
+
// - The reputational cost is shared by every customer's transactional
|
|
153
|
+
// mail, which is precisely what AGL-1966 is about.
|
|
154
|
+
//
|
|
155
|
+
// The HOST list (`hosts/{hostId}/suppressions`) is deliberately NOT
|
|
156
|
+
// consulted: it also holds marketing unsubscribes, and unsubscribing from
|
|
157
|
+
// a store's newsletter must never stop that store's password resets.
|
|
158
|
+
//
|
|
159
|
+
// `isEmailSuppressed` fails CLOSED — a read that throws answers "treat as
|
|
160
|
+
// suppressed". That adds no new outage mode here: the member lookup above
|
|
161
|
+
// is a Firestore read on the same database, so a Firestore outage has
|
|
162
|
+
// already taken this handler to its silent-success catch.
|
|
163
|
+
if (await isEmailSuppressed(email)) {
|
|
164
|
+
return res.status(200).json({
|
|
165
|
+
ok: true
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (!isEmailConfigured()) {
|
|
169
|
+
// Config gaps must not become an existence oracle either; log for
|
|
170
|
+
// the operator and keep the visitor-facing contract.
|
|
171
|
+
console.error('membership/recover: email is not configured');
|
|
172
|
+
return res.status(200).json({
|
|
173
|
+
ok: true
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const token = mintPasswordResetToken(hostId, memberDoc.id, memberDoc.get('passwordScrypt'));
|
|
177
|
+
// `hostPublicOrigin`, not a hand-rolled apex (AGL-2195). This URL is
|
|
178
|
+
// minted server-side and mailed out, so a wrong apex is not a display
|
|
179
|
+
// bug — it is a live link on somebody else's domain.
|
|
180
|
+
const siteBase = (_hostPublicOrigin = hostPublicOrigin({
|
|
181
|
+
cname: hostSnapshot.get('cname'),
|
|
182
|
+
subdomain: hostSnapshot.get('subdomain')
|
|
183
|
+
})) != null ? _hostPublicOrigin : '';
|
|
184
|
+
const resetUrl = `${siteBase}/recover?token=${encodeURIComponent(token)}`;
|
|
185
|
+
const siteName = String((_ref2 = (_hostSnapshot_get = hostSnapshot.get('displayName')) != null ? _hostSnapshot_get : hostSnapshot.get('subdomain')) != null ? _ref2 : 'your site');
|
|
186
|
+
// White-label sender identity (White-Label Phase 3): the store's brand via
|
|
187
|
+
// the one shared resolver (the copy already uses the site name).
|
|
188
|
+
const branding = resolveBrandingProfile((_this = await getOrgForHost(hostId).catch(()=>null)) == null ? void 0 : _this.org);
|
|
189
|
+
// Control 4 — the per-site daily ceiling on recovery mail (AGL-1966).
|
|
190
|
+
//
|
|
191
|
+
// Consumed here, as late as possible, because it counts MESSAGES and not
|
|
192
|
+
// requests: everything above this line can refuse without having decided
|
|
193
|
+
// to send, and a counter that ticked for those would exhaust a site's
|
|
194
|
+
// budget on traffic that never cost a message.
|
|
195
|
+
//
|
|
196
|
+
// The refusal is the silent-success exit, NOT a 429. This branch is
|
|
197
|
+
// reachable only for an address that really is a member, so a visible
|
|
198
|
+
// error here would be the existence oracle the whole handler is built to
|
|
199
|
+
// avoid — the one place where the cheap answer is the wrong one.
|
|
200
|
+
//
|
|
201
|
+
// The accepted cost: during an attack on a site, a genuine member can get
|
|
202
|
+
// no mail and no error. The ceiling is sized more than an order of
|
|
203
|
+
// magnitude above any real site's daily resets so that reaching it is
|
|
204
|
+
// evidence of the attack rather than of a busy Tuesday.
|
|
205
|
+
const sendBudget = await consumeMembershipRecoverSend({
|
|
206
|
+
hostId
|
|
207
|
+
});
|
|
208
|
+
if (!sendBudget.allowed) {
|
|
209
|
+
console.error('membership/recover: daily send ceiling reached', {
|
|
210
|
+
hostId,
|
|
211
|
+
retryAfterSeconds: sendBudget.retryAfterSeconds
|
|
212
|
+
});
|
|
213
|
+
return res.status(200).json({
|
|
214
|
+
ok: true
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
await sendEmail({
|
|
218
|
+
to: email,
|
|
219
|
+
subject: `Reset your ${siteName} password`,
|
|
220
|
+
text: `Someone asked to reset the password for your ${siteName} ` + 'account. If that was you, set a new password here:\n\n' + `${resetUrl}\n\n` + 'The link works once and expires in 1 hour. If you did not ' + 'ask for this, you can safely ignore this email — your ' + 'password is unchanged.',
|
|
221
|
+
fromName: branding.fromName,
|
|
222
|
+
sendingIdentity: await hostSendingIdentity(hostId),
|
|
223
|
+
audience: 'tenant',
|
|
224
|
+
context: 'membership recovery'
|
|
225
|
+
});
|
|
226
|
+
// Cost meter (AGL-1438). Transactional, and the clearest case for why a
|
|
227
|
+
// quota may not refuse one: this email is how the member gets back into
|
|
228
|
+
// their account.
|
|
229
|
+
await meterHostEmail(hostId);
|
|
230
|
+
return res.status(200).json({
|
|
231
|
+
ok: true
|
|
232
|
+
});
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error(error);
|
|
235
|
+
// Even a backend failure stays silent-success: the enumeration
|
|
236
|
+
// contract beats the visitor's error visibility here, and the send is
|
|
237
|
+
// best-effort by design.
|
|
238
|
+
return res.status(200).json({
|
|
239
|
+
ok: true
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
//# sourceMappingURL=membership-recover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/membership-recover.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport { resolveBrandingProfile } from '@aglyn/aglyn/server'\nimport { hostPublicOrigin } from '@aglyn/aglyn/server'\nimport {\n consumeMembershipRecoverAttempt,\n consumeMembershipRecoverSend,\n firebaseAdmin,\n getOrgForHost,\n hostSendingIdentity,\n isEmailSuppressed,\n meterHostEmail,\n RECOVER_MIN_MEMBER_AGE_MS,\n} from '@aglyn/tenant-data-admin'\nimport { isEmailConfigured, sendEmail } from '@aglyn/shared-util-email'\nimport { mintPasswordResetToken } from './membership'\nimport { readClientIp } from '@aglyn/aglyn/app-utils/request-ip'\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/**\n * Password-recovery request (AGL-552). ALWAYS answers `200 {ok:true}` once\n * the input validates and the caller is inside its rate budget — whether or\n * not the email belongs to a member — so the endpoint can't be used to probe\n * account existence. When a member matches, a single-use one-hour token\n * (bound to the current password hash, see membership.ts) is mailed via\n * Resend with a link to the site's `/recover` route.\n *\n * ## Abuse controls (AGL-1966)\n *\n * This is an unauthenticated endpoint that makes us send mail from\n * `noreply@aglyn.com` to an address the caller chose, and it composes with\n * `membership/register` — which admits arbitrary addresses because member\n * accounts are unlimited on every plan (AGL-889) — into a mail relay. The\n * bounces from that land on the sending reputation shared by every customer's\n * transactional mail.\n *\n * Four controls, in the order they run, and the ORDER is part of each:\n *\n * 1. **Attempt caps** — per recipient and per IP, durable\n * (`membership-recover-throttle.ts`). Consumed BEFORE the member lookup so\n * a member and a non-member spend the identical budget and receive the\n * identical answer; a visible 429 from here therefore leaks nothing. The\n * per-instance `Map` this replaced reset on every cold start and was kept\n * per instance, which on Vercel is close to no limit at all (AGL-794).\n * 2. **The young-member guard** — a member row created inside\n * `RECOVER_MIN_MEMBER_AGE_MS` is not worth a reset mail. Register-then-\n * recover is the whole attack and nobody forgets a password ten minutes\n * after choosing it. Silent-success exit.\n * 3. **The platform suppression list** — see the note on the call below.\n * Silent-success exit.\n * 4. **The per-site daily send ceiling** — consumed only when a message is\n * actually about to go out. Because that branch is reachable only for real\n * members, its refusal MUST be the silent-success exit rather than an\n * error, or the ceiling becomes the oracle everything else avoids.\n *\n * What remains unbounded is stated rather than papered over: a genuine botnet\n * still gets `RECOVER_ATTEMPTS_PER_IP` per source, and an attacker who can\n * create many sites gets the per-site ceiling once per site. Host creation is\n * itself rate-limited, and bounding the botnet case needs an edge WAF, not an\n * application limiter — the same conclusion `plugin-api-rate-limit.ts` reaches.\n */\nexport const membershipRecoverHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.body?.hostId ?? '')\n const email = String(req.body?.email ?? '')\n .trim()\n .toLowerCase()\n if (!hostId || !EMAIL_PATTERN.test(email)) {\n return res.status(400).json({ error: 'Invalid request' })\n }\n // Null when nothing readable names the caller; the throttle then skips its\n // per-source cap and the per-recipient one carries the control alone.\n const ip = readClientIp(req.headers, {\n remoteAddress: req.socket?.remoteAddress,\n })\n // Control 1. Before the lookup, so both branches spend the same budget.\n // A store failure degrades to the in-process cap rather than refusing\n // (`consumeRateLimit` fails soft on an unreachable store, closed on\n // contention) — a Firestore blip must not lock every site's members out of\n // their own password reset.\n const attempt = await consumeMembershipRecoverAttempt({ email, ip })\n if (!attempt.allowed) {\n res.setHeader('Retry-After', String(attempt.retryAfterSeconds))\n return res.status(429).json({ error: 'Too many attempts' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const [hostSnapshot, membersQuery] = await Promise.all([\n hostRef.get(),\n hostRef\n .collection('siteMembers')\n .where('email', '==', email)\n .limit(1)\n .get(),\n ])\n const memberDoc = membersQuery.docs[0]\n // Unknown site, unknown email, suspended account (AGL-546): all take\n // the same silent-success exit — the response never distinguishes.\n if (\n !hostSnapshot.exists ||\n !memberDoc ||\n memberDoc.get('suspended') === true\n ) {\n return res.status(200).json({ ok: true })\n }\n // Control 2 — the register-then-recover composition (AGL-1966).\n //\n // `createdAt` is a server timestamp written by `membership-register.ts`.\n // A row that has none is a legacy member, not a fresh one: rows predating\n // the field must NOT be refused, so an unreadable or absent value reads as\n // \"old enough\". This guard exists to refuse a member created seconds ago,\n // which is a case where the field is always present by construction.\n const createdAtMs = (() => {\n const raw = memberDoc.get('createdAt') as\n | { toMillis?: () => number }\n | undefined\n try {\n return typeof raw?.toMillis === 'function' ? raw.toMillis() : null\n } catch {\n return null\n }\n })()\n if (\n createdAtMs !== null &&\n Date.now() - createdAtMs < RECOVER_MIN_MEMBER_AGE_MS\n ) {\n return res.status(200).json({ ok: true })\n }\n // Control 3 — the platform suppression list (AGL-1918 / AGL-2407).\n //\n // `email-suppression.ts` argues that TRANSACTIONAL mail deliberately does\n // not consult this list, because refusing a password reset over an\n // unrelated old bounce locks a real customer out of their own account.\n // That argument is about mail a signed-in human just asked us for. It does\n // not reach here, and this is the exception it does not cover:\n //\n // - The address is chosen by an ANONYMOUS caller, not by the account\n // holder. \"The human just did something\" is exactly the premise that is\n // missing on an unauthenticated endpoint.\n // - The list holds PERMANENT bounces and spam complaints only — transient\n // bounces are excluded at the webhook. A permanent bounce means the\n // mailbox does not exist, so the send cannot reach anyone; all it can do\n // is tell a provider that `aglyn.com` re-mails dead addresses. There is\n // no locked-out customer on the other side of it to protect.\n // - The reputational cost is shared by every customer's transactional\n // mail, which is precisely what AGL-1966 is about.\n //\n // The HOST list (`hosts/{hostId}/suppressions`) is deliberately NOT\n // consulted: it also holds marketing unsubscribes, and unsubscribing from\n // a store's newsletter must never stop that store's password resets.\n //\n // `isEmailSuppressed` fails CLOSED — a read that throws answers \"treat as\n // suppressed\". That adds no new outage mode here: the member lookup above\n // is a Firestore read on the same database, so a Firestore outage has\n // already taken this handler to its silent-success catch.\n if (await isEmailSuppressed(email)) {\n return res.status(200).json({ ok: true })\n }\n if (!isEmailConfigured()) {\n // Config gaps must not become an existence oracle either; log for\n // the operator and keep the visitor-facing contract.\n console.error('membership/recover: email is not configured')\n return res.status(200).json({ ok: true })\n }\n const token = mintPasswordResetToken(\n hostId,\n memberDoc.id,\n memberDoc.get('passwordScrypt'),\n )\n // `hostPublicOrigin`, not a hand-rolled apex (AGL-2195). This URL is\n // minted server-side and mailed out, so a wrong apex is not a display\n // bug — it is a live link on somebody else's domain.\n const siteBase =\n hostPublicOrigin({\n cname: hostSnapshot.get('cname'),\n subdomain: hostSnapshot.get('subdomain'),\n }) ?? ''\n const resetUrl = `${siteBase}/recover?token=${encodeURIComponent(token)}`\n const siteName = String(\n hostSnapshot.get('displayName') ?? hostSnapshot.get('subdomain') ?? 'your site',\n )\n // White-label sender identity (White-Label Phase 3): the store's brand via\n // the one shared resolver (the copy already uses the site name).\n const branding = resolveBrandingProfile(\n (await getOrgForHost(hostId).catch(() => null))?.org as never,\n )\n // Control 4 — the per-site daily ceiling on recovery mail (AGL-1966).\n //\n // Consumed here, as late as possible, because it counts MESSAGES and not\n // requests: everything above this line can refuse without having decided\n // to send, and a counter that ticked for those would exhaust a site's\n // budget on traffic that never cost a message.\n //\n // The refusal is the silent-success exit, NOT a 429. This branch is\n // reachable only for an address that really is a member, so a visible\n // error here would be the existence oracle the whole handler is built to\n // avoid — the one place where the cheap answer is the wrong one.\n //\n // The accepted cost: during an attack on a site, a genuine member can get\n // no mail and no error. The ceiling is sized more than an order of\n // magnitude above any real site's daily resets so that reaching it is\n // evidence of the attack rather than of a busy Tuesday.\n const sendBudget = await consumeMembershipRecoverSend({ hostId })\n if (!sendBudget.allowed) {\n console.error('membership/recover: daily send ceiling reached', {\n hostId,\n retryAfterSeconds: sendBudget.retryAfterSeconds,\n })\n return res.status(200).json({ ok: true })\n }\n await sendEmail({\n to: email,\n subject: `Reset your ${siteName} password`,\n text:\n `Someone asked to reset the password for your ${siteName} ` +\n 'account. If that was you, set a new password here:\\n\\n' +\n `${resetUrl}\\n\\n` +\n 'The link works once and expires in 1 hour. If you did not ' +\n 'ask for this, you can safely ignore this email — your ' +\n 'password is unchanged.',\n fromName: branding.fromName,\n sendingIdentity: await hostSendingIdentity(hostId),\n audience: 'tenant',\n context: 'membership recovery',\n })\n // Cost meter (AGL-1438). Transactional, and the clearest case for why a\n // quota may not refuse one: this email is how the member gets back into\n // their account.\n await meterHostEmail(hostId)\n return res.status(200).json({ ok: true })\n } catch (error) {\n console.error(error)\n // Even a backend failure stays silent-success: the enumeration\n // contract beats the visitor's error visibility here, and the send is\n // best-effort by design.\n return res.status(200).json({ ok: true })\n }\n}\n"],"names":["resolveBrandingProfile","hostPublicOrigin","consumeMembershipRecoverAttempt","consumeMembershipRecoverSend","firebaseAdmin","getOrgForHost","hostSendingIdentity","isEmailSuppressed","meterHostEmail","RECOVER_MIN_MEMBER_AGE_MS","isEmailConfigured","sendEmail","mintPasswordResetToken","readClientIp","EMAIL_PATTERN","membershipRecoverHandler","req","res","method","status","json","error","hostId","String","body","email","trim","toLowerCase","test","ip","headers","remoteAddress","socket","attempt","allowed","setHeader","retryAfterSeconds","hostSnapshot","firestore","app","hostRef","collection","doc","membersQuery","Promise","all","get","where","limit","memberDoc","docs","exists","ok","createdAtMs","raw","toMillis","Date","now","console","token","id","siteBase","cname","subdomain","resetUrl","encodeURIComponent","siteName","branding","catch","org","sendBudget","to","subject","text","fromName","sendingIdentity","audience","context"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/**
|
|
19
|
+
* Site member sign-up (AGL-109): creates the member record (scrypt hash),
|
|
20
|
+
* doubles as a lead, and signs the visitor in via the session cookie.
|
|
21
|
+
*/
|
|
22
|
+
export declare const membershipRegisterHandler: PluginApiHandler;
|