@onesub/server 0.25.0 → 0.27.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/README.md CHANGED
@@ -323,7 +323,9 @@ Canonical Postgres DDL shipped at [`sql/schema.sql`](./sql/schema.sql). Apply wi
323
323
  ## Security
324
324
 
325
325
  - Apple JWS signature verified end-to-end against **Apple Root CA G3** (as of `0.6.0`)
326
- - Google RTDN: `Authorization: Bearer` JWT verified against Google JWKS when `pushAudience` is configured
326
+ - Google RTDN: `Authorization: Bearer` JWT verified against Google JWKS when `pushAudience` is
327
+ configured. Without it the request cannot be attributed to Google and is refused with 401 under
328
+ `NODE_ENV=production`, unless `google.allowUnauthenticatedWebhook` opts in
327
329
  - `transactionId` ownership enforced — same receipt can't be reused across users (`0.5.0+`)
328
330
  - zod input validation + 50 KB body cap
329
331
  - Full write-up: [`docs/SECURITY.md`](../../docs/SECURITY.md)
@@ -1,12 +1,20 @@
1
1
  /**
2
- * Startup warning for the two conditions that leave the Google RTDN route open.
2
+ * Startup warnings for the Google RTDN route's two loose conditions.
3
3
  *
4
- * `POST /onesub/webhook/google` verifies the Pub/Sub OIDC token only when an app
5
- * declares `pushAudience`, and serves any `packageName` when none declares one.
6
- * The route is mounted either way. Both behaviours are pre-existing and
7
- * deliberate a host may front the route with its own verificationso this
8
- * warns instead of refusing, and these tests pin down that it says so exactly
9
- * when it should and stays quiet otherwise.
4
+ * The authentication half changed in 0.27.0: without a `pushAudience` the route used
5
+ * to accept the request, and now refuses it with 401 in production unless
6
+ * `google.allowUnauthenticatedWebhook` says otherwise. So there are two warnings to
7
+ * tell apart, and getting them backwards would be worse than having nonean
8
+ * operator reading "insecure" when the endpoint is actually refusing traffic goes
9
+ * hunting for a breach instead of a missing config value.
10
+ *
11
+ * Open mode (no `packageName`) is unchanged: it still warns rather than refusing.
12
+ *
13
+ * The assertions here were rewritten, not adjusted. Three of them were negative
14
+ * matches against the old warning's wording — text this file no longer produces at
15
+ * all, so they would have passed however loudly the new warning fired. A negative
16
+ * assertion against a string that cannot occur is not coverage, so each one now
17
+ * names both current warnings explicitly.
10
18
  */
11
19
  export {};
12
20
  //# sourceMappingURL=google-webhook-open-warning.test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"google-webhook-open-warning.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/google-webhook-open-warning.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
1
+ {"version":3,"file":"google-webhook-open-warning.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/google-webhook-open-warning.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `POST /onesub/webhook/google` refuses requests it cannot attribute to Google.
3
+ *
4
+ * ## Why this replaced masking the purchase token
5
+ *
6
+ * The RTDN paths are only dangerous because a caller who knows a `purchaseToken` or
7
+ * an `orderId` could reach them unauthenticated: the voided-purchase branch acts on
8
+ * the payload alone, cancelling a subscription by token or deleting a one-time
9
+ * purchase row by order id.
10
+ *
11
+ * Treating those ids as secrets does not work. For a Google subscription the purchase
12
+ * token **is** the record's `originalTransactionId` (`providers/google.ts` keys on it
13
+ * deliberately — RTDNs and `linkedPurchaseToken` chains carry nothing else), so it
14
+ * lives in the database, in every notification payload, and in the webhook lines
15
+ * where it is the subject of the investigation. Redacting it from logs would have left
16
+ * the capability intact while suggesting otherwise.
17
+ *
18
+ * Refusing unattributable requests removes the capability instead.
19
+ *
20
+ * ## Production-gated, and why that is not a loophole
21
+ *
22
+ * Locally and in CI no real RTDN arrives, and requiring Pub/Sub credentials there
23
+ * would break every webhook test in this repo and the `onesub dev` server for no
24
+ * security gain. This matches how the mockMode hard guard and the sandbox-receipt
25
+ * rejection are already gated. The residual risk — a production deployment that
26
+ * forgets `NODE_ENV=production` — is the same one those two already carry, so this
27
+ * adds no new footgun, and the boot warning fires on the same condition.
28
+ */
29
+ export {};
30
+ //# sourceMappingURL=google-webhook-unauthenticated.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-webhook-unauthenticated.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/google-webhook-unauthenticated.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * What the routes, stores and webhook handlers put on a log line.
3
+ *
4
+ * This is the half of the migration that delivers the point of it. `userId` does not
5
+ * exist in the provider layer — it arrives in a request body — so "show me every
6
+ * rejection for this user" only became answerable when these call sites moved.
7
+ * `provider-log-fields.test.ts` covers Apple and Google; this covers the other 54.
8
+ *
9
+ * Driven through HTTP with supertest rather than by calling handlers, because two of
10
+ * the values under test (`userId`, `bundleId`) are attacker-supplied through the
11
+ * request and the escaping only matters on that path. `bundleId` in particular is
12
+ * read out of an **unverified** JWS by `peekAppleBundleId`, which makes
13
+ * `apps.ts`'s "No app configured" line the most attacker-reachable log site in the
14
+ * package.
15
+ *
16
+ * Exact-line assertions again, for the reason given in `provider-log-fields.test.ts`:
17
+ * a substring match cannot tell a field from a sentence that mentions the value.
18
+ */
19
+ export {};
20
+ //# sourceMappingURL=route-log-fields.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-log-fields.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/route-log-fields.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -1 +1 @@
1
- {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../src/apps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG1E;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC;IACjD;;;;OAIG;IACH,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,CAAC;CAC9C;AAED,MAAM,WAAW,OAAO;IACtB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC;AAQD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAMtE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CA8DxE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUjE"}
1
+ {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../src/apps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG1E;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC;IACjD;;;;OAIG;IACH,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,CAAC;CAC9C;AAED,MAAM,WAAW,OAAO;IACtB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC;AAQD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAMtE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAiExE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUjE"}