@teleporthq/teleport-shared 0.43.67 → 0.43.69

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.
Files changed (107) hide show
  1. package/__tests__/_helpers/embed-runtime-emitter.ts +197 -0
  2. package/__tests__/utils/discount-engine.ts +1548 -0
  3. package/__tests__/utils/product-options.ts +2458 -0
  4. package/__tests__/utils/rich-text-embeds-runtime-module.ts +34 -0
  5. package/__tests__/utils/subscription-access.ts +37 -0
  6. package/dist/cjs/index.d.ts +9 -1
  7. package/dist/cjs/index.d.ts.map +1 -1
  8. package/dist/cjs/index.js +17 -1
  9. package/dist/cjs/index.js.map +1 -1
  10. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  11. package/dist/cjs/utils/cart-currency.d.ts +27 -0
  12. package/dist/cjs/utils/cart-currency.d.ts.map +1 -0
  13. package/dist/cjs/utils/cart-currency.js +31 -0
  14. package/dist/cjs/utils/cart-currency.js.map +1 -0
  15. package/dist/cjs/utils/cart-line-pricing.d.ts +25 -0
  16. package/dist/cjs/utils/cart-line-pricing.d.ts.map +1 -0
  17. package/dist/cjs/utils/cart-line-pricing.js +29 -0
  18. package/dist/cjs/utils/cart-line-pricing.js.map +1 -0
  19. package/dist/cjs/utils/discount-engine.d.ts +74 -0
  20. package/dist/cjs/utils/discount-engine.d.ts.map +1 -0
  21. package/dist/cjs/utils/discount-engine.js +435 -0
  22. package/dist/cjs/utils/discount-engine.js.map +1 -0
  23. package/dist/cjs/utils/product-options.d.ts +25 -0
  24. package/dist/cjs/utils/product-options.d.ts.map +1 -0
  25. package/dist/cjs/utils/product-options.js +1155 -0
  26. package/dist/cjs/utils/product-options.js.map +1 -0
  27. package/dist/cjs/utils/rich-text-embeds-codegen.d.ts +13 -0
  28. package/dist/cjs/utils/rich-text-embeds-codegen.d.ts.map +1 -1
  29. package/dist/cjs/utils/rich-text-embeds-codegen.js +15 -104
  30. package/dist/cjs/utils/rich-text-embeds-codegen.js.map +1 -1
  31. package/dist/cjs/utils/rich-text-embeds-runtime-source.d.ts +2 -0
  32. package/dist/cjs/utils/rich-text-embeds-runtime-source.d.ts.map +1 -0
  33. package/dist/cjs/utils/rich-text-embeds-runtime-source.js +409 -0
  34. package/dist/cjs/utils/rich-text-embeds-runtime-source.js.map +1 -0
  35. package/dist/cjs/utils/session-cookie-resolver.d.ts +85 -0
  36. package/dist/cjs/utils/session-cookie-resolver.d.ts.map +1 -0
  37. package/dist/cjs/utils/session-cookie-resolver.js +108 -0
  38. package/dist/cjs/utils/session-cookie-resolver.js.map +1 -0
  39. package/dist/cjs/utils/subscription-access.d.ts +36 -0
  40. package/dist/cjs/utils/subscription-access.d.ts.map +1 -0
  41. package/dist/cjs/utils/subscription-access.js +61 -0
  42. package/dist/cjs/utils/subscription-access.js.map +1 -0
  43. package/dist/cjs/utils/table-access.d.ts +114 -0
  44. package/dist/cjs/utils/table-access.d.ts.map +1 -0
  45. package/dist/cjs/utils/table-access.js +238 -0
  46. package/dist/cjs/utils/table-access.js.map +1 -0
  47. package/dist/cjs/utils/web-push.d.ts +43 -0
  48. package/dist/cjs/utils/web-push.d.ts.map +1 -0
  49. package/dist/cjs/utils/web-push.js +62 -0
  50. package/dist/cjs/utils/web-push.js.map +1 -0
  51. package/dist/esm/index.d.ts +9 -1
  52. package/dist/esm/index.d.ts.map +1 -1
  53. package/dist/esm/index.js +9 -1
  54. package/dist/esm/index.js.map +1 -1
  55. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  56. package/dist/esm/utils/cart-currency.d.ts +27 -0
  57. package/dist/esm/utils/cart-currency.d.ts.map +1 -0
  58. package/dist/esm/utils/cart-currency.js +27 -0
  59. package/dist/esm/utils/cart-currency.js.map +1 -0
  60. package/dist/esm/utils/cart-line-pricing.d.ts +25 -0
  61. package/dist/esm/utils/cart-line-pricing.d.ts.map +1 -0
  62. package/dist/esm/utils/cart-line-pricing.js +25 -0
  63. package/dist/esm/utils/cart-line-pricing.js.map +1 -0
  64. package/dist/esm/utils/discount-engine.d.ts +74 -0
  65. package/dist/esm/utils/discount-engine.d.ts.map +1 -0
  66. package/dist/esm/utils/discount-engine.js +431 -0
  67. package/dist/esm/utils/discount-engine.js.map +1 -0
  68. package/dist/esm/utils/product-options.d.ts +25 -0
  69. package/dist/esm/utils/product-options.d.ts.map +1 -0
  70. package/dist/esm/utils/product-options.js +1151 -0
  71. package/dist/esm/utils/product-options.js.map +1 -0
  72. package/dist/esm/utils/rich-text-embeds-codegen.d.ts +13 -0
  73. package/dist/esm/utils/rich-text-embeds-codegen.d.ts.map +1 -1
  74. package/dist/esm/utils/rich-text-embeds-codegen.js +16 -105
  75. package/dist/esm/utils/rich-text-embeds-codegen.js.map +1 -1
  76. package/dist/esm/utils/rich-text-embeds-runtime-source.d.ts +2 -0
  77. package/dist/esm/utils/rich-text-embeds-runtime-source.d.ts.map +1 -0
  78. package/dist/esm/utils/rich-text-embeds-runtime-source.js +406 -0
  79. package/dist/esm/utils/rich-text-embeds-runtime-source.js.map +1 -0
  80. package/dist/esm/utils/session-cookie-resolver.d.ts +85 -0
  81. package/dist/esm/utils/session-cookie-resolver.d.ts.map +1 -0
  82. package/dist/esm/utils/session-cookie-resolver.js +103 -0
  83. package/dist/esm/utils/session-cookie-resolver.js.map +1 -0
  84. package/dist/esm/utils/subscription-access.d.ts +36 -0
  85. package/dist/esm/utils/subscription-access.d.ts.map +1 -0
  86. package/dist/esm/utils/subscription-access.js +57 -0
  87. package/dist/esm/utils/subscription-access.js.map +1 -0
  88. package/dist/esm/utils/table-access.d.ts +114 -0
  89. package/dist/esm/utils/table-access.d.ts.map +1 -0
  90. package/dist/esm/utils/table-access.js +233 -0
  91. package/dist/esm/utils/table-access.js.map +1 -0
  92. package/dist/esm/utils/web-push.d.ts +43 -0
  93. package/dist/esm/utils/web-push.d.ts.map +1 -0
  94. package/dist/esm/utils/web-push.js +59 -0
  95. package/dist/esm/utils/web-push.js.map +1 -0
  96. package/package.json +3 -3
  97. package/src/index.ts +16 -0
  98. package/src/utils/cart-currency.ts +76 -0
  99. package/src/utils/cart-line-pricing.ts +151 -0
  100. package/src/utils/discount-engine.ts +437 -0
  101. package/src/utils/product-options.ts +1150 -0
  102. package/src/utils/rich-text-embeds-codegen.ts +16 -183
  103. package/src/utils/rich-text-embeds-runtime-source.ts +405 -0
  104. package/src/utils/session-cookie-resolver.ts +305 -0
  105. package/src/utils/subscription-access.ts +59 -0
  106. package/src/utils/table-access.ts +516 -0
  107. package/src/utils/web-push.ts +61 -0
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ /**
3
+ * Resolving the NextAuth session token in a generated route, WITHOUT asking
4
+ * `process.env.NEXTAUTH_URL` what the session cookie is called.
5
+ *
6
+ * ## ⛔ THE REPORTED DEFECT
7
+ *
8
+ * A published project showed "Failed to load dashboard data. Please refresh."
9
+ * on the home page, "Failed to load meeting data." on the list page and "could
10
+ * not fetch the templates" on a third — for a user who was signed in, on a page
11
+ * whose own `/api/auth/session` returned that user. The failing call was the
12
+ * page-load workflow's server segment, answering **401 `Unauthenticated`** from
13
+ * `guardWorkflowRequest`. Reloading sometimes fixed it; fanning 50 concurrent
14
+ * requests at the same route returned **33 × 401 and 17 × 200**, while 40
15
+ * concurrent `/api/auth/session` calls resolved the user 40/40.
16
+ *
17
+ * ## The mechanism
18
+ *
19
+ * next-auth v4's `getToken()` derives the cookie it reads from an env var:
20
+ *
21
+ * ```js
22
+ * secureCookie = process.env.NEXTAUTH_URL?.startsWith('https://') ?? !!process.env.VERCEL,
23
+ * cookieName = secureCookie ? '__Secure-next-auth.session-token' : 'next-auth.session-token',
24
+ * ```
25
+ *
26
+ * A generated project ships `NEXTAUTH_URL=http://localhost:3000` — the build
27
+ * cannot know the domain it will be published to, so `pages/api/auth/[...nextauth].js`
28
+ * repairs `process.env.NEXTAUTH_URL` **from the incoming request** instead. That
29
+ * repair is process-local: it fixes the value for whichever serverless instance
30
+ * happened to serve an auth request, and for no other. Every other instance
31
+ * still holds `http://localhost:3000`, so `startsWith('https://')` is `false`
32
+ * (NOT `undefined` — the `??` fallback to `!!process.env.VERCEL` never runs),
33
+ * `getToken` looks for `next-auth.session-token`, the browser is holding
34
+ * `__Secure-next-auth.session-token`, nothing is found, and a signed-in user is
35
+ * reported as anonymous.
36
+ *
37
+ * Hence the coin-flip: it depends on whether the instance that served your
38
+ * request had previously served an auth request. Nothing about the session, the
39
+ * secret or the cookie is wrong.
40
+ *
41
+ * The same defect is why the generated middleware carries a `hasSessionCookie`
42
+ * fallback and a round trip to `/api/auth/session`, attributed there to an
43
+ * "Edge runtime quirk" — the Edge process holds that same localhost value.
44
+ *
45
+ * ## The rule
46
+ *
47
+ * ⭐ **A request must be authenticated from the REQUEST, never from an ambient
48
+ * env var that describes the deployment.** So this resolver reads which session
49
+ * cookie the caller actually sent, and tells `getToken` exactly that — falling
50
+ * back to the request's own protocol when the cookie header is unavailable.
51
+ *
52
+ * Emitted as plain ES5 text and inlined into each generated route: a route that
53
+ * carries its own copy cannot be broken by a missing sibling module or an
54
+ * untraced dependency, and every call site gets the identical implementation
55
+ * from this one definition. It lives in teleport-shared because both the
56
+ * workflow routes and the per-table data routes decode the session.
57
+ */
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.generateCommonJsSessionTokenResolverCode = exports.generateSessionTokenResolverCode = exports.SESSION_TOKEN_RESOLVER_FN = void 0;
60
+ /** The `__tq`-prefixed globals the emitted snippet declares. */
61
+ exports.SESSION_TOKEN_RESOLVER_FN = '__tqResolveSessionToken';
62
+ /**
63
+ * Session-cookie names next-auth/Auth.js writes, with the `secureCookie` flag
64
+ * that produces each. Order within a protocol is irrelevant — only ONE of these
65
+ * is ever present — but the https/http split decides which pair is tried first.
66
+ *
67
+ * `__Host-` is deliberately absent: next-auth uses it only for the CSRF and
68
+ * callback cookies, never for the session token.
69
+ */
70
+ var KNOWN_SESSION_COOKIES = [
71
+ { name: '__Secure-next-auth.session-token', secure: true },
72
+ { name: 'next-auth.session-token', secure: false },
73
+ { name: '__Secure-authjs.session-token', secure: true },
74
+ { name: 'authjs.session-token', secure: false },
75
+ ];
76
+ /**
77
+ * ES5 source for the shared session-token resolver, safe to inline into any
78
+ * generated CommonJS route or ESM middleware.
79
+ *
80
+ * Declares:
81
+ * - `__tqCookieNamesPresent(req)` — the set of cookie names on the request,
82
+ * parsed properly (a substring test would see `next-auth.session-token`
83
+ * inside `__Secure-next-auth.session-token` and pick the wrong one).
84
+ * - `__tqRequestIsSecure(req)` — https as the REQUEST reports it.
85
+ * - `__tqSessionTokenCandidates(req)` — `{cookieName, secureCookie}` pairs to try.
86
+ * - `__tqResolveSessionToken(getToken, req, secret)` — the token, or null.
87
+ *
88
+ * `getToken` is passed IN rather than required here, so the same body serves a
89
+ * CommonJS route (lazy `require('next-auth/jwt')`) and the Edge middleware
90
+ * (static `import`). Pure, total, never throws, no env reads.
91
+ */
92
+ var generateSessionTokenResolverCode = function () {
93
+ return "// GENERATED \u2014 see generateSessionTokenResolverCode in\n// @teleporthq/teleport-shared/src/utils/session-cookie-resolver.ts.\n//\n// next-auth's getToken() picks the session cookie name from\n// process.env.NEXTAUTH_URL, which a generated project ships as a localhost\n// default \u2014 so on a published https domain it looks for the non-secure cookie\n// and reports a signed-in user as anonymous. These helpers read the cookie the\n// request actually carries and tell getToken exactly which one it is.\nvar __TQ_SESSION_COOKIES = ".concat(JSON.stringify(KNOWN_SESSION_COOKIES.map(function (c) { return [c.name, c.secure]; })), ";\n\n// Every cookie NAME on the request. Parsed on the \"; \" separator rather than\n// matched as a substring: \"next-auth.session-token\" occurs inside\n// \"__Secure-next-auth.session-token\", so a substring test reports the plain\n// cookie as present whenever the secure one is, and getToken then decodes the\n// wrong (missing) cookie.\nfunction __tqCookieNamesPresent(req) {\n var present = {};\n var raw = '';\n try {\n var headers = req && req.headers;\n if (headers && typeof headers.get === 'function') {\n // Edge / fetch Headers.\n raw = headers.get('cookie') || '';\n } else if (headers) {\n var value = headers.cookie || headers.Cookie;\n raw = Array.isArray(value) ? value.join('; ') : value || '';\n }\n } catch (e) {\n raw = '';\n }\n var parts = String(raw).split(';');\n for (var i = 0; i < parts.length; i++) {\n var eq = parts[i].indexOf('=');\n if (eq > 0) {\n present[parts[i].slice(0, eq).trim()] = true;\n }\n }\n // Second source: the runtime's own parsed cookies. A Next.js API route hands\n // over a plain object; the Edge RequestCookies exposes getAll().\n try {\n var cookies = req && req.cookies;\n if (cookies && typeof cookies.getAll === 'function') {\n var all = cookies.getAll() || [];\n for (var a = 0; a < all.length; a++) {\n if (all[a] && all[a].name) {\n present[all[a].name] = true;\n }\n }\n } else if (cookies && typeof cookies === 'object') {\n for (var key in cookies) {\n if (Object.prototype.hasOwnProperty.call(cookies, key)) {\n present[key] = true;\n }\n }\n }\n } catch (e) {\n /* the header pass above is enough */\n }\n return present;\n}\n\n// Is this request https? Read from the proxy header the platform sets, then the\n// socket, then the URL \u2014 never from an env var describing the deployment.\nfunction __tqRequestIsSecure(req) {\n try {\n var headers = (req && req.headers) || {};\n var proto =\n typeof headers.get === 'function'\n ? headers.get('x-forwarded-proto')\n : headers['x-forwarded-proto'];\n if (Array.isArray(proto)) {\n proto = proto[0];\n }\n if (typeof proto === 'string' && proto) {\n // A chain of proxies appends: \"https,http\" \u2014 the client-facing hop is first.\n return proto.split(',')[0].trim().toLowerCase() === 'https';\n }\n if (req && req.nextUrl && req.nextUrl.protocol === 'https:') {\n return true;\n }\n if (req && req.socket && req.socket.encrypted) {\n return true;\n }\n if (req && req.connection && req.connection.encrypted) {\n return true;\n }\n if (typeof (req && req.url) === 'string' && req.url.indexOf('https://') === 0) {\n return true;\n }\n } catch (e) {\n /* fall through to false */\n }\n return false;\n}\n\n// The { cookieName, secureCookie } pairs worth trying, most likely first.\n// Normally exactly one cookie is present and this returns a single entry; the\n// list only grows when a site has been reached over both http and https.\nfunction __tqSessionTokenCandidates(req) {\n var present = __tqCookieNamesPresent(req);\n var secure = __tqRequestIsSecure(req);\n var candidates = [];\n for (var i = 0; i < __TQ_SESSION_COOKIES.length; i++) {\n var name = __TQ_SESSION_COOKIES[i][0];\n var isSecureName = __TQ_SESSION_COOKIES[i][1];\n if (present[name] || present[name + '.0']) {\n candidates.push({ cookieName: name, secureCookie: isSecureName });\n }\n }\n candidates.sort(function (a, b) {\n if (a.secureCookie === b.secureCookie) {\n return 0;\n }\n return a.secureCookie === secure ? -1 : 1;\n });\n if (candidates.length === 0) {\n // No cookie we recognise. The project may configure a custom cookie name in\n // authOptions, so hand getToken the request's own protocol and let it apply\n // its defaults \u2014 same behaviour as before this resolver existed, minus the\n // localhost env var.\n candidates.push({ secureCookie: secure });\n }\n return candidates;\n}\n\n// When a browser carries BOTH session cookies, both usually decode \u2014 they are\n// two snapshots of the same account written at different times (NextAuth\n// switches between the plain and the \"__Secure-\" name with the origin it\n// derives, and never clears the other one). The one NextAuth is maintaining is\n// re-issued on every session read, so it is the most recently issued; the\n// other is frozen at whatever the account looked like when NextAuth last used\n// that name, role included. Trusting it by protocol alone made a promoted admin\n// read as \"user\" at the edge while /api/auth/session said \"admin\".\nfunction __tqTokenIssuedAt(token) {\n var issued = token && typeof token === 'object' ? token.iat : null;\n return typeof issued === 'number' && isFinite(issued) ? issued : 0;\n}\n\n// The decoded session token, or null. Decodes every candidate cookie (normally\n// exactly one) and keeps the most recently issued, so a project that renamed\n// its cookie, or one reached over both protocols, resolves to the session\n// NextAuth is actually maintaining. A tie keeps the protocol-matching\n// candidate. Never throws.\nfunction ").concat(exports.SESSION_TOKEN_RESOLVER_FN, "(getToken, req, secret) {\n if (!secret || typeof getToken !== 'function') {\n return Promise.resolve(null);\n }\n var candidates = __tqSessionTokenCandidates(req);\n var decode = function (candidate) {\n var params = { req: req, secret: secret, secureCookie: candidate.secureCookie };\n if (candidate.cookieName) {\n params.cookieName = candidate.cookieName;\n }\n return Promise.resolve()\n .then(function () {\n return getToken(params);\n })\n .catch(function () {\n return null;\n });\n };\n var decodes = [];\n for (var i = 0; i < candidates.length; i++) {\n decodes.push(decode(candidates[i]));\n }\n return Promise.all(decodes)\n .then(function (tokens) {\n var best = null;\n for (var t = 0; t < tokens.length; t++) {\n if (tokens[t] && (best === null || __tqTokenIssuedAt(tokens[t]) > __tqTokenIssuedAt(best))) {\n best = tokens[t];\n }\n }\n return best;\n })\n .catch(function () {\n return null;\n });\n}\n");
94
+ };
95
+ exports.generateSessionTokenResolverCode = generateSessionTokenResolverCode;
96
+ /**
97
+ * The resolver plus a lazy `require('next-auth/jwt')`, for a CommonJS route.
98
+ *
99
+ * The require is lazy and guarded because a project generated WITHOUT
100
+ * authentication does not ship `next-auth`; such a project also has no session
101
+ * to read, so resolving to `null` there is the correct answer rather than a
102
+ * module-load crash.
103
+ */
104
+ var generateCommonJsSessionTokenResolverCode = function () {
105
+ return "".concat((0, exports.generateSessionTokenResolverCode)(), "\n// Lazy + guarded: a project generated without authentication has no next-auth\n// installed, and no session to read either \u2014 null is the right answer there.\nfunction __tqGetTokenFn() {\n try {\n var jwt = require('next-auth/jwt');\n return jwt && typeof jwt.getToken === 'function' ? jwt.getToken : null;\n } catch (e) {\n return null;\n }\n}\n\n// The session token for this request, or null. Reads NEXTAUTH_SECRET itself.\nfunction __tqSessionToken(req) {\n var secret = process.env.NEXTAUTH_SECRET;\n if (!secret) {\n return Promise.resolve(null);\n }\n return ").concat(exports.SESSION_TOKEN_RESOLVER_FN, "(__tqGetTokenFn(), req, secret);\n}\n");
106
+ };
107
+ exports.generateCommonJsSessionTokenResolverCode = generateCommonJsSessionTokenResolverCode;
108
+ //# sourceMappingURL=session-cookie-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-cookie-resolver.js","sourceRoot":"","sources":["../../../src/utils/session-cookie-resolver.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;;;AAEH,gEAAgE;AACnD,QAAA,yBAAyB,GAAG,yBAAyB,CAAA;AAElE;;;;;;;GAOG;AACH,IAAM,qBAAqB,GAAqD;IAC9E,EAAE,IAAI,EAAE,kCAAkC,EAAE,MAAM,EAAE,IAAI,EAAE;IAC1D,EAAE,IAAI,EAAE,yBAAyB,EAAE,MAAM,EAAE,KAAK,EAAE;IAClD,EAAE,IAAI,EAAE,+BAA+B,EAAE,MAAM,EAAE,IAAI,EAAE;IACvD,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,KAAK,EAAE;CAChD,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACI,IAAM,gCAAgC,GAAG;IAC9C,OAAO,qiBAQoB,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,GAAG,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAlB,CAAkB,CAAC,CAAC,mqKAuItF,iCAAyB,yhCAoCnC,CAAA;AACD,CAAC,CAAA;AArLY,QAAA,gCAAgC,oCAqL5C;AAED;;;;;;;GAOG;AACI,IAAM,wCAAwC,GAAG;IACtD,OAAO,UAAG,IAAA,wCAAgC,GAAE,6lBAkBnC,iCAAyB,0CAEnC,CAAA;AACD,CAAC,CAAA;AAtBY,QAAA,wCAAwC,4CAsBpD"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Which subscription rows entitle a buyer to a subscriber-only page.
3
+ *
4
+ * `teleport_subscriptions.status` moves through `pending` (the first order is
5
+ * placed, the provider has not confirmed the mandate), `trialing`, `active`,
6
+ * `past_due` (a renewal charge failed and the provider retries), `paused`,
7
+ * `cancelled` and `expired`. A buyer keeps their content while the provider is
8
+ * still honouring the mandate — through a trial, an active period and the
9
+ * retry window of a failed charge. `pending` never confirmed, `paused` stopped
10
+ * collecting, and the two closed statuses are over. A row with
11
+ * `cancel_at_period_end` stays entitled: its status only turns `cancelled`
12
+ * once the provider ends it at the period end.
13
+ *
14
+ * The GUI keeps the same list as `ENTITLED_SUBSCRIPTION_STATUS_VALUES`
15
+ * (`features/e-commerce/constants/subscriptions.ts`), pinned by a parity spec.
16
+ */
17
+ export declare const SUBSCRIPTION_ENTITLED_STATUSES: ReadonlyArray<string>;
18
+ export declare const SUBSCRIPTIONS_TABLE = "teleport_subscriptions";
19
+ /**
20
+ * The one parameterised statement every entitlement check runs:
21
+ * $1 — the session user id (compared as text: auth ids are strings, the
22
+ * column is a uuid),
23
+ * $2 — the entitled statuses,
24
+ * $3 — the product ids the page names; an EMPTY array means any product.
25
+ *
26
+ * A subscription the STORE bills (a provider that cannot charge a saved
27
+ * payment method) carries `access_until`: the end of the paid period plus the
28
+ * grace for the next payment. Past it the row stops entitling on its own, so
29
+ * access ends on time even when the scheduled billing run is late or off.
30
+ * Read through `to_jsonb` so a store whose table predates the column still
31
+ * answers (a missing key reads NULL, which never limits).
32
+ */
33
+ export declare const SUBSCRIPTION_ENTITLEMENT_SQL: string;
34
+ /** Trimmed, de-duplicated, non-empty ids — whatever shape the caller had them in. */
35
+ export declare const normalizeSubscriptionProductIds: (value: unknown) => string[];
36
+ //# sourceMappingURL=subscription-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-access.d.ts","sourceRoot":"","sources":["../../../src/utils/subscription-access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,8BAA8B,EAAE,aAAa,CAAC,MAAM,CAIhE,CAAA;AAED,eAAO,MAAM,mBAAmB,2BAA2B,CAAA;AAE3D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B,QAK0B,CAAA;AAEnE,qFAAqF;AACrF,eAAO,MAAM,+BAA+B,UAAW,OAAO,KAAG,MAAM,EAYtE,CAAA"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeSubscriptionProductIds = exports.SUBSCRIPTION_ENTITLEMENT_SQL = exports.SUBSCRIPTIONS_TABLE = exports.SUBSCRIPTION_ENTITLED_STATUSES = void 0;
4
+ /**
5
+ * Which subscription rows entitle a buyer to a subscriber-only page.
6
+ *
7
+ * `teleport_subscriptions.status` moves through `pending` (the first order is
8
+ * placed, the provider has not confirmed the mandate), `trialing`, `active`,
9
+ * `past_due` (a renewal charge failed and the provider retries), `paused`,
10
+ * `cancelled` and `expired`. A buyer keeps their content while the provider is
11
+ * still honouring the mandate — through a trial, an active period and the
12
+ * retry window of a failed charge. `pending` never confirmed, `paused` stopped
13
+ * collecting, and the two closed statuses are over. A row with
14
+ * `cancel_at_period_end` stays entitled: its status only turns `cancelled`
15
+ * once the provider ends it at the period end.
16
+ *
17
+ * The GUI keeps the same list as `ENTITLED_SUBSCRIPTION_STATUS_VALUES`
18
+ * (`features/e-commerce/constants/subscriptions.ts`), pinned by a parity spec.
19
+ */
20
+ exports.SUBSCRIPTION_ENTITLED_STATUSES = [
21
+ 'trialing',
22
+ 'active',
23
+ 'past_due',
24
+ ];
25
+ exports.SUBSCRIPTIONS_TABLE = 'teleport_subscriptions';
26
+ /**
27
+ * The one parameterised statement every entitlement check runs:
28
+ * $1 — the session user id (compared as text: auth ids are strings, the
29
+ * column is a uuid),
30
+ * $2 — the entitled statuses,
31
+ * $3 — the product ids the page names; an EMPTY array means any product.
32
+ *
33
+ * A subscription the STORE bills (a provider that cannot charge a saved
34
+ * payment method) carries `access_until`: the end of the paid period plus the
35
+ * grace for the next payment. Past it the row stops entitling on its own, so
36
+ * access ends on time even when the scheduled billing run is late or off.
37
+ * Read through `to_jsonb` so a store whose table predates the column still
38
+ * answers (a missing key reads NULL, which never limits).
39
+ */
40
+ exports.SUBSCRIPTION_ENTITLEMENT_SQL = "SELECT 1 FROM ".concat(exports.SUBSCRIPTIONS_TABLE, " s ") +
41
+ 'WHERE s.user_id::text = $1 AND s.status = ANY($2::text[]) ' +
42
+ 'AND (cardinality($3::text[]) = 0 OR s.product_id::text = ANY($3::text[])) ' +
43
+ "AND ((to_jsonb(s) ->> 'access_until') IS NULL " +
44
+ "OR (to_jsonb(s) ->> 'access_until')::timestamp > NOW()) LIMIT 1";
45
+ /** Trimmed, de-duplicated, non-empty ids — whatever shape the caller had them in. */
46
+ var normalizeSubscriptionProductIds = function (value) {
47
+ var raw = Array.isArray(value) ? value : typeof value === 'string' ? value.split(',') : [];
48
+ var seen = new Set();
49
+ var ids = [];
50
+ for (var _i = 0, raw_1 = raw; _i < raw_1.length; _i++) {
51
+ var entry = raw_1[_i];
52
+ var id = String(entry !== null && entry !== void 0 ? entry : '').trim();
53
+ if (id && !seen.has(id)) {
54
+ seen.add(id);
55
+ ids.push(id);
56
+ }
57
+ }
58
+ return ids;
59
+ };
60
+ exports.normalizeSubscriptionProductIds = normalizeSubscriptionProductIds;
61
+ //# sourceMappingURL=subscription-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-access.js","sourceRoot":"","sources":["../../../src/utils/subscription-access.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;GAeG;AACU,QAAA,8BAA8B,GAA0B;IACnE,UAAU;IACV,QAAQ;IACR,UAAU;CACX,CAAA;AAEY,QAAA,mBAAmB,GAAG,wBAAwB,CAAA;AAE3D;;;;;;;;;;;;;GAaG;AACU,QAAA,4BAA4B,GACvC,wBAAiB,2BAAmB,QAAK;IACzC,4DAA4D;IAC5D,4EAA4E;IAC5E,gDAAgD;IAChD,iEAAiE,CAAA;AAEnE,qFAAqF;AAC9E,IAAM,+BAA+B,GAAG,UAAC,KAAc;IAC5D,IAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5F,IAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,IAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAoB,UAAG,EAAH,WAAG,EAAH,iBAAG,EAAH,IAAG,EAAE;QAApB,IAAM,KAAK,YAAA;QACd,IAAM,EAAE,GAAG,MAAM,CAAC,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACrC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;YACvB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACZ,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;SACb;KACF;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAZY,QAAA,+BAA+B,mCAY3C"}
@@ -0,0 +1,114 @@
1
+ import type { UIDLAuthentication } from '@teleporthq/teleport-types';
2
+ /**
3
+ * Which store tables a browser may touch through the generated data routes.
4
+ *
5
+ * Every generated Next store exposes two families of data routes: the
6
+ * workflow data API (`/api/data/<source>/<op>`, called by workflow data nodes)
7
+ * and one read route per bound table (`/api/teleport-<table>-<source>`, called
8
+ * by the pages' data providers, with a `rawQuery` escape hatch). Both accept
9
+ * a browser's request. For most tables that is the product: a catalogue is
10
+ * public. For the tables below it is theft.
11
+ *
12
+ * `MONEY_TABLES` are money and the keys to it: a gift card's code and
13
+ * balance, its ledger, every voucher code (a generated batch is a list of
14
+ * single-use coupons) and the redemption ledger. A browser reads or writes
15
+ * them only as a signed-in member of a trusted role (the generated admin) —
16
+ * the server-side workflow segments that look a card up, debit it or redeem a
17
+ * code identify themselves with the app secret instead.
18
+ *
19
+ * `CUSTOMER_RECORD_TABLES` are the customers' own records: who ordered what
20
+ * and where it ships, their invoices, parcels, returns, carts and reviews,
21
+ * and the accounts themselves. A buyer is served their own order through the
22
+ * page-load workflow (a server segment), the merchant every order through
23
+ * the admin's session — nothing in a generated storefront lists them from
24
+ * the browser, so a browser that asks for the list is not the store.
25
+ *
26
+ * `PROTECTED_TABLES` is both, spliced into both route families and the
27
+ * restricted-caller SQL check, so no reader can disagree about what is
28
+ * private.
29
+ *
30
+ * `WRITE_PROTECTED_TABLES` decide how much is given away but must stay readable:
31
+ * the storefront provider loads the active automatic rules to price the order
32
+ * summary. A browser that could update `usage_count` or `discount_value` could
33
+ * discount forever, so writes need the same trust.
34
+ */
35
+ export declare const MONEY_TABLES: ReadonlyArray<string>;
36
+ export declare const CUSTOMER_RECORD_TABLES: ReadonlyArray<string>;
37
+ export declare const PROTECTED_TABLES: ReadonlyArray<string>;
38
+ export declare const WRITE_PROTECTED_TABLES: ReadonlyArray<string>;
39
+ /**
40
+ * The auth users table's CREDENTIALS — the password hash and the OAuth
41
+ * provider's tokens (an access or refresh token IS the user's account at the
42
+ * provider). Profile columns stay readable; these never leave the server in a
43
+ * row, whoever asks: the generated admin's users list shows no password either.
44
+ */
45
+ export declare const AUTH_CREDENTIAL_COLUMNS: ReadonlyArray<string>;
46
+ /** The table the generated auth reads and writes those credentials in. */
47
+ export declare const AUTH_CREDENTIAL_TABLES: ReadonlyArray<string>;
48
+ export interface PublicTableFeed {
49
+ /** The only columns a browser is ever served. */
50
+ columns: ReadonlyArray<string>;
51
+ /** SQL every browser read of the table is narrowed with. */
52
+ predicate: string;
53
+ }
54
+ /**
55
+ * A table a browser may read ONLY as a fixed projection of the rows the
56
+ * DATABASE considers live — never whole, never through raw SQL, and never
57
+ * filtered or sorted by a column outside the projection (a filter is an
58
+ * oracle: `usage_count > 100` answers for a column that is never returned).
59
+ *
60
+ * `teleport_discounts` is the merchant's campaign plan: drafts, paused rules,
61
+ * launches scheduled for next month, how often each has been used. The
62
+ * storefront needs none of that; it prices the order summary from the rules
63
+ * that apply right now. Serving exactly those rows is what keeps an
64
+ * unannounced campaign unannounced, and it makes the WINDOW the database's
65
+ * judgement, the same clock that re-prices the order at submit: the storefront
66
+ * prices every row it is served and re-derives nothing from the browser clock,
67
+ * so a disagreement with the server's re-pricing is resolved by re-reading the
68
+ * feed.
69
+ */
70
+ export declare const PUBLIC_FEED_TABLES: Readonly<Record<string, PublicTableFeed>>;
71
+ /**
72
+ * The roles allowed to read a protected table from a browser: whoever may open
73
+ * the generated admin panel, read off the protection the UIDL records for its
74
+ * folder and pages. A project without an admin panel has no page that binds a
75
+ * money table, so the empty list (server-side callers only) is the right answer.
76
+ */
77
+ export declare const resolveTrustedReaderRoles: (auth?: UIDLAuthentication | null) => string[];
78
+ /**
79
+ * ES5 source of the guard, inlined into a generated route. Declares:
80
+ * - `__taIsInternalRequest(req)` — the caller presented the app secret, or
81
+ * is the module's own server-side `fetchData` (`req.__tqServerCall`).
82
+ * - `__taIsRestrictedRequest(req)` — a server-side node that presents the app
83
+ * secret but runs SQL it did not write (the AI data-query node): trusted to
84
+ * run raw SQL, never trusted with the protected tables.
85
+ * - `__taBrowserIsReadOnly(req)` — true for a browser in an app that has an
86
+ * app secret: every server-side caller identifies itself there, so a browser
87
+ * reads through the structured routes only — no raw SQL, no writes.
88
+ * - `__taNormalizeTable(name)` — the bare table a spelling names.
89
+ * - `__taSqlMentionsTable(sql, table)` — the statement names the table as an
90
+ * identifier (bare, double-quoted or schema-qualified; comments and string
91
+ * literals cannot hide or fake a mention).
92
+ * - `__taAssertSqlTextAllowed(sql)` — throws a 403 error when an untrusted
93
+ * caller's raw statement touches a protected table or a feed table, or
94
+ * writes a write-protected one.
95
+ * - `__taPublicFeed(req, tableName)` — the projection an untrusted caller is
96
+ * served of a feed table (`{ columns, predicate }`), or `null` when the
97
+ * caller may read the table as asked (a server-side one) or the table has
98
+ * no feed.
99
+ * - `__taAssertFeedFields(tableName, feed, fields)` — throws a 403 error when
100
+ * a filter or sort field is not one of the feed's columns.
101
+ * - `__taWithoutCredentials(rows, tableName)` — the rows with the auth users
102
+ * table's credential columns removed: all of them for that table's rows,
103
+ * the unambiguous ones for a raw statement's (`tableName` null).
104
+ * - `__taIsCredentialTable(tableName)` — the table holds those credentials.
105
+ * - `__taGuardRead(req, tableName, rawQuery)` — for the per-table read routes:
106
+ * resolves to `null` when the request may proceed, else `{status, message}`.
107
+ * A protected table is readable by an internal caller or by a session whose
108
+ * role is in `trustedReaderRoles`; the session is decoded by the
109
+ * `__tqSessionToken` resolver, which the host route must inline as well.
110
+ */
111
+ export declare const generateTableAccessHelperCode: (params: {
112
+ trustedReaderRoles: ReadonlyArray<string>;
113
+ }) => string;
114
+ //# sourceMappingURL=table-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-access.d.ts","sourceRoot":"","sources":["../../../src/utils/table-access.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,MAAM,CAiB9C,CAAA;AAED,eAAO,MAAM,sBAAsB,EAAE,aAAa,CAAC,MAAM,CAqCxD,CAAA;AAED,eAAO,MAAM,gBAAgB,EAAE,aAAa,CAAC,MAAM,CAAgD,CAAA;AAEnG,eAAO,MAAM,sBAAsB,EAAE,aAAa,CAAC,MAAM,CAA0B,CAAA;AAEnF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC,MAAM,CASzD,CAAA;AAED,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,EAAE,aAAa,CAAC,MAAM,CAAa,CAAA;AAYtE,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC9B,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CA0BxE,CAAA;AAMD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,UAAW,kBAAkB,GAAG,IAAI,KAAG,MAAM,EAiBlF,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,6BAA6B,WAAY;IACpD,kBAAkB,EAAE,cAAc,MAAM,CAAC,CAAA;CAC1C,KAAG,MAiRH,CAAA"}
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
3
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
4
+ if (ar || !(i in from)) {
5
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
6
+ ar[i] = from[i];
7
+ }
8
+ }
9
+ return to.concat(ar || Array.prototype.slice.call(from));
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.generateTableAccessHelperCode = exports.resolveTrustedReaderRoles = exports.PUBLIC_FEED_TABLES = exports.AUTH_CREDENTIAL_TABLES = exports.AUTH_CREDENTIAL_COLUMNS = exports.WRITE_PROTECTED_TABLES = exports.PROTECTED_TABLES = exports.CUSTOMER_RECORD_TABLES = exports.MONEY_TABLES = void 0;
13
+ /**
14
+ * Which store tables a browser may touch through the generated data routes.
15
+ *
16
+ * Every generated Next store exposes two families of data routes: the
17
+ * workflow data API (`/api/data/<source>/<op>`, called by workflow data nodes)
18
+ * and one read route per bound table (`/api/teleport-<table>-<source>`, called
19
+ * by the pages' data providers, with a `rawQuery` escape hatch). Both accept
20
+ * a browser's request. For most tables that is the product: a catalogue is
21
+ * public. For the tables below it is theft.
22
+ *
23
+ * `MONEY_TABLES` are money and the keys to it: a gift card's code and
24
+ * balance, its ledger, every voucher code (a generated batch is a list of
25
+ * single-use coupons) and the redemption ledger. A browser reads or writes
26
+ * them only as a signed-in member of a trusted role (the generated admin) —
27
+ * the server-side workflow segments that look a card up, debit it or redeem a
28
+ * code identify themselves with the app secret instead.
29
+ *
30
+ * `CUSTOMER_RECORD_TABLES` are the customers' own records: who ordered what
31
+ * and where it ships, their invoices, parcels, returns, carts and reviews,
32
+ * and the accounts themselves. A buyer is served their own order through the
33
+ * page-load workflow (a server segment), the merchant every order through
34
+ * the admin's session — nothing in a generated storefront lists them from
35
+ * the browser, so a browser that asks for the list is not the store.
36
+ *
37
+ * `PROTECTED_TABLES` is both, spliced into both route families and the
38
+ * restricted-caller SQL check, so no reader can disagree about what is
39
+ * private.
40
+ *
41
+ * `WRITE_PROTECTED_TABLES` decide how much is given away but must stay readable:
42
+ * the storefront provider loads the active automatic rules to price the order
43
+ * summary. A browser that could update `usage_count` or `discount_value` could
44
+ * discount forever, so writes need the same trust.
45
+ */
46
+ exports.MONEY_TABLES = [
47
+ 'teleport_gift_cards',
48
+ 'teleport_gift_card_transactions',
49
+ 'teleport_vouchers',
50
+ 'teleport_voucher_redemptions',
51
+ // The sent-email log keeps the RENDERED message, and a gift-card email
52
+ // carries the full code — protecting the card table while serving its
53
+ // codes from the email log would be no protection at all.
54
+ 'teleport_sent_emails',
55
+ // A live reset token IS the account: whoever reads one sets the password.
56
+ // Only the server-side reset workflows ever read or write the table.
57
+ 'password_reset_tokens',
58
+ // A digital product's files and license keys ARE the product: the order
59
+ // page's server segment mints a download link per paid line, and the
60
+ // download route assigns a key; nothing else reads them.
61
+ 'teleport_product_files',
62
+ 'teleport_license_keys',
63
+ ];
64
+ exports.CUSTOMER_RECORD_TABLES = [
65
+ 'users',
66
+ 'teleport_orders',
67
+ 'teleport_order_items',
68
+ 'teleport_invoices',
69
+ 'teleport_invoice_items',
70
+ 'teleport_events',
71
+ 'teleport_shipments',
72
+ 'teleport_shipment_items',
73
+ 'teleport_withdrawal_requests',
74
+ 'teleport_withdrawal_request_items',
75
+ 'teleport_abandoned_carts',
76
+ 'teleport_cart',
77
+ 'teleport_cart_items',
78
+ 'teleport_favourites',
79
+ // Who subscribed to what and what they downloaded: the buyer's own page reads
80
+ // them through a server segment, the admin through its session.
81
+ 'teleport_subscriptions',
82
+ 'teleport_downloads',
83
+ // A review is public once approved, but the row carries the reviewer's
84
+ // email and the ones still in moderation; the product page reads the
85
+ // approved ones server-side.
86
+ 'teleport_product_reviews',
87
+ // A push subscription is a capability: whoever holds the endpoint and its
88
+ // keys can notify that device. Only the app's push routes read the table.
89
+ 'teleport_push_subscriptions',
90
+ // A comment is public once approved, but the row carries the commenter's
91
+ // email and the comments still waiting for approval; the post page reads
92
+ // the approved ones server-side, public columns only.
93
+ 'teleport_blog_comments',
94
+ // What visitors told the AI assistant, and the merchant's private knowledge
95
+ // base it answers from: the assistant reads them in its own server
96
+ // segments, the admin lists them with its session.
97
+ 'teleport_ai_chat_knowledge_sources',
98
+ 'teleport_ai_chat_documents',
99
+ 'teleport_ai_chat_conversations',
100
+ 'teleport_ai_chat_messages',
101
+ ];
102
+ exports.PROTECTED_TABLES = __spreadArray(__spreadArray([], exports.MONEY_TABLES, true), exports.CUSTOMER_RECORD_TABLES, true);
103
+ exports.WRITE_PROTECTED_TABLES = ['teleport_discounts'];
104
+ /**
105
+ * The auth users table's CREDENTIALS — the password hash and the OAuth
106
+ * provider's tokens (an access or refresh token IS the user's account at the
107
+ * provider). Profile columns stay readable; these never leave the server in a
108
+ * row, whoever asks: the generated admin's users list shows no password either.
109
+ */
110
+ exports.AUTH_CREDENTIAL_COLUMNS = [
111
+ 'password',
112
+ 'access_token',
113
+ 'refresh_token',
114
+ 'id_token',
115
+ 'session_state',
116
+ 'token_type',
117
+ 'scope',
118
+ 'expires_at',
119
+ ];
120
+ /** The table the generated auth reads and writes those credentials in. */
121
+ exports.AUTH_CREDENTIAL_TABLES = ['users'];
122
+ /**
123
+ * The credential names no other table has a reason to use: dropped from the
124
+ * rows of a raw statement too, where a column cannot be traced to its table
125
+ * (`scope` and `expires_at` are ordinary columns elsewhere — a gift card
126
+ * expires).
127
+ */
128
+ var UNAMBIGUOUS_CREDENTIAL_COLUMNS = exports.AUTH_CREDENTIAL_COLUMNS.filter(function (column) { return column !== 'scope' && column !== 'expires_at'; });
129
+ /**
130
+ * A table a browser may read ONLY as a fixed projection of the rows the
131
+ * DATABASE considers live — never whole, never through raw SQL, and never
132
+ * filtered or sorted by a column outside the projection (a filter is an
133
+ * oracle: `usage_count > 100` answers for a column that is never returned).
134
+ *
135
+ * `teleport_discounts` is the merchant's campaign plan: drafts, paused rules,
136
+ * launches scheduled for next month, how often each has been used. The
137
+ * storefront needs none of that; it prices the order summary from the rules
138
+ * that apply right now. Serving exactly those rows is what keeps an
139
+ * unannounced campaign unannounced, and it makes the WINDOW the database's
140
+ * judgement, the same clock that re-prices the order at submit: the storefront
141
+ * prices every row it is served and re-derives nothing from the browser clock,
142
+ * so a disagreement with the server's re-pricing is resolved by re-reading the
143
+ * feed.
144
+ */
145
+ exports.PUBLIC_FEED_TABLES = {
146
+ teleport_discounts: {
147
+ // Exactly what the shared engine normalises a rule from (`__deNormalizeRule`),
148
+ // plus `status`, which the storefront still checks. The window bounds,
149
+ // `description` and `usage_count` are the merchant's, and stay with the
150
+ // merchant.
151
+ columns: [
152
+ 'id',
153
+ 'name',
154
+ 'status',
155
+ 'priority',
156
+ 'discount_type',
157
+ 'discount_value',
158
+ 'applies_to_all_products',
159
+ 'product_ids',
160
+ 'category_ids',
161
+ 'min_order_value',
162
+ 'min_quantity',
163
+ 'first_order_only',
164
+ 'buy_quantity',
165
+ 'get_quantity',
166
+ 'stackable',
167
+ ],
168
+ predicate: "status = 'active' AND (starts_at IS NULL OR starts_at <= NOW()) AND (ends_at IS NULL OR ends_at > NOW())",
169
+ },
170
+ };
171
+ /** The generated back office: its folder in the pages tree and its route prefix. */
172
+ var ADMIN_FOLDER_NAME = 'admin-panel';
173
+ var ADMIN_ROUTE_PREFIX = '/admin';
174
+ /**
175
+ * The roles allowed to read a protected table from a browser: whoever may open
176
+ * the generated admin panel, read off the protection the UIDL records for its
177
+ * folder and pages. A project without an admin panel has no page that binds a
178
+ * money table, so the empty list (server-side callers only) is the right answer.
179
+ */
180
+ var resolveTrustedReaderRoles = function (auth) {
181
+ var roles = new Set();
182
+ if (!auth) {
183
+ return [];
184
+ }
185
+ Object.values(auth.folderProtection || {}).forEach(function (folder) {
186
+ if (folder && folder.folderName === ADMIN_FOLDER_NAME) {
187
+ ;
188
+ (folder.allowedRoles || []).forEach(function (role) { return roles.add(role); });
189
+ }
190
+ });
191
+ Object.values(auth.pageProtection || {}).forEach(function (page) {
192
+ var route = page && typeof page.route === 'string' ? page.route : '';
193
+ if (route === ADMIN_ROUTE_PREFIX || route.startsWith("".concat(ADMIN_ROUTE_PREFIX, "/"))) {
194
+ ;
195
+ (page.allowedRoles || []).forEach(function (role) { return roles.add(role); });
196
+ }
197
+ });
198
+ return Array.from(roles).sort();
199
+ };
200
+ exports.resolveTrustedReaderRoles = resolveTrustedReaderRoles;
201
+ /**
202
+ * ES5 source of the guard, inlined into a generated route. Declares:
203
+ * - `__taIsInternalRequest(req)` — the caller presented the app secret, or
204
+ * is the module's own server-side `fetchData` (`req.__tqServerCall`).
205
+ * - `__taIsRestrictedRequest(req)` — a server-side node that presents the app
206
+ * secret but runs SQL it did not write (the AI data-query node): trusted to
207
+ * run raw SQL, never trusted with the protected tables.
208
+ * - `__taBrowserIsReadOnly(req)` — true for a browser in an app that has an
209
+ * app secret: every server-side caller identifies itself there, so a browser
210
+ * reads through the structured routes only — no raw SQL, no writes.
211
+ * - `__taNormalizeTable(name)` — the bare table a spelling names.
212
+ * - `__taSqlMentionsTable(sql, table)` — the statement names the table as an
213
+ * identifier (bare, double-quoted or schema-qualified; comments and string
214
+ * literals cannot hide or fake a mention).
215
+ * - `__taAssertSqlTextAllowed(sql)` — throws a 403 error when an untrusted
216
+ * caller's raw statement touches a protected table or a feed table, or
217
+ * writes a write-protected one.
218
+ * - `__taPublicFeed(req, tableName)` — the projection an untrusted caller is
219
+ * served of a feed table (`{ columns, predicate }`), or `null` when the
220
+ * caller may read the table as asked (a server-side one) or the table has
221
+ * no feed.
222
+ * - `__taAssertFeedFields(tableName, feed, fields)` — throws a 403 error when
223
+ * a filter or sort field is not one of the feed's columns.
224
+ * - `__taWithoutCredentials(rows, tableName)` — the rows with the auth users
225
+ * table's credential columns removed: all of them for that table's rows,
226
+ * the unambiguous ones for a raw statement's (`tableName` null).
227
+ * - `__taIsCredentialTable(tableName)` — the table holds those credentials.
228
+ * - `__taGuardRead(req, tableName, rawQuery)` — for the per-table read routes:
229
+ * resolves to `null` when the request may proceed, else `{status, message}`.
230
+ * A protected table is readable by an internal caller or by a session whose
231
+ * role is in `trustedReaderRoles`; the session is decoded by the
232
+ * `__tqSessionToken` resolver, which the host route must inline as well.
233
+ */
234
+ var generateTableAccessHelperCode = function (params) {
235
+ return "// GENERATED \u2014 see generateTableAccessHelperCode in\n// @teleporthq/teleport-shared/src/utils/table-access.ts.\n//\n// Tables that ARE money, and tables that decide how much of it is given away.\n// A browser reads the first group only as a trusted role (the generated admin)\n// and writes the second group never; server-side workflow segments present the\n// app secret instead. One list for both data-route families.\nvar __TA_PROTECTED_TABLES = ".concat(JSON.stringify(exports.PROTECTED_TABLES), ";\nvar __TA_WRITE_PROTECTED_TABLES = ").concat(JSON.stringify(exports.WRITE_PROTECTED_TABLES), ";\n// Readable from a browser only as this projection of the rows the database\n// calls live \u2014 see PUBLIC_FEED_TABLES. The per-table routes serve a whole\n// table (and raw SQL), which cannot express a feed, so there they are refused\n// like a protected table.\nvar __TA_PUBLIC_FEED_TABLES = ").concat(JSON.stringify(exports.PUBLIC_FEED_TABLES), ";\nvar __TA_FEED_TABLES = Object.keys(__TA_PUBLIC_FEED_TABLES);\nvar __TA_TRUSTED_READER_ROLES = ").concat(JSON.stringify(params.trustedReaderRoles), ";\nvar __TA_CREDENTIAL_COLUMNS = ").concat(JSON.stringify(exports.AUTH_CREDENTIAL_COLUMNS), ";\nvar __TA_UNAMBIGUOUS_CREDENTIAL_COLUMNS = ").concat(JSON.stringify(UNAMBIGUOUS_CREDENTIAL_COLUMNS), ";\nvar __TA_CREDENTIAL_TABLES = ").concat(JSON.stringify(exports.AUTH_CREDENTIAL_TABLES), ";\nvar __TA_SQL_WRITE_RE = /\\b(?:insert|update|delete|merge|truncate)\\b/i;\n// What a statement can reach without spelling a table's name where this scan\n// can see it: a Unicode-escaped identifier (U&\"\\0075sers\" is `users`), the\n// functions that run SQL text or dump whole tables, schemas or the database\n// (the table name then sits in a string, or is not named at all), and the\n// planner statistics, which hold sampled column values of every table.\nvar __TA_OPAQUE_SQL_RE = /u&\"|\\b(?:query|table|cursor|schema|database)_to_xml\\w*\\s*\\(|\\bts_(?:stat|rewrite)\\s*\\(|\\bdblink\\w*\\s*\\(|\\bpg_read_(?:binary_)?file\\s*\\(|\\blo_(?:import|export|get)\\s*\\(|\\bpg_stat(?:s|istic)\\w*/i;\n\n// Only server code can read NEXTAUTH_SECRET, so a browser cannot forge the\n// header a server segment sends. An in-process call (getStaticProps and the\n// page-load workflows call the handler directly with a synthetic request)\n// marks its request object instead \u2014 a property, never a header, so nothing\n// on the wire can claim it.\nfunction __taPresentsAppSecret(req) {\n var secret = req && req.headers && req.headers['x-internal-data-secret'];\n return !!(secret && process.env.NEXTAUTH_SECRET && secret === process.env.NEXTAUTH_SECRET);\n}\n\nfunction __taIsRestrictedRequest(req) {\n return __taPresentsAppSecret(req) && !!(req.headers && req.headers['x-tq-restricted-sql'] === '1');\n}\n\nfunction __taIsInternalRequest(req) {\n if (req && req.__tqServerCall === true) return true;\n return __taPresentsAppSecret(req) && !__taIsRestrictedRequest(req);\n}\n\n// Generated browser code reads through the structured routes and never sends\n// SQL or a write of its own \u2014 every data node runs server-side and presents\n// the secret. So where a secret exists to tell the two apart, anything else a\n// browser sends is refused whole: no statement scan has to be right about it.\n// An app without one (no authentication, hence no store) keeps the scan.\nfunction __taBrowserIsReadOnly(req) {\n return !!process.env.NEXTAUTH_SECRET && !__taIsInternalRequest(req) && !__taIsRestrictedRequest(req);\n}\n\n// Comments and string literals out, so a table name inside a string neither\n// hides a reference nor fakes one. Quoted identifiers stay, whole: in Postgres\n// they name tables, which is exactly what is being looked for.\n//\n// \u26D4 This is a lexer, not a search: a quote or comment marker inside a quoted\n// identifier (\"x'\", \"--\") is part of the name, a `$` or an `E` inside an\n// identifier (a$b$, ELSE'\u2026') starts neither a dollar quote nor an escape\n// string, and a dollar-quoted body is consumed by its own tag. An UNTERMINATED\n// literal, identifier, comment or dollar quote makes the whole scan fail closed\n// (ok:false): a statement this cleaner cannot read is one it cannot vouch for.\nfunction __taCleanSql(sql) {\n var text = String(sql || '');\n var out = '';\n var i = 0;\n function inIdentifier(at) { return at > 0 && /[A-Za-z0-9_$]/.test(text[at - 1]); }\n while (i < text.length) {\n var ch = text[i];\n var next = text[i + 1];\n if (ch === '-' && next === '-') {\n while (i < text.length && text[i] !== '\\n') i++;\n continue;\n }\n if (ch === '/' && next === '*') {\n i += 2;\n var depth = 1;\n while (i < text.length && depth > 0) {\n if (text[i] === '/' && text[i + 1] === '*') { depth++; i += 2; continue; }\n if (text[i] === '*' && text[i + 1] === '/') { depth--; i += 2; continue; }\n i++;\n }\n if (depth > 0) return { ok: false, text: out };\n out += ' ';\n continue;\n }\n // \"\u2026\" is one identifier token; \"\" inside it is an escaped quote.\n if (ch === '\"') {\n var end = i + 1;\n var closedIdentifier = false;\n while (end < text.length) {\n if (text[end] === '\"' && text[end + 1] === '\"') { end += 2; continue; }\n if (text[end] === '\"') { closedIdentifier = true; break; }\n end++;\n }\n if (!closedIdentifier) return { ok: false, text: out };\n out += text.slice(i, end + 1);\n i = end + 1;\n continue;\n }\n // $tag$ \u2026 $tag$ \u2014 the body is data whatever it holds.\n if (ch === '$' && !inIdentifier(i)) {\n var tagMatch = /^\\$[A-Za-z_][A-Za-z0-9_]*\\$|^\\$\\$/.exec(text.slice(i));\n if (tagMatch) {\n var tag = tagMatch[0];\n var close = text.indexOf(tag, i + tag.length);\n if (close === -1) return { ok: false, text: out };\n i = close + tag.length;\n out += ' ';\n continue;\n }\n }\n // E'\u2026' understands backslash escapes; a plain '\u2026' doubles the quote.\n if (ch === \"'\" || ((ch === 'E' || ch === 'e') && next === \"'\" && !inIdentifier(i))) {\n var escapes = ch !== \"'\";\n if (escapes) i++;\n i++;\n var closed = false;\n while (i < text.length) {\n if (escapes && text[i] === '\\\\') { i += 2; continue; }\n if (text[i] === \"'\" && text[i + 1] === \"'\") { i += 2; continue; }\n if (text[i] === \"'\") { i++; closed = true; break; }\n i++;\n }\n if (!closed) return { ok: false, text: out };\n out += ' ';\n continue;\n }\n out += ch;\n i++;\n }\n return { ok: true, text: out };\n}\n\nfunction __taNormalizeTable(tableName) {\n var name = String(tableName || '').trim().toLowerCase();\n var parts = name.split('.');\n return parts[parts.length - 1].replace(/\"/g, '');\n}\n\n// An unreadable statement, or one that can reach a table without naming it,\n// counts as mentioning EVERY protected table: fail closed rather than let a\n// spelling this scan cannot see decide it is harmless.\nfunction __taSqlMentionsTable(sql, table) {\n var cleaned = __taCleanSql(sql);\n if (!cleaned.ok || __TA_OPAQUE_SQL_RE.test(cleaned.text)) return true;\n var pattern = new RegExp('(?:^|[^A-Za-z0-9_])\"?' + table + '\"?(?![A-Za-z0-9_])', 'i');\n return pattern.test(cleaned.text);\n}\n\nfunction __taIsCredentialTable(tableName) {\n return __TA_CREDENTIAL_TABLES.indexOf(__taNormalizeTable(tableName)) !== -1;\n}\n\n// Credentials never travel in a row: a column is dropped by name, however the\n// caller spelled it in its projection.\nfunction __taWithoutCredentials(rows, tableName) {\n if (!Array.isArray(rows)) return rows;\n var dropped = tableName && __taIsCredentialTable(tableName)\n ? __TA_CREDENTIAL_COLUMNS\n : tableName ? [] : __TA_UNAMBIGUOUS_CREDENTIAL_COLUMNS;\n if (dropped.length === 0) return rows;\n return rows.map(function(row) {\n if (!row || typeof row !== 'object') return row;\n var copy = {};\n Object.keys(row).forEach(function(key) {\n if (dropped.indexOf(String(key).toLowerCase()) === -1) copy[key] = row[key];\n });\n return copy;\n });\n}\n\nfunction __taForbid(table) {\n var err = new Error('Forbidden: ' + table + ' is only accessible to server-side workflow nodes');\n err.status = 403;\n throw err;\n}\n\nfunction __taAssertSqlTextAllowed(sql) {\n var text = String(sql || '');\n // A statement can hand back a whole row (`SELECT u FROM users u`,\n // `row_to_json(u)`) without naming a credential column, so the table itself\n // is out of reach of anything but the server's own calls.\n for (var ci = 0; ci < __TA_CREDENTIAL_TABLES.length; ci++) {\n if (__taSqlMentionsTable(text, __TA_CREDENTIAL_TABLES[ci])) __taForbid(__TA_CREDENTIAL_TABLES[ci]);\n }\n for (var pi = 0; pi < __TA_PROTECTED_TABLES.length; pi++) {\n if (__taSqlMentionsTable(text, __TA_PROTECTED_TABLES[pi])) __taForbid(__TA_PROTECTED_TABLES[pi]);\n }\n // A feed table has exactly one browser-readable shape, and a statement the\n // caller wrote is not it.\n for (var fi = 0; fi < __TA_FEED_TABLES.length; fi++) {\n if (__taSqlMentionsTable(text, __TA_FEED_TABLES[fi])) __taForbid(__TA_FEED_TABLES[fi]);\n }\n var cleaned = __taCleanSql(text);\n // Unreadable, or a write: either way the write-protected tables are refused.\n if (cleaned.ok && !__TA_SQL_WRITE_RE.test(cleaned.text)) return;\n for (var wi = 0; wi < __TA_WRITE_PROTECTED_TABLES.length; wi++) {\n if (__taSqlMentionsTable(text, __TA_WRITE_PROTECTED_TABLES[wi])) __taForbid(__TA_WRITE_PROTECTED_TABLES[wi]);\n }\n}\n\nfunction __taRoleOf(token) {\n if (!token || typeof token !== 'object') return null;\n if (typeof token.role === 'string') return token.role;\n if (typeof token.roleName === 'string') return token.roleName;\n if (Array.isArray(token.roles) && typeof token.roles[0] === 'string') return token.roles[0];\n return null;\n}\n\n// The tables a read of `tableName` (or the raw statement) touches that this\n// route family may not serve to a browser: the protected ones, and the feed\n// tables, whose one browser-readable projection lives on the data API instead.\nvar __TA_ROUTE_PROTECTED_TABLES = __TA_PROTECTED_TABLES.concat(__TA_FEED_TABLES);\n\nfunction __taProtectedTablesRead(tableName, rawQuery) {\n var touched = [];\n var bare = __taNormalizeTable(tableName);\n if (__TA_ROUTE_PROTECTED_TABLES.indexOf(bare) !== -1) touched.push(bare);\n if (typeof rawQuery === 'string' && rawQuery.length > 0) {\n for (var i = 0; i < __TA_ROUTE_PROTECTED_TABLES.length; i++) {\n if (touched.indexOf(__TA_ROUTE_PROTECTED_TABLES[i]) === -1 && __taSqlMentionsTable(rawQuery, __TA_ROUTE_PROTECTED_TABLES[i])) {\n touched.push(__TA_ROUTE_PROTECTED_TABLES[i]);\n }\n }\n }\n return touched;\n}\n\n// The projection an untrusted caller gets of a feed table, or null when the\n// table has no feed or the caller is a server-side one reading it whole.\nfunction __taPublicFeed(req, tableName) {\n if (__taIsInternalRequest(req)) return null;\n var feed = __TA_PUBLIC_FEED_TABLES[__taNormalizeTable(tableName)];\n return feed || null;\n}\n\n// A feed is filtered and sorted only by the columns it serves: a condition on\n// any other column (or an expression over one) would answer for it.\nfunction __taAssertFeedFields(tableName, feed, fields) {\n for (var i = 0; i < fields.length; i++) {\n if (!fields[i]) continue;\n var column = String(fields[i]).trim().replace(/\"/g, '').toLowerCase();\n if (feed.columns.indexOf(column) === -1) __taForbid(__taNormalizeTable(tableName) + '.' + fields[i]);\n }\n}\n\n// null when the read may proceed, else { status, message }.\nasync function __taGuardRead(req, tableName, rawQuery) {\n if (typeof rawQuery === 'string' && rawQuery.length > 0 && __taBrowserIsReadOnly(req)) {\n return { status: 403, message: 'Forbidden: raw queries are only available to server-side calls' };\n }\n var touched = __taProtectedTablesRead(tableName, rawQuery);\n if (touched.length === 0) return null;\n if (__taIsInternalRequest(req)) return null;\n var token = null;\n try {\n token = await __tqSessionToken(req);\n } catch (e) {\n token = null;\n }\n if (!token) return { status: 401, message: 'Unauthenticated' };\n var role = __taRoleOf(token);\n if (!role || __TA_TRUSTED_READER_ROLES.indexOf(role) === -1) {\n return { status: 403, message: 'Forbidden: ' + touched[0] + ' is only readable by the store administrators' };\n }\n return null;\n}\n");
236
+ };
237
+ exports.generateTableAccessHelperCode = generateTableAccessHelperCode;
238
+ //# sourceMappingURL=table-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-access.js","sourceRoot":"","sources":["../../../src/utils/table-access.ts"],"names":[],"mappings":"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"}