@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.
- package/__tests__/_helpers/embed-runtime-emitter.ts +197 -0
- package/__tests__/utils/discount-engine.ts +1548 -0
- package/__tests__/utils/product-options.ts +2458 -0
- package/__tests__/utils/rich-text-embeds-runtime-module.ts +34 -0
- package/__tests__/utils/subscription-access.ts +37 -0
- package/dist/cjs/index.d.ts +9 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +17 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils/cart-currency.d.ts +27 -0
- package/dist/cjs/utils/cart-currency.d.ts.map +1 -0
- package/dist/cjs/utils/cart-currency.js +31 -0
- package/dist/cjs/utils/cart-currency.js.map +1 -0
- package/dist/cjs/utils/cart-line-pricing.d.ts +25 -0
- package/dist/cjs/utils/cart-line-pricing.d.ts.map +1 -0
- package/dist/cjs/utils/cart-line-pricing.js +29 -0
- package/dist/cjs/utils/cart-line-pricing.js.map +1 -0
- package/dist/cjs/utils/discount-engine.d.ts +74 -0
- package/dist/cjs/utils/discount-engine.d.ts.map +1 -0
- package/dist/cjs/utils/discount-engine.js +435 -0
- package/dist/cjs/utils/discount-engine.js.map +1 -0
- package/dist/cjs/utils/product-options.d.ts +25 -0
- package/dist/cjs/utils/product-options.d.ts.map +1 -0
- package/dist/cjs/utils/product-options.js +1155 -0
- package/dist/cjs/utils/product-options.js.map +1 -0
- package/dist/cjs/utils/rich-text-embeds-codegen.d.ts +13 -0
- package/dist/cjs/utils/rich-text-embeds-codegen.d.ts.map +1 -1
- package/dist/cjs/utils/rich-text-embeds-codegen.js +15 -104
- package/dist/cjs/utils/rich-text-embeds-codegen.js.map +1 -1
- package/dist/cjs/utils/rich-text-embeds-runtime-source.d.ts +2 -0
- package/dist/cjs/utils/rich-text-embeds-runtime-source.d.ts.map +1 -0
- package/dist/cjs/utils/rich-text-embeds-runtime-source.js +409 -0
- package/dist/cjs/utils/rich-text-embeds-runtime-source.js.map +1 -0
- package/dist/cjs/utils/session-cookie-resolver.d.ts +85 -0
- package/dist/cjs/utils/session-cookie-resolver.d.ts.map +1 -0
- package/dist/cjs/utils/session-cookie-resolver.js +108 -0
- package/dist/cjs/utils/session-cookie-resolver.js.map +1 -0
- package/dist/cjs/utils/subscription-access.d.ts +36 -0
- package/dist/cjs/utils/subscription-access.d.ts.map +1 -0
- package/dist/cjs/utils/subscription-access.js +61 -0
- package/dist/cjs/utils/subscription-access.js.map +1 -0
- package/dist/cjs/utils/table-access.d.ts +114 -0
- package/dist/cjs/utils/table-access.d.ts.map +1 -0
- package/dist/cjs/utils/table-access.js +238 -0
- package/dist/cjs/utils/table-access.js.map +1 -0
- package/dist/cjs/utils/web-push.d.ts +43 -0
- package/dist/cjs/utils/web-push.d.ts.map +1 -0
- package/dist/cjs/utils/web-push.js +62 -0
- package/dist/cjs/utils/web-push.js.map +1 -0
- package/dist/esm/index.d.ts +9 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +9 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils/cart-currency.d.ts +27 -0
- package/dist/esm/utils/cart-currency.d.ts.map +1 -0
- package/dist/esm/utils/cart-currency.js +27 -0
- package/dist/esm/utils/cart-currency.js.map +1 -0
- package/dist/esm/utils/cart-line-pricing.d.ts +25 -0
- package/dist/esm/utils/cart-line-pricing.d.ts.map +1 -0
- package/dist/esm/utils/cart-line-pricing.js +25 -0
- package/dist/esm/utils/cart-line-pricing.js.map +1 -0
- package/dist/esm/utils/discount-engine.d.ts +74 -0
- package/dist/esm/utils/discount-engine.d.ts.map +1 -0
- package/dist/esm/utils/discount-engine.js +431 -0
- package/dist/esm/utils/discount-engine.js.map +1 -0
- package/dist/esm/utils/product-options.d.ts +25 -0
- package/dist/esm/utils/product-options.d.ts.map +1 -0
- package/dist/esm/utils/product-options.js +1151 -0
- package/dist/esm/utils/product-options.js.map +1 -0
- package/dist/esm/utils/rich-text-embeds-codegen.d.ts +13 -0
- package/dist/esm/utils/rich-text-embeds-codegen.d.ts.map +1 -1
- package/dist/esm/utils/rich-text-embeds-codegen.js +16 -105
- package/dist/esm/utils/rich-text-embeds-codegen.js.map +1 -1
- package/dist/esm/utils/rich-text-embeds-runtime-source.d.ts +2 -0
- package/dist/esm/utils/rich-text-embeds-runtime-source.d.ts.map +1 -0
- package/dist/esm/utils/rich-text-embeds-runtime-source.js +406 -0
- package/dist/esm/utils/rich-text-embeds-runtime-source.js.map +1 -0
- package/dist/esm/utils/session-cookie-resolver.d.ts +85 -0
- package/dist/esm/utils/session-cookie-resolver.d.ts.map +1 -0
- package/dist/esm/utils/session-cookie-resolver.js +103 -0
- package/dist/esm/utils/session-cookie-resolver.js.map +1 -0
- package/dist/esm/utils/subscription-access.d.ts +36 -0
- package/dist/esm/utils/subscription-access.d.ts.map +1 -0
- package/dist/esm/utils/subscription-access.js +57 -0
- package/dist/esm/utils/subscription-access.js.map +1 -0
- package/dist/esm/utils/table-access.d.ts +114 -0
- package/dist/esm/utils/table-access.d.ts.map +1 -0
- package/dist/esm/utils/table-access.js +233 -0
- package/dist/esm/utils/table-access.js.map +1 -0
- package/dist/esm/utils/web-push.d.ts +43 -0
- package/dist/esm/utils/web-push.d.ts.map +1 -0
- package/dist/esm/utils/web-push.js +59 -0
- package/dist/esm/utils/web-push.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +16 -0
- package/src/utils/cart-currency.ts +76 -0
- package/src/utils/cart-line-pricing.ts +151 -0
- package/src/utils/discount-engine.ts +437 -0
- package/src/utils/product-options.ts +1150 -0
- package/src/utils/rich-text-embeds-codegen.ts +16 -183
- package/src/utils/rich-text-embeds-runtime-source.ts +405 -0
- package/src/utils/session-cookie-resolver.ts +305 -0
- package/src/utils/subscription-access.ts +59 -0
- package/src/utils/table-access.ts +516 -0
- package/src/utils/web-push.ts +61 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving the NextAuth session token in a generated route, WITHOUT asking
|
|
3
|
+
* `process.env.NEXTAUTH_URL` what the session cookie is called.
|
|
4
|
+
*
|
|
5
|
+
* ## ⛔ THE REPORTED DEFECT
|
|
6
|
+
*
|
|
7
|
+
* A published project showed "Failed to load dashboard data. Please refresh."
|
|
8
|
+
* on the home page, "Failed to load meeting data." on the list page and "could
|
|
9
|
+
* not fetch the templates" on a third — for a user who was signed in, on a page
|
|
10
|
+
* whose own `/api/auth/session` returned that user. The failing call was the
|
|
11
|
+
* page-load workflow's server segment, answering **401 `Unauthenticated`** from
|
|
12
|
+
* `guardWorkflowRequest`. Reloading sometimes fixed it; fanning 50 concurrent
|
|
13
|
+
* requests at the same route returned **33 × 401 and 17 × 200**, while 40
|
|
14
|
+
* concurrent `/api/auth/session` calls resolved the user 40/40.
|
|
15
|
+
*
|
|
16
|
+
* ## The mechanism
|
|
17
|
+
*
|
|
18
|
+
* next-auth v4's `getToken()` derives the cookie it reads from an env var:
|
|
19
|
+
*
|
|
20
|
+
* ```js
|
|
21
|
+
* secureCookie = process.env.NEXTAUTH_URL?.startsWith('https://') ?? !!process.env.VERCEL,
|
|
22
|
+
* cookieName = secureCookie ? '__Secure-next-auth.session-token' : 'next-auth.session-token',
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* A generated project ships `NEXTAUTH_URL=http://localhost:3000` — the build
|
|
26
|
+
* cannot know the domain it will be published to, so `pages/api/auth/[...nextauth].js`
|
|
27
|
+
* repairs `process.env.NEXTAUTH_URL` **from the incoming request** instead. That
|
|
28
|
+
* repair is process-local: it fixes the value for whichever serverless instance
|
|
29
|
+
* happened to serve an auth request, and for no other. Every other instance
|
|
30
|
+
* still holds `http://localhost:3000`, so `startsWith('https://')` is `false`
|
|
31
|
+
* (NOT `undefined` — the `??` fallback to `!!process.env.VERCEL` never runs),
|
|
32
|
+
* `getToken` looks for `next-auth.session-token`, the browser is holding
|
|
33
|
+
* `__Secure-next-auth.session-token`, nothing is found, and a signed-in user is
|
|
34
|
+
* reported as anonymous.
|
|
35
|
+
*
|
|
36
|
+
* Hence the coin-flip: it depends on whether the instance that served your
|
|
37
|
+
* request had previously served an auth request. Nothing about the session, the
|
|
38
|
+
* secret or the cookie is wrong.
|
|
39
|
+
*
|
|
40
|
+
* The same defect is why the generated middleware carries a `hasSessionCookie`
|
|
41
|
+
* fallback and a round trip to `/api/auth/session`, attributed there to an
|
|
42
|
+
* "Edge runtime quirk" — the Edge process holds that same localhost value.
|
|
43
|
+
*
|
|
44
|
+
* ## The rule
|
|
45
|
+
*
|
|
46
|
+
* ⭐ **A request must be authenticated from the REQUEST, never from an ambient
|
|
47
|
+
* env var that describes the deployment.** So this resolver reads which session
|
|
48
|
+
* cookie the caller actually sent, and tells `getToken` exactly that — falling
|
|
49
|
+
* back to the request's own protocol when the cookie header is unavailable.
|
|
50
|
+
*
|
|
51
|
+
* Emitted as plain ES5 text and inlined into each generated route: a route that
|
|
52
|
+
* carries its own copy cannot be broken by a missing sibling module or an
|
|
53
|
+
* untraced dependency, and every call site gets the identical implementation
|
|
54
|
+
* from this one definition. It lives in teleport-shared because both the
|
|
55
|
+
* workflow routes and the per-table data routes decode the session.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/** The `__tq`-prefixed globals the emitted snippet declares. */
|
|
59
|
+
export const SESSION_TOKEN_RESOLVER_FN = '__tqResolveSessionToken'
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Session-cookie names next-auth/Auth.js writes, with the `secureCookie` flag
|
|
63
|
+
* that produces each. Order within a protocol is irrelevant — only ONE of these
|
|
64
|
+
* is ever present — but the https/http split decides which pair is tried first.
|
|
65
|
+
*
|
|
66
|
+
* `__Host-` is deliberately absent: next-auth uses it only for the CSRF and
|
|
67
|
+
* callback cookies, never for the session token.
|
|
68
|
+
*/
|
|
69
|
+
const KNOWN_SESSION_COOKIES: ReadonlyArray<{ name: string; secure: boolean }> = [
|
|
70
|
+
{ name: '__Secure-next-auth.session-token', secure: true },
|
|
71
|
+
{ name: 'next-auth.session-token', secure: false },
|
|
72
|
+
{ name: '__Secure-authjs.session-token', secure: true },
|
|
73
|
+
{ name: 'authjs.session-token', secure: false },
|
|
74
|
+
]
|
|
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
|
+
export const generateSessionTokenResolverCode = (): string => {
|
|
93
|
+
return `// GENERATED — see generateSessionTokenResolverCode in
|
|
94
|
+
// @teleporthq/teleport-shared/src/utils/session-cookie-resolver.ts.
|
|
95
|
+
//
|
|
96
|
+
// next-auth's getToken() picks the session cookie name from
|
|
97
|
+
// process.env.NEXTAUTH_URL, which a generated project ships as a localhost
|
|
98
|
+
// default — so on a published https domain it looks for the non-secure cookie
|
|
99
|
+
// and reports a signed-in user as anonymous. These helpers read the cookie the
|
|
100
|
+
// request actually carries and tell getToken exactly which one it is.
|
|
101
|
+
var __TQ_SESSION_COOKIES = ${JSON.stringify(KNOWN_SESSION_COOKIES.map((c) => [c.name, c.secure]))};
|
|
102
|
+
|
|
103
|
+
// Every cookie NAME on the request. Parsed on the "; " separator rather than
|
|
104
|
+
// matched as a substring: "next-auth.session-token" occurs inside
|
|
105
|
+
// "__Secure-next-auth.session-token", so a substring test reports the plain
|
|
106
|
+
// cookie as present whenever the secure one is, and getToken then decodes the
|
|
107
|
+
// wrong (missing) cookie.
|
|
108
|
+
function __tqCookieNamesPresent(req) {
|
|
109
|
+
var present = {};
|
|
110
|
+
var raw = '';
|
|
111
|
+
try {
|
|
112
|
+
var headers = req && req.headers;
|
|
113
|
+
if (headers && typeof headers.get === 'function') {
|
|
114
|
+
// Edge / fetch Headers.
|
|
115
|
+
raw = headers.get('cookie') || '';
|
|
116
|
+
} else if (headers) {
|
|
117
|
+
var value = headers.cookie || headers.Cookie;
|
|
118
|
+
raw = Array.isArray(value) ? value.join('; ') : value || '';
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
raw = '';
|
|
122
|
+
}
|
|
123
|
+
var parts = String(raw).split(';');
|
|
124
|
+
for (var i = 0; i < parts.length; i++) {
|
|
125
|
+
var eq = parts[i].indexOf('=');
|
|
126
|
+
if (eq > 0) {
|
|
127
|
+
present[parts[i].slice(0, eq).trim()] = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Second source: the runtime's own parsed cookies. A Next.js API route hands
|
|
131
|
+
// over a plain object; the Edge RequestCookies exposes getAll().
|
|
132
|
+
try {
|
|
133
|
+
var cookies = req && req.cookies;
|
|
134
|
+
if (cookies && typeof cookies.getAll === 'function') {
|
|
135
|
+
var all = cookies.getAll() || [];
|
|
136
|
+
for (var a = 0; a < all.length; a++) {
|
|
137
|
+
if (all[a] && all[a].name) {
|
|
138
|
+
present[all[a].name] = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else if (cookies && typeof cookies === 'object') {
|
|
142
|
+
for (var key in cookies) {
|
|
143
|
+
if (Object.prototype.hasOwnProperty.call(cookies, key)) {
|
|
144
|
+
present[key] = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
/* the header pass above is enough */
|
|
150
|
+
}
|
|
151
|
+
return present;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Is this request https? Read from the proxy header the platform sets, then the
|
|
155
|
+
// socket, then the URL — never from an env var describing the deployment.
|
|
156
|
+
function __tqRequestIsSecure(req) {
|
|
157
|
+
try {
|
|
158
|
+
var headers = (req && req.headers) || {};
|
|
159
|
+
var proto =
|
|
160
|
+
typeof headers.get === 'function'
|
|
161
|
+
? headers.get('x-forwarded-proto')
|
|
162
|
+
: headers['x-forwarded-proto'];
|
|
163
|
+
if (Array.isArray(proto)) {
|
|
164
|
+
proto = proto[0];
|
|
165
|
+
}
|
|
166
|
+
if (typeof proto === 'string' && proto) {
|
|
167
|
+
// A chain of proxies appends: "https,http" — the client-facing hop is first.
|
|
168
|
+
return proto.split(',')[0].trim().toLowerCase() === 'https';
|
|
169
|
+
}
|
|
170
|
+
if (req && req.nextUrl && req.nextUrl.protocol === 'https:') {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (req && req.socket && req.socket.encrypted) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
if (req && req.connection && req.connection.encrypted) {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (typeof (req && req.url) === 'string' && req.url.indexOf('https://') === 0) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
} catch (e) {
|
|
183
|
+
/* fall through to false */
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// The { cookieName, secureCookie } pairs worth trying, most likely first.
|
|
189
|
+
// Normally exactly one cookie is present and this returns a single entry; the
|
|
190
|
+
// list only grows when a site has been reached over both http and https.
|
|
191
|
+
function __tqSessionTokenCandidates(req) {
|
|
192
|
+
var present = __tqCookieNamesPresent(req);
|
|
193
|
+
var secure = __tqRequestIsSecure(req);
|
|
194
|
+
var candidates = [];
|
|
195
|
+
for (var i = 0; i < __TQ_SESSION_COOKIES.length; i++) {
|
|
196
|
+
var name = __TQ_SESSION_COOKIES[i][0];
|
|
197
|
+
var isSecureName = __TQ_SESSION_COOKIES[i][1];
|
|
198
|
+
if (present[name] || present[name + '.0']) {
|
|
199
|
+
candidates.push({ cookieName: name, secureCookie: isSecureName });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
candidates.sort(function (a, b) {
|
|
203
|
+
if (a.secureCookie === b.secureCookie) {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
return a.secureCookie === secure ? -1 : 1;
|
|
207
|
+
});
|
|
208
|
+
if (candidates.length === 0) {
|
|
209
|
+
// No cookie we recognise. The project may configure a custom cookie name in
|
|
210
|
+
// authOptions, so hand getToken the request's own protocol and let it apply
|
|
211
|
+
// its defaults — same behaviour as before this resolver existed, minus the
|
|
212
|
+
// localhost env var.
|
|
213
|
+
candidates.push({ secureCookie: secure });
|
|
214
|
+
}
|
|
215
|
+
return candidates;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// When a browser carries BOTH session cookies, both usually decode — they are
|
|
219
|
+
// two snapshots of the same account written at different times (NextAuth
|
|
220
|
+
// switches between the plain and the "__Secure-" name with the origin it
|
|
221
|
+
// derives, and never clears the other one). The one NextAuth is maintaining is
|
|
222
|
+
// re-issued on every session read, so it is the most recently issued; the
|
|
223
|
+
// other is frozen at whatever the account looked like when NextAuth last used
|
|
224
|
+
// that name, role included. Trusting it by protocol alone made a promoted admin
|
|
225
|
+
// read as "user" at the edge while /api/auth/session said "admin".
|
|
226
|
+
function __tqTokenIssuedAt(token) {
|
|
227
|
+
var issued = token && typeof token === 'object' ? token.iat : null;
|
|
228
|
+
return typeof issued === 'number' && isFinite(issued) ? issued : 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// The decoded session token, or null. Decodes every candidate cookie (normally
|
|
232
|
+
// exactly one) and keeps the most recently issued, so a project that renamed
|
|
233
|
+
// its cookie, or one reached over both protocols, resolves to the session
|
|
234
|
+
// NextAuth is actually maintaining. A tie keeps the protocol-matching
|
|
235
|
+
// candidate. Never throws.
|
|
236
|
+
function ${SESSION_TOKEN_RESOLVER_FN}(getToken, req, secret) {
|
|
237
|
+
if (!secret || typeof getToken !== 'function') {
|
|
238
|
+
return Promise.resolve(null);
|
|
239
|
+
}
|
|
240
|
+
var candidates = __tqSessionTokenCandidates(req);
|
|
241
|
+
var decode = function (candidate) {
|
|
242
|
+
var params = { req: req, secret: secret, secureCookie: candidate.secureCookie };
|
|
243
|
+
if (candidate.cookieName) {
|
|
244
|
+
params.cookieName = candidate.cookieName;
|
|
245
|
+
}
|
|
246
|
+
return Promise.resolve()
|
|
247
|
+
.then(function () {
|
|
248
|
+
return getToken(params);
|
|
249
|
+
})
|
|
250
|
+
.catch(function () {
|
|
251
|
+
return null;
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
var decodes = [];
|
|
255
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
256
|
+
decodes.push(decode(candidates[i]));
|
|
257
|
+
}
|
|
258
|
+
return Promise.all(decodes)
|
|
259
|
+
.then(function (tokens) {
|
|
260
|
+
var best = null;
|
|
261
|
+
for (var t = 0; t < tokens.length; t++) {
|
|
262
|
+
if (tokens[t] && (best === null || __tqTokenIssuedAt(tokens[t]) > __tqTokenIssuedAt(best))) {
|
|
263
|
+
best = tokens[t];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return best;
|
|
267
|
+
})
|
|
268
|
+
.catch(function () {
|
|
269
|
+
return null;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
`
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The resolver plus a lazy `require('next-auth/jwt')`, for a CommonJS route.
|
|
277
|
+
*
|
|
278
|
+
* The require is lazy and guarded because a project generated WITHOUT
|
|
279
|
+
* authentication does not ship `next-auth`; such a project also has no session
|
|
280
|
+
* to read, so resolving to `null` there is the correct answer rather than a
|
|
281
|
+
* module-load crash.
|
|
282
|
+
*/
|
|
283
|
+
export const generateCommonJsSessionTokenResolverCode = (): string => {
|
|
284
|
+
return `${generateSessionTokenResolverCode()}
|
|
285
|
+
// Lazy + guarded: a project generated without authentication has no next-auth
|
|
286
|
+
// installed, and no session to read either — null is the right answer there.
|
|
287
|
+
function __tqGetTokenFn() {
|
|
288
|
+
try {
|
|
289
|
+
var jwt = require('next-auth/jwt');
|
|
290
|
+
return jwt && typeof jwt.getToken === 'function' ? jwt.getToken : null;
|
|
291
|
+
} catch (e) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// The session token for this request, or null. Reads NEXTAUTH_SECRET itself.
|
|
297
|
+
function __tqSessionToken(req) {
|
|
298
|
+
var secret = process.env.NEXTAUTH_SECRET;
|
|
299
|
+
if (!secret) {
|
|
300
|
+
return Promise.resolve(null);
|
|
301
|
+
}
|
|
302
|
+
return ${SESSION_TOKEN_RESOLVER_FN}(__tqGetTokenFn(), req, secret);
|
|
303
|
+
}
|
|
304
|
+
`
|
|
305
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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 const SUBSCRIPTION_ENTITLED_STATUSES: ReadonlyArray<string> = [
|
|
18
|
+
'trialing',
|
|
19
|
+
'active',
|
|
20
|
+
'past_due',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
export const SUBSCRIPTIONS_TABLE = 'teleport_subscriptions'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The one parameterised statement every entitlement check runs:
|
|
27
|
+
* $1 — the session user id (compared as text: auth ids are strings, the
|
|
28
|
+
* column is a uuid),
|
|
29
|
+
* $2 — the entitled statuses,
|
|
30
|
+
* $3 — the product ids the page names; an EMPTY array means any product.
|
|
31
|
+
*
|
|
32
|
+
* A subscription the STORE bills (a provider that cannot charge a saved
|
|
33
|
+
* payment method) carries `access_until`: the end of the paid period plus the
|
|
34
|
+
* grace for the next payment. Past it the row stops entitling on its own, so
|
|
35
|
+
* access ends on time even when the scheduled billing run is late or off.
|
|
36
|
+
* Read through `to_jsonb` so a store whose table predates the column still
|
|
37
|
+
* answers (a missing key reads NULL, which never limits).
|
|
38
|
+
*/
|
|
39
|
+
export const SUBSCRIPTION_ENTITLEMENT_SQL =
|
|
40
|
+
`SELECT 1 FROM ${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
|
+
|
|
46
|
+
/** Trimmed, de-duplicated, non-empty ids — whatever shape the caller had them in. */
|
|
47
|
+
export const normalizeSubscriptionProductIds = (value: unknown): string[] => {
|
|
48
|
+
const raw = Array.isArray(value) ? value : typeof value === 'string' ? value.split(',') : []
|
|
49
|
+
const seen = new Set<string>()
|
|
50
|
+
const ids: string[] = []
|
|
51
|
+
for (const entry of raw) {
|
|
52
|
+
const id = String(entry ?? '').trim()
|
|
53
|
+
if (id && !seen.has(id)) {
|
|
54
|
+
seen.add(id)
|
|
55
|
+
ids.push(id)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return ids
|
|
59
|
+
}
|