@peektravel/app-utilities 0.7.2 → 0.7.3

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
@@ -124,10 +124,12 @@ Two kinds of failures surface as exceptions:
124
124
  - `PiiAccessDisabledError` — a payment / booking-modification operation was
125
125
  called on an access service created without `fullCustomerAccess` (see [Access options
126
126
  / PII](#access-options--pii)). Carries `.operation` (the blocked method name).
127
- - `InvalidPeekTokenError` — `verifyPeekAuthToken` (or `parseInstallWebhook` /
128
- `verifyInstallWebhook`)
129
- received a token whose signature is valid but whose `user` block has no `id`.
130
- Carries `.field` (`"user.id"`). Thrown alongside the `jsonwebtoken` errors.
127
+
128
+ Once a token's signature verifies, claim *extraction* never throws: the whole
129
+ `user` block may be absent (`claims.user === null`) and every field inside it is
130
+ independently nullable, with a sentinel value (`""` or `"null"`) normalized to
131
+ `null`. `user.id` is the signed-in person's own id — not an account or partner
132
+ id — so its absence is not an error.
131
133
 
132
134
  **Plain `Error` validation/precondition failures** thrown by the service layer
133
135
  *before* any network call — e.g. an empty config field, a `bookingId` that
@@ -306,8 +308,7 @@ signed `app_registry_v2` JWT plus a JSON body. Peek sends the token in the
306
308
  `x-peek-auth` header — pass that value straight in (`token` accepts it with or
307
309
  without a `Bearer ` prefix). It validates the HMAC signature,
308
310
  expiry, issuer, and `"Joken"` audience (the same checks as `verifyPeekAuthToken`,
309
- throwing the underlying `jsonwebtoken` error — or `InvalidPeekTokenError` when a
310
- present `user` block has no `id`), then returns one flat, JSON-safe
311
+ throwing the underlying `jsonwebtoken` error), then returns one flat, JSON-safe
311
312
  `InstallWebhook`: `installId`, `accountId` (**also called the partner ID**),
312
313
  `accountName`, `platform`, `isTest`, `timezone`, `apiUrl`, `status`, `rawStatus`,
313
314
  `displayVersion`, and a **nullable** `user`. The **JSON body is the source of the
package/dist/index.cjs CHANGED
@@ -247,15 +247,6 @@ var PeekHttpError = class extends Error {
247
247
  this.body = body;
248
248
  }
249
249
  };
250
- var InvalidPeekTokenError = class extends Error {
251
- /** The token field that was missing or empty (e.g. `"user.id"`). */
252
- field;
253
- constructor(field) {
254
- super(`Peek token is missing required field "${field}"`);
255
- this.name = "InvalidPeekTokenError";
256
- this.field = field;
257
- }
258
- };
259
250
  var PiiAccessDisabledError = class extends Error {
260
251
  /** The name of the operation that was blocked (e.g. `"makePayment"`). */
261
252
  operation;
@@ -2060,6 +2051,11 @@ var GraphQLClient = class {
2060
2051
  return headers;
2061
2052
  }
2062
2053
  };
2054
+
2055
+ // src/models/peek/auth-token.ts
2056
+ var PEEK_PLATFORMS = ["peek", "cng", "acme"];
2057
+
2058
+ // src/internal/peek/peek-auth-token.ts
2063
2059
  var PEEK_TOKEN_ISSUER = "app_registry_v2";
2064
2060
  var PEEK_TOKEN_AUDIENCE = "Joken";
2065
2061
  var BEARER_PREFIX = "bearer ";
@@ -2067,6 +2063,16 @@ function stripBearer(token) {
2067
2063
  const trimmed = token.trim();
2068
2064
  return trimmed.toLowerCase().startsWith(BEARER_PREFIX) ? trimmed.slice(BEARER_PREFIX.length).trim() : trimmed;
2069
2065
  }
2066
+ var NULLISH_STRINGS = /* @__PURE__ */ new Set(["", "null"]);
2067
+ function cleanTokenString(value) {
2068
+ if (typeof value !== "string") return null;
2069
+ const trimmed = value.trim();
2070
+ return NULLISH_STRINGS.has(trimmed.toLowerCase()) ? null : trimmed;
2071
+ }
2072
+ function cleanTokenPlatform(value) {
2073
+ const cleaned = cleanTokenString(value);
2074
+ return cleaned !== null && PEEK_PLATFORMS.includes(cleaned) ? cleaned : null;
2075
+ }
2070
2076
  function verifyPeekJwt(token, secret) {
2071
2077
  return jwt__namespace.verify(stripBearer(token), secret, {
2072
2078
  issuer: PEEK_TOKEN_ISSUER,
@@ -2074,16 +2080,14 @@ function verifyPeekJwt(token, secret) {
2074
2080
  });
2075
2081
  }
2076
2082
  function mapPeekTokenUser(u) {
2077
- if (!u.id) {
2078
- throw new InvalidPeekTokenError("user.id");
2079
- }
2083
+ if (!u) return null;
2080
2084
  return {
2081
- email: u.email,
2082
- id: u.id,
2083
- isAdmin: u.is_admin,
2084
- locale: u.locale,
2085
- name: u.name,
2086
- platform: u.platform
2085
+ email: cleanTokenString(u.email),
2086
+ id: cleanTokenString(u.id),
2087
+ isAdmin: typeof u.is_admin === "boolean" ? u.is_admin : null,
2088
+ locale: cleanTokenString(u.locale),
2089
+ name: cleanTokenString(u.name),
2090
+ platform: cleanTokenPlatform(u.platform)
2087
2091
  };
2088
2092
  }
2089
2093
 
@@ -3603,13 +3607,15 @@ var PeekAccessService = class {
3603
3607
  * malformed
3604
3608
  * - `TokenExpiredError` — past `exp`
3605
3609
  * - `NotBeforeError` — before `nbf`
3606
- * - `InvalidPeekTokenError` — signature valid but the `user` block is missing
3607
- * its `id`
3610
+ *
3611
+ * Claim *extraction* is defensive: once the signature verifies, a token with
3612
+ * no `user` block yields `claims.user === null`, and any individual user field
3613
+ * that is absent or a sentinel (`""`, `"null"`) is normalized to `null`. This
3614
+ * method never throws on a shape problem — only on a verification failure.
3608
3615
  *
3609
3616
  * @throws {JsonWebTokenError} signature invalid or token malformed
3610
3617
  * @throws {TokenExpiredError} token has expired
3611
3618
  * @throws {NotBeforeError} token not yet valid
3612
- * @throws {InvalidPeekTokenError} verified token has no `user.id`
3613
3619
  */
3614
3620
  verifyPeekAuthToken(token) {
3615
3621
  const payload = verifyPeekJwt(token, this.jwtSecret);
@@ -4303,9 +4309,6 @@ function extractWaiverNode(payload) {
4303
4309
  return record;
4304
4310
  }
4305
4311
 
4306
- // src/models/peek/auth-token.ts
4307
- var PEEK_PLATFORMS = ["peek", "cng", "acme"];
4308
-
4309
4312
  // src/models/peek/install.ts
4310
4313
  var INSTALL_STATUSES = [
4311
4314
  "installed",
@@ -4354,8 +4357,8 @@ function parseInstallWebhook(token, body, secret) {
4354
4357
  };
4355
4358
  }
4356
4359
  function resolveInstallUser(bodyUser, tokenUser) {
4357
- if (bodyUser?.id) return mapPeekTokenUser(bodyUser);
4358
- return tokenUser ? mapPeekTokenUser(tokenUser) : null;
4360
+ if (cleanTokenString(bodyUser?.id) !== null) return mapPeekTokenUser(bodyUser);
4361
+ return mapPeekTokenUser(tokenUser);
4359
4362
  }
4360
4363
  function verifyInstallWebhook(token, secret) {
4361
4364
  const payload = verifyPeekJwt(token, secret);
@@ -4364,7 +4367,7 @@ function verifyInstallWebhook(token, secret) {
4364
4367
  account: { id: payload.account?.id ?? "" },
4365
4368
  status: payload.status ?? "",
4366
4369
  displayVersion: payload.display_version ?? "",
4367
- user: payload.user ? mapPeekTokenUser(payload.user) : null
4370
+ user: mapPeekTokenUser(payload.user)
4368
4371
  };
4369
4372
  }
4370
4373
 
@@ -4382,7 +4385,6 @@ exports.CngApiError = CngApiError;
4382
4385
  exports.CngProductService = CngProductService;
4383
4386
  exports.DailyNoteService = DailyNoteService;
4384
4387
  exports.INSTALL_STATUSES = INSTALL_STATUSES;
4385
- exports.InvalidPeekTokenError = InvalidPeekTokenError;
4386
4388
  exports.MembershipService = MembershipService;
4387
4389
  exports.PeekAccessService = PeekAccessService;
4388
4390
  exports.PeekGraphQLError = PeekGraphQLError;