@getsupertab/supertab-connect-sdk 0.1.0-beta.29 → 0.1.0-beta.31

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
@@ -23,11 +23,14 @@ const supertabConnect = new SupertabConnect({
23
23
  merchantSystemUrn: "your_merchant_system_urn",
24
24
  });
25
25
 
26
- // Verify a token
27
- const verification = await supertabConnect.verifyToken(token);
26
+ // Verify a license token
27
+ const verification = await supertabConnect.verifyLicenseToken(
28
+ licenseToken,
29
+ "https://example.com/article"
30
+ );
28
31
 
29
32
  // Record an event
30
- await supertabConnect.recordEvent("page_viewed", token, {
33
+ await supertabConnect.recordEvent("page_viewed", {
31
34
  page_url: "https://example.com/article",
32
35
  user_agent: "Mozilla/5.0...",
33
36
  });
@@ -104,9 +107,9 @@ const supertabConnect = new SupertabConnect({
104
107
  Resets the singleton instance of SupertabConnect allowing to create an instance with a new config.
105
108
  We expect this to not be called in the usual production setup as the SDK is designed to intercept requests using specific public methods.
106
109
 
107
- ### `fastlyHandleRequests(request: Request, merchantSystemUrn: string, merchantApiKey: string): Promise<Response>`
110
+ ### `fastlyHandleRequests(request: Request, merchantSystemUrn: string, merchantApiKey: string, enableRSL?: boolean): Promise<Response>`
108
111
 
109
- Handles the Supertab Connect part for each incoming HTTP request within Fastly CDN: it verifies the JWT token and records the event.
112
+ Handles the Supertab Connect part for each incoming HTTP request within Fastly CDN: it verifies the license token and records the event.
110
113
 
111
114
  For examples see the [Fastly Compute Example](#fastly-compute-example) section above.
112
115
 
@@ -115,16 +118,17 @@ For examples see the [Fastly Compute Example](#fastly-compute-example) section a
115
118
  - `request` (Request): The incoming HTTP request object
116
119
  - `merchantSystemUrn` (string): Your merchant system identifier (recommended to be stored in a Fastly SecretStore)
117
120
  - `merchantApiKey` (string): Your Supertab merchant API key (recommended to be stored in a Fastly SecretStore)
121
+ - `enableRSL` (boolean, optional): Enable RSL license.xml hosting (default: false)
118
122
 
119
123
  **Returns:**
120
124
 
121
125
  - `Promise<Response>`: Result of bot detection, verification and event recording
122
- - If the requester is not a bot to be blocked, or if the token is present and valid, returns 200 OK
123
- - If token is invalid or missing, returns 403 Forbidden with either INVALID_TOKEN or MISSING_TOKEN as a reason
126
+ - If the requester is not a bot to be blocked, or if the license token is present and valid, returns 200 OK
127
+ - If license token is invalid or missing, returns 401 Unauthorized
124
128
 
125
129
  ### `cloudflareHandleRequests(request: Request, env: Env, ctx: any = null): Promise<Response>`
126
130
 
127
- Handles the Supertab Connect part for each incoming HTTP request within CloudFlare CDN: it verifies the JWT token and records the event.
131
+ Handles the Supertab Connect part for each incoming HTTP request within CloudFlare CDN: it verifies the license token and records the event.
128
132
 
129
133
  For examples see the [CloudFlare Worker Example](#cloudflare-worker-example) section above.
130
134
 
@@ -137,8 +141,8 @@ For examples see the [CloudFlare Worker Example](#cloudflare-worker-example) sec
137
141
  **Returns:**
138
142
 
139
143
  - `Promise<Response>`: Result of bot detection, verification and event recording
140
- - If the requester is not a bot to be blocked, or if the token is present and valid, returns 200 OK
141
- - If token is invalid or missing, returns 403 Forbidden with either INVALID_TOKEN or MISSING_TOKEN as a reason
144
+ - If the requester is not a bot to be blocked, or if the license token is present and valid, returns 200 OK
145
+ - If license token is invalid or missing, returns 401 Unauthorized
142
146
 
143
147
  ### `handleRequest(request: Request, botDetectionHandler?: (request: Request, ctx?: any) => boolean, ctx?: any): Promise<Response>`
144
148
 
@@ -154,54 +158,59 @@ Any of out-of-the-box bot detector methods can be used or a custom one can be su
154
158
  **Returns:**
155
159
 
156
160
  - `Promise<Response>`: Result of bot detection, verification and event recording
157
- - If the requester is not a bot to be blocked, or if the token is present and valid, returns 200 OK
158
- - If token is invalid or missing, returns 403 Forbidden with either INVALID_TOKEN or MISSING_TOKEN as a reason
161
+ - If the requester is not a bot to be blocked, or if the license token is present and valid, returns 200 OK
162
+ - If license token is invalid or missing, returns 401 Unauthorized
159
163
 
160
- ### `verifyToken(token: string): Promise<TokenVerificationResult>`
164
+ ### `verifyLicenseToken(licenseToken: string, requestUrl: string): Promise<LicenseTokenVerificationResult>`
161
165
 
162
- Verifies self-signed JWT Tokens sent by the Customer and signed by their private-key. Internally, it fetches the JWKs hosted by Supertab Connect for the customer and verifies using the public key available.
166
+ Verifies license tokens issued by the Supertab platform. Internally, it fetches the platform JWKs hosted by Supertab Connect and verifies the ES256 signature.
163
167
 
164
168
  **Parameters:**
165
169
 
166
- - `token` (string): The JWT token to verify
170
+ - `licenseToken` (string): The license token to verify
171
+ - `requestUrl` (string): The URL of the request being made (used for audience validation)
167
172
 
168
173
  **Returns:**
169
174
 
170
- - `Promise<TokenVerificationResult>`: Object with verification result
175
+ - `Promise<LicenseTokenVerificationResult>`: Object with verification result
171
176
  - `valid`: boolean indicating if token is valid
172
177
  - `reason`: string reason for failure (if invalid)
178
+ - `licenseId`: the license ID (if valid)
173
179
  - `payload`: decoded token payload (if valid)
174
180
 
175
181
  Example
176
182
 
177
183
  ```js
178
- const token = "eyJhbGciOiJSUzI1..."; // Token from Authorization header
179
- const verification = await supertabConnect.verifyToken(token);
184
+ const licenseToken = "eyJhbGciOiJFUzI1..."; // Token from Authorization header
185
+ const verification = await supertabConnect.verifyLicenseToken(
186
+ licenseToken,
187
+ "https://example.com/article"
188
+ );
180
189
 
181
190
  if (verification.valid) {
182
191
  // Allow access to content
183
- console.log("Token verified successfully", verification.payload);
192
+ console.log("License verified successfully", verification.licenseId);
184
193
  } else {
185
194
  // Block access
186
- console.log("Token verification failed:", verification.reason);
195
+ console.log("License verification failed:", verification.reason);
187
196
  }
188
197
  ```
189
198
 
190
- ### `recordEvent(eventName: string, customerToken?: string, properties?: Record<string, any>): Promise<void>`
199
+ ### `recordEvent(eventName: string, properties?: Record<string, any>, licenseId?: string): Promise<void>`
191
200
 
192
201
  Records an event in the Supertab Connect platform.
193
202
 
194
203
  **Parameters:**
195
204
 
196
205
  - `eventName` (string): Name of the event to record
197
- - `customerToken` (string, optional): The self-signed JWT token sent by the customer
198
206
  - `properties` (object, optional): Additional properties to include with the event
207
+ - `licenseId` (string, optional): License ID associated with the event
199
208
 
200
209
  Example:
201
210
 
202
211
  ```js
203
212
  // Record a page view with additional properties
204
- await supertabConnect.recordEvent("page_viewed", token, {
213
+ await supertabConnect.recordEvent("page_viewed", {
205
214
  page_url: request.url,
206
215
  user_agent: request.headers.get("User-Agent"),
207
216
  referrer: request.headers.get("Referer"),
@@ -221,34 +230,18 @@ A simple check based on the information passed in request's Headers to decide wh
221
230
 
222
231
  - `boolean`: True if the request's Headers fall into the conditions to identify requester as a bot; False otherwise.
223
232
 
224
- ### `generateCustomerJWT(customerURN: string, kid: string, privateKeyPem: string, expirationSeconds?: number): Promise<string>`
233
+ ### `generateLicenseToken(clientId: string, kid: string, privateKeyPem: string, resourceUrl: string, licenseXml: string): Promise<string>`
225
234
 
226
- Generates a self‑signed RS256 JWT for a Customer with a `kid` header. The `privateKeyPem` must be in PEM format.
235
+ Requests a license token from the Supertab Connect token endpoint using OAuth2 client assertion.
227
236
 
228
237
  **Parameters:**
229
238
 
230
- - `customerURN` (string): The Customer URN (must start with `urn:stc:customer:`)
239
+ - `clientId` (string): OAuth client identifier used for the assertion issuer/subject claims, fetched from the System Detail page
231
240
  - `kid` (string): Key ID to include in the JWT header
232
- - `privateKeyPem` (string): the private key in PEM format
233
- - `expirationSeconds` (number, optional): Expiry in seconds (default: `3600`)
241
+ - `privateKeyPem` (string): Private key in PEM format used to sign the client assertion
242
+ - `resourceUrl` (string): Resource URL attempting to access with a License
243
+ - `licenseXml` (string): XML license document associated with the resource url attempting to access
234
244
 
235
245
  **Returns:**
236
246
 
237
- - `Promise<string>`: The signed JWT
238
-
239
- **Example:**
240
-
241
- ```ts
242
- import { SupertabConnect } from "@getsupertab/supertab-connect-sdk";
243
-
244
- const token = await SupertabConnect.generateCustomerJWT(
245
- "urn:stc:customer:79fcac58-1966-470a-be40-c34847aecabd",
246
- "key_dc0d3d1103c319e9",
247
- `-----BEGIN PRIVATE KEY-----
248
- ...
249
- -----END PRIVATE KEY-----`,
250
- 3600
251
- );
252
-
253
- console.log("Generated JWT:", token);
254
- ```
247
+ - `Promise<string>`: The issued license access token
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var _=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var x=(n,e)=>{for(var t in e)_(n,t,{get:e[t],enumerable:!0})},D=(n,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of N(e))!C.call(n,r)&&r!==t&&_(n,r,{get:()=>e[r],enumerable:!(s=P(e,r))||s.enumerable});return n};var q=n=>D(_({},"__esModule",{value:!0}),n);var $={};x($,{SupertabConnect:()=>b});module.exports=q($);var m="stc-backend";var w=require("jose");async function F(n,e){let t=["ES256","RS256"];for(let s of t)try{return{key:await(0,w.importPKCS8)(n,s),alg:s}}catch(r){e&&console.debug(`Private key did not import using ${s}, retrying...`,r)}throw new Error("Unsupported private key format. Expected RSA or P-256 EC private key.")}async function I({clientId:n,kid:e,privateKeyPem:t,tokenEndpoint:s,resourceUrl:r,licenseXml:i,debug:o}){let{key:g,alg:p}=await F(t,o),h=Math.floor(Date.now()/1e3),c=await new w.SignJWT({}).setProtectedHeader({alg:p,kid:e}).setIssuer(n).setSubject(n).setIssuedAt(h).setExpirationTime(h+300).setAudience(s).sign(g),a=new URLSearchParams({grant_type:"rsl",client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:c,license:i,resource:r}),u={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:a.toString()};try{let d=await fetch(s,u);if(!d.ok){let y=await d.text().catch(()=>""),E=`Failed to obtain license token: ${d.status} ${d.statusText}${y?` - ${y}`:""}`;throw new Error(E)}let f;try{f=await d.json()}catch(y){throw o&&console.error("Failed to parse license token response as JSON:",y),new Error("Failed to parse license token response as JSON")}if(!f?.access_token)throw new Error("License token response missing access_token");return f.access_token}catch(d){throw o&&console.error("Error generating license token:",d),d}}var k=require("jose");var A=new Map;function K(){let n={method:"GET"};return globalThis?.fastly&&(n={...n,backend:m}),n}async function J({cacheKey:n,url:e,debug:t,failureMessage:s,logLabel:r}){if(!A.has(n))try{let i=await fetch(e,K());if(!i.ok)throw new Error(`${s}: ${i.status}`);let o=await i.json();A.set(n,o)}catch(i){throw t&&console.error(r,i),i}return A.get(n)}async function R(n,e){let t=`${n}/.well-known/jwks.json/platform`;return console.log("Fetching platform JWKS from:",t),J({cacheKey:"platform_jwks",url:t,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}var T=n=>n.replace(/\/+$/,"");async function v({licenseToken:n,requestUrl:e,supertabBaseUrl:t,debug:s}){if(!n)return{valid:!1,reason:"missing_license_token"};let r;try{r=(0,k.decodeProtectedHeader)(n)}catch(a){return s&&console.error("Invalid license JWT header:",a),{valid:!1,reason:"invalid_license_header"}}if(r.alg!=="ES256")return s&&console.error("Unsupported license JWT alg:",r.alg),{valid:!1,reason:"invalid_license_algorithm"};let i;try{i=(0,k.decodeJwt)(n)}catch(a){return s&&console.error("Invalid license JWT payload:",a),{valid:!1,reason:"invalid_license_payload"}}let o=i.license_id,g=i.iss;if(!g||!g.startsWith(t))return s&&console.error("Invalid license JWT issuer:",g),{valid:!1,reason:"invalid_license_issuer",licenseId:o};let p=Array.isArray(i.aud)?i.aud.filter(a=>typeof a=="string"):typeof i.aud=="string"?[i.aud]:[],h=T(e);if(!p.some(a=>{let u=T(a);return u?h.startsWith(u):!1}))return s&&console.error("License JWT audience does not match request URL:",i.aud),{valid:!1,reason:"invalid_license_audience",licenseId:o};try{let a=await R(t,s),d=await(0,k.jwtVerify)(n,async f=>{let y=a.keys.find(E=>E.kid===f.kid);if(!y)throw new Error(`No matching platform key found: ${f.kid}`);return y},{issuer:g,algorithms:[r.alg],clockTolerance:"1m"});return{valid:!0,licenseId:o,payload:d.payload}}catch(a){return s&&console.error("License JWT verification failed:",a),a instanceof Error&&a.message?.includes("exp")?{valid:!1,reason:"license_token_expired",licenseId:o}:{valid:!1,reason:"license_signature_verification_failed",licenseId:o}}}function H({requestUrl:n}){let e=new URL(n);return`${e.protocol}//${e.host}/license.xml`}async function S({licenseToken:n,url:e,userAgent:t,ctx:s,supertabBaseUrl:r,merchantSystemUrn:i,debug:o,recordEvent:g}){let p=await v({licenseToken:n,requestUrl:e,supertabBaseUrl:r,debug:o});async function h(c){let a={page_url:e,user_agent:t,verification_status:p.valid?"valid":"invalid",verification_reason:p.reason||"success"},u=g(c,a,p.licenseId);return s?.waitUntil&&s.waitUntil(u),u}if(!p.valid){await h(p.reason||"license_token_verification_failed");let c="invalid_request",a="Access to this resource requires a license";switch(p.reason){case"missing_license_token":c="invalid_request",a="Access to this resource requires a license";break;case"license_token_expired":c="invalid_token",a="The license token has expired";break;case"license_signature_verification_failed":c="invalid_token",a="The license token signature is invalid";break;case"invalid_license_header":c="invalid_token",a="The license token header is invalid";break;case"invalid_license_payload":c="invalid_token",a="The license token payload is invalid";break;case"invalid_license_issuer":c="invalid_token",a="The license token issuer is invalid";break;case"invalid_license_audience":c="invalid_token",a="The license token audience is invalid";break;default:c="invalid_request",a="Access to this resource requires a license"}let u=H({requestUrl:e}),d=`${r}/docs/errors#${c}`,f=new Headers({"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${c}", error_description="${a}", error_uri="${d}"`,Link:`${u}; rel="license"; type="application/rsl+xml"`}),y=`Access to this resource requires a valid license token. Error: ${c} - ${a}`;return new Response(y,{status:401,headers:f})}return await h("license_used"),new Response("\u2705 License Token Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}function V(){let n={method:"GET"};return globalThis?.fastly&&(n={...n,backend:m}),n}async function U(n,e){let t=`${n}/merchants/systems/${e}/license.xml`,s=await fetch(t,V());if(!s.ok)return new Response("License not found",{status:404});let r=await s.text();return new Response(r,{status:200,headers:new Headers({"Content-Type":"application/xml"})})}var L=!0,l=class l{constructor(e,t=!1){if(!t&&l._instance){if(!(e.apiKey===l._instance.apiKey&&e.merchantSystemUrn===l._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return l._instance}if(t&&l._instance&&l.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,l._instance=this}static resetInstance(){l._instance=null}static setBaseUrl(e){l.baseUrl=e}async verifyLicenseToken(e,t){return v({licenseToken:e,requestUrl:t,supertabBaseUrl:l.baseUrl,debug:L})}async recordEvent(e,t={},s){let r={event_name:e,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",license_id:s,properties:t};try{let i={method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(r)};globalThis?.fastly&&(i={...i,backend:m});let o=await fetch(`${l.baseUrl}/events`,i);o.ok||console.log(`Failed to record event: ${o.status}`)}catch(i){console.log("Error recording event:",i)}}static checkIfBotRequest(e){let t=e.headers.get("User-Agent")||"",s=e.headers.get("accept")||"",r=e.headers.get("sec-ch-ua"),i=e.headers.get("accept-language"),o=e.cf?.botManagement?.score,g=["chatgpt-user","perplexitybot","gptbot","anthropic-ai","ccbot","claude-web","claudebot","cohere-ai","youbot","diffbot","oai-searchbot","meta-externalagent","timpibot","amazonbot","bytespider","perplexity-user","googlebot","bot","curl","wget"],p=t.toLowerCase(),h=g.some(f=>p.includes(f)),c=t.toLowerCase().includes("headless")||t.toLowerCase().includes("puppeteer")||!r,a=!t.toLowerCase().includes("headless")||!t.toLowerCase().includes("puppeteer")||!r,u=!s||!i,d=typeof o=="number"&&o<30;return console.log("Bot Detection Details:",{botUaMatch:h,headlessIndicators:c,missingHeaders:u,lowBotScore:d,botScore:o}),(p.includes("safari")||p.includes("mozilla"))&&c&&a?!1:h||c||u||d}static async cloudflareHandleRequests(e,t,s){let{MERCHANT_SYSTEM_URN:r,MERCHANT_API_KEY:i}=t;return new l({apiKey:i,merchantSystemUrn:r}).handleRequest(e,l.checkIfBotRequest,s)}static async fastlyHandleRequests(e,t,s,r=!1){let i=new l({apiKey:s,merchantSystemUrn:t});return r&&new URL(e.url).pathname==="/license.xml"?await U(l.baseUrl,t):i.handleRequest(e,l.checkIfBotRequest,null)}async handleRequest(e,t,s){let r=e.headers.get("Authorization")||"",i=r.startsWith("License ")?r.slice(8):"",o=e.url,g=e.headers.get("User-Agent")||"unknown";return t&&!t(e,s)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):S({licenseToken:i,url:o,userAgent:g,ctx:s,supertabBaseUrl:l.baseUrl,merchantSystemUrn:this.merchantSystemUrn,debug:L,recordEvent:(p,h,c)=>this.recordEvent(p,h,c)})}static async generateLicenseToken(e,t,s,r,i){let o=l.baseUrl+"/rsl/token";return I({clientId:e,kid:t,privateKeyPem:s,tokenEndpoint:o,resourceUrl:r,licenseXml:i,debug:L})}};l.baseUrl="https://api-connect.supertab.co",l._instance=null;var b=l;0&&(module.exports={SupertabConnect});
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/customer.ts","../src/license.ts","../src/jwks.ts"],"sourcesContent":["import {\n SupertabConnectConfig,\n Env,\n EventPayload,\n FASTLY_BACKEND,\n LicenseTokenVerificationResult,\n} from \"./types\";\nimport { generateLicenseToken as generateLicenseTokenHelper } from \"./customer\";\nimport {\n baseLicenseHandleRequest as baseLicenseHandleRequestHelper,\n hostRSLicenseXML as hostRSLicenseXMLHelper,\n verifyLicenseToken as verifyLicenseTokenHelper,\n} from \"./license\";\n\nexport type { Env } from \"./types\";\n\nconst debug = true; // Set to true for debugging purposes\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.supertab.co\";\n private merchantSystemUrn!: string;\n\n private static _instance: SupertabConnect | null = null;\n\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (\n !(\n config.apiKey === SupertabConnect._instance.apiKey &&\n config.merchantSystemUrn ===\n SupertabConnect._instance.merchantSystemUrn\n )\n ) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey || !config.merchantSystemUrn) {\n throw new Error(\n \"Missing required configuration: apiKey and merchantSystemUrn are required\"\n );\n }\n this.apiKey = config.apiKey;\n this.merchantSystemUrn = config.merchantSystemUrn;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Verify a license token\n * @param licenseToken The license token to verify\n * @param requestUrl The URL of the request being made\n * @returns A promise that resolves with the verification result\n */\n async verifyLicenseToken(\n licenseToken: string,\n requestUrl: string\n ): Promise<LicenseTokenVerificationResult> {\n return verifyLicenseTokenHelper({\n licenseToken,\n requestUrl,\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug,\n });\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param properties Additional properties to include with the event\n * @param licenseId Optional license ID associated with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n properties: Record<string, any> = {},\n licenseId?: string\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: any = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n };\n // @ts-ignore\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n const response = await fetch(\n `${SupertabConnect.baseUrl}/events`,\n options\n );\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string,\n enableRSL: boolean = false,\n ): Promise<Response> {\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: merchantApiKey,\n merchantSystemUrn: merchantSystemUrn,\n });\n\n if (enableRSL) {\n if (new URL(request.url).pathname === \"/license.xml\") {\n return await hostRSLicenseXMLHelper(\n SupertabConnect.baseUrl,\n merchantSystemUrn\n );\n }\n }\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n null\n );\n }\n\n async handleRequest(\n request: Request,\n botDetectionHandler?: (request: Request, ctx?: any) => boolean,\n ctx?: any\n ): Promise<Response> {\n // 1. Extract license token, URL, and user agent from the request\n const auth = request.headers.get(\"Authorization\") || \"\";\n const licenseToken = auth.startsWith(\"License \") ? auth.slice(8) : \"\";\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Handle the license token request\n return baseLicenseHandleRequestHelper({\n licenseToken,\n url,\n userAgent: user_agent,\n ctx,\n supertabBaseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: this.merchantSystemUrn,\n debug,\n recordEvent: (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string\n ) => this.recordEvent(eventName, properties, licenseId),\n });\n }\n\n /**\n * Request a license token from the Supertab Connect token endpoint.\n * @param clientId OAuth client identifier used for the assertion issuer/subject claims.\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem Private key in PEM format used to sign the client assertion.\n * @param tokenEndpoint Token endpoint URL.\n * @param resourceUrl Resource URL attempting to access with a License.\n * @param licenseXml XML license document to include in the request payload.\n * @returns Promise resolving to the issued license access token string.\n */\n static async generateLicenseToken(\n clientId: string,\n kid: string,\n privateKeyPem: string,\n resourceUrl: string,\n licenseXml: string\n ): Promise<string> {\n const tokenEndpoint = SupertabConnect.baseUrl + \"/rsl/token\";\n return generateLicenseTokenHelper({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n });\n }\n}\n","export interface SupertabConnectConfig {\n apiKey: string;\n merchantSystemUrn: string;\n}\n\n/**\n * Defines the shape for environment variables (used in CloudFlare integration).\n * These are used to identify and authenticate the Merchant System with the Supertab Connect API.\n */\nexport interface Env {\n\t/** The unique identifier for the merchant system. */\n\tMERCHANT_SYSTEM_URN: string;\n\t/** The API key for authenticating with the Supertab Connect. */\n\tMERCHANT_API_KEY: string;\n\t[key: string]: string;\n}\n\nexport interface EventPayload {\n event_name: string;\n license_id?: string;\n merchant_system_urn: string;\n properties: Record<string, any>;\n}\n\nexport interface LicenseTokenVerificationResult {\n valid: boolean;\n reason?: string;\n licenseId?: string;\n payload?: any;\n}\n\nexport enum LicenseTokenInvalidReason {\n MISSING_TOKEN = \"missing_license_token\",\n INVALID_HEADER = \"invalid_license_header\",\n INVALID_ALG = \"invalid_license_algorithm\",\n INVALID_PAYLOAD = \"invalid_license_payload\",\n INVALID_ISSUER = \"invalid_license_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"license_signature_verification_failed\",\n EXPIRED = \"license_token_expired\",\n INVALID_AUDIENCE = \"invalid_license_audience\",\n}\n\nexport const FASTLY_BACKEND = \"stc-backend\";\n\nexport interface FetchOptions extends RequestInit {\n // Fastly-specific extension for backend routing\n backend?: string;\n}","import { importPKCS8, SignJWT } from \"jose\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const supportedAlgs: SupportedAlg[] = [\"ES256\", \"RS256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\nexport async function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: payload.toString(),\n };\n\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: any;\n try {\n data = await response.json();\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nexport type { GenerateLicenseTokenParams };\n","import {\n decodeProtectedHeader,\n decodeJwt,\n JWTPayload,\n JWTHeaderParameters,\n jwtVerify,\n} from \"jose\";\nimport {\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n FASTLY_BACKEND,\n FetchOptions,\n} from \"./types\";\nimport { fetchPlatformJwks } from \"./jwks\";\n\nconst stripTrailingSlash = (value: string) => value.replace(/\\/+$/, \"\");\n\nexport type VerifyLicenseTokenParams = {\n licenseToken: string;\n requestUrl: string;\n supertabBaseUrl: string;\n debug: boolean;\n};\n\nexport async function verifyLicenseToken({\n licenseToken,\n requestUrl,\n supertabBaseUrl,\n debug,\n}: VerifyLicenseTokenParams): Promise<LicenseTokenVerificationResult> {\n if (!licenseToken) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(licenseToken) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT header:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"ES256\") {\n if (debug) {\n console.error(\"Unsupported license JWT alg:\", header.alg);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ALG,\n };\n }\n\n let payload: JWTPayload;\n try {\n payload = decodeJwt(licenseToken);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT payload:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n // @ts-ignore\n const licenseId: string | undefined = payload.license_id;\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(supertabBaseUrl)) {\n if (debug) {\n console.error(\"Invalid license JWT issuer:\", issuer);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ISSUER,\n licenseId,\n };\n }\n\n const audienceValues = Array.isArray(payload.aud)\n ? payload.aud.filter((entry): entry is string => typeof entry === \"string\")\n : typeof payload.aud === \"string\"\n ? [payload.aud]\n : [];\n\n const requestUrlNormalized = stripTrailingSlash(requestUrl);\n const matchesRequestUrl = audienceValues.some((value) => {\n const normalizedAudience = stripTrailingSlash(value);\n if (!normalizedAudience) return false;\n return requestUrlNormalized.startsWith(normalizedAudience);\n });\n\n if (!matchesRequestUrl) {\n if (debug) {\n console.error(\n \"License JWT audience does not match request URL:\",\n payload.aud\n );\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_AUDIENCE,\n licenseId,\n };\n }\n\n try {\n const jwks = await fetchPlatformJwks(supertabBaseUrl, debug);\n\n const getKey = async (jwtHeader: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === jwtHeader.kid);\n if (!jwk) {\n throw new Error(`No matching platform key found: ${jwtHeader.kid}`);\n }\n return jwk;\n };\n\n const result = await jwtVerify(licenseToken, getKey, {\n issuer,\n algorithms: [header.alg],\n clockTolerance: \"1m\",\n });\n\n return {\n valid: true,\n licenseId,\n payload: result.payload,\n };\n } catch (error) {\n if (debug) {\n console.error(\"License JWT verification failed:\", error);\n }\n\n if (error instanceof Error && error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.EXPIRED,\n licenseId,\n };\n }\n\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n licenseId,\n };\n }\n}\n\nexport function generateLicenseLink({\n requestUrl,\n}: {\n requestUrl: string;\n}): string {\n const baseURL = new URL(requestUrl);\n return `${baseURL.protocol}//${baseURL.host}/license.xml`;\n}\n\ntype RecordEventFn = (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string\n) => Promise<void>;\n\ntype BaseLicenseHandleRequestParams = {\n licenseToken: string;\n url: string;\n userAgent: string;\n ctx: any;\n supertabBaseUrl: string;\n merchantSystemUrn?: string;\n debug: boolean;\n recordEvent: RecordEventFn;\n};\n\nexport async function baseLicenseHandleRequest({\n licenseToken,\n url,\n userAgent,\n ctx,\n supertabBaseUrl,\n merchantSystemUrn,\n debug,\n recordEvent,\n}: BaseLicenseHandleRequestParams): Promise<Response> {\n const verification = await verifyLicenseToken({\n licenseToken,\n requestUrl: url,\n supertabBaseUrl,\n debug,\n });\n\n async function recordLicenseEvent(eventName: string) {\n const eventProperties = {\n page_url: url,\n user_agent: userAgent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n\n const eventPromise = recordEvent(\n eventName,\n eventProperties,\n verification.licenseId\n );\n\n if (ctx?.waitUntil) {\n ctx.waitUntil(eventPromise);\n }\n\n return eventPromise;\n }\n\n if (!verification.valid) {\n await recordLicenseEvent(\n verification.reason || \"license_token_verification_failed\"\n );\n\n let rslError = \"invalid_request\";\n let errorDescription = \"Access to this resource requires a license\";\n\n switch (verification.reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n break;\n case LicenseTokenInvalidReason.EXPIRED:\n rslError = \"invalid_token\";\n errorDescription = \"The license token has expired\";\n break;\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n rslError = \"invalid_token\";\n errorDescription = \"The license token signature is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_HEADER:\n rslError = \"invalid_token\";\n errorDescription = \"The license token header is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n rslError = \"invalid_token\";\n errorDescription = \"The license token payload is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n rslError = \"invalid_token\";\n errorDescription = \"The license token issuer is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n rslError = \"invalid_token\";\n errorDescription = \"The license token audience is invalid\";\n break;\n default:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n }\n\n const licenseLink = generateLicenseLink({\n requestUrl: url,\n });\n const errorUri = `${supertabBaseUrl}/docs/errors#${rslError}`;\n\n const headers = new Headers({\n \"Content-Type\": \"text/plain; charset=UTF-8\",\n \"WWW-Authenticate\": `License error=\"${rslError}\", error_description=\"${errorDescription}\", error_uri=\"${errorUri}\"`,\n Link: `${licenseLink}; rel=\"license\"; type=\"application/rsl+xml\"`,\n });\n\n const responseBody = `Access to this resource requires a valid license token. Error: ${rslError} - ${errorDescription}`;\n\n return new Response(responseBody, {\n status: 401,\n headers,\n });\n }\n\n await recordLicenseEvent(\"license_used\");\n return new Response(\"✅ License Token Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n}\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nexport async function hostRSLicenseXML(\n supertabBaseUrl: string,\n merchantSystemUrn: string\n): Promise<Response> {\n const licenseUrl = `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/license.xml`;\n const response = await fetch(licenseUrl, buildFetchOptions());\n\n if (!response.ok) {\n return new Response(\"License not found\", { status: 404 });\n }\n\n const licenseXml = await response.text();\n\n return new Response(licenseXml, {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/xml\" }),\n });\n}\n","import { FASTLY_BACKEND, FetchOptions } from \"./types\";\n\nconst jwksCache = new Map<string, any>();\n\ntype JwksCacheKey = string;\n\ntype FetchJwksParams = {\n cacheKey: JwksCacheKey;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<any> {\n if (!jwksCache.has(cacheKey)) {\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json();\n jwksCache.set(cacheKey, jwksData);\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(cacheKey);\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/platform`;\n console.log(\"Fetching platform JWKS from:\", jwksUrl);\n\n return fetchAndCacheJwks({\n cacheKey: \"platform_jwks\",\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch platform JWKS\",\n logLabel: \"Error fetching platform JWKS:\",\n });\n}\n\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n"],"mappings":"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","names":["index_exports","__export","SupertabConnect","__toCommonJS","FASTLY_BACKEND","import_jose","importKeyForAlgs","privateKeyPem","debug","supportedAlgs","algorithm","importError","generateLicenseToken","clientId","kid","tokenEndpoint","resourceUrl","licenseXml","key","alg","now","clientAssertion","payload","requestOptions","response","errorBody","errorMessage","data","parseError","error","import_jose","jwksCache","buildFetchOptions","options","FASTLY_BACKEND","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","response","jwksData","error","fetchPlatformJwks","baseUrl","jwksUrl","stripTrailingSlash","value","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","header","error","payload","licenseId","issuer","audienceValues","entry","requestUrlNormalized","normalizedAudience","jwks","fetchPlatformJwks","result","jwtHeader","jwk","key","generateLicenseLink","baseURL","baseLicenseHandleRequest","url","userAgent","ctx","merchantSystemUrn","recordEvent","verification","recordLicenseEvent","eventName","eventProperties","eventPromise","rslError","errorDescription","licenseLink","errorUri","headers","responseBody","buildFetchOptions","options","FASTLY_BACKEND","hostRSLicenseXML","licenseUrl","response","licenseXml","debug","_SupertabConnect","config","reset","url","licenseToken","requestUrl","verifyLicenseToken","eventName","properties","licenseId","payload","options","FASTLY_BACKEND","response","error","request","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","ctx","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","enableRSL","supertabConnect","hostRSLicenseXML","botDetectionHandler","auth","user_agent","baseLicenseHandleRequest","clientId","kid","privateKeyPem","resourceUrl","licenseXml","tokenEndpoint","generateLicenseToken","SupertabConnect"]}
@@ -13,9 +13,10 @@ interface Env {
13
13
  MERCHANT_API_KEY: string;
14
14
  [key: string]: string;
15
15
  }
16
- interface TokenVerificationResult {
16
+ interface LicenseTokenVerificationResult {
17
17
  valid: boolean;
18
18
  reason?: string;
19
+ licenseId?: string;
19
20
  payload?: any;
20
21
  }
21
22
 
@@ -36,11 +37,12 @@ declare class SupertabConnect {
36
37
  */
37
38
  static setBaseUrl(url: string): void;
38
39
  /**
39
- * Verify a JWT token
40
- * @param token The JWT token to verify
40
+ * Verify a license token
41
+ * @param licenseToken The license token to verify
42
+ * @param requestUrl The URL of the request being made
41
43
  * @returns A promise that resolves with the verification result
42
44
  */
43
- verifyToken(token: string): Promise<TokenVerificationResult>;
45
+ verifyLicenseToken(licenseToken: string, requestUrl: string): Promise<LicenseTokenVerificationResult>;
44
46
  /**
45
47
  * Records an analytics event
46
48
  * @param eventName Name of the event to record
@@ -49,20 +51,10 @@ declare class SupertabConnect {
49
51
  * @returns Promise that resolves when the event is recorded
50
52
  */
51
53
  recordEvent(eventName: string, properties?: Record<string, any>, licenseId?: string): Promise<void>;
52
- /**
53
- * Handle the request, report an event to Supertab Connect and return a response
54
- */
55
- private baseHandleRequest;
56
- /**
57
- * Handle the request for license tokens, report an event to Supertab Connect and return a response
58
- */
59
- private baseLicenseHandleRequest;
60
- private extractDataFromRequest;
61
54
  static checkIfBotRequest(request: Request): boolean;
62
55
  static cloudflareHandleRequests(request: Request, env: Env, ctx: any): Promise<Response>;
63
56
  static fastlyHandleRequests(request: Request, merchantSystemUrn: string, merchantApiKey: string, enableRSL?: boolean): Promise<Response>;
64
57
  handleRequest(request: Request, botDetectionHandler?: (request: Request, ctx?: any) => boolean, ctx?: any): Promise<Response>;
65
- hostRSLicenseXML(): Promise<Response>;
66
58
  /**
67
59
  * Request a license token from the Supertab Connect token endpoint.
68
60
  * @param clientId OAuth client identifier used for the assertion issuer/subject claims.
@@ -74,14 +66,6 @@ declare class SupertabConnect {
74
66
  * @returns Promise resolving to the issued license access token string.
75
67
  */
76
68
  static generateLicenseToken(clientId: string, kid: string, privateKeyPem: string, resourceUrl: string, licenseXml: string): Promise<string>;
77
- /** Generate a customer JWT
78
- * @param customerURN The customer's unique resource name (URN).
79
- * @param kid The key ID to include in the JWT header.
80
- * @param privateKeyPem The private key in PEM format used to sign the JWT.
81
- * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).
82
- * @returns A promise that resolves to the generated JWT as a string.
83
- */
84
- static generateCustomerJWT(customerURN: string, kid: string, privateKeyPem: string, expirationSeconds?: number): Promise<string>;
85
69
  }
86
70
 
87
71
  export { type Env, SupertabConnect };
package/dist/index.d.ts CHANGED
@@ -13,9 +13,10 @@ interface Env {
13
13
  MERCHANT_API_KEY: string;
14
14
  [key: string]: string;
15
15
  }
16
- interface TokenVerificationResult {
16
+ interface LicenseTokenVerificationResult {
17
17
  valid: boolean;
18
18
  reason?: string;
19
+ licenseId?: string;
19
20
  payload?: any;
20
21
  }
21
22
 
@@ -36,11 +37,12 @@ declare class SupertabConnect {
36
37
  */
37
38
  static setBaseUrl(url: string): void;
38
39
  /**
39
- * Verify a JWT token
40
- * @param token The JWT token to verify
40
+ * Verify a license token
41
+ * @param licenseToken The license token to verify
42
+ * @param requestUrl The URL of the request being made
41
43
  * @returns A promise that resolves with the verification result
42
44
  */
43
- verifyToken(token: string): Promise<TokenVerificationResult>;
45
+ verifyLicenseToken(licenseToken: string, requestUrl: string): Promise<LicenseTokenVerificationResult>;
44
46
  /**
45
47
  * Records an analytics event
46
48
  * @param eventName Name of the event to record
@@ -49,20 +51,10 @@ declare class SupertabConnect {
49
51
  * @returns Promise that resolves when the event is recorded
50
52
  */
51
53
  recordEvent(eventName: string, properties?: Record<string, any>, licenseId?: string): Promise<void>;
52
- /**
53
- * Handle the request, report an event to Supertab Connect and return a response
54
- */
55
- private baseHandleRequest;
56
- /**
57
- * Handle the request for license tokens, report an event to Supertab Connect and return a response
58
- */
59
- private baseLicenseHandleRequest;
60
- private extractDataFromRequest;
61
54
  static checkIfBotRequest(request: Request): boolean;
62
55
  static cloudflareHandleRequests(request: Request, env: Env, ctx: any): Promise<Response>;
63
56
  static fastlyHandleRequests(request: Request, merchantSystemUrn: string, merchantApiKey: string, enableRSL?: boolean): Promise<Response>;
64
57
  handleRequest(request: Request, botDetectionHandler?: (request: Request, ctx?: any) => boolean, ctx?: any): Promise<Response>;
65
- hostRSLicenseXML(): Promise<Response>;
66
58
  /**
67
59
  * Request a license token from the Supertab Connect token endpoint.
68
60
  * @param clientId OAuth client identifier used for the assertion issuer/subject claims.
@@ -74,14 +66,6 @@ declare class SupertabConnect {
74
66
  * @returns Promise resolving to the issued license access token string.
75
67
  */
76
68
  static generateLicenseToken(clientId: string, kid: string, privateKeyPem: string, resourceUrl: string, licenseXml: string): Promise<string>;
77
- /** Generate a customer JWT
78
- * @param customerURN The customer's unique resource name (URN).
79
- * @param kid The key ID to include in the JWT header.
80
- * @param privateKeyPem The private key in PEM format used to sign the JWT.
81
- * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).
82
- * @returns A promise that resolves to the generated JWT as a string.
83
- */
84
- static generateCustomerJWT(customerURN: string, kid: string, privateKeyPem: string, expirationSeconds?: number): Promise<string>;
85
69
  }
86
70
 
87
71
  export { type Env, SupertabConnect };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var I=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var H=(a,e)=>{for(var t in e)I(a,t,{get:e[t],enumerable:!0})},q=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of J(e))!x.call(a,n)&&n!==t&&I(a,n,{get:()=>e[n],enumerable:!(s=D(e,n))||s.enumerable});return a};var K=a=>q(I({},"__esModule",{value:!0}),a);var j={};H(j,{SupertabConnect:()=>S});module.exports=K(j);var h="stc-backend";var E=require("jose");var w=require("jose");async function W(a,e){let t=["ES256","RS256"];for(let s of t)try{return{key:await(0,w.importPKCS8)(a,s),alg:s}}catch(n){e&&console.debug(`Private key did not import using ${s}, retrying...`,n)}throw new Error("Unsupported private key format. Expected RSA or P-256 EC private key.")}async function L({clientId:a,kid:e,privateKeyPem:t,tokenEndpoint:s,resourceUrl:n,licenseXml:r,debug:i}){let{key:u,alg:d}=await W(t,i),p=Math.floor(Date.now()/1e3),c=await new w.SignJWT({}).setProtectedHeader({alg:d,kid:e}).setIssuer(a).setSubject(a).setIssuedAt(p).setExpirationTime(p+300).setAudience(s).sign(u),o=new URLSearchParams({grant_type:"rsl",client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:c,license:r,resource:n}),f={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:o.toString()};try{let g=await fetch(s,f);if(!g.ok){let m=await g.text().catch(()=>""),A=`Failed to obtain license token: ${g.status} ${g.statusText}${m?` - ${m}`:""}`;throw new Error(A)}let y;try{y=await g.json()}catch(m){throw i&&console.error("Failed to parse license token response as JSON:",m),new Error("Failed to parse license token response as JSON")}if(!y?.access_token)throw new Error("License token response missing access_token");return y.access_token}catch(g){throw i&&console.error("Error generating license token:",g),g}}async function T({customerURN:a,kid:e,privateKeyPem:t,expirationSeconds:s=3600}){let n="RS256",r=await(0,w.importPKCS8)(t,n),i=Math.floor(Date.now()/1e3);return new w.SignJWT({}).setProtectedHeader({alg:n,kid:e}).setIssuer(a).setIssuedAt(i).setExpirationTime(i+s).sign(r)}var _=require("jose");var v=new Map;function F(){let a={method:"GET"};return globalThis?.fastly&&(a={...a,backend:h}),a}async function b({cacheKey:a,url:e,debug:t,failureMessage:s,logLabel:n}){if(!v.has(a))try{let r=await fetch(e,F());if(!r.ok)throw new Error(`${s}: ${r.status}`);let i=await r.json();v.set(a,i)}catch(r){throw t&&console.error(n,r),r}return v.get(a)}async function P(a,e,t){let s=`${a}/.well-known/jwks.json/${encodeURIComponent(e)}`;return b({cacheKey:e,url:s,debug:t,failureMessage:"Failed to fetch JWKS",logLabel:"Error fetching JWKS:"})}async function U(a,e){let t=`${a}/.well-known/jwks.json/platform`;return console.log("Fetching platform JWKS from:",t),b({cacheKey:"platform_jwks",url:t,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}var N=a=>a.replace(/\/+$/,"");async function V({licenseToken:a,requestUrl:e,supertabBaseUrl:t,debug:s}){if(!a)return{valid:!1,reason:"missing_license_token"};let n;try{n=(0,_.decodeProtectedHeader)(a)}catch(o){return s&&console.error("Invalid license JWT header:",o),{valid:!1,reason:"invalid_license_header"}}if(n.alg!=="ES256")return s&&console.error("Unsupported license JWT alg:",n.alg),{valid:!1,reason:"invalid_license_algorithm"};let r;try{r=(0,_.decodeJwt)(a)}catch(o){return s&&console.error("Invalid license JWT payload:",o),{valid:!1,reason:"invalid_license_payload"}}let i=r.license_id,u=r.iss;if(!u||!u.startsWith(t))return s&&console.error("Invalid license JWT issuer:",u),{valid:!1,reason:"invalid_license_issuer",licenseId:i};let d=Array.isArray(r.aud)?r.aud.filter(o=>typeof o=="string"):typeof r.aud=="string"?[r.aud]:[],p=N(e);if(!d.some(o=>{let f=N(o);return f?p.startsWith(f):!1}))return s&&console.error("License JWT audience does not match request URL:",r.aud),{valid:!1,reason:"invalid_license_audience",licenseId:i};try{let o=await U(t,s),g=await(0,_.jwtVerify)(a,async y=>{let m=o.keys.find(A=>A.kid===y.kid);if(!m)throw new Error(`No matching platform key found: ${y.kid}`);return m},{issuer:u,algorithms:[n.alg],clockTolerance:"1m"});return{valid:!0,licenseId:i,payload:g.payload}}catch(o){return s&&console.error("License JWT verification failed:",o),o instanceof Error&&o.message?.includes("exp")?{valid:!1,reason:"license_token_expired",licenseId:i}:{valid:!1,reason:"license_signature_verification_failed",licenseId:i}}}function O({requestUrl:a}){let e=new URL(a);return`${e.protocol}//${e.host}/license.xml`}async function C({licenseToken:a,url:e,userAgent:t,ctx:s,supertabBaseUrl:n,merchantSystemUrn:r,debug:i,recordEvent:u}){let d=await V({licenseToken:a,requestUrl:e,supertabBaseUrl:n,debug:i});async function p(c){let o={page_url:e,user_agent:t,verification_status:d.valid?"valid":"invalid",verification_reason:d.reason||"success"},f=u(c,o,d.licenseId);return s?.waitUntil&&s.waitUntil(f),f}if(!d.valid){await p(d.reason||"license_token_verification_failed");let c="invalid_request",o="Access to this resource requires a license";switch(d.reason){case"missing_license_token":c="invalid_request",o="Access to this resource requires a license";break;case"license_token_expired":c="invalid_token",o="The license token has expired";break;case"license_signature_verification_failed":c="invalid_token",o="The license token signature is invalid";break;case"invalid_license_header":c="invalid_token",o="The license token header is invalid";break;case"invalid_license_payload":c="invalid_token",o="The license token payload is invalid";break;case"invalid_license_issuer":c="invalid_token",o="The license token issuer is invalid";break;case"invalid_license_audience":c="invalid_token",o="The license token audience is invalid";break;default:c="invalid_request",o="Access to this resource requires a license"}let f=O({requestUrl:e}),g=`${n}/docs/errors#${c}`,y=new Headers({"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${c}", error_description="${o}", error_uri="${g}"`,Link:`${f}; rel="license"; type="application/rsl+xml"`}),m=`Access to this resource requires a valid license token. Error: ${c} - ${o}`;return new Response(m,{status:401,headers:y})}return await p("license_used"),new Response("\u2705 License Token Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}function $(){let a={method:"GET"};return globalThis?.fastly&&(a={...a,backend:h}),a}async function R(a,e){let t=`${a}/merchants/systems/${e}/license.xml`,s=await fetch(t,$());if(!s.ok)return new Response("License not found",{status:404});let n=await s.text();return new Response(n,{status:200,headers:new Headers({"Content-Type":"application/xml"})})}var k=!0,l=class l{constructor(e,t=!1){if(!t&&l._instance){if(!(e.apiKey===l._instance.apiKey&&e.merchantSystemUrn===l._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return l._instance}if(t&&l._instance&&l.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,l._instance=this}static resetInstance(){l._instance=null}static setBaseUrl(e){l.baseUrl=e}async verifyToken(e){if(!e)return{valid:!1,reason:"missing_token"};let t;try{t=(0,E.decodeProtectedHeader)(e)}catch(r){return k&&console.error("Invalid JWT header:",r),{valid:!1,reason:"invalid_header"}}if(t.alg!=="RS256")return{valid:!1,reason:"invalid_algorithm"};let s;try{s=(0,E.decodeJwt)(e)}catch(r){return k&&console.error("Invalid JWT payload:",r),{valid:!1,reason:"invalid_payload"}}let n=s.iss;if(!n||!n.startsWith("urn:stc:customer:"))return{valid:!1,reason:"invalid_issuer"};try{let r=await P(l.baseUrl,n,k);return{valid:!0,payload:(await(0,E.jwtVerify)(e,async d=>{let p=r.keys.find(c=>c.kid===d.kid);if(!p)throw new Error(`No matching key found: ${d.kid}`);return p},{issuer:n,algorithms:["RS256"],clockTolerance:"1m"})).payload}}catch(r){return k&&console.error("JWT verification failed:",r),r.message?.includes("exp")?{valid:!1,reason:"token_expired"}:{valid:!1,reason:"signature_verification_failed"}}}async recordEvent(e,t={},s){let n={event_name:e,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",license_id:s,properties:t};try{let r={method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(n)};globalThis?.fastly&&(r={...r,backend:h});let i=await fetch(`${l.baseUrl}/events`,r);i.ok||console.log(`Failed to record event: ${i.status}`)}catch(r){console.log("Error recording event:",r)}}async baseHandleRequest(e,t,s,n){let r=await this.verifyToken(e);async function i(u,d,p){let c={page_url:t,user_agent:s,verification_status:r.valid?"valid":"invalid",verification_reason:r.reason||"success"};if(p){let o=u.recordEvent(d,c);return p.waitUntil(o),o}else return await u.recordEvent(d,c)}if(!r.valid){await i(this,r.reason||"token_verification_failed",n);let u="Payment required: you need to present a valid Supertab Connect token to access this content. Check out the provided url for details",d="\u274C Content access denied"+(r.reason?`: ${r.reason}`:""),c={url:`${l.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`,message:u,details:d};return new Response(JSON.stringify(c),{status:402,headers:new Headers({"Content-Type":"application/json"})})}return await i(this,"page_viewed",n),new Response("\u2705 Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}async baseLicenseHandleRequest(e,t,s,n){return C({licenseToken:e,url:t,userAgent:s,ctx:n,supertabBaseUrl:l.baseUrl,merchantSystemUrn:this.merchantSystemUrn,debug:k,recordEvent:(r,i,u)=>this.recordEvent(r,i,u)})}extractDataFromRequest(e){let t=e.headers.get("Authorization")||"",s=t.startsWith("Bearer ")?t.slice(7):"",n=t.startsWith("License ")?t.slice(8):"",r=e.url,i=e.headers.get("User-Agent")||"unknown";return{token:s,licenseToken:n,url:r,user_agent:i}}static checkIfBotRequest(e){let t=e.headers.get("User-Agent")||"",s=e.headers.get("accept")||"",n=e.headers.get("sec-ch-ua"),r=e.headers.get("accept-language"),i=e.cf?.botManagement?.score,u=["chatgpt-user","perplexitybot","gptbot","anthropic-ai","ccbot","claude-web","claudebot","cohere-ai","youbot","diffbot","oai-searchbot","meta-externalagent","timpibot","amazonbot","bytespider","perplexity-user","googlebot","bot","curl","wget"],d=t.toLowerCase(),p=u.some(y=>d.includes(y)),c=t.toLowerCase().includes("headless")||t.toLowerCase().includes("puppeteer")||!n,o=!t.toLowerCase().includes("headless")||!t.toLowerCase().includes("puppeteer")||!n,f=!s||!r,g=typeof i=="number"&&i<30;return console.log("Bot Detection Details:",{botUaMatch:p,headlessIndicators:c,missingHeaders:f,lowBotScore:g,botScore:i}),(d.includes("safari")||d.includes("mozilla"))&&c&&o?!1:p||c||f||g}static async cloudflareHandleRequests(e,t,s){let{MERCHANT_SYSTEM_URN:n,MERCHANT_API_KEY:r}=t;return new l({apiKey:r,merchantSystemUrn:n}).handleRequest(e,l.checkIfBotRequest,s)}static async fastlyHandleRequests(e,t,s,n=!1){let r=new l({apiKey:s,merchantSystemUrn:t});return n&&new URL(e.url).pathname==="/license.xml"?await R(l.baseUrl,t):r.handleRequest(e,l.checkIfBotRequest,null)}async handleRequest(e,t,s){let{token:n,licenseToken:r,url:i,user_agent:u}=this.extractDataFromRequest(e);return t&&!t(e,s)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):n?this.baseHandleRequest(n,i,u,s):this.baseLicenseHandleRequest(r,i,u,s)}async hostRSLicenseXML(){return R(l.baseUrl,this.merchantSystemUrn)}static async generateLicenseToken(e,t,s,n,r){let i=l.baseUrl+"/rsl/token";return L({clientId:e,kid:t,privateKeyPem:s,tokenEndpoint:i,resourceUrl:n,licenseXml:r,debug:k})}static async generateCustomerJWT(e,t,s,n=3600){return T({customerURN:e,kid:t,privateKeyPem:s,expirationSeconds:n})}};l.baseUrl="https://api-connect.supertab.co",l._instance=null;var S=l;0&&(module.exports={SupertabConnect});
1
+ var m="stc-backend";import{importPKCS8 as T,SignJWT as S}from"jose";async function U(r,e){let t=["ES256","RS256"];for(let n of t)try{return{key:await T(r,n),alg:n}}catch(i){e&&console.debug(`Private key did not import using ${n}, retrying...`,i)}throw new Error("Unsupported private key format. Expected RSA or P-256 EC private key.")}async function A({clientId:r,kid:e,privateKeyPem:t,tokenEndpoint:n,resourceUrl:i,licenseXml:s,debug:o}){let{key:g,alg:p}=await U(t,o),h=Math.floor(Date.now()/1e3),c=await new S({}).setProtectedHeader({alg:p,kid:e}).setIssuer(r).setSubject(r).setIssuedAt(h).setExpirationTime(h+300).setAudience(n).sign(g),a=new URLSearchParams({grant_type:"rsl",client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:c,license:s,resource:i}),u={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:a.toString()};try{let d=await fetch(n,u);if(!d.ok){let y=await d.text().catch(()=>""),k=`Failed to obtain license token: ${d.status} ${d.statusText}${y?` - ${y}`:""}`;throw new Error(k)}let f;try{f=await d.json()}catch(y){throw o&&console.error("Failed to parse license token response as JSON:",y),new Error("Failed to parse license token response as JSON")}if(!f?.access_token)throw new Error("License token response missing access_token");return f.access_token}catch(d){throw o&&console.error("Error generating license token:",d),d}}import{decodeProtectedHeader as C,decodeJwt as x,jwtVerify as D}from"jose";var w=new Map;function P(){let r={method:"GET"};return globalThis?.fastly&&(r={...r,backend:m}),r}async function N({cacheKey:r,url:e,debug:t,failureMessage:n,logLabel:i}){if(!w.has(r))try{let s=await fetch(e,P());if(!s.ok)throw new Error(`${n}: ${s.status}`);let o=await s.json();w.set(r,o)}catch(s){throw t&&console.error(i,s),s}return w.get(r)}async function v(r,e){let t=`${r}/.well-known/jwks.json/platform`;return console.log("Fetching platform JWKS from:",t),N({cacheKey:"platform_jwks",url:t,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}var L=r=>r.replace(/\/+$/,"");async function E({licenseToken:r,requestUrl:e,supertabBaseUrl:t,debug:n}){if(!r)return{valid:!1,reason:"missing_license_token"};let i;try{i=C(r)}catch(a){return n&&console.error("Invalid license JWT header:",a),{valid:!1,reason:"invalid_license_header"}}if(i.alg!=="ES256")return n&&console.error("Unsupported license JWT alg:",i.alg),{valid:!1,reason:"invalid_license_algorithm"};let s;try{s=x(r)}catch(a){return n&&console.error("Invalid license JWT payload:",a),{valid:!1,reason:"invalid_license_payload"}}let o=s.license_id,g=s.iss;if(!g||!g.startsWith(t))return n&&console.error("Invalid license JWT issuer:",g),{valid:!1,reason:"invalid_license_issuer",licenseId:o};let p=Array.isArray(s.aud)?s.aud.filter(a=>typeof a=="string"):typeof s.aud=="string"?[s.aud]:[],h=L(e);if(!p.some(a=>{let u=L(a);return u?h.startsWith(u):!1}))return n&&console.error("License JWT audience does not match request URL:",s.aud),{valid:!1,reason:"invalid_license_audience",licenseId:o};try{let a=await v(t,n),d=await D(r,async f=>{let y=a.keys.find(k=>k.kid===f.kid);if(!y)throw new Error(`No matching platform key found: ${f.kid}`);return y},{issuer:g,algorithms:[i.alg],clockTolerance:"1m"});return{valid:!0,licenseId:o,payload:d.payload}}catch(a){return n&&console.error("License JWT verification failed:",a),a instanceof Error&&a.message?.includes("exp")?{valid:!1,reason:"license_token_expired",licenseId:o}:{valid:!1,reason:"license_signature_verification_failed",licenseId:o}}}function q({requestUrl:r}){let e=new URL(r);return`${e.protocol}//${e.host}/license.xml`}async function b({licenseToken:r,url:e,userAgent:t,ctx:n,supertabBaseUrl:i,merchantSystemUrn:s,debug:o,recordEvent:g}){let p=await E({licenseToken:r,requestUrl:e,supertabBaseUrl:i,debug:o});async function h(c){let a={page_url:e,user_agent:t,verification_status:p.valid?"valid":"invalid",verification_reason:p.reason||"success"},u=g(c,a,p.licenseId);return n?.waitUntil&&n.waitUntil(u),u}if(!p.valid){await h(p.reason||"license_token_verification_failed");let c="invalid_request",a="Access to this resource requires a license";switch(p.reason){case"missing_license_token":c="invalid_request",a="Access to this resource requires a license";break;case"license_token_expired":c="invalid_token",a="The license token has expired";break;case"license_signature_verification_failed":c="invalid_token",a="The license token signature is invalid";break;case"invalid_license_header":c="invalid_token",a="The license token header is invalid";break;case"invalid_license_payload":c="invalid_token",a="The license token payload is invalid";break;case"invalid_license_issuer":c="invalid_token",a="The license token issuer is invalid";break;case"invalid_license_audience":c="invalid_token",a="The license token audience is invalid";break;default:c="invalid_request",a="Access to this resource requires a license"}let u=q({requestUrl:e}),d=`${i}/docs/errors#${c}`,f=new Headers({"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${c}", error_description="${a}", error_uri="${d}"`,Link:`${u}; rel="license"; type="application/rsl+xml"`}),y=`Access to this resource requires a valid license token. Error: ${c} - ${a}`;return new Response(y,{status:401,headers:f})}return await h("license_used"),new Response("\u2705 License Token Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}function F(){let r={method:"GET"};return globalThis?.fastly&&(r={...r,backend:m}),r}async function I(r,e){let t=`${r}/merchants/systems/${e}/license.xml`,n=await fetch(t,F());if(!n.ok)return new Response("License not found",{status:404});let i=await n.text();return new Response(i,{status:200,headers:new Headers({"Content-Type":"application/xml"})})}var _=!0,l=class l{constructor(e,t=!1){if(!t&&l._instance){if(!(e.apiKey===l._instance.apiKey&&e.merchantSystemUrn===l._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return l._instance}if(t&&l._instance&&l.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,l._instance=this}static resetInstance(){l._instance=null}static setBaseUrl(e){l.baseUrl=e}async verifyLicenseToken(e,t){return E({licenseToken:e,requestUrl:t,supertabBaseUrl:l.baseUrl,debug:_})}async recordEvent(e,t={},n){let i={event_name:e,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",license_id:n,properties:t};try{let s={method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(i)};globalThis?.fastly&&(s={...s,backend:m});let o=await fetch(`${l.baseUrl}/events`,s);o.ok||console.log(`Failed to record event: ${o.status}`)}catch(s){console.log("Error recording event:",s)}}static checkIfBotRequest(e){let t=e.headers.get("User-Agent")||"",n=e.headers.get("accept")||"",i=e.headers.get("sec-ch-ua"),s=e.headers.get("accept-language"),o=e.cf?.botManagement?.score,g=["chatgpt-user","perplexitybot","gptbot","anthropic-ai","ccbot","claude-web","claudebot","cohere-ai","youbot","diffbot","oai-searchbot","meta-externalagent","timpibot","amazonbot","bytespider","perplexity-user","googlebot","bot","curl","wget"],p=t.toLowerCase(),h=g.some(f=>p.includes(f)),c=t.toLowerCase().includes("headless")||t.toLowerCase().includes("puppeteer")||!i,a=!t.toLowerCase().includes("headless")||!t.toLowerCase().includes("puppeteer")||!i,u=!n||!s,d=typeof o=="number"&&o<30;return console.log("Bot Detection Details:",{botUaMatch:h,headlessIndicators:c,missingHeaders:u,lowBotScore:d,botScore:o}),(p.includes("safari")||p.includes("mozilla"))&&c&&a?!1:h||c||u||d}static async cloudflareHandleRequests(e,t,n){let{MERCHANT_SYSTEM_URN:i,MERCHANT_API_KEY:s}=t;return new l({apiKey:s,merchantSystemUrn:i}).handleRequest(e,l.checkIfBotRequest,n)}static async fastlyHandleRequests(e,t,n,i=!1){let s=new l({apiKey:n,merchantSystemUrn:t});return i&&new URL(e.url).pathname==="/license.xml"?await I(l.baseUrl,t):s.handleRequest(e,l.checkIfBotRequest,null)}async handleRequest(e,t,n){let i=e.headers.get("Authorization")||"",s=i.startsWith("License ")?i.slice(8):"",o=e.url,g=e.headers.get("User-Agent")||"unknown";return t&&!t(e,n)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):b({licenseToken:s,url:o,userAgent:g,ctx:n,supertabBaseUrl:l.baseUrl,merchantSystemUrn:this.merchantSystemUrn,debug:_,recordEvent:(p,h,c)=>this.recordEvent(p,h,c)})}static async generateLicenseToken(e,t,n,i,s){let o=l.baseUrl+"/rsl/token";return A({clientId:e,kid:t,privateKeyPem:n,tokenEndpoint:o,resourceUrl:i,licenseXml:s,debug:_})}};l.baseUrl="https://api-connect.supertab.co",l._instance=null;var R=l;export{R as SupertabConnect};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/customer.ts","../src/license.ts","../src/jwks.ts"],"sourcesContent":["import {\n SupertabConnectConfig,\n Env,\n EventPayload,\n TokenVerificationResult,\n TokenInvalidReason,\n FASTLY_BACKEND,\n} from \"./types\";\nimport {\n jwtVerify,\n decodeProtectedHeader,\n decodeJwt,\n JWTHeaderParameters,\n JWTPayload,\n} from \"jose\";\nimport {\n generateLicenseToken as generateLicenseTokenHelper,\n generateCustomerJWT as generateCustomerJWTHelper,\n} from \"./customer\";\nimport {\n baseLicenseHandleRequest as baseLicenseHandleRequestHelper,\n hostRSLicenseXML as hostRSLicenseXMLHelper,\n} from \"./license\";\nimport { fetchIssuerJwks, fetchPlatformJwks } from \"./jwks\";\n\nexport type { Env } from \"./types\";\n\nconst debug = true; // Set to true for debugging purposes\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.supertab.co\";\n private merchantSystemUrn!: string;\n\n private static _instance: SupertabConnect | null = null;\n\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (\n !(\n config.apiKey === SupertabConnect._instance.apiKey &&\n config.merchantSystemUrn ===\n SupertabConnect._instance.merchantSystemUrn\n )\n ) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey || !config.merchantSystemUrn) {\n throw new Error(\n \"Missing required configuration: apiKey and merchantSystemUrn are required\"\n );\n }\n this.apiKey = config.apiKey;\n this.merchantSystemUrn = config.merchantSystemUrn;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Verify a JWT token\n * @param token The JWT token to verify\n * @returns A promise that resolves with the verification result\n */\n async verifyToken(token: string): Promise<TokenVerificationResult> {\n // 1. Check if token exists\n if (!token) {\n return {\n valid: false,\n reason: TokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n // 2. Verify header and algorithm\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(token) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT header:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"RS256\") {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ALG,\n };\n }\n\n // 3. Verify payload and issuer\n let payload: JWTPayload;\n try {\n payload = decodeJwt(token);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT payload:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(\"urn:stc:customer:\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ISSUER,\n };\n }\n\n // 4. Verify signature\n try {\n const jwks = await fetchIssuerJwks(\n SupertabConnect.baseUrl,\n issuer,\n debug\n );\n\n // Create a key finder function for verification\n const getKey = async (header: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === header.kid);\n if (!jwk) throw new Error(`No matching key found: ${header.kid}`);\n return jwk;\n };\n\n const result = await jwtVerify(token, getKey, {\n issuer,\n algorithms: [\"RS256\"],\n clockTolerance: \"1m\",\n });\n\n // Success case - token is valid\n return {\n valid: true,\n payload: result.payload,\n };\n } catch (error: any) {\n if (debug) {\n console.error(\"JWT verification failed:\", error);\n }\n\n // Check if token is expired\n if (error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.EXPIRED,\n };\n }\n\n return {\n valid: false,\n reason: TokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n };\n }\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param properties Additional properties to include with the event\n * @param licenseId Optional license ID associated with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n properties: Record<string, any> = {},\n licenseId?: string\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: any = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n };\n // @ts-ignore\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n const response = await fetch(\n `${SupertabConnect.baseUrl}/events`,\n options\n );\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n /**\n * Handle the request, report an event to Supertab Connect and return a response\n */\n private async baseHandleRequest(\n token: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n // 1. Verify token\n const verification = await this.verifyToken(token);\n\n // Record event helper\n async function recordEvent(\n stc: SupertabConnect,\n eventName: string,\n ctx: any\n ) {\n const eventProperties = {\n page_url: url,\n user_agent: user_agent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n if (ctx) {\n const eventPromise = stc.recordEvent(eventName, eventProperties);\n ctx.waitUntil(eventPromise);\n return eventPromise;\n } else {\n return await stc.recordEvent(eventName, eventProperties);\n }\n }\n\n // 2. Handle based on verification result\n if (!verification.valid) {\n await recordEvent(\n this,\n verification.reason || \"token_verification_failed\",\n ctx\n );\n const message =\n \"Payment required: you need to present a valid Supertab Connect token to access this content. \" +\n \"Check out the provided url for details\";\n const details =\n \"❌ Content access denied\" +\n (verification.reason ? `: ${verification.reason}` : \"\");\n const contentAccessUrl = `${SupertabConnect.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`;\n\n const responseBody = {\n url: contentAccessUrl,\n message: message,\n details: details,\n };\n\n return new Response(JSON.stringify(responseBody), {\n status: 402,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Success\n await recordEvent(this, \"page_viewed\", ctx);\n return new Response(\"✅ Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n /**\n * Handle the request for license tokens, report an event to Supertab Connect and return a response\n */\n private async baseLicenseHandleRequest(\n licenseToken: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n return baseLicenseHandleRequestHelper({\n licenseToken,\n url,\n userAgent: user_agent,\n ctx,\n supertabBaseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: this.merchantSystemUrn,\n debug,\n recordEvent: (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string\n ) => this.recordEvent(eventName, properties, licenseId),\n });\n }\n\n private extractDataFromRequest(request: Request): {\n token: string;\n licenseToken: string;\n url: string;\n user_agent: string;\n } {\n // Parse token\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"Bearer \") ? auth.slice(7) : \"\";\n const licenseToken = auth.startsWith(\"License \") ? auth.slice(8) : \"\";\n\n // Extract URL and user agent\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n return { token, licenseToken, url, user_agent };\n }\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string,\n enableRSL: boolean = false,\n ): Promise<Response> {\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: merchantApiKey,\n merchantSystemUrn: merchantSystemUrn,\n });\n\n if (enableRSL) {\n if (new URL(request.url).pathname === \"/license.xml\") {\n return await hostRSLicenseXMLHelper(\n SupertabConnect.baseUrl,\n merchantSystemUrn\n );\n }\n }\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n null\n );\n }\n\n async handleRequest(\n request: Request,\n botDetectionHandler?: (request: Request, ctx?: any) => boolean,\n ctx?: any\n ): Promise<Response> {\n // 1. Extract token, license token, URL, and user agent from the request\n const { token, licenseToken, url, user_agent } =\n this.extractDataFromRequest(request);\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Check for bearer token first, then fallback to license token\n if (token) {\n return this.baseHandleRequest(token, url, user_agent, ctx);\n }\n\n // 4. Call the base licenhandle request method and return the result\n return this.baseLicenseHandleRequest(licenseToken, url, user_agent, ctx);\n }\n\n async hostRSLicenseXML(): Promise<Response> {\n return hostRSLicenseXMLHelper(\n SupertabConnect.baseUrl,\n this.merchantSystemUrn\n );\n }\n\n /**\n * Request a license token from the Supertab Connect token endpoint.\n * @param clientId OAuth client identifier used for the assertion issuer/subject claims.\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem Private key in PEM format used to sign the client assertion.\n * @param tokenEndpoint Token endpoint URL.\n * @param resourceUrl Resource URL attempting to access with a License.\n * @param licenseXml XML license document to include in the request payload.\n * @returns Promise resolving to the issued license access token string.\n */\n static async generateLicenseToken(\n clientId: string,\n kid: string,\n privateKeyPem: string,\n resourceUrl: string,\n licenseXml: string\n ): Promise<string> {\n const tokenEndpoint = SupertabConnect.baseUrl + \"/rsl/token\";\n return generateLicenseTokenHelper({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n });\n }\n\n\n /** Generate a customer JWT\n * @param customerURN The customer's unique resource name (URN).\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem The private key in PEM format used to sign the JWT.\n * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).\n * @returns A promise that resolves to the generated JWT as a string.\n */\n static async generateCustomerJWT(\n customerURN: string,\n kid: string,\n privateKeyPem: string,\n expirationSeconds: number = 3600\n ): Promise<string> {\n return generateCustomerJWTHelper({\n customerURN,\n kid,\n privateKeyPem,\n expirationSeconds,\n });\n }\n}\n","export interface SupertabConnectConfig {\n apiKey: string;\n merchantSystemUrn: string;\n}\n\n/**\n * Defines the shape for environment variables (used in CloudFlare integration).\n * These are used to identify and authenticate the Merchant System with the Supertab Connect API.\n */\nexport interface Env {\n\t/** The unique identifier for the merchant system. */\n\tMERCHANT_SYSTEM_URN: string;\n\t/** The API key for authenticating with the Supertab Connect. */\n\tMERCHANT_API_KEY: string;\n\t[key: string]: string;\n}\n\nexport interface EventPayload {\n event_name: string;\n license_id?: string;\n merchant_system_urn: string;\n properties: Record<string, any>;\n}\n\nexport interface TokenVerificationResult {\n valid: boolean;\n reason?: string;\n payload?: any;\n}\n\nexport enum TokenInvalidReason {\n MISSING_TOKEN = \"missing_token\",\n INVALID_HEADER = \"invalid_header\",\n INVALID_ALG = \"invalid_algorithm\",\n INVALID_PAYLOAD = \"invalid_payload\",\n INVALID_ISSUER = \"invalid_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"signature_verification_failed\",\n EXPIRED = \"token_expired\",\n}\n\nexport interface LicenseTokenVerificationResult {\n valid: boolean;\n reason?: string;\n licenseId?: string;\n payload?: any;\n}\n\nexport enum LicenseTokenInvalidReason {\n MISSING_TOKEN = \"missing_license_token\",\n INVALID_HEADER = \"invalid_license_header\",\n INVALID_ALG = \"invalid_license_algorithm\",\n INVALID_PAYLOAD = \"invalid_license_payload\",\n INVALID_ISSUER = \"invalid_license_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"license_signature_verification_failed\",\n EXPIRED = \"license_token_expired\",\n INVALID_AUDIENCE = \"invalid_license_audience\",\n}\n\nexport const FASTLY_BACKEND = \"stc-backend\";\n\nexport interface FetchOptions extends RequestInit {\n // Fastly-specific extension for backend routing\n backend?: string;\n}","import { importPKCS8, SignJWT } from \"jose\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\ntype GenerateCustomerJwtParams = {\n customerURN: string;\n kid: string;\n privateKeyPem: string;\n expirationSeconds?: number;\n};\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const supportedAlgs: SupportedAlg[] = [\"ES256\", \"RS256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\nexport async function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: payload.toString(),\n };\n\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: any;\n try {\n data = await response.json();\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nexport async function generateCustomerJWT({\n customerURN,\n kid,\n privateKeyPem,\n expirationSeconds = 3600,\n}: GenerateCustomerJwtParams): Promise<string> {\n const alg: SupportedAlg = \"RS256\";\n const key = await importPKCS8(privateKeyPem, alg);\n\n const now = Math.floor(Date.now() / 1000);\n\n return new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(customerURN)\n .setIssuedAt(now)\n .setExpirationTime(now + expirationSeconds)\n .sign(key);\n}\n\nexport type { GenerateLicenseTokenParams, GenerateCustomerJwtParams };\n","import {\n decodeProtectedHeader,\n decodeJwt,\n JWTPayload,\n JWTHeaderParameters,\n jwtVerify,\n} from \"jose\";\nimport {\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n FASTLY_BACKEND,\n FetchOptions,\n} from \"./types\";\nimport { fetchPlatformJwks } from \"./jwks\";\n\nconst stripTrailingSlash = (value: string) => value.replace(/\\/+$/, \"\");\n\nexport type VerifyLicenseTokenParams = {\n licenseToken: string;\n requestUrl: string;\n supertabBaseUrl: string;\n debug: boolean;\n};\n\nexport async function verifyLicenseToken({\n licenseToken,\n requestUrl,\n supertabBaseUrl,\n debug,\n}: VerifyLicenseTokenParams): Promise<LicenseTokenVerificationResult> {\n if (!licenseToken) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(licenseToken) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT header:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"ES256\") {\n if (debug) {\n console.error(\"Unsupported license JWT alg:\", header.alg);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ALG,\n };\n }\n\n let payload: JWTPayload;\n try {\n payload = decodeJwt(licenseToken);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT payload:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n // @ts-ignore\n const licenseId: string | undefined = payload.license_id;\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(supertabBaseUrl)) {\n if (debug) {\n console.error(\"Invalid license JWT issuer:\", issuer);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ISSUER,\n licenseId,\n };\n }\n\n const audienceValues = Array.isArray(payload.aud)\n ? payload.aud.filter((entry): entry is string => typeof entry === \"string\")\n : typeof payload.aud === \"string\"\n ? [payload.aud]\n : [];\n\n const requestUrlNormalized = stripTrailingSlash(requestUrl);\n const matchesRequestUrl = audienceValues.some((value) => {\n const normalizedAudience = stripTrailingSlash(value);\n if (!normalizedAudience) return false;\n return requestUrlNormalized.startsWith(normalizedAudience);\n });\n\n if (!matchesRequestUrl) {\n if (debug) {\n console.error(\n \"License JWT audience does not match request URL:\",\n payload.aud\n );\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_AUDIENCE,\n licenseId,\n };\n }\n\n try {\n const jwks = await fetchPlatformJwks(supertabBaseUrl, debug);\n\n const getKey = async (jwtHeader: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === jwtHeader.kid);\n if (!jwk) {\n throw new Error(`No matching platform key found: ${jwtHeader.kid}`);\n }\n return jwk;\n };\n\n const result = await jwtVerify(licenseToken, getKey, {\n issuer,\n algorithms: [header.alg],\n clockTolerance: \"1m\",\n });\n\n return {\n valid: true,\n licenseId,\n payload: result.payload,\n };\n } catch (error) {\n if (debug) {\n console.error(\"License JWT verification failed:\", error);\n }\n\n if (error instanceof Error && error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.EXPIRED,\n licenseId,\n };\n }\n\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n licenseId,\n };\n }\n}\n\nexport function generateLicenseLink({\n requestUrl,\n}: {\n requestUrl: string;\n}): string {\n const baseURL = new URL(requestUrl);\n return `${baseURL.protocol}//${baseURL.host}/license.xml`;\n}\n\ntype RecordEventFn = (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string\n) => Promise<void>;\n\ntype BaseLicenseHandleRequestParams = {\n licenseToken: string;\n url: string;\n userAgent: string;\n ctx: any;\n supertabBaseUrl: string;\n merchantSystemUrn?: string;\n debug: boolean;\n recordEvent: RecordEventFn;\n};\n\nexport async function baseLicenseHandleRequest({\n licenseToken,\n url,\n userAgent,\n ctx,\n supertabBaseUrl,\n merchantSystemUrn,\n debug,\n recordEvent,\n}: BaseLicenseHandleRequestParams): Promise<Response> {\n const verification = await verifyLicenseToken({\n licenseToken,\n requestUrl: url,\n supertabBaseUrl,\n debug,\n });\n\n async function recordLicenseEvent(eventName: string) {\n const eventProperties = {\n page_url: url,\n user_agent: userAgent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n\n const eventPromise = recordEvent(\n eventName,\n eventProperties,\n verification.licenseId\n );\n\n if (ctx?.waitUntil) {\n ctx.waitUntil(eventPromise);\n }\n\n return eventPromise;\n }\n\n if (!verification.valid) {\n await recordLicenseEvent(\n verification.reason || \"license_token_verification_failed\"\n );\n\n let rslError = \"invalid_request\";\n let errorDescription = \"Access to this resource requires a license\";\n\n switch (verification.reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n break;\n case LicenseTokenInvalidReason.EXPIRED:\n rslError = \"invalid_token\";\n errorDescription = \"The license token has expired\";\n break;\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n rslError = \"invalid_token\";\n errorDescription = \"The license token signature is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_HEADER:\n rslError = \"invalid_token\";\n errorDescription = \"The license token header is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n rslError = \"invalid_token\";\n errorDescription = \"The license token payload is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n rslError = \"invalid_token\";\n errorDescription = \"The license token issuer is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n rslError = \"invalid_token\";\n errorDescription = \"The license token audience is invalid\";\n break;\n default:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n }\n\n const licenseLink = generateLicenseLink({\n requestUrl: url,\n });\n const errorUri = `${supertabBaseUrl}/docs/errors#${rslError}`;\n\n const headers = new Headers({\n \"Content-Type\": \"text/plain; charset=UTF-8\",\n \"WWW-Authenticate\": `License error=\"${rslError}\", error_description=\"${errorDescription}\", error_uri=\"${errorUri}\"`,\n Link: `${licenseLink}; rel=\"license\"; type=\"application/rsl+xml\"`,\n });\n\n const responseBody = `Access to this resource requires a valid license token. Error: ${rslError} - ${errorDescription}`;\n\n return new Response(responseBody, {\n status: 401,\n headers,\n });\n }\n\n await recordLicenseEvent(\"license_used\");\n return new Response(\"✅ License Token Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n}\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nexport async function hostRSLicenseXML(\n supertabBaseUrl: string,\n merchantSystemUrn: string\n): Promise<Response> {\n const licenseUrl = `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/license.xml`;\n const response = await fetch(licenseUrl, buildFetchOptions());\n\n if (!response.ok) {\n return new Response(\"License not found\", { status: 404 });\n }\n\n const licenseXml = await response.text();\n\n return new Response(licenseXml, {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/xml\" }),\n });\n}\n","import { FASTLY_BACKEND, FetchOptions } from \"./types\";\n\nconst jwksCache = new Map<string, any>();\n\ntype JwksCacheKey = string;\n\ntype FetchJwksParams = {\n cacheKey: JwksCacheKey;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<any> {\n if (!jwksCache.has(cacheKey)) {\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json();\n jwksCache.set(cacheKey, jwksData);\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(cacheKey);\n}\n\nexport async function fetchIssuerJwks(\n baseUrl: string,\n issuer: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/${encodeURIComponent(\n issuer\n )}`;\n\n return fetchAndCacheJwks({\n cacheKey: issuer,\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch JWKS\",\n logLabel: \"Error fetching JWKS:\",\n });\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/platform`;\n console.log(\"Fetching platform JWKS from:\", jwksUrl);\n\n return fetchAndCacheJwks({\n cacheKey: \"platform_jwks\",\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch platform JWKS\",\n logLabel: \"Error fetching platform JWKS:\",\n });\n}\n\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n"],"mappings":"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","names":["index_exports","__export","SupertabConnect","__toCommonJS","FASTLY_BACKEND","import_jose","import_jose","importKeyForAlgs","privateKeyPem","debug","supportedAlgs","algorithm","importError","generateLicenseToken","clientId","kid","tokenEndpoint","resourceUrl","licenseXml","key","alg","now","clientAssertion","payload","requestOptions","response","errorBody","errorMessage","data","parseError","error","generateCustomerJWT","customerURN","expirationSeconds","import_jose","jwksCache","buildFetchOptions","options","FASTLY_BACKEND","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","response","jwksData","error","fetchIssuerJwks","baseUrl","issuer","jwksUrl","fetchPlatformJwks","stripTrailingSlash","value","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","header","error","payload","licenseId","issuer","audienceValues","entry","requestUrlNormalized","normalizedAudience","jwks","fetchPlatformJwks","result","jwtHeader","jwk","key","generateLicenseLink","baseURL","baseLicenseHandleRequest","url","userAgent","ctx","merchantSystemUrn","recordEvent","verification","recordLicenseEvent","eventName","eventProperties","eventPromise","rslError","errorDescription","licenseLink","errorUri","headers","responseBody","buildFetchOptions","options","FASTLY_BACKEND","hostRSLicenseXML","licenseUrl","response","licenseXml","debug","_SupertabConnect","config","reset","url","token","header","error","payload","issuer","jwks","fetchIssuerJwks","jwk","key","eventName","properties","licenseId","options","FASTLY_BACKEND","response","user_agent","ctx","verification","recordEvent","stc","eventProperties","eventPromise","message","details","responseBody","licenseToken","baseLicenseHandleRequest","request","auth","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","enableRSL","supertabConnect","hostRSLicenseXML","botDetectionHandler","clientId","kid","privateKeyPem","resourceUrl","licenseXml","tokenEndpoint","generateLicenseToken","customerURN","expirationSeconds","generateCustomerJWT","SupertabConnect"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/customer.ts","../src/license.ts","../src/jwks.ts","../src/index.ts"],"sourcesContent":["export interface SupertabConnectConfig {\n apiKey: string;\n merchantSystemUrn: string;\n}\n\n/**\n * Defines the shape for environment variables (used in CloudFlare integration).\n * These are used to identify and authenticate the Merchant System with the Supertab Connect API.\n */\nexport interface Env {\n\t/** The unique identifier for the merchant system. */\n\tMERCHANT_SYSTEM_URN: string;\n\t/** The API key for authenticating with the Supertab Connect. */\n\tMERCHANT_API_KEY: string;\n\t[key: string]: string;\n}\n\nexport interface EventPayload {\n event_name: string;\n license_id?: string;\n merchant_system_urn: string;\n properties: Record<string, any>;\n}\n\nexport interface LicenseTokenVerificationResult {\n valid: boolean;\n reason?: string;\n licenseId?: string;\n payload?: any;\n}\n\nexport enum LicenseTokenInvalidReason {\n MISSING_TOKEN = \"missing_license_token\",\n INVALID_HEADER = \"invalid_license_header\",\n INVALID_ALG = \"invalid_license_algorithm\",\n INVALID_PAYLOAD = \"invalid_license_payload\",\n INVALID_ISSUER = \"invalid_license_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"license_signature_verification_failed\",\n EXPIRED = \"license_token_expired\",\n INVALID_AUDIENCE = \"invalid_license_audience\",\n}\n\nexport const FASTLY_BACKEND = \"stc-backend\";\n\nexport interface FetchOptions extends RequestInit {\n // Fastly-specific extension for backend routing\n backend?: string;\n}","import { importPKCS8, SignJWT } from \"jose\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const supportedAlgs: SupportedAlg[] = [\"ES256\", \"RS256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\nexport async function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: payload.toString(),\n };\n\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: any;\n try {\n data = await response.json();\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nexport type { GenerateLicenseTokenParams };\n","import {\n decodeProtectedHeader,\n decodeJwt,\n JWTPayload,\n JWTHeaderParameters,\n jwtVerify,\n} from \"jose\";\nimport {\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n FASTLY_BACKEND,\n FetchOptions,\n} from \"./types\";\nimport { fetchPlatformJwks } from \"./jwks\";\n\nconst stripTrailingSlash = (value: string) => value.replace(/\\/+$/, \"\");\n\nexport type VerifyLicenseTokenParams = {\n licenseToken: string;\n requestUrl: string;\n supertabBaseUrl: string;\n debug: boolean;\n};\n\nexport async function verifyLicenseToken({\n licenseToken,\n requestUrl,\n supertabBaseUrl,\n debug,\n}: VerifyLicenseTokenParams): Promise<LicenseTokenVerificationResult> {\n if (!licenseToken) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(licenseToken) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT header:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"ES256\") {\n if (debug) {\n console.error(\"Unsupported license JWT alg:\", header.alg);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ALG,\n };\n }\n\n let payload: JWTPayload;\n try {\n payload = decodeJwt(licenseToken);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT payload:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n // @ts-ignore\n const licenseId: string | undefined = payload.license_id;\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(supertabBaseUrl)) {\n if (debug) {\n console.error(\"Invalid license JWT issuer:\", issuer);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ISSUER,\n licenseId,\n };\n }\n\n const audienceValues = Array.isArray(payload.aud)\n ? payload.aud.filter((entry): entry is string => typeof entry === \"string\")\n : typeof payload.aud === \"string\"\n ? [payload.aud]\n : [];\n\n const requestUrlNormalized = stripTrailingSlash(requestUrl);\n const matchesRequestUrl = audienceValues.some((value) => {\n const normalizedAudience = stripTrailingSlash(value);\n if (!normalizedAudience) return false;\n return requestUrlNormalized.startsWith(normalizedAudience);\n });\n\n if (!matchesRequestUrl) {\n if (debug) {\n console.error(\n \"License JWT audience does not match request URL:\",\n payload.aud\n );\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_AUDIENCE,\n licenseId,\n };\n }\n\n try {\n const jwks = await fetchPlatformJwks(supertabBaseUrl, debug);\n\n const getKey = async (jwtHeader: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === jwtHeader.kid);\n if (!jwk) {\n throw new Error(`No matching platform key found: ${jwtHeader.kid}`);\n }\n return jwk;\n };\n\n const result = await jwtVerify(licenseToken, getKey, {\n issuer,\n algorithms: [header.alg],\n clockTolerance: \"1m\",\n });\n\n return {\n valid: true,\n licenseId,\n payload: result.payload,\n };\n } catch (error) {\n if (debug) {\n console.error(\"License JWT verification failed:\", error);\n }\n\n if (error instanceof Error && error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.EXPIRED,\n licenseId,\n };\n }\n\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n licenseId,\n };\n }\n}\n\nexport function generateLicenseLink({\n requestUrl,\n}: {\n requestUrl: string;\n}): string {\n const baseURL = new URL(requestUrl);\n return `${baseURL.protocol}//${baseURL.host}/license.xml`;\n}\n\ntype RecordEventFn = (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string\n) => Promise<void>;\n\ntype BaseLicenseHandleRequestParams = {\n licenseToken: string;\n url: string;\n userAgent: string;\n ctx: any;\n supertabBaseUrl: string;\n merchantSystemUrn?: string;\n debug: boolean;\n recordEvent: RecordEventFn;\n};\n\nexport async function baseLicenseHandleRequest({\n licenseToken,\n url,\n userAgent,\n ctx,\n supertabBaseUrl,\n merchantSystemUrn,\n debug,\n recordEvent,\n}: BaseLicenseHandleRequestParams): Promise<Response> {\n const verification = await verifyLicenseToken({\n licenseToken,\n requestUrl: url,\n supertabBaseUrl,\n debug,\n });\n\n async function recordLicenseEvent(eventName: string) {\n const eventProperties = {\n page_url: url,\n user_agent: userAgent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n\n const eventPromise = recordEvent(\n eventName,\n eventProperties,\n verification.licenseId\n );\n\n if (ctx?.waitUntil) {\n ctx.waitUntil(eventPromise);\n }\n\n return eventPromise;\n }\n\n if (!verification.valid) {\n await recordLicenseEvent(\n verification.reason || \"license_token_verification_failed\"\n );\n\n let rslError = \"invalid_request\";\n let errorDescription = \"Access to this resource requires a license\";\n\n switch (verification.reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n break;\n case LicenseTokenInvalidReason.EXPIRED:\n rslError = \"invalid_token\";\n errorDescription = \"The license token has expired\";\n break;\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n rslError = \"invalid_token\";\n errorDescription = \"The license token signature is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_HEADER:\n rslError = \"invalid_token\";\n errorDescription = \"The license token header is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n rslError = \"invalid_token\";\n errorDescription = \"The license token payload is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n rslError = \"invalid_token\";\n errorDescription = \"The license token issuer is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n rslError = \"invalid_token\";\n errorDescription = \"The license token audience is invalid\";\n break;\n default:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n }\n\n const licenseLink = generateLicenseLink({\n requestUrl: url,\n });\n const errorUri = `${supertabBaseUrl}/docs/errors#${rslError}`;\n\n const headers = new Headers({\n \"Content-Type\": \"text/plain; charset=UTF-8\",\n \"WWW-Authenticate\": `License error=\"${rslError}\", error_description=\"${errorDescription}\", error_uri=\"${errorUri}\"`,\n Link: `${licenseLink}; rel=\"license\"; type=\"application/rsl+xml\"`,\n });\n\n const responseBody = `Access to this resource requires a valid license token. Error: ${rslError} - ${errorDescription}`;\n\n return new Response(responseBody, {\n status: 401,\n headers,\n });\n }\n\n await recordLicenseEvent(\"license_used\");\n return new Response(\"✅ License Token Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n}\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nexport async function hostRSLicenseXML(\n supertabBaseUrl: string,\n merchantSystemUrn: string\n): Promise<Response> {\n const licenseUrl = `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/license.xml`;\n const response = await fetch(licenseUrl, buildFetchOptions());\n\n if (!response.ok) {\n return new Response(\"License not found\", { status: 404 });\n }\n\n const licenseXml = await response.text();\n\n return new Response(licenseXml, {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/xml\" }),\n });\n}\n","import { FASTLY_BACKEND, FetchOptions } from \"./types\";\n\nconst jwksCache = new Map<string, any>();\n\ntype JwksCacheKey = string;\n\ntype FetchJwksParams = {\n cacheKey: JwksCacheKey;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<any> {\n if (!jwksCache.has(cacheKey)) {\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json();\n jwksCache.set(cacheKey, jwksData);\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(cacheKey);\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/platform`;\n console.log(\"Fetching platform JWKS from:\", jwksUrl);\n\n return fetchAndCacheJwks({\n cacheKey: \"platform_jwks\",\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch platform JWKS\",\n logLabel: \"Error fetching platform JWKS:\",\n });\n}\n\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n","import {\n SupertabConnectConfig,\n Env,\n EventPayload,\n FASTLY_BACKEND,\n LicenseTokenVerificationResult,\n} from \"./types\";\nimport { generateLicenseToken as generateLicenseTokenHelper } from \"./customer\";\nimport {\n baseLicenseHandleRequest as baseLicenseHandleRequestHelper,\n hostRSLicenseXML as hostRSLicenseXMLHelper,\n verifyLicenseToken as verifyLicenseTokenHelper,\n} from \"./license\";\n\nexport type { Env } from \"./types\";\n\nconst debug = true; // Set to true for debugging purposes\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.supertab.co\";\n private merchantSystemUrn!: string;\n\n private static _instance: SupertabConnect | null = null;\n\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (\n !(\n config.apiKey === SupertabConnect._instance.apiKey &&\n config.merchantSystemUrn ===\n SupertabConnect._instance.merchantSystemUrn\n )\n ) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey || !config.merchantSystemUrn) {\n throw new Error(\n \"Missing required configuration: apiKey and merchantSystemUrn are required\"\n );\n }\n this.apiKey = config.apiKey;\n this.merchantSystemUrn = config.merchantSystemUrn;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Verify a license token\n * @param licenseToken The license token to verify\n * @param requestUrl The URL of the request being made\n * @returns A promise that resolves with the verification result\n */\n async verifyLicenseToken(\n licenseToken: string,\n requestUrl: string\n ): Promise<LicenseTokenVerificationResult> {\n return verifyLicenseTokenHelper({\n licenseToken,\n requestUrl,\n supertabBaseUrl: SupertabConnect.baseUrl,\n debug,\n });\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param properties Additional properties to include with the event\n * @param licenseId Optional license ID associated with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n properties: Record<string, any> = {},\n licenseId?: string\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: any = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n };\n // @ts-ignore\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n const response = await fetch(\n `${SupertabConnect.baseUrl}/events`,\n options\n );\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string,\n enableRSL: boolean = false,\n ): Promise<Response> {\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: merchantApiKey,\n merchantSystemUrn: merchantSystemUrn,\n });\n\n if (enableRSL) {\n if (new URL(request.url).pathname === \"/license.xml\") {\n return await hostRSLicenseXMLHelper(\n SupertabConnect.baseUrl,\n merchantSystemUrn\n );\n }\n }\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n null\n );\n }\n\n async handleRequest(\n request: Request,\n botDetectionHandler?: (request: Request, ctx?: any) => boolean,\n ctx?: any\n ): Promise<Response> {\n // 1. Extract license token, URL, and user agent from the request\n const auth = request.headers.get(\"Authorization\") || \"\";\n const licenseToken = auth.startsWith(\"License \") ? auth.slice(8) : \"\";\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Handle the license token request\n return baseLicenseHandleRequestHelper({\n licenseToken,\n url,\n userAgent: user_agent,\n ctx,\n supertabBaseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: this.merchantSystemUrn,\n debug,\n recordEvent: (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string\n ) => this.recordEvent(eventName, properties, licenseId),\n });\n }\n\n /**\n * Request a license token from the Supertab Connect token endpoint.\n * @param clientId OAuth client identifier used for the assertion issuer/subject claims.\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem Private key in PEM format used to sign the client assertion.\n * @param tokenEndpoint Token endpoint URL.\n * @param resourceUrl Resource URL attempting to access with a License.\n * @param licenseXml XML license document to include in the request payload.\n * @returns Promise resolving to the issued license access token string.\n */\n static async generateLicenseToken(\n clientId: string,\n kid: string,\n privateKeyPem: string,\n resourceUrl: string,\n licenseXml: string\n ): Promise<string> {\n const tokenEndpoint = SupertabConnect.baseUrl + \"/rsl/token\";\n return generateLicenseTokenHelper({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n });\n }\n}\n"],"mappings":"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","names":["FASTLY_BACKEND","importPKCS8","SignJWT","importKeyForAlgs","privateKeyPem","debug","supportedAlgs","algorithm","importError","generateLicenseToken","clientId","kid","tokenEndpoint","resourceUrl","licenseXml","key","alg","now","clientAssertion","payload","requestOptions","response","errorBody","errorMessage","data","parseError","error","decodeProtectedHeader","decodeJwt","jwtVerify","jwksCache","buildFetchOptions","options","FASTLY_BACKEND","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","response","jwksData","error","fetchPlatformJwks","baseUrl","jwksUrl","stripTrailingSlash","value","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","header","decodeProtectedHeader","error","payload","decodeJwt","licenseId","issuer","audienceValues","entry","requestUrlNormalized","normalizedAudience","jwks","fetchPlatformJwks","result","jwtVerify","jwtHeader","jwk","key","generateLicenseLink","baseURL","baseLicenseHandleRequest","url","userAgent","ctx","merchantSystemUrn","recordEvent","verification","recordLicenseEvent","eventName","eventProperties","eventPromise","rslError","errorDescription","licenseLink","errorUri","headers","responseBody","buildFetchOptions","options","FASTLY_BACKEND","hostRSLicenseXML","licenseUrl","response","licenseXml","debug","_SupertabConnect","config","reset","url","licenseToken","requestUrl","verifyLicenseToken","eventName","properties","licenseId","payload","options","FASTLY_BACKEND","response","error","request","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","ctx","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","enableRSL","supertabConnect","hostRSLicenseXML","botDetectionHandler","auth","user_agent","baseLicenseHandleRequest","clientId","kid","privateKeyPem","resourceUrl","licenseXml","tokenEndpoint","generateLicenseToken","SupertabConnect"]}
package/package.json CHANGED
@@ -1,10 +1,18 @@
1
1
  {
2
2
  "name": "@getsupertab/supertab-connect-sdk",
3
- "version": "0.1.0-beta.29",
3
+ "version": "0.1.0-beta.31",
4
4
  "description": "Supertab Connect SDK (beta)",
5
- "main": "dist/index.js",
6
- "module": "dist/index.mjs",
7
- "types": "dist/index.d.ts",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
8
16
  "sideEffects": true,
9
17
  "files": [
10
18
  "dist",
package/dist/index.mjs DELETED
@@ -1,2 +0,0 @@
1
- var h="stc-backend";import{jwtVerify as W,decodeProtectedHeader as F,decodeJwt as V}from"jose";import{importPKCS8 as A,SignJWT as I}from"jose";async function N(i,e){let r=["ES256","RS256"];for(let s of r)try{return{key:await A(i,s),alg:s}}catch(n){e&&console.debug(`Private key did not import using ${s}, retrying...`,n)}throw new Error("Unsupported private key format. Expected RSA or P-256 EC private key.")}async function v({clientId:i,kid:e,privateKeyPem:r,tokenEndpoint:s,resourceUrl:n,licenseXml:t,debug:a}){let{key:u,alg:d}=await N(r,a),p=Math.floor(Date.now()/1e3),c=await new I({}).setProtectedHeader({alg:d,kid:e}).setIssuer(i).setSubject(i).setIssuedAt(p).setExpirationTime(p+300).setAudience(s).sign(u),o=new URLSearchParams({grant_type:"rsl",client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:c,license:t,resource:n}),f={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:o.toString()};try{let g=await fetch(s,f);if(!g.ok){let m=await g.text().catch(()=>""),_=`Failed to obtain license token: ${g.status} ${g.statusText}${m?` - ${m}`:""}`;throw new Error(_)}let y;try{y=await g.json()}catch(m){throw a&&console.error("Failed to parse license token response as JSON:",m),new Error("Failed to parse license token response as JSON")}if(!y?.access_token)throw new Error("License token response missing access_token");return y.access_token}catch(g){throw a&&console.error("Error generating license token:",g),g}}async function R({customerURN:i,kid:e,privateKeyPem:r,expirationSeconds:s=3600}){let n="RS256",t=await A(r,n),a=Math.floor(Date.now()/1e3);return new I({}).setProtectedHeader({alg:n,kid:e}).setIssuer(i).setIssuedAt(a).setExpirationTime(a+s).sign(t)}import{decodeProtectedHeader as D,decodeJwt as J,jwtVerify as x}from"jose";var k=new Map;function C(){let i={method:"GET"};return globalThis?.fastly&&(i={...i,backend:h}),i}async function S({cacheKey:i,url:e,debug:r,failureMessage:s,logLabel:n}){if(!k.has(i))try{let t=await fetch(e,C());if(!t.ok)throw new Error(`${s}: ${t.status}`);let a=await t.json();k.set(i,a)}catch(t){throw r&&console.error(n,t),t}return k.get(i)}async function L(i,e,r){let s=`${i}/.well-known/jwks.json/${encodeURIComponent(e)}`;return S({cacheKey:e,url:s,debug:r,failureMessage:"Failed to fetch JWKS",logLabel:"Error fetching JWKS:"})}async function T(i,e){let r=`${i}/.well-known/jwks.json/platform`;return console.log("Fetching platform JWKS from:",r),S({cacheKey:"platform_jwks",url:r,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}var b=i=>i.replace(/\/+$/,"");async function H({licenseToken:i,requestUrl:e,supertabBaseUrl:r,debug:s}){if(!i)return{valid:!1,reason:"missing_license_token"};let n;try{n=D(i)}catch(o){return s&&console.error("Invalid license JWT header:",o),{valid:!1,reason:"invalid_license_header"}}if(n.alg!=="ES256")return s&&console.error("Unsupported license JWT alg:",n.alg),{valid:!1,reason:"invalid_license_algorithm"};let t;try{t=J(i)}catch(o){return s&&console.error("Invalid license JWT payload:",o),{valid:!1,reason:"invalid_license_payload"}}let a=t.license_id,u=t.iss;if(!u||!u.startsWith(r))return s&&console.error("Invalid license JWT issuer:",u),{valid:!1,reason:"invalid_license_issuer",licenseId:a};let d=Array.isArray(t.aud)?t.aud.filter(o=>typeof o=="string"):typeof t.aud=="string"?[t.aud]:[],p=b(e);if(!d.some(o=>{let f=b(o);return f?p.startsWith(f):!1}))return s&&console.error("License JWT audience does not match request URL:",t.aud),{valid:!1,reason:"invalid_license_audience",licenseId:a};try{let o=await T(r,s),g=await x(i,async y=>{let m=o.keys.find(_=>_.kid===y.kid);if(!m)throw new Error(`No matching platform key found: ${y.kid}`);return m},{issuer:u,algorithms:[n.alg],clockTolerance:"1m"});return{valid:!0,licenseId:a,payload:g.payload}}catch(o){return s&&console.error("License JWT verification failed:",o),o instanceof Error&&o.message?.includes("exp")?{valid:!1,reason:"license_token_expired",licenseId:a}:{valid:!1,reason:"license_signature_verification_failed",licenseId:a}}}function q({requestUrl:i}){let e=new URL(i);return`${e.protocol}//${e.host}/license.xml`}async function P({licenseToken:i,url:e,userAgent:r,ctx:s,supertabBaseUrl:n,merchantSystemUrn:t,debug:a,recordEvent:u}){let d=await H({licenseToken:i,requestUrl:e,supertabBaseUrl:n,debug:a});async function p(c){let o={page_url:e,user_agent:r,verification_status:d.valid?"valid":"invalid",verification_reason:d.reason||"success"},f=u(c,o,d.licenseId);return s?.waitUntil&&s.waitUntil(f),f}if(!d.valid){await p(d.reason||"license_token_verification_failed");let c="invalid_request",o="Access to this resource requires a license";switch(d.reason){case"missing_license_token":c="invalid_request",o="Access to this resource requires a license";break;case"license_token_expired":c="invalid_token",o="The license token has expired";break;case"license_signature_verification_failed":c="invalid_token",o="The license token signature is invalid";break;case"invalid_license_header":c="invalid_token",o="The license token header is invalid";break;case"invalid_license_payload":c="invalid_token",o="The license token payload is invalid";break;case"invalid_license_issuer":c="invalid_token",o="The license token issuer is invalid";break;case"invalid_license_audience":c="invalid_token",o="The license token audience is invalid";break;default:c="invalid_request",o="Access to this resource requires a license"}let f=q({requestUrl:e}),g=`${n}/docs/errors#${c}`,y=new Headers({"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${c}", error_description="${o}", error_uri="${g}"`,Link:`${f}; rel="license"; type="application/rsl+xml"`}),m=`Access to this resource requires a valid license token. Error: ${c} - ${o}`;return new Response(m,{status:401,headers:y})}return await p("license_used"),new Response("\u2705 License Token Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}function K(){let i={method:"GET"};return globalThis?.fastly&&(i={...i,backend:h}),i}async function E(i,e){let r=`${i}/merchants/systems/${e}/license.xml`,s=await fetch(r,K());if(!s.ok)return new Response("License not found",{status:404});let n=await s.text();return new Response(n,{status:200,headers:new Headers({"Content-Type":"application/xml"})})}var w=!0,l=class l{constructor(e,r=!1){if(!r&&l._instance){if(!(e.apiKey===l._instance.apiKey&&e.merchantSystemUrn===l._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return l._instance}if(r&&l._instance&&l.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,l._instance=this}static resetInstance(){l._instance=null}static setBaseUrl(e){l.baseUrl=e}async verifyToken(e){if(!e)return{valid:!1,reason:"missing_token"};let r;try{r=F(e)}catch(t){return w&&console.error("Invalid JWT header:",t),{valid:!1,reason:"invalid_header"}}if(r.alg!=="RS256")return{valid:!1,reason:"invalid_algorithm"};let s;try{s=V(e)}catch(t){return w&&console.error("Invalid JWT payload:",t),{valid:!1,reason:"invalid_payload"}}let n=s.iss;if(!n||!n.startsWith("urn:stc:customer:"))return{valid:!1,reason:"invalid_issuer"};try{let t=await L(l.baseUrl,n,w);return{valid:!0,payload:(await W(e,async d=>{let p=t.keys.find(c=>c.kid===d.kid);if(!p)throw new Error(`No matching key found: ${d.kid}`);return p},{issuer:n,algorithms:["RS256"],clockTolerance:"1m"})).payload}}catch(t){return w&&console.error("JWT verification failed:",t),t.message?.includes("exp")?{valid:!1,reason:"token_expired"}:{valid:!1,reason:"signature_verification_failed"}}}async recordEvent(e,r={},s){let n={event_name:e,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",license_id:s,properties:r};try{let t={method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(n)};globalThis?.fastly&&(t={...t,backend:h});let a=await fetch(`${l.baseUrl}/events`,t);a.ok||console.log(`Failed to record event: ${a.status}`)}catch(t){console.log("Error recording event:",t)}}async baseHandleRequest(e,r,s,n){let t=await this.verifyToken(e);async function a(u,d,p){let c={page_url:r,user_agent:s,verification_status:t.valid?"valid":"invalid",verification_reason:t.reason||"success"};if(p){let o=u.recordEvent(d,c);return p.waitUntil(o),o}else return await u.recordEvent(d,c)}if(!t.valid){await a(this,t.reason||"token_verification_failed",n);let u="Payment required: you need to present a valid Supertab Connect token to access this content. Check out the provided url for details",d="\u274C Content access denied"+(t.reason?`: ${t.reason}`:""),c={url:`${l.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`,message:u,details:d};return new Response(JSON.stringify(c),{status:402,headers:new Headers({"Content-Type":"application/json"})})}return await a(this,"page_viewed",n),new Response("\u2705 Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}async baseLicenseHandleRequest(e,r,s,n){return P({licenseToken:e,url:r,userAgent:s,ctx:n,supertabBaseUrl:l.baseUrl,merchantSystemUrn:this.merchantSystemUrn,debug:w,recordEvent:(t,a,u)=>this.recordEvent(t,a,u)})}extractDataFromRequest(e){let r=e.headers.get("Authorization")||"",s=r.startsWith("Bearer ")?r.slice(7):"",n=r.startsWith("License ")?r.slice(8):"",t=e.url,a=e.headers.get("User-Agent")||"unknown";return{token:s,licenseToken:n,url:t,user_agent:a}}static checkIfBotRequest(e){let r=e.headers.get("User-Agent")||"",s=e.headers.get("accept")||"",n=e.headers.get("sec-ch-ua"),t=e.headers.get("accept-language"),a=e.cf?.botManagement?.score,u=["chatgpt-user","perplexitybot","gptbot","anthropic-ai","ccbot","claude-web","claudebot","cohere-ai","youbot","diffbot","oai-searchbot","meta-externalagent","timpibot","amazonbot","bytespider","perplexity-user","googlebot","bot","curl","wget"],d=r.toLowerCase(),p=u.some(y=>d.includes(y)),c=r.toLowerCase().includes("headless")||r.toLowerCase().includes("puppeteer")||!n,o=!r.toLowerCase().includes("headless")||!r.toLowerCase().includes("puppeteer")||!n,f=!s||!t,g=typeof a=="number"&&a<30;return console.log("Bot Detection Details:",{botUaMatch:p,headlessIndicators:c,missingHeaders:f,lowBotScore:g,botScore:a}),(d.includes("safari")||d.includes("mozilla"))&&c&&o?!1:p||c||f||g}static async cloudflareHandleRequests(e,r,s){let{MERCHANT_SYSTEM_URN:n,MERCHANT_API_KEY:t}=r;return new l({apiKey:t,merchantSystemUrn:n}).handleRequest(e,l.checkIfBotRequest,s)}static async fastlyHandleRequests(e,r,s,n=!1){let t=new l({apiKey:s,merchantSystemUrn:r});return n&&new URL(e.url).pathname==="/license.xml"?await E(l.baseUrl,r):t.handleRequest(e,l.checkIfBotRequest,null)}async handleRequest(e,r,s){let{token:n,licenseToken:t,url:a,user_agent:u}=this.extractDataFromRequest(e);return r&&!r(e,s)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):n?this.baseHandleRequest(n,a,u,s):this.baseLicenseHandleRequest(t,a,u,s)}async hostRSLicenseXML(){return E(l.baseUrl,this.merchantSystemUrn)}static async generateLicenseToken(e,r,s,n,t){let a=l.baseUrl+"/rsl/token";return v({clientId:e,kid:r,privateKeyPem:s,tokenEndpoint:a,resourceUrl:n,licenseXml:t,debug:w})}static async generateCustomerJWT(e,r,s,n=3600){return R({customerURN:e,kid:r,privateKeyPem:s,expirationSeconds:n})}};l.baseUrl="https://api-connect.supertab.co",l._instance=null;var U=l;export{U as SupertabConnect};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/types.ts","../src/index.ts","../src/customer.ts","../src/license.ts","../src/jwks.ts"],"sourcesContent":["export interface SupertabConnectConfig {\n apiKey: string;\n merchantSystemUrn: string;\n}\n\n/**\n * Defines the shape for environment variables (used in CloudFlare integration).\n * These are used to identify and authenticate the Merchant System with the Supertab Connect API.\n */\nexport interface Env {\n\t/** The unique identifier for the merchant system. */\n\tMERCHANT_SYSTEM_URN: string;\n\t/** The API key for authenticating with the Supertab Connect. */\n\tMERCHANT_API_KEY: string;\n\t[key: string]: string;\n}\n\nexport interface EventPayload {\n event_name: string;\n license_id?: string;\n merchant_system_urn: string;\n properties: Record<string, any>;\n}\n\nexport interface TokenVerificationResult {\n valid: boolean;\n reason?: string;\n payload?: any;\n}\n\nexport enum TokenInvalidReason {\n MISSING_TOKEN = \"missing_token\",\n INVALID_HEADER = \"invalid_header\",\n INVALID_ALG = \"invalid_algorithm\",\n INVALID_PAYLOAD = \"invalid_payload\",\n INVALID_ISSUER = \"invalid_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"signature_verification_failed\",\n EXPIRED = \"token_expired\",\n}\n\nexport interface LicenseTokenVerificationResult {\n valid: boolean;\n reason?: string;\n licenseId?: string;\n payload?: any;\n}\n\nexport enum LicenseTokenInvalidReason {\n MISSING_TOKEN = \"missing_license_token\",\n INVALID_HEADER = \"invalid_license_header\",\n INVALID_ALG = \"invalid_license_algorithm\",\n INVALID_PAYLOAD = \"invalid_license_payload\",\n INVALID_ISSUER = \"invalid_license_issuer\",\n SIGNATURE_VERIFICATION_FAILED = \"license_signature_verification_failed\",\n EXPIRED = \"license_token_expired\",\n INVALID_AUDIENCE = \"invalid_license_audience\",\n}\n\nexport const FASTLY_BACKEND = \"stc-backend\";\n\nexport interface FetchOptions extends RequestInit {\n // Fastly-specific extension for backend routing\n backend?: string;\n}","import {\n SupertabConnectConfig,\n Env,\n EventPayload,\n TokenVerificationResult,\n TokenInvalidReason,\n FASTLY_BACKEND,\n} from \"./types\";\nimport {\n jwtVerify,\n decodeProtectedHeader,\n decodeJwt,\n JWTHeaderParameters,\n JWTPayload,\n} from \"jose\";\nimport {\n generateLicenseToken as generateLicenseTokenHelper,\n generateCustomerJWT as generateCustomerJWTHelper,\n} from \"./customer\";\nimport {\n baseLicenseHandleRequest as baseLicenseHandleRequestHelper,\n hostRSLicenseXML as hostRSLicenseXMLHelper,\n} from \"./license\";\nimport { fetchIssuerJwks, fetchPlatformJwks } from \"./jwks\";\n\nexport type { Env } from \"./types\";\n\nconst debug = true; // Set to true for debugging purposes\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.supertab.co\";\n private merchantSystemUrn!: string;\n\n private static _instance: SupertabConnect | null = null;\n\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (\n !(\n config.apiKey === SupertabConnect._instance.apiKey &&\n config.merchantSystemUrn ===\n SupertabConnect._instance.merchantSystemUrn\n )\n ) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey || !config.merchantSystemUrn) {\n throw new Error(\n \"Missing required configuration: apiKey and merchantSystemUrn are required\"\n );\n }\n this.apiKey = config.apiKey;\n this.merchantSystemUrn = config.merchantSystemUrn;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Verify a JWT token\n * @param token The JWT token to verify\n * @returns A promise that resolves with the verification result\n */\n async verifyToken(token: string): Promise<TokenVerificationResult> {\n // 1. Check if token exists\n if (!token) {\n return {\n valid: false,\n reason: TokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n // 2. Verify header and algorithm\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(token) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT header:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"RS256\") {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ALG,\n };\n }\n\n // 3. Verify payload and issuer\n let payload: JWTPayload;\n try {\n payload = decodeJwt(token);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT payload:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(\"urn:stc:customer:\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ISSUER,\n };\n }\n\n // 4. Verify signature\n try {\n const jwks = await fetchIssuerJwks(\n SupertabConnect.baseUrl,\n issuer,\n debug\n );\n\n // Create a key finder function for verification\n const getKey = async (header: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === header.kid);\n if (!jwk) throw new Error(`No matching key found: ${header.kid}`);\n return jwk;\n };\n\n const result = await jwtVerify(token, getKey, {\n issuer,\n algorithms: [\"RS256\"],\n clockTolerance: \"1m\",\n });\n\n // Success case - token is valid\n return {\n valid: true,\n payload: result.payload,\n };\n } catch (error: any) {\n if (debug) {\n console.error(\"JWT verification failed:\", error);\n }\n\n // Check if token is expired\n if (error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.EXPIRED,\n };\n }\n\n return {\n valid: false,\n reason: TokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n };\n }\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param properties Additional properties to include with the event\n * @param licenseId Optional license ID associated with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n properties: Record<string, any> = {},\n licenseId?: string\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: any = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n };\n // @ts-ignore\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n const response = await fetch(\n `${SupertabConnect.baseUrl}/events`,\n options\n );\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n /**\n * Handle the request, report an event to Supertab Connect and return a response\n */\n private async baseHandleRequest(\n token: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n // 1. Verify token\n const verification = await this.verifyToken(token);\n\n // Record event helper\n async function recordEvent(\n stc: SupertabConnect,\n eventName: string,\n ctx: any\n ) {\n const eventProperties = {\n page_url: url,\n user_agent: user_agent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n if (ctx) {\n const eventPromise = stc.recordEvent(eventName, eventProperties);\n ctx.waitUntil(eventPromise);\n return eventPromise;\n } else {\n return await stc.recordEvent(eventName, eventProperties);\n }\n }\n\n // 2. Handle based on verification result\n if (!verification.valid) {\n await recordEvent(\n this,\n verification.reason || \"token_verification_failed\",\n ctx\n );\n const message =\n \"Payment required: you need to present a valid Supertab Connect token to access this content. \" +\n \"Check out the provided url for details\";\n const details =\n \"❌ Content access denied\" +\n (verification.reason ? `: ${verification.reason}` : \"\");\n const contentAccessUrl = `${SupertabConnect.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`;\n\n const responseBody = {\n url: contentAccessUrl,\n message: message,\n details: details,\n };\n\n return new Response(JSON.stringify(responseBody), {\n status: 402,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Success\n await recordEvent(this, \"page_viewed\", ctx);\n return new Response(\"✅ Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n /**\n * Handle the request for license tokens, report an event to Supertab Connect and return a response\n */\n private async baseLicenseHandleRequest(\n licenseToken: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n return baseLicenseHandleRequestHelper({\n licenseToken,\n url,\n userAgent: user_agent,\n ctx,\n supertabBaseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: this.merchantSystemUrn,\n debug,\n recordEvent: (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string\n ) => this.recordEvent(eventName, properties, licenseId),\n });\n }\n\n private extractDataFromRequest(request: Request): {\n token: string;\n licenseToken: string;\n url: string;\n user_agent: string;\n } {\n // Parse token\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"Bearer \") ? auth.slice(7) : \"\";\n const licenseToken = auth.startsWith(\"License \") ? auth.slice(8) : \"\";\n\n // Extract URL and user agent\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n return { token, licenseToken, url, user_agent };\n }\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string,\n enableRSL: boolean = false,\n ): Promise<Response> {\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: merchantApiKey,\n merchantSystemUrn: merchantSystemUrn,\n });\n\n if (enableRSL) {\n if (new URL(request.url).pathname === \"/license.xml\") {\n return await hostRSLicenseXMLHelper(\n SupertabConnect.baseUrl,\n merchantSystemUrn\n );\n }\n }\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n null\n );\n }\n\n async handleRequest(\n request: Request,\n botDetectionHandler?: (request: Request, ctx?: any) => boolean,\n ctx?: any\n ): Promise<Response> {\n // 1. Extract token, license token, URL, and user agent from the request\n const { token, licenseToken, url, user_agent } =\n this.extractDataFromRequest(request);\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Check for bearer token first, then fallback to license token\n if (token) {\n return this.baseHandleRequest(token, url, user_agent, ctx);\n }\n\n // 4. Call the base licenhandle request method and return the result\n return this.baseLicenseHandleRequest(licenseToken, url, user_agent, ctx);\n }\n\n async hostRSLicenseXML(): Promise<Response> {\n return hostRSLicenseXMLHelper(\n SupertabConnect.baseUrl,\n this.merchantSystemUrn\n );\n }\n\n /**\n * Request a license token from the Supertab Connect token endpoint.\n * @param clientId OAuth client identifier used for the assertion issuer/subject claims.\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem Private key in PEM format used to sign the client assertion.\n * @param tokenEndpoint Token endpoint URL.\n * @param resourceUrl Resource URL attempting to access with a License.\n * @param licenseXml XML license document to include in the request payload.\n * @returns Promise resolving to the issued license access token string.\n */\n static async generateLicenseToken(\n clientId: string,\n kid: string,\n privateKeyPem: string,\n resourceUrl: string,\n licenseXml: string\n ): Promise<string> {\n const tokenEndpoint = SupertabConnect.baseUrl + \"/rsl/token\";\n return generateLicenseTokenHelper({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n });\n }\n\n\n /** Generate a customer JWT\n * @param customerURN The customer's unique resource name (URN).\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem The private key in PEM format used to sign the JWT.\n * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).\n * @returns A promise that resolves to the generated JWT as a string.\n */\n static async generateCustomerJWT(\n customerURN: string,\n kid: string,\n privateKeyPem: string,\n expirationSeconds: number = 3600\n ): Promise<string> {\n return generateCustomerJWTHelper({\n customerURN,\n kid,\n privateKeyPem,\n expirationSeconds,\n });\n }\n}\n","import { importPKCS8, SignJWT } from \"jose\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\ntype GenerateCustomerJwtParams = {\n customerURN: string;\n kid: string;\n privateKeyPem: string;\n expirationSeconds?: number;\n};\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const supportedAlgs: SupportedAlg[] = [\"ES256\", \"RS256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\nexport async function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: payload.toString(),\n };\n\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: any;\n try {\n data = await response.json();\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nexport async function generateCustomerJWT({\n customerURN,\n kid,\n privateKeyPem,\n expirationSeconds = 3600,\n}: GenerateCustomerJwtParams): Promise<string> {\n const alg: SupportedAlg = \"RS256\";\n const key = await importPKCS8(privateKeyPem, alg);\n\n const now = Math.floor(Date.now() / 1000);\n\n return new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(customerURN)\n .setIssuedAt(now)\n .setExpirationTime(now + expirationSeconds)\n .sign(key);\n}\n\nexport type { GenerateLicenseTokenParams, GenerateCustomerJwtParams };\n","import {\n decodeProtectedHeader,\n decodeJwt,\n JWTPayload,\n JWTHeaderParameters,\n jwtVerify,\n} from \"jose\";\nimport {\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n FASTLY_BACKEND,\n FetchOptions,\n} from \"./types\";\nimport { fetchPlatformJwks } from \"./jwks\";\n\nconst stripTrailingSlash = (value: string) => value.replace(/\\/+$/, \"\");\n\nexport type VerifyLicenseTokenParams = {\n licenseToken: string;\n requestUrl: string;\n supertabBaseUrl: string;\n debug: boolean;\n};\n\nexport async function verifyLicenseToken({\n licenseToken,\n requestUrl,\n supertabBaseUrl,\n debug,\n}: VerifyLicenseTokenParams): Promise<LicenseTokenVerificationResult> {\n if (!licenseToken) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(licenseToken) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT header:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"ES256\") {\n if (debug) {\n console.error(\"Unsupported license JWT alg:\", header.alg);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ALG,\n };\n }\n\n let payload: JWTPayload;\n try {\n payload = decodeJwt(licenseToken);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid license JWT payload:\", error);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n // @ts-ignore\n const licenseId: string | undefined = payload.license_id;\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(supertabBaseUrl)) {\n if (debug) {\n console.error(\"Invalid license JWT issuer:\", issuer);\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_ISSUER,\n licenseId,\n };\n }\n\n const audienceValues = Array.isArray(payload.aud)\n ? payload.aud.filter((entry): entry is string => typeof entry === \"string\")\n : typeof payload.aud === \"string\"\n ? [payload.aud]\n : [];\n\n const requestUrlNormalized = stripTrailingSlash(requestUrl);\n const matchesRequestUrl = audienceValues.some((value) => {\n const normalizedAudience = stripTrailingSlash(value);\n if (!normalizedAudience) return false;\n return requestUrlNormalized.startsWith(normalizedAudience);\n });\n\n if (!matchesRequestUrl) {\n if (debug) {\n console.error(\n \"License JWT audience does not match request URL:\",\n payload.aud\n );\n }\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.INVALID_AUDIENCE,\n licenseId,\n };\n }\n\n try {\n const jwks = await fetchPlatformJwks(supertabBaseUrl, debug);\n\n const getKey = async (jwtHeader: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === jwtHeader.kid);\n if (!jwk) {\n throw new Error(`No matching platform key found: ${jwtHeader.kid}`);\n }\n return jwk;\n };\n\n const result = await jwtVerify(licenseToken, getKey, {\n issuer,\n algorithms: [header.alg],\n clockTolerance: \"1m\",\n });\n\n return {\n valid: true,\n licenseId,\n payload: result.payload,\n };\n } catch (error) {\n if (debug) {\n console.error(\"License JWT verification failed:\", error);\n }\n\n if (error instanceof Error && error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.EXPIRED,\n licenseId,\n };\n }\n\n return {\n valid: false,\n reason: LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n licenseId,\n };\n }\n}\n\nexport function generateLicenseLink({\n requestUrl,\n}: {\n requestUrl: string;\n}): string {\n const baseURL = new URL(requestUrl);\n return `${baseURL.protocol}//${baseURL.host}/license.xml`;\n}\n\ntype RecordEventFn = (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string\n) => Promise<void>;\n\ntype BaseLicenseHandleRequestParams = {\n licenseToken: string;\n url: string;\n userAgent: string;\n ctx: any;\n supertabBaseUrl: string;\n merchantSystemUrn?: string;\n debug: boolean;\n recordEvent: RecordEventFn;\n};\n\nexport async function baseLicenseHandleRequest({\n licenseToken,\n url,\n userAgent,\n ctx,\n supertabBaseUrl,\n merchantSystemUrn,\n debug,\n recordEvent,\n}: BaseLicenseHandleRequestParams): Promise<Response> {\n const verification = await verifyLicenseToken({\n licenseToken,\n requestUrl: url,\n supertabBaseUrl,\n debug,\n });\n\n async function recordLicenseEvent(eventName: string) {\n const eventProperties = {\n page_url: url,\n user_agent: userAgent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n\n const eventPromise = recordEvent(\n eventName,\n eventProperties,\n verification.licenseId\n );\n\n if (ctx?.waitUntil) {\n ctx.waitUntil(eventPromise);\n }\n\n return eventPromise;\n }\n\n if (!verification.valid) {\n await recordLicenseEvent(\n verification.reason || \"license_token_verification_failed\"\n );\n\n let rslError = \"invalid_request\";\n let errorDescription = \"Access to this resource requires a license\";\n\n switch (verification.reason) {\n case LicenseTokenInvalidReason.MISSING_TOKEN:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n break;\n case LicenseTokenInvalidReason.EXPIRED:\n rslError = \"invalid_token\";\n errorDescription = \"The license token has expired\";\n break;\n case LicenseTokenInvalidReason.SIGNATURE_VERIFICATION_FAILED:\n rslError = \"invalid_token\";\n errorDescription = \"The license token signature is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_HEADER:\n rslError = \"invalid_token\";\n errorDescription = \"The license token header is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n rslError = \"invalid_token\";\n errorDescription = \"The license token payload is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n rslError = \"invalid_token\";\n errorDescription = \"The license token issuer is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n rslError = \"invalid_token\";\n errorDescription = \"The license token audience is invalid\";\n break;\n default:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n }\n\n const licenseLink = generateLicenseLink({\n requestUrl: url,\n });\n const errorUri = `${supertabBaseUrl}/docs/errors#${rslError}`;\n\n const headers = new Headers({\n \"Content-Type\": \"text/plain; charset=UTF-8\",\n \"WWW-Authenticate\": `License error=\"${rslError}\", error_description=\"${errorDescription}\", error_uri=\"${errorUri}\"`,\n Link: `${licenseLink}; rel=\"license\"; type=\"application/rsl+xml\"`,\n });\n\n const responseBody = `Access to this resource requires a valid license token. Error: ${rslError} - ${errorDescription}`;\n\n return new Response(responseBody, {\n status: 401,\n headers,\n });\n }\n\n await recordLicenseEvent(\"license_used\");\n return new Response(\"✅ License Token Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n}\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nexport async function hostRSLicenseXML(\n supertabBaseUrl: string,\n merchantSystemUrn: string\n): Promise<Response> {\n const licenseUrl = `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/license.xml`;\n const response = await fetch(licenseUrl, buildFetchOptions());\n\n if (!response.ok) {\n return new Response(\"License not found\", { status: 404 });\n }\n\n const licenseXml = await response.text();\n\n return new Response(licenseXml, {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/xml\" }),\n });\n}\n","import { FASTLY_BACKEND, FetchOptions } from \"./types\";\n\nconst jwksCache = new Map<string, any>();\n\ntype JwksCacheKey = string;\n\ntype FetchJwksParams = {\n cacheKey: JwksCacheKey;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<any> {\n if (!jwksCache.has(cacheKey)) {\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json();\n jwksCache.set(cacheKey, jwksData);\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(cacheKey);\n}\n\nexport async function fetchIssuerJwks(\n baseUrl: string,\n issuer: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/${encodeURIComponent(\n issuer\n )}`;\n\n return fetchAndCacheJwks({\n cacheKey: issuer,\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch JWKS\",\n logLabel: \"Error fetching JWKS:\",\n });\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/platform`;\n console.log(\"Fetching platform JWKS from:\", jwksUrl);\n\n return fetchAndCacheJwks({\n cacheKey: \"platform_jwks\",\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch platform JWKS\",\n logLabel: \"Error fetching platform JWKS:\",\n });\n}\n\nexport function clearJwksCache(): void {\n jwksCache.clear();\n}\n"],"mappings":"AA0DO,IAAMA,EAAiB,cClD9B,OACE,aAAAC,EACA,yBAAAC,EACA,aAAAC,MAGK,OCdP,OAAS,eAAAC,EAAa,WAAAC,MAAe,OAqBrC,eAAeC,EACbC,EACAC,EACgD,CAChD,IAAMC,EAAgC,CAAC,QAAS,OAAO,EAEvD,QAAWC,KAAaD,EACtB,GAAI,CAEF,MAAO,CAAE,IADG,MAAML,EAAYG,EAAeG,CAAS,EACxC,IAAKA,CAAU,CAC/B,OAASC,EAAa,CAChBH,GACF,QAAQ,MACN,oCAAoCE,CAAS,gBAC7CC,CACF,CAEJ,CAGF,MAAM,IAAI,MACR,uEACF,CACF,CAEA,eAAsBC,EAAqB,CACzC,SAAAC,EACA,IAAAC,EACA,cAAAP,EACA,cAAAQ,EACA,YAAAC,EACA,WAAAC,EACA,MAAAT,CACF,EAAgD,CAC9C,GAAM,CAAE,IAAAU,EAAK,IAAAC,CAAI,EAAI,MAAMb,EAAiBC,EAAeC,CAAK,EAC1DY,EAAM,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EAElCC,EAAkB,MAAM,IAAIhB,EAAQ,CAAC,CAAC,EACzC,mBAAmB,CAAE,IAAAc,EAAK,IAAAL,CAAI,CAAC,EAC/B,UAAUD,CAAQ,EAClB,WAAWA,CAAQ,EACnB,YAAYO,CAAG,EACf,kBAAkBA,EAAM,GAAG,EAC3B,YAAYL,CAAa,EACzB,KAAKG,CAAG,EAELI,EAAU,IAAI,gBAAgB,CAClC,WAAY,MACZ,sBACE,yDACF,iBAAkBD,EAClB,QAASJ,EACT,SAAUD,CACZ,CAAC,EAEKO,EAA8B,CAClC,OAAQ,OACR,QAAS,CACP,eAAgB,oCAChB,OAAQ,kBACV,EACA,KAAMD,EAAQ,SAAS,CACzB,EAEA,GAAI,CACF,IAAME,EAAW,MAAM,MAAMT,EAAeQ,CAAc,EAE1D,GAAI,CAACC,EAAS,GAAI,CAChB,IAAMC,EAAY,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EAChDE,EAAe,mCACnBF,EAAS,MACX,IAAIA,EAAS,UAAU,GAAGC,EAAY,MAAMA,CAAS,GAAK,EAAE,GAC5D,MAAM,IAAI,MAAMC,CAAY,CAC9B,CAEA,IAAIC,EACJ,GAAI,CACFA,EAAO,MAAMH,EAAS,KAAK,CAC7B,OAASI,EAAY,CACnB,MAAIpB,GACF,QAAQ,MACN,kDACAoB,CACF,EAEI,IAAI,MAAM,gDAAgD,CAClE,CAEA,GAAI,CAACD,GAAM,aACT,MAAM,IAAI,MAAM,6CAA6C,EAG/D,OAAOA,EAAK,YACd,OAASE,EAAO,CACd,MAAIrB,GACF,QAAQ,MAAM,kCAAmCqB,CAAK,EAElDA,CACR,CACF,CAEA,eAAsBC,EAAoB,CACxC,YAAAC,EACA,IAAAjB,EACA,cAAAP,EACA,kBAAAyB,EAAoB,IACtB,EAA+C,CAC7C,IAAMb,EAAoB,QACpBD,EAAM,MAAMd,EAAYG,EAAeY,CAAG,EAE1CC,EAAM,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EAExC,OAAO,IAAIf,EAAQ,CAAC,CAAC,EAClB,mBAAmB,CAAE,IAAAc,EAAK,IAAAL,CAAI,CAAC,EAC/B,UAAUiB,CAAW,EACrB,YAAYX,CAAG,EACf,kBAAkBA,EAAMY,CAAiB,EACzC,KAAKd,CAAG,CACb,CC3IA,OACE,yBAAAe,EACA,aAAAC,EAGA,aAAAC,MACK,OCJP,IAAMC,EAAY,IAAI,IAYtB,SAASC,GAAkC,CACzC,IAAIC,EAAwB,CAAE,OAAQ,KAAM,EAE5C,OAAI,YAAY,SACdA,EAAU,CAAE,GAAGA,EAAS,QAASC,CAAe,GAE3CD,CACT,CAEA,eAAeE,EAAkB,CAC/B,SAAAC,EACA,IAAAC,EACA,MAAAC,EACA,eAAAC,EACA,SAAAC,CACF,EAAkC,CAChC,GAAI,CAACT,EAAU,IAAIK,CAAQ,EACzB,GAAI,CACF,IAAMK,EAAW,MAAM,MAAMJ,EAAKL,EAAkB,CAAC,EAErD,GAAI,CAACS,EAAS,GACZ,MAAM,IAAI,MAAM,GAAGF,CAAc,KAAKE,EAAS,MAAM,EAAE,EAGzD,IAAMC,EAAW,MAAMD,EAAS,KAAK,EACrCV,EAAU,IAAIK,EAAUM,CAAQ,CAClC,OAASC,EAAO,CACd,MAAIL,GACF,QAAQ,MAAME,EAAUG,CAAK,EAEzBA,CACR,CAGF,OAAOZ,EAAU,IAAIK,CAAQ,CAC/B,CAEA,eAAsBQ,EACpBC,EACAC,EACAR,EACc,CACd,IAAMS,EAAU,GAAGF,CAAO,0BAA0B,mBAClDC,CACF,CAAC,GAED,OAAOX,EAAkB,CACvB,SAAUW,EACV,IAAKC,EACL,MAAAT,EACA,eAAgB,uBAChB,SAAU,sBACZ,CAAC,CACH,CAEA,eAAsBU,EACpBH,EACAP,EACc,CACd,IAAMS,EAAU,GAAGF,CAAO,kCAC1B,eAAQ,IAAI,+BAAgCE,CAAO,EAE5CZ,EAAkB,CACvB,SAAU,gBACV,IAAKY,EACL,MAAAT,EACA,eAAgB,gCAChB,SAAU,+BACZ,CAAC,CACH,CDpEA,IAAMW,EAAsBC,GAAkBA,EAAM,QAAQ,OAAQ,EAAE,EAStE,eAAsBC,EAAmB,CACvC,aAAAC,EACA,WAAAC,EACA,gBAAAC,EACA,MAAAC,CACF,EAAsE,CACpE,GAAI,CAACH,EACH,MAAO,CACL,MAAO,GACP,8BACF,EAGF,IAAII,EACJ,GAAI,CACFA,EAASC,EAAsBL,CAAY,CAC7C,OAASM,EAAO,CACd,OAAIH,GACF,QAAQ,MAAM,8BAA+BG,CAAK,EAE7C,CACL,MAAO,GACP,+BACF,CACF,CAEA,GAAIF,EAAO,MAAQ,QACjB,OAAID,GACF,QAAQ,MAAM,+BAAgCC,EAAO,GAAG,EAEnD,CACL,MAAO,GACP,kCACF,EAGF,IAAIG,EACJ,GAAI,CACFA,EAAUC,EAAUR,CAAY,CAClC,OAASM,EAAO,CACd,OAAIH,GACF,QAAQ,MAAM,+BAAgCG,CAAK,EAE9C,CACL,MAAO,GACP,gCACF,CACF,CAGA,IAAMG,EAAgCF,EAAQ,WAExCG,EAA6BH,EAAQ,IAC3C,GAAI,CAACG,GAAU,CAACA,EAAO,WAAWR,CAAe,EAC/C,OAAIC,GACF,QAAQ,MAAM,8BAA+BO,CAAM,EAE9C,CACL,MAAO,GACP,gCACA,UAAAD,CACF,EAGF,IAAME,EAAiB,MAAM,QAAQJ,EAAQ,GAAG,EAC5CA,EAAQ,IAAI,OAAQK,GAA2B,OAAOA,GAAU,QAAQ,EACxE,OAAOL,EAAQ,KAAQ,SACvB,CAACA,EAAQ,GAAG,EACZ,CAAC,EAECM,EAAuBhB,EAAmBI,CAAU,EAO1D,GAAI,CANsBU,EAAe,KAAMb,GAAU,CACvD,IAAMgB,EAAqBjB,EAAmBC,CAAK,EACnD,OAAKgB,EACED,EAAqB,WAAWC,CAAkB,EADzB,EAElC,CAAC,EAGC,OAAIX,GACF,QAAQ,MACN,mDACAI,EAAQ,GACV,EAEK,CACL,MAAO,GACP,kCACA,UAAAE,CACF,EAGF,GAAI,CACF,IAAMM,EAAO,MAAMC,EAAkBd,EAAiBC,CAAK,EAUrDc,EAAS,MAAMC,EAAUlB,EARhB,MAAOmB,GAAmC,CACvD,IAAMC,EAAML,EAAK,KAAK,KAAMM,GAAaA,EAAI,MAAQF,EAAU,GAAG,EAClE,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,mCAAmCD,EAAU,GAAG,EAAE,EAEpE,OAAOC,CACT,EAEqD,CACnD,OAAAV,EACA,WAAY,CAACN,EAAO,GAAG,EACvB,eAAgB,IAClB,CAAC,EAED,MAAO,CACL,MAAO,GACP,UAAAK,EACA,QAASQ,EAAO,OAClB,CACF,OAASX,EAAO,CAKd,OAJIH,GACF,QAAQ,MAAM,mCAAoCG,CAAK,EAGrDA,aAAiB,OAASA,EAAM,SAAS,SAAS,KAAK,EAClD,CACL,MAAO,GACP,+BACA,UAAAG,CACF,EAGK,CACL,MAAO,GACP,+CACA,UAAAA,CACF,CACF,CACF,CAEO,SAASa,EAAoB,CAClC,WAAArB,CACF,EAEW,CACT,IAAMsB,EAAU,IAAI,IAAItB,CAAU,EAClC,MAAO,GAAGsB,EAAQ,QAAQ,KAAKA,EAAQ,IAAI,cAC7C,CAmBA,eAAsBC,EAAyB,CAC7C,aAAAxB,EACA,IAAAyB,EACA,UAAAC,EACA,IAAAC,EACA,gBAAAzB,EACA,kBAAA0B,EACA,MAAAzB,EACA,YAAA0B,CACF,EAAsD,CACpD,IAAMC,EAAe,MAAM/B,EAAmB,CAC5C,aAAAC,EACA,WAAYyB,EACZ,gBAAAvB,EACA,MAAAC,CACF,CAAC,EAED,eAAe4B,EAAmBC,EAAmB,CACnD,IAAMC,EAAkB,CACtB,SAAUR,EACV,WAAYC,EACZ,oBAAqBI,EAAa,MAAQ,QAAU,UACpD,oBAAqBA,EAAa,QAAU,SAC9C,EAEMI,EAAeL,EACnBG,EACAC,EACAH,EAAa,SACf,EAEA,OAAIH,GAAK,WACPA,EAAI,UAAUO,CAAY,EAGrBA,CACT,CAEA,GAAI,CAACJ,EAAa,MAAO,CACvB,MAAMC,EACJD,EAAa,QAAU,mCACzB,EAEA,IAAIK,EAAW,kBACXC,EAAmB,6CAEvB,OAAQN,EAAa,OAAQ,CAC3B,4BACEK,EAAW,kBACXC,EAAmB,6CACnB,MACF,4BACED,EAAW,gBACXC,EAAmB,gCACnB,MACF,4CACED,EAAW,gBACXC,EAAmB,yCACnB,MACF,6BACED,EAAW,gBACXC,EAAmB,sCACnB,MACF,8BACED,EAAW,gBACXC,EAAmB,uCACnB,MACF,6BACED,EAAW,gBACXC,EAAmB,sCACnB,MACF,+BACED,EAAW,gBACXC,EAAmB,wCACnB,MACF,QACED,EAAW,kBACXC,EAAmB,4CACvB,CAEA,IAAMC,EAAcf,EAAoB,CACtC,WAAYG,CACd,CAAC,EACKa,EAAW,GAAGpC,CAAe,gBAAgBiC,CAAQ,GAErDI,EAAU,IAAI,QAAQ,CAC1B,eAAgB,4BAChB,mBAAoB,kBAAkBJ,CAAQ,yBAAyBC,CAAgB,iBAAiBE,CAAQ,IAChH,KAAM,GAAGD,CAAW,6CACtB,CAAC,EAEKG,EAAe,kEAAkEL,CAAQ,MAAMC,CAAgB,GAErH,OAAO,IAAI,SAASI,EAAc,CAChC,OAAQ,IACR,QAAAD,CACF,CAAC,CACH,CAEA,aAAMR,EAAmB,cAAc,EAChC,IAAI,SAAS,sCAAkC,CACpD,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,CAC7D,CAAC,CACH,CAEA,SAASU,GAAkC,CACzC,IAAIC,EAAwB,CAAE,OAAQ,KAAM,EAE5C,OAAI,YAAY,SACdA,EAAU,CAAE,GAAGA,EAAS,QAASC,CAAe,GAE3CD,CACT,CAEA,eAAsBE,EACpB1C,EACA0B,EACmB,CACnB,IAAMiB,EAAa,GAAG3C,CAAe,sBAAsB0B,CAAiB,eACtEkB,EAAW,MAAM,MAAMD,EAAYJ,EAAkB,CAAC,EAE5D,GAAI,CAACK,EAAS,GACZ,OAAO,IAAI,SAAS,oBAAqB,CAAE,OAAQ,GAAI,CAAC,EAG1D,IAAMC,EAAa,MAAMD,EAAS,KAAK,EAEvC,OAAO,IAAI,SAASC,EAAY,CAC9B,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,iBAAkB,CAAC,CAC5D,CAAC,CACH,CFjSA,IAAMC,EAAQ,GAODC,EAAN,MAAMA,CAAgB,CAOpB,YAAYC,EAA+BC,EAAiB,GAAO,CACxE,GAAI,CAACA,GAASF,EAAgB,UAAW,CAEvC,GACE,EACEC,EAAO,SAAWD,EAAgB,UAAU,QAC5CC,EAAO,oBACLD,EAAgB,UAAU,mBAG9B,MAAM,IAAI,MACR,8GACF,EAIF,OAAOA,EAAgB,SACzB,CAMA,GALIE,GAASF,EAAgB,WAE3BA,EAAgB,cAAc,EAG5B,CAACC,EAAO,QAAU,CAACA,EAAO,kBAC5B,MAAM,IAAI,MACR,2EACF,EAEF,KAAK,OAASA,EAAO,OACrB,KAAK,kBAAoBA,EAAO,kBAGhCD,EAAgB,UAAY,IAC9B,CAEA,OAAc,eAAsB,CAClCA,EAAgB,UAAY,IAC9B,CAKA,OAAc,WAAWG,EAAmB,CAC1CH,EAAgB,QAAUG,CAC5B,CAOA,MAAM,YAAYC,EAAiD,CAEjE,GAAI,CAACA,EACH,MAAO,CACL,MAAO,GACP,sBACF,EAIF,IAAIC,EACJ,GAAI,CACFA,EAASC,EAAsBF,CAAK,CACtC,OAASG,EAAO,CACd,OAAIR,GACF,QAAQ,MAAM,sBAAuBQ,CAAK,EAErC,CACL,MAAO,GACP,uBACF,CACF,CAEA,GAAIF,EAAO,MAAQ,QACjB,MAAO,CACL,MAAO,GACP,0BACF,EAIF,IAAIG,EACJ,GAAI,CACFA,EAAUC,EAAUL,CAAK,CAC3B,OAASG,EAAO,CACd,OAAIR,GACF,QAAQ,MAAM,uBAAwBQ,CAAK,EAEtC,CACL,MAAO,GACP,wBACF,CACF,CAEA,IAAMG,EAA6BF,EAAQ,IAC3C,GAAI,CAACE,GAAU,CAACA,EAAO,WAAW,mBAAmB,EACnD,MAAO,CACL,MAAO,GACP,uBACF,EAIF,GAAI,CACF,IAAMC,EAAO,MAAMC,EACjBZ,EAAgB,QAChBU,EACAX,CACF,EAgBA,MAAO,CACL,MAAO,GACP,SATa,MAAMc,EAAUT,EANhB,MAAOC,GAAgC,CACpD,IAAMS,EAAMH,EAAK,KAAK,KAAMI,GAAaA,EAAI,MAAQV,EAAO,GAAG,EAC/D,GAAI,CAACS,EAAK,MAAM,IAAI,MAAM,0BAA0BT,EAAO,GAAG,EAAE,EAChE,OAAOS,CACT,EAE8C,CAC5C,OAAAJ,EACA,WAAY,CAAC,OAAO,EACpB,eAAgB,IAClB,CAAC,GAKiB,OAClB,CACF,OAASH,EAAY,CAMnB,OALIR,GACF,QAAQ,MAAM,2BAA4BQ,CAAK,EAI7CA,EAAM,SAAS,SAAS,KAAK,EACxB,CACL,MAAO,GACP,sBACF,EAGK,CACL,MAAO,GACP,sCACF,CACF,CACF,CASA,MAAM,YACJS,EACAC,EAAkC,CAAC,EACnCC,EACe,CACf,IAAMV,EAAwB,CAC5B,WAAYQ,EACZ,oBAAqB,KAAK,kBAAoB,KAAK,kBAAoB,GACvE,WAAYE,EACZ,WAAAD,CACF,EAEA,GAAI,CACF,IAAIE,EAAe,CACjB,OAAQ,OACR,QAAS,CACP,cAAe,UAAU,KAAK,MAAM,GACpC,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAUX,CAAO,CAC9B,EAEI,YAAY,SACdW,EAAU,CAAE,GAAGA,EAAS,QAASC,CAAe,GAElD,IAAMC,EAAW,MAAM,MACrB,GAAGrB,EAAgB,OAAO,UAC1BmB,CACF,EAEKE,EAAS,IACZ,QAAQ,IAAI,2BAA2BA,EAAS,MAAM,EAAE,CAE5D,OAASd,EAAO,CACd,QAAQ,IAAI,yBAA0BA,CAAK,CAC7C,CACF,CAKA,MAAc,kBACZH,EACAD,EACAmB,EACAC,EACmB,CAEnB,IAAMC,EAAe,MAAM,KAAK,YAAYpB,CAAK,EAGjD,eAAeqB,EACbC,EACAV,EACAO,EACA,CACA,IAAMI,EAAkB,CACtB,SAAUxB,EACV,WAAYmB,EACZ,oBAAqBE,EAAa,MAAQ,QAAU,UACpD,oBAAqBA,EAAa,QAAU,SAC9C,EACA,GAAID,EAAK,CACP,IAAMK,EAAeF,EAAI,YAAYV,EAAWW,CAAe,EAC/D,OAAAJ,EAAI,UAAUK,CAAY,EACnBA,CACT,KACE,QAAO,MAAMF,EAAI,YAAYV,EAAWW,CAAe,CAE3D,CAGA,GAAI,CAACH,EAAa,MAAO,CACvB,MAAMC,EACJ,KACAD,EAAa,QAAU,4BACvBD,CACF,EACA,IAAMM,EACJ,sIAEIC,EACJ,gCACCN,EAAa,OAAS,KAAKA,EAAa,MAAM,GAAK,IAGhDO,EAAe,CACnB,IAHuB,GAAG/B,EAAgB,OAAO,sBAAsB,KAAK,iBAAiB,uBAI7F,QAAS6B,EACT,QAASC,CACX,EAEA,OAAO,IAAI,SAAS,KAAK,UAAUC,CAAY,EAAG,CAChD,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,CAC7D,CAAC,CACH,CAGA,aAAMN,EAAY,KAAM,cAAeF,CAAG,EACnC,IAAI,SAAS,gCAA4B,CAC9C,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,CAC7D,CAAC,CACH,CAKA,MAAc,yBACZS,EACA7B,EACAmB,EACAC,EACmB,CACnB,OAAOU,EAA+B,CACpC,aAAAD,EACA,IAAA7B,EACA,UAAWmB,EACX,IAAAC,EACA,gBAAiBvB,EAAgB,QACjC,kBAAmB,KAAK,kBACxB,MAAAD,EACA,YAAa,CACXiB,EACAC,EACAC,IACG,KAAK,YAAYF,EAAWC,EAAYC,CAAS,CACxD,CAAC,CACH,CAEQ,uBAAuBgB,EAK7B,CAEA,IAAMC,EAAOD,EAAQ,QAAQ,IAAI,eAAe,GAAK,GAC/C9B,EAAQ+B,EAAK,WAAW,SAAS,EAAIA,EAAK,MAAM,CAAC,EAAI,GACrDH,EAAeG,EAAK,WAAW,UAAU,EAAIA,EAAK,MAAM,CAAC,EAAI,GAG7DhC,EAAM+B,EAAQ,IACdZ,EAAaY,EAAQ,QAAQ,IAAI,YAAY,GAAK,UAExD,MAAO,CAAE,MAAA9B,EAAO,aAAA4B,EAAc,IAAA7B,EAAK,WAAAmB,CAAW,CAChD,CAEA,OAAO,kBAAkBY,EAA2B,CAClD,IAAME,EAAYF,EAAQ,QAAQ,IAAI,YAAY,GAAK,GACjDG,EAASH,EAAQ,QAAQ,IAAI,QAAQ,GAAK,GAC1CI,EAAUJ,EAAQ,QAAQ,IAAI,WAAW,EACzCK,EAAiBL,EAAQ,QAAQ,IAAI,iBAAiB,EACtDM,EAAYN,EAAgB,IAAI,eAAe,MAE/CO,EAAU,CACd,eACA,gBACA,SACA,eACA,QACA,aACA,YACA,YACA,SACA,UACA,gBACA,qBACA,WACA,YACA,aACA,kBACA,YACA,MACA,OACA,MACF,EAEMC,EAAqBN,EAAU,YAAY,EAC3CO,EAAaF,EAAQ,KAAMG,GAAQF,EAAmB,SAASE,CAAG,CAAC,EAGnEC,EACJT,EAAU,YAAY,EAAE,SAAS,UAAU,GAC3CA,EAAU,YAAY,EAAE,SAAS,WAAW,GAC5C,CAACE,EAEGQ,EACJ,CAACV,EAAU,YAAY,EAAE,SAAS,UAAU,GAC5C,CAACA,EAAU,YAAY,EAAE,SAAS,WAAW,GAC7C,CAACE,EAGGS,EAAiB,CAACV,GAAU,CAACE,EAG7BS,EAAc,OAAOR,GAAa,UAAYA,EAAW,GAU/D,OATA,QAAQ,IAAI,yBAA0B,CACpC,WAAAG,EACA,mBAAAE,EACA,eAAAE,EACA,YAAAC,EACA,SAAAR,CACF,CAAC,GAICE,EAAmB,SAAS,QAAQ,GACpCA,EAAmB,SAAS,SAAS,IAGjCG,GAAsBC,EACjB,GAKJH,GAAcE,GAAsBE,GAAkBC,CAC/D,CAEA,aAAa,yBACXd,EACAe,EACA1B,EACmB,CAEnB,GAAM,CAAE,oBAAA2B,EAAqB,iBAAAC,CAAiB,EAAIF,EASlD,OANwB,IAAIjD,EAAgB,CAC1C,OAAQmD,EACR,kBAAmBD,CACrB,CAAC,EAGsB,cACrBhB,EACAlC,EAAgB,kBAChBuB,CACF,CACF,CAEA,aAAa,qBACXW,EACAkB,EACAC,EACAC,EAAqB,GACF,CAEnB,IAAMC,EAAkB,IAAIvD,EAAgB,CAC1C,OAAQqD,EACR,kBAAmBD,CACrB,CAAC,EAED,OAAIE,GACE,IAAI,IAAIpB,EAAQ,GAAG,EAAE,WAAa,eAC7B,MAAMsB,EACXxD,EAAgB,QAChBoD,CACF,EAKGG,EAAgB,cACrBrB,EACAlC,EAAgB,kBAChB,IACF,CACF,CAEA,MAAM,cACJkC,EACAuB,EACAlC,EACmB,CAEnB,GAAM,CAAE,MAAAnB,EAAO,aAAA4B,EAAc,IAAA7B,EAAK,WAAAmB,CAAW,EAC3C,KAAK,uBAAuBY,CAAO,EAGrC,OAAIuB,GAAuB,CAACA,EAAoBvB,EAASX,CAAG,EACnD,IAAI,SAAS,wCAAoC,CACtD,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,CAC7D,CAAC,EAICnB,EACK,KAAK,kBAAkBA,EAAOD,EAAKmB,EAAYC,CAAG,EAIpD,KAAK,yBAAyBS,EAAc7B,EAAKmB,EAAYC,CAAG,CACzE,CAEA,MAAM,kBAAsC,CAC1C,OAAOiC,EACLxD,EAAgB,QAChB,KAAK,iBACP,CACF,CAYA,aAAa,qBACX0D,EACAC,EACAC,EACAC,EACAC,EACiB,CACjB,IAAMC,EAAgB/D,EAAgB,QAAU,aAChD,OAAOgE,EAA2B,CAChC,SAAAN,EACA,IAAAC,EACA,cAAAC,EACA,cAAAG,EACA,YAAAF,EACA,WAAAC,EACA,MAAA/D,CACF,CAAC,CACH,CAUA,aAAa,oBACXkE,EACAN,EACAC,EACAM,EAA4B,KACX,CACjB,OAAOC,EAA0B,CAC/B,YAAAF,EACA,IAAAN,EACA,cAAAC,EACA,kBAAAM,CACF,CAAC,CACH,CACF,EAtgBalE,EAEI,QAAkB,kCAFtBA,EAKI,UAAoC,KAL9C,IAAMoE,EAANpE","names":["FASTLY_BACKEND","jwtVerify","decodeProtectedHeader","decodeJwt","importPKCS8","SignJWT","importKeyForAlgs","privateKeyPem","debug","supportedAlgs","algorithm","importError","generateLicenseToken","clientId","kid","tokenEndpoint","resourceUrl","licenseXml","key","alg","now","clientAssertion","payload","requestOptions","response","errorBody","errorMessage","data","parseError","error","generateCustomerJWT","customerURN","expirationSeconds","decodeProtectedHeader","decodeJwt","jwtVerify","jwksCache","buildFetchOptions","options","FASTLY_BACKEND","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","response","jwksData","error","fetchIssuerJwks","baseUrl","issuer","jwksUrl","fetchPlatformJwks","stripTrailingSlash","value","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","header","decodeProtectedHeader","error","payload","decodeJwt","licenseId","issuer","audienceValues","entry","requestUrlNormalized","normalizedAudience","jwks","fetchPlatformJwks","result","jwtVerify","jwtHeader","jwk","key","generateLicenseLink","baseURL","baseLicenseHandleRequest","url","userAgent","ctx","merchantSystemUrn","recordEvent","verification","recordLicenseEvent","eventName","eventProperties","eventPromise","rslError","errorDescription","licenseLink","errorUri","headers","responseBody","buildFetchOptions","options","FASTLY_BACKEND","hostRSLicenseXML","licenseUrl","response","licenseXml","debug","_SupertabConnect","config","reset","url","token","header","decodeProtectedHeader","error","payload","decodeJwt","issuer","jwks","fetchIssuerJwks","jwtVerify","jwk","key","eventName","properties","licenseId","options","FASTLY_BACKEND","response","user_agent","ctx","verification","recordEvent","stc","eventProperties","eventPromise","message","details","responseBody","licenseToken","baseLicenseHandleRequest","request","auth","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","enableRSL","supertabConnect","hostRSLicenseXML","botDetectionHandler","clientId","kid","privateKeyPem","resourceUrl","licenseXml","tokenEndpoint","generateLicenseToken","customerURN","expirationSeconds","generateCustomerJWT","SupertabConnect"]}