@getsupertab/supertab-connect-sdk 0.1.0-beta.31 → 0.1.0-beta.32
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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -5
- package/dist/index.d.ts +3 -5
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var w=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var x=(r,e)=>{for(var t in e)w(r,t,{get:e[t],enumerable:!0})},D=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of N(e))!C.call(r,i)&&i!==t&&w(r,i,{get:()=>e[i],enumerable:!(n=P(e,i))||n.enumerable});return r};var q=r=>D(w({},"__esModule",{value:!0}),r);var $={};x($,{SupertabConnect:()=>A});module.exports=q($);var f="stc-backend";var L=require("jose");async function F(r,e,t){try{let n=await fetch(r,e);if(!n.ok){let s=await n.text().catch(()=>""),o=`Failed to obtain license token: ${n.status} ${n.statusText}${s?` - ${s}`:""}`;throw new Error(o)}let i;try{i=await n.json()}catch(s){throw t&&console.error("Failed to parse license token response as JSON:",s),new Error("Failed to parse license token response as JSON")}if(!i?.access_token)throw new Error("License token response missing access_token");return i.access_token}catch(n){throw t&&console.error("Error generating license token:",n),n}}async function v({clientId:r,clientSecret:e,tokenEndpoint:t,resourceUrl:n,licenseXml:i,debug:s}){let o=new URLSearchParams({grant_type:"client_credentials",license:i,resource:n}),p={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json",Authorization:"Basic "+btoa(`${r}:${e}`)},body:o.toString()};return F(t,p,s)}var m=require("jose");var E=new Map;function K(){let r={method:"GET"};return globalThis?.fastly&&(r={...r,backend:f}),r}async function O({cacheKey:r,url:e,debug:t,failureMessage:n,logLabel:i}){if(!E.has(r))try{let s=await fetch(e,K());if(!s.ok)throw new Error(`${n}: ${s.status}`);let o=await s.json();E.set(r,o)}catch(s){throw t&&console.error(i,s),s}return E.get(r)}async function T(r,e){let t=`${r}/.well-known/jwks.json/platform`;return console.log("Fetching platform JWKS from:",t),O({cacheKey:"platform_jwks",url:t,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}var I=r=>r.replace(/\/+$/,"");async function b({licenseToken:r,requestUrl:e,supertabBaseUrl:t,debug:n}){if(!r)return{valid:!1,reason:"missing_license_token"};let i;try{i=(0,m.decodeProtectedHeader)(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=(0,m.decodeJwt)(r)}catch(a){return n&&console.error("Invalid license JWT payload:",a),{valid:!1,reason:"invalid_license_payload"}}let o=s.license_id,p=s.iss;if(!p||!p.startsWith(t))return n&&console.error("Invalid license JWT issuer:",p),{valid:!1,reason:"invalid_license_issuer",licenseId:o};let d=Array.isArray(s.aud)?s.aud.filter(a=>typeof a=="string"):typeof s.aud=="string"?[s.aud]:[],g=I(e);if(!d.some(a=>{let u=I(a);return u?g.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 T(t,n),h=await(0,m.jwtVerify)(r,async y=>{let k=a.keys.find(U=>U.kid===y.kid);if(!k)throw new Error(`No matching platform key found: ${y.kid}`);return k},{issuer:p,algorithms:[i.alg],clockTolerance:"1m"});return{valid:!0,licenseId:o,payload:h.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 J({requestUrl:r}){let e=new URL(r);return`${e.protocol}//${e.host}/license.xml`}async function R({licenseToken:r,url:e,userAgent:t,ctx:n,supertabBaseUrl:i,merchantSystemUrn:s,debug:o,recordEvent:p}){let d=await b({licenseToken:r,requestUrl:e,supertabBaseUrl:i,debug:o});async function g(l){let a={page_url:e,user_agent:t,verification_status:d.valid?"valid":"invalid",verification_reason:d.reason||"success"},u=p(l,a,d.licenseId);return n?.waitUntil&&n.waitUntil(u),u}if(!d.valid){await g(d.reason||"license_token_verification_failed");let l="invalid_request",a="Access to this resource requires a license";switch(d.reason){case"missing_license_token":l="invalid_request",a="Access to this resource requires a license";break;case"license_token_expired":l="invalid_token",a="The license token has expired";break;case"license_signature_verification_failed":l="invalid_token",a="The license token signature is invalid";break;case"invalid_license_header":l="invalid_token",a="The license token header is invalid";break;case"invalid_license_payload":l="invalid_token",a="The license token payload is invalid";break;case"invalid_license_issuer":l="invalid_token",a="The license token issuer is invalid";break;case"invalid_license_audience":l="invalid_token",a="The license token audience is invalid";break;default:l="invalid_request",a="Access to this resource requires a license"}let u=J({requestUrl:e}),h=`${i}/docs/errors#${l}`,y=new Headers({"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${l}", error_description="${a}", error_uri="${h}"`,Link:`${u}; rel="license"; type="application/rsl+xml"`}),k=`Access to this resource requires a valid license token. Error: ${l} - ${a}`;return new Response(k,{status:401,headers:y})}return await g("license_used"),new Response("\u2705 License Token Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}function H(){let r={method:"GET"};return globalThis?.fastly&&(r={...r,backend:f}),r}async function S(r,e){let t=`${r}/merchants/systems/${e}/license.xml`,n=await fetch(t,H());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,c=class c{constructor(e,t=!1){if(!t&&c._instance){if(!(e.apiKey===c._instance.apiKey&&e.merchantSystemUrn===c._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return c._instance}if(t&&c._instance&&c.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,c._instance=this}static resetInstance(){c._instance=null}static setBaseUrl(e){c.baseUrl=e}async verifyLicenseToken(e,t){return b({licenseToken:e,requestUrl:t,supertabBaseUrl:c.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:f});let o=await fetch(`${c.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,p=["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(),g=p.some(y=>d.includes(y)),l=t.toLowerCase().includes("headless")||t.toLowerCase().includes("puppeteer")||!i,a=!t.toLowerCase().includes("headless")||!t.toLowerCase().includes("puppeteer")||!i,u=!n||!s,h=typeof o=="number"&&o<30;return console.log("Bot Detection Details:",{botUaMatch:g,headlessIndicators:l,missingHeaders:u,lowBotScore:h,botScore:o}),(d.includes("safari")||d.includes("mozilla"))&&l&&a?!1:g||l||u||h}static async cloudflareHandleRequests(e,t,n){let{MERCHANT_SYSTEM_URN:i,MERCHANT_API_KEY:s}=t;return new c({apiKey:s,merchantSystemUrn:i}).handleRequest(e,c.checkIfBotRequest,n)}static async fastlyHandleRequests(e,t,n,i=!1){let s=new c({apiKey:n,merchantSystemUrn:t});return i&&new URL(e.url).pathname==="/license.xml"?await S(c.baseUrl,t):s.handleRequest(e,c.checkIfBotRequest,null)}async handleRequest(e,t,n){let i=e.headers.get("Authorization")||"",s=i.startsWith("License ")?i.slice(8):"",o=e.url,p=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"})}):R({licenseToken:s,url:o,userAgent:p,ctx:n,supertabBaseUrl:c.baseUrl,merchantSystemUrn:this.merchantSystemUrn,debug:_,recordEvent:(d,g,l)=>this.recordEvent(d,g,l)})}static async obtainLicenseToken(e,t,n,i){let s=c.baseUrl+"/rsl/token";return v({clientId:e,clientSecret:t,tokenEndpoint:s,resourceUrl:n,licenseXml:i,debug:_})}};c.baseUrl="https://api-connect.supertab.co",c._instance=null;var A=c;0&&(module.exports={SupertabConnect});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.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 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"]}
|
|
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 { obtainLicenseToken as obtainLicenseTokenHelper } 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.\n * @param clientSecret OAuth client secret for client_credentials flow.\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 obtainLicenseToken(\n clientId: string,\n clientSecret: string,\n resourceUrl: string,\n licenseXml: string\n ): Promise<string> {\n const tokenEndpoint = SupertabConnect.baseUrl + \"/rsl/token\";\n return obtainLicenseTokenHelper({\n clientId,\n clientSecret,\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\ntype ObtainLicenseTokenParams = {\n clientId: string;\n clientSecret: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\nasync function retrieveLicenseToken(\n tokenEndpoint: string,\n requestOptions: RequestInit,\n debug: boolean | undefined\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\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\n// Temporarily not exporting this function to reflect only client credentials flow being supported\nasync 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 return retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n}\n\nexport async function obtainLicenseToken({\n clientId,\n clientSecret,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: ObtainLicenseTokenParams): Promise<string> {\n const payload = new URLSearchParams({\n grant_type: \"client_credentials\",\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 Authorization: \"Basic \" + btoa(`${clientId}:${clientSecret}`),\n },\n body: payload.toString(),\n };\n\n return retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n}\n\nexport type { GenerateLicenseTokenParams, ObtainLicenseTokenParams };\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","retrieveLicenseToken","tokenEndpoint","requestOptions","debug","response","errorBody","errorMessage","data","parseError","error","obtainLicenseToken","clientId","clientSecret","tokenEndpoint","resourceUrl","licenseXml","debug","payload","requestOptions","retrieveLicenseToken","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","clientSecret","resourceUrl","licenseXml","tokenEndpoint","obtainLicenseToken","SupertabConnect"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -57,15 +57,13 @@ declare class SupertabConnect {
|
|
|
57
57
|
handleRequest(request: Request, botDetectionHandler?: (request: Request, ctx?: any) => boolean, ctx?: any): Promise<Response>;
|
|
58
58
|
/**
|
|
59
59
|
* Request a license token from the Supertab Connect token endpoint.
|
|
60
|
-
* @param clientId OAuth client identifier
|
|
61
|
-
* @param
|
|
62
|
-
* @param privateKeyPem Private key in PEM format used to sign the client assertion.
|
|
63
|
-
* @param tokenEndpoint Token endpoint URL.
|
|
60
|
+
* @param clientId OAuth client identifier.
|
|
61
|
+
* @param clientSecret OAuth client secret for client_credentials flow.
|
|
64
62
|
* @param resourceUrl Resource URL attempting to access with a License.
|
|
65
63
|
* @param licenseXml XML license document to include in the request payload.
|
|
66
64
|
* @returns Promise resolving to the issued license access token string.
|
|
67
65
|
*/
|
|
68
|
-
static
|
|
66
|
+
static obtainLicenseToken(clientId: string, clientSecret: string, resourceUrl: string, licenseXml: string): Promise<string>;
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
export { type Env, SupertabConnect };
|
package/dist/index.d.ts
CHANGED
|
@@ -57,15 +57,13 @@ declare class SupertabConnect {
|
|
|
57
57
|
handleRequest(request: Request, botDetectionHandler?: (request: Request, ctx?: any) => boolean, ctx?: any): Promise<Response>;
|
|
58
58
|
/**
|
|
59
59
|
* Request a license token from the Supertab Connect token endpoint.
|
|
60
|
-
* @param clientId OAuth client identifier
|
|
61
|
-
* @param
|
|
62
|
-
* @param privateKeyPem Private key in PEM format used to sign the client assertion.
|
|
63
|
-
* @param tokenEndpoint Token endpoint URL.
|
|
60
|
+
* @param clientId OAuth client identifier.
|
|
61
|
+
* @param clientSecret OAuth client secret for client_credentials flow.
|
|
64
62
|
* @param resourceUrl Resource URL attempting to access with a License.
|
|
65
63
|
* @param licenseXml XML license document to include in the request payload.
|
|
66
64
|
* @returns Promise resolving to the issued license access token string.
|
|
67
65
|
*/
|
|
68
|
-
static
|
|
66
|
+
static obtainLicenseToken(clientId: string, clientSecret: string, resourceUrl: string, licenseXml: string): Promise<string>;
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
export { type Env, SupertabConnect };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var f="stc-backend";import{importPKCS8 as K,SignJWT as O}from"jose";async function R(i,e,t){try{let n=await fetch(i,e);if(!n.ok){let s=await n.text().catch(()=>""),o=`Failed to obtain license token: ${n.status} ${n.statusText}${s?` - ${s}`:""}`;throw new Error(o)}let r;try{r=await n.json()}catch(s){throw t&&console.error("Failed to parse license token response as JSON:",s),new Error("Failed to parse license token response as JSON")}if(!r?.access_token)throw new Error("License token response missing access_token");return r.access_token}catch(n){throw t&&console.error("Error generating license token:",n),n}}async function b({clientId:i,clientSecret:e,tokenEndpoint:t,resourceUrl:n,licenseXml:r,debug:s}){let o=new URLSearchParams({grant_type:"client_credentials",license:r,resource:n}),p={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json",Authorization:"Basic "+btoa(`${i}:${e}`)},body:o.toString()};return R(t,p,s)}import{decodeProtectedHeader as P,decodeJwt as N,jwtVerify as C}from"jose";var k=new Map;function S(){let i={method:"GET"};return globalThis?.fastly&&(i={...i,backend:f}),i}async function U({cacheKey:i,url:e,debug:t,failureMessage:n,logLabel:r}){if(!k.has(i))try{let s=await fetch(e,S());if(!s.ok)throw new Error(`${n}: ${s.status}`);let o=await s.json();k.set(i,o)}catch(s){throw t&&console.error(r,s),s}return k.get(i)}async function _(i,e){let t=`${i}/.well-known/jwks.json/platform`;return console.log("Fetching platform JWKS from:",t),U({cacheKey:"platform_jwks",url:t,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}var A=i=>i.replace(/\/+$/,"");async function w({licenseToken:i,requestUrl:e,supertabBaseUrl:t,debug:n}){if(!i)return{valid:!1,reason:"missing_license_token"};let r;try{r=P(i)}catch(a){return n&&console.error("Invalid license JWT header:",a),{valid:!1,reason:"invalid_license_header"}}if(r.alg!=="ES256")return n&&console.error("Unsupported license JWT alg:",r.alg),{valid:!1,reason:"invalid_license_algorithm"};let s;try{s=N(i)}catch(a){return n&&console.error("Invalid license JWT payload:",a),{valid:!1,reason:"invalid_license_payload"}}let o=s.license_id,p=s.iss;if(!p||!p.startsWith(t))return n&&console.error("Invalid license JWT issuer:",p),{valid:!1,reason:"invalid_license_issuer",licenseId:o};let d=Array.isArray(s.aud)?s.aud.filter(a=>typeof a=="string"):typeof s.aud=="string"?[s.aud]:[],g=A(e);if(!d.some(a=>{let u=A(a);return u?g.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 _(t,n),h=await C(i,async y=>{let m=a.keys.find(I=>I.kid===y.kid);if(!m)throw new Error(`No matching platform key found: ${y.kid}`);return m},{issuer:p,algorithms:[r.alg],clockTolerance:"1m"});return{valid:!0,licenseId:o,payload:h.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 x({requestUrl:i}){let e=new URL(i);return`${e.protocol}//${e.host}/license.xml`}async function L({licenseToken:i,url:e,userAgent:t,ctx:n,supertabBaseUrl:r,merchantSystemUrn:s,debug:o,recordEvent:p}){let d=await w({licenseToken:i,requestUrl:e,supertabBaseUrl:r,debug:o});async function g(l){let a={page_url:e,user_agent:t,verification_status:d.valid?"valid":"invalid",verification_reason:d.reason||"success"},u=p(l,a,d.licenseId);return n?.waitUntil&&n.waitUntil(u),u}if(!d.valid){await g(d.reason||"license_token_verification_failed");let l="invalid_request",a="Access to this resource requires a license";switch(d.reason){case"missing_license_token":l="invalid_request",a="Access to this resource requires a license";break;case"license_token_expired":l="invalid_token",a="The license token has expired";break;case"license_signature_verification_failed":l="invalid_token",a="The license token signature is invalid";break;case"invalid_license_header":l="invalid_token",a="The license token header is invalid";break;case"invalid_license_payload":l="invalid_token",a="The license token payload is invalid";break;case"invalid_license_issuer":l="invalid_token",a="The license token issuer is invalid";break;case"invalid_license_audience":l="invalid_token",a="The license token audience is invalid";break;default:l="invalid_request",a="Access to this resource requires a license"}let u=x({requestUrl:e}),h=`${r}/docs/errors#${l}`,y=new Headers({"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${l}", error_description="${a}", error_uri="${h}"`,Link:`${u}; rel="license"; type="application/rsl+xml"`}),m=`Access to this resource requires a valid license token. Error: ${l} - ${a}`;return new Response(m,{status:401,headers:y})}return await g("license_used"),new Response("\u2705 License Token Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}function D(){let i={method:"GET"};return globalThis?.fastly&&(i={...i,backend:f}),i}async function v(i,e){let t=`${i}/merchants/systems/${e}/license.xml`,n=await fetch(t,D());if(!n.ok)return new Response("License not found",{status:404});let r=await n.text();return new Response(r,{status:200,headers:new Headers({"Content-Type":"application/xml"})})}var E=!0,c=class c{constructor(e,t=!1){if(!t&&c._instance){if(!(e.apiKey===c._instance.apiKey&&e.merchantSystemUrn===c._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return c._instance}if(t&&c._instance&&c.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,c._instance=this}static resetInstance(){c._instance=null}static setBaseUrl(e){c.baseUrl=e}async verifyLicenseToken(e,t){return w({licenseToken:e,requestUrl:t,supertabBaseUrl:c.baseUrl,debug:E})}async recordEvent(e,t={},n){let r={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(r)};globalThis?.fastly&&(s={...s,backend:f});let o=await fetch(`${c.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")||"",r=e.headers.get("sec-ch-ua"),s=e.headers.get("accept-language"),o=e.cf?.botManagement?.score,p=["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(),g=p.some(y=>d.includes(y)),l=t.toLowerCase().includes("headless")||t.toLowerCase().includes("puppeteer")||!r,a=!t.toLowerCase().includes("headless")||!t.toLowerCase().includes("puppeteer")||!r,u=!n||!s,h=typeof o=="number"&&o<30;return console.log("Bot Detection Details:",{botUaMatch:g,headlessIndicators:l,missingHeaders:u,lowBotScore:h,botScore:o}),(d.includes("safari")||d.includes("mozilla"))&&l&&a?!1:g||l||u||h}static async cloudflareHandleRequests(e,t,n){let{MERCHANT_SYSTEM_URN:r,MERCHANT_API_KEY:s}=t;return new c({apiKey:s,merchantSystemUrn:r}).handleRequest(e,c.checkIfBotRequest,n)}static async fastlyHandleRequests(e,t,n,r=!1){let s=new c({apiKey:n,merchantSystemUrn:t});return r&&new URL(e.url).pathname==="/license.xml"?await v(c.baseUrl,t):s.handleRequest(e,c.checkIfBotRequest,null)}async handleRequest(e,t,n){let r=e.headers.get("Authorization")||"",s=r.startsWith("License ")?r.slice(8):"",o=e.url,p=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"})}):L({licenseToken:s,url:o,userAgent:p,ctx:n,supertabBaseUrl:c.baseUrl,merchantSystemUrn:this.merchantSystemUrn,debug:E,recordEvent:(d,g,l)=>this.recordEvent(d,g,l)})}static async obtainLicenseToken(e,t,n,r){let s=c.baseUrl+"/rsl/token";return b({clientId:e,clientSecret:t,tokenEndpoint:s,resourceUrl:n,licenseXml:r,debug:E})}};c.baseUrl="https://api-connect.supertab.co",c._instance=null;var T=c;export{T as SupertabConnect};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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\ntype ObtainLicenseTokenParams = {\n clientId: string;\n clientSecret: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\nasync function retrieveLicenseToken(\n tokenEndpoint: string,\n requestOptions: RequestInit,\n debug: boolean | undefined\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\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\n// Temporarily not exporting this function to reflect only client credentials flow being supported\nasync 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 return retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n}\n\nexport async function obtainLicenseToken({\n clientId,\n clientSecret,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: ObtainLicenseTokenParams): Promise<string> {\n const payload = new URLSearchParams({\n grant_type: \"client_credentials\",\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 Authorization: \"Basic \" + btoa(`${clientId}:${clientSecret}`),\n },\n body: payload.toString(),\n };\n\n return retrieveLicenseToken(tokenEndpoint, requestOptions, debug);\n}\n\nexport type { GenerateLicenseTokenParams, ObtainLicenseTokenParams };\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 { obtainLicenseToken as obtainLicenseTokenHelper } 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.\n * @param clientSecret OAuth client secret for client_credentials flow.\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 obtainLicenseToken(\n clientId: string,\n clientSecret: string,\n resourceUrl: string,\n licenseXml: string\n ): Promise<string> {\n const tokenEndpoint = SupertabConnect.baseUrl + \"/rsl/token\";\n return obtainLicenseTokenHelper({\n clientId,\n clientSecret,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n });\n }\n}\n"],"mappings":"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","names":["FASTLY_BACKEND","importPKCS8","SignJWT","retrieveLicenseToken","tokenEndpoint","requestOptions","debug","response","errorBody","errorMessage","data","parseError","error","obtainLicenseToken","clientId","clientSecret","tokenEndpoint","resourceUrl","licenseXml","debug","payload","requestOptions","retrieveLicenseToken","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","clientSecret","resourceUrl","licenseXml","tokenEndpoint","obtainLicenseToken","SupertabConnect"]}
|