@getsupertab/supertab-connect-sdk 0.1.0-beta.18 → 0.1.0-beta.19

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.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var g=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var b=(p,e)=>{for(var t in e)g(p,t,{get:e[t],enumerable:!0})},S=(p,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of E(e))!I.call(p,r)&&r!==t&&g(p,r,{get:()=>e[r],enumerable:!(s=R(e,r))||s.enumerable});return p};var A=p=>S(g({},"__esModule",{value:!0}),p);var C={};b(C,{SupertabConnect:()=>f});module.exports=A(C);var u=require("jose"),m=new Map,h=!0,a=class a{constructor(e,t=!1){if(!t&&a._instance){if(!(e.apiKey===a._instance.apiKey&&e.merchantSystemUrn===a._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return a._instance}if(t&&a._instance&&a.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,this.baseUrl="https://api-connect.sbx.supertab.co",a._instance=this}static resetInstance(){a._instance=null}async getJwksForIssuer(e){if(!m.has(e)){let t=`${this.baseUrl}/.well-known/jwks.json/${encodeURIComponent(e)}`;try{let s=await fetch(t);if(!s.ok)throw new Error(`Failed to fetch JWKS: ${s.status}`);let r=await s.json();m.set(e,r)}catch(s){throw h&&console.error("Error fetching JWKS:",s),s}}return m.get(e)}async verifyToken(e){if(!e)return{valid:!1,reason:"missing_token"};let t;try{t=(0,u.decodeProtectedHeader)(e)}catch(n){return h&&console.error("Invalid JWT header:",n),{valid:!1,reason:"invalid_header"}}if(t.alg!=="RS256")return{valid:!1,reason:"invalid_algorithm"};let s;try{s=(0,u.decodeJwt)(e)}catch(n){return h&&console.error("Invalid JWT payload:",n),{valid:!1,reason:"invalid_payload"}}let r=s.iss;if(!r||!r.startsWith("urn:stc:customer:"))return{valid:!1,reason:"invalid_issuer"};try{let n=await this.getJwksForIssuer(r);return{valid:!0,payload:(await(0,u.jwtVerify)(e,async i=>{let d=n.keys.find(l=>l.kid===i.kid);if(!d)throw new Error(`No matching key found: ${i.kid}`);return d},{issuer:r,algorithms:["RS256"],clockTolerance:"1m"})).payload}}catch(n){return h&&console.error("JWT verification failed:",n),n.message?.includes("exp")?{valid:!1,reason:"token_expired"}:{valid:!1,reason:"signature_verification_failed"}}}async recordEvent(e,t,s={}){let r={event_name:e,customer_system_token:t,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",properties:s};try{let n=await fetch(`${this.baseUrl}/events`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(r)});n.ok||console.log(`Failed to record event: ${n.status}`)}catch(n){console.log("Error recording event:",n)}}async baseHandleRequest(e,t,s,r){let n=await this.verifyToken(e);async function o(c,i,d){let l={page_url:t,user_agent:s,verification_status:n.valid?"valid":"invalid",verification_reason:n.reason||"success"};if(d){let y=c.recordEvent(i,e,l);return d.waitUntil(y),y}else return await c.recordEvent(i,e,l)}if(!n.valid){await o(this,n.reason||"token_verification_failed",r);let c="Payment required: you need to present a valid Supertab Connect token to access this content. Check out the provided url for details",i="\u274C Content access denied"+(n.reason?`: ${n.reason}`:""),l={url:`${this.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`,message:c,details:i};return new Response(JSON.stringify(l),{status:402,headers:new Headers({"Content-Type":"application/json"})})}return await o(this,"page_viewed",r),new Response("\u2705 Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}extractDataFromRequest(e){let t=e.headers.get("Authorization")||"",s=t.startsWith("Bearer ")?t.slice(7):"",r=e.url,n=e.headers.get("User-Agent")||"unknown";return{token:s,url:r,user_agent:n}}static checkIfBotRequest(e){let t=e.headers.get("User-Agent")||"",s=e.headers.get("accept")||"",r=e.headers.get("sec-ch-ua"),n=e.headers.get("accept-language"),o=e.cf?.botManagement?.score,c=["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"],i=t.toLowerCase(),d=c.some(_=>i.includes(_)),l=t.toLowerCase().includes("headless")||t.toLowerCase().includes("puppeteer")||!r,y=!t.toLowerCase().includes("headless")||!t.toLowerCase().includes("puppeteer")||!r,w=!s||!n,v=typeof o=="number"&&o<30;return console.log("Bot Detection Details:",{botUaMatch:d,headlessIndicators:l,missingHeaders:w,lowBotScore:v,botScore:o}),(i.includes("safari")||i.includes("mozilla"))&&l&&y?!1:d||l||w||v}static async cloudflareHandleRequests(e,t,s){let{MERCHANT_SYSTEM_URN:r,MERCHANT_API_KEY:n}=t;return new a({apiKey:n,merchantSystemUrn:r}).handleRequest(e,a.checkIfBotRequest,s)}static async fastlyHandleRequests(e,t,s){return new a({apiKey:s,merchantSystemUrn:t}).handleRequest(e,a.checkIfBotRequest,null)}async handleRequest(e,t,s){let{token:r,url:n,user_agent:o}=this.extractDataFromRequest(e);return t&&!t(e,s)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):this.baseHandleRequest(r,n,o,s)}static async generateCustomerJWT(e,t,s,r=3600){let n="RS256",o=await(0,u.importPKCS8)(s,n),c=Math.floor(Date.now()/1e3);return new u.SignJWT({}).setProtectedHeader({alg:n,kid:t}).setIssuer(e).setIssuedAt(c).setExpirationTime(c+r).sign(o)}};a._instance=null;var f=a;0&&(module.exports={SupertabConnect});
1
+ "use strict";var g=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var I=(p,e)=>{for(var t in e)g(p,t,{get:e[t],enumerable:!0})},S=(p,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of E(e))!R.call(p,s)&&s!==t&&g(p,s,{get:()=>e[s],enumerable:!(r=_(e,s))||r.enumerable});return p};var A=p=>S(g({},"__esModule",{value:!0}),p);var C={};I(C,{SupertabConnect:()=>f});module.exports=A(C);var d=require("jose"),m=new Map,h=!0,o=class o{constructor(e,t=!1){if(!t&&o._instance){if(!(e.apiKey===o._instance.apiKey&&e.merchantSystemUrn===o._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return o._instance}if(t&&o._instance&&o.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,this.baseUrl="https://api-connect.sbx.supertab.co",o._instance=this}static resetInstance(){o._instance=null}async getJwksForIssuer(e){if(!m.has(e)){let t=`${this.baseUrl}/.well-known/jwks.json/${encodeURIComponent(e)}`;try{let r={method:"GET"};globalThis?.fastly&&(r={...r,backend:"sbx-backend"});let s=await fetch(t,r);if(!s.ok)throw new Error(`Failed to fetch JWKS: ${s.status}`);let n=await s.json();m.set(e,n)}catch(r){throw h&&console.error("Error fetching JWKS:",r),r}}return m.get(e)}async verifyToken(e){if(!e)return{valid:!1,reason:"missing_token"};let t;try{t=(0,d.decodeProtectedHeader)(e)}catch(n){return h&&console.error("Invalid JWT header:",n),{valid:!1,reason:"invalid_header"}}if(t.alg!=="RS256")return{valid:!1,reason:"invalid_algorithm"};let r;try{r=(0,d.decodeJwt)(e)}catch(n){return h&&console.error("Invalid JWT payload:",n),{valid:!1,reason:"invalid_payload"}}let s=r.iss;if(!s||!s.startsWith("urn:stc:customer:"))return{valid:!1,reason:"invalid_issuer"};try{let n=await this.getJwksForIssuer(s);return{valid:!0,payload:(await(0,d.jwtVerify)(e,async i=>{let u=n.keys.find(l=>l.kid===i.kid);if(!u)throw new Error(`No matching key found: ${i.kid}`);return u},{issuer:s,algorithms:["RS256"],clockTolerance:"1m"})).payload}}catch(n){return h&&console.error("JWT verification failed:",n),n.message?.includes("exp")?{valid:!1,reason:"token_expired"}:{valid:!1,reason:"signature_verification_failed"}}}async recordEvent(e,t,r={}){let s={event_name:e,customer_system_token:t,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",properties:r};try{let n={method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(s)};globalThis?.fastly&&(n={...n,backend:"sbx-backend"});let a=await fetch(`${this.baseUrl}/events`,n);a.ok||console.log(`Failed to record event: ${a.status}`)}catch(n){console.log("Error recording event:",n)}}async baseHandleRequest(e,t,r,s){let n=await this.verifyToken(e);async function a(c,i,u){let l={page_url:t,user_agent:r,verification_status:n.valid?"valid":"invalid",verification_reason:n.reason||"success"};if(u){let y=c.recordEvent(i,e,l);return u.waitUntil(y),y}else return await c.recordEvent(i,e,l)}if(!n.valid){await a(this,n.reason||"token_verification_failed",s);let c="Payment required: you need to present a valid Supertab Connect token to access this content. Check out the provided url for details",i="\u274C Content access denied"+(n.reason?`: ${n.reason}`:""),l={url:`${this.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`,message:c,details:i};return new Response(JSON.stringify(l),{status:402,headers:new Headers({"Content-Type":"application/json"})})}return await a(this,"page_viewed",s),new Response("\u2705 Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}extractDataFromRequest(e){let t=e.headers.get("Authorization")||"",r=t.startsWith("Bearer ")?t.slice(7):"",s=e.url,n=e.headers.get("User-Agent")||"unknown";return{token:r,url:s,user_agent:n}}static checkIfBotRequest(e){let t=e.headers.get("User-Agent")||"",r=e.headers.get("accept")||"",s=e.headers.get("sec-ch-ua"),n=e.headers.get("accept-language"),a=e.cf?.botManagement?.score,c=["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"],i=t.toLowerCase(),u=c.some(v=>i.includes(v)),l=t.toLowerCase().includes("headless")||t.toLowerCase().includes("puppeteer")||!s,y=!t.toLowerCase().includes("headless")||!t.toLowerCase().includes("puppeteer")||!s,w=!r||!n,b=typeof a=="number"&&a<30;return console.log("Bot Detection Details:",{botUaMatch:u,headlessIndicators:l,missingHeaders:w,lowBotScore:b,botScore:a}),(i.includes("safari")||i.includes("mozilla"))&&l&&y?!1:u||l||w||b}static async cloudflareHandleRequests(e,t,r){let{MERCHANT_SYSTEM_URN:s,MERCHANT_API_KEY:n}=t;return new o({apiKey:n,merchantSystemUrn:s}).handleRequest(e,o.checkIfBotRequest,r)}static async fastlyHandleRequests(e,t,r){return new o({apiKey:r,merchantSystemUrn:t}).handleRequest(e,o.checkIfBotRequest,null)}async handleRequest(e,t,r){let{token:s,url:n,user_agent:a}=this.extractDataFromRequest(e);return t&&!t(e,r)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):this.baseHandleRequest(s,n,a,r)}static async generateCustomerJWT(e,t,r,s=3600){let n="RS256",a=await(0,d.importPKCS8)(r,n),c=Math.floor(Date.now()/1e3);return new d.SignJWT({}).setProtectedHeader({alg:n,kid:t}).setIssuer(e).setIssuedAt(c).setExpirationTime(c+s).sign(a)}};o._instance=null;var f=o;0&&(module.exports={SupertabConnect});
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n SupertabConnectConfig,\n Env,\n EventPayload,\n TokenVerificationResult,\n TokenInvalidReason,\n} from \"./types\";\nimport {\n jwtVerify,\n decodeProtectedHeader,\n decodeJwt,\n JWTHeaderParameters,\n JWTPayload,\n importPKCS8,\n SignJWT,\n} from \"jose\";\n\nexport type { Env } from \"./types\";\n\n// In-memory cache for JWK sets\nconst jwksCache = new Map<string, any>();\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 baseUrl?: string;\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 this.baseUrl = \"https://api-connect.sbx.supertab.co\";\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 * Get the JWKS for a given issuer, using cache if available\n * @private\n */\n private async getJwksForIssuer(issuer: string): Promise<any> {\n if (!jwksCache.has(issuer)) {\n const jwksUrl = `${\n this.baseUrl\n }/.well-known/jwks.json/${encodeURIComponent(issuer)}`;\n\n try {\n const jwksResponse = await fetch(jwksUrl);\n if (!jwksResponse.ok) {\n throw new Error(`Failed to fetch JWKS: ${jwksResponse.status}`);\n }\n\n const jwksData = await jwksResponse.json();\n jwksCache.set(issuer, jwksData);\n } catch (error) {\n if (debug) {\n console.error(\"Error fetching JWKS:\", error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(issuer);\n }\n\n /**\n * Verify a JWT token\n * @param token The JWT token to verify\n * @returns A promise that resolves with the verification result\n */\n async verifyToken(token: string): Promise<TokenVerificationResult> {\n // 1. Check if token exists\n if (!token) {\n return {\n valid: false,\n reason: TokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n // 2. Verify header and algorithm\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(token) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT header:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"RS256\") {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ALG,\n };\n }\n\n // 3. Verify payload and issuer\n let payload: JWTPayload;\n try {\n payload = decodeJwt(token);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT payload:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(\"urn:stc:customer:\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ISSUER,\n };\n }\n\n // 4. Verify signature\n try {\n const jwks = await this.getJwksForIssuer(issuer);\n\n // Create a key finder function for verification\n const getKey = async (header: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === header.kid);\n if (!jwk) throw new Error(`No matching key found: ${header.kid}`);\n return jwk;\n };\n\n const result = await jwtVerify(token, getKey, {\n issuer,\n algorithms: [\"RS256\"],\n clockTolerance: \"1m\",\n });\n\n // Success case - token is valid\n return {\n valid: true,\n payload: result.payload,\n };\n } catch (error: any) {\n if (debug) {\n console.error(\"JWT verification failed:\", error);\n }\n\n // Check if token is expired\n if (error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.EXPIRED,\n };\n }\n\n return {\n valid: false,\n reason: TokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n };\n }\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param customerToken Optional customer token for the event\n * @param properties Additional properties to include with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n customerToken?: string,\n properties: Record<string, any> = {}\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n customer_system_token: customerToken,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\n properties,\n };\n\n try {\n const response = await fetch(`${this.baseUrl}/events`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n /**\n * Handle the request, report an event to Supertab Connect and return a response\n */\n private async baseHandleRequest(\n token: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n // 1. Verify token\n const verification = await this.verifyToken(token);\n\n // Record event helper\n async function recordEvent(\n stc: SupertabConnect,\n eventName: string,\n ctx: any\n ) {\n const eventProperties = {\n page_url: url,\n user_agent: user_agent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n if (ctx) {\n const eventPromise = stc.recordEvent(eventName, token, eventProperties);\n ctx.waitUntil(eventPromise);\n return eventPromise;\n } else {\n return await stc.recordEvent(eventName, token, eventProperties);\n }\n }\n\n // 2. Handle based on verification result\n if (!verification.valid) {\n await recordEvent(\n this,\n verification.reason || \"token_verification_failed\",\n ctx\n );\n const message =\n \"Payment required: you need to present a valid Supertab Connect token to access this content. \" +\n \"Check out the provided url for details\";\n const details =\n \"❌ Content access denied\" +\n (verification.reason ? `: ${verification.reason}` : \"\");\n const contentAccessUrl = `${this.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`;\n\n const responseBody = {\n url: contentAccessUrl,\n message: message,\n details: details,\n };\n\n return new Response(JSON.stringify(responseBody), {\n status: 402,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Success\n await recordEvent(this, \"page_viewed\", ctx);\n return new Response(\"✅ Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n private extractDataFromRequest(request: Request): {\n token: string;\n url: string;\n user_agent: string;\n } {\n // Parse token\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"Bearer \") ? auth.slice(7) : \"\";\n\n // Extract URL and user agent\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n return { token, url, user_agent };\n }\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string\n ): Promise<Response> {\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: merchantApiKey,\n merchantSystemUrn: merchantSystemUrn,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n null\n );\n }\n\n async handleRequest(\n request: Request,\n botDetectionHandler?: (request: Request, ctx?: any) => boolean,\n ctx?: any\n ): Promise<Response> {\n // 1. Extract token, URL, and user agent from the request\n const { token, url, user_agent } = this.extractDataFromRequest(request);\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Call the base handle request method and return the result\n return this.baseHandleRequest(token, url, user_agent, ctx);\n }\n\n /** Generate a customer JWT\n * @param customerURN The customer's unique resource name (URN).\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem The private key in PEM format used to sign the JWT.\n * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).\n * @returns A promise that resolves to the generated JWT as a string.\n */\n static async generateCustomerJWT(\n customerURN: string,\n kid: string,\n privateKeyPem: string,\n expirationSeconds: number = 3600\n ): Promise<string> {\n const alg = \"RS256\";\n const key = await importPKCS8(privateKeyPem, alg);\n\n const now = Math.floor(Date.now() / 1000);\n\n return new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(customerURN)\n .setIssuedAt(now)\n .setExpirationTime(now + expirationSeconds)\n .sign(key);\n }\n}\n"],"mappings":"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","names":["index_exports","__export","SupertabConnect","__toCommonJS","import_jose","jwksCache","debug","_SupertabConnect","config","reset","issuer","jwksUrl","jwksResponse","jwksData","error","token","header","payload","jwks","jwk","key","eventName","customerToken","properties","response","url","user_agent","ctx","verification","recordEvent","stc","eventProperties","eventPromise","message","details","responseBody","request","auth","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","botDetectionHandler","customerURN","kid","privateKeyPem","expirationSeconds","alg","now","SupertabConnect"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n SupertabConnectConfig,\n Env,\n EventPayload,\n TokenVerificationResult,\n TokenInvalidReason,\n} from \"./types\";\nimport {\n jwtVerify,\n decodeProtectedHeader,\n decodeJwt,\n JWTHeaderParameters,\n JWTPayload,\n importPKCS8,\n SignJWT,\n} from \"jose\";\n\nexport type { Env } from \"./types\";\n\n// In-memory cache for JWK sets\nconst jwksCache = new Map<string, any>();\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 baseUrl?: string;\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 this.baseUrl = \"https://api-connect.sbx.supertab.co\";\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 * Get the JWKS for a given issuer, using cache if available\n * @private\n */\n private async getJwksForIssuer(issuer: string): Promise<any> {\n if (!jwksCache.has(issuer)) {\n const jwksUrl = `${\n this.baseUrl\n }/.well-known/jwks.json/${encodeURIComponent(issuer)}`;\n\n try {\n let options: any = { method: \"GET\" };\n // @ts-ignore\n if (globalThis?.fastly) {\n options = { ...options, backend: \"sbx-backend\" };\n }\n const jwksResponse = await fetch(jwksUrl, options);\n\n if (!jwksResponse.ok) {\n throw new Error(`Failed to fetch JWKS: ${jwksResponse.status}`);\n }\n\n const jwksData = await jwksResponse.json();\n jwksCache.set(issuer, jwksData);\n } catch (error) {\n if (debug) {\n console.error(\"Error fetching JWKS:\", error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(issuer);\n }\n\n /**\n * Verify a JWT token\n * @param token The JWT token to verify\n * @returns A promise that resolves with the verification result\n */\n async verifyToken(token: string): Promise<TokenVerificationResult> {\n // 1. Check if token exists\n if (!token) {\n return {\n valid: false,\n reason: TokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n // 2. Verify header and algorithm\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(token) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT header:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"RS256\") {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ALG,\n };\n }\n\n // 3. Verify payload and issuer\n let payload: JWTPayload;\n try {\n payload = decodeJwt(token);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT payload:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(\"urn:stc:customer:\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ISSUER,\n };\n }\n\n // 4. Verify signature\n try {\n const jwks = await this.getJwksForIssuer(issuer);\n\n // Create a key finder function for verification\n const getKey = async (header: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === header.kid);\n if (!jwk) throw new Error(`No matching key found: ${header.kid}`);\n return jwk;\n };\n\n const result = await jwtVerify(token, getKey, {\n issuer,\n algorithms: [\"RS256\"],\n clockTolerance: \"1m\",\n });\n\n // Success case - token is valid\n return {\n valid: true,\n payload: result.payload,\n };\n } catch (error: any) {\n if (debug) {\n console.error(\"JWT verification failed:\", error);\n }\n\n // Check if token is expired\n if (error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.EXPIRED,\n };\n }\n\n return {\n valid: false,\n reason: TokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n };\n }\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param customerToken Optional customer token for the event\n * @param properties Additional properties to include with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n customerToken?: string,\n properties: Record<string, any> = {}\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n customer_system_token: customerToken,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\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: \"sbx-backend\" };\n }\n const response = await fetch(`${this.baseUrl}/events`, options);\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n /**\n * Handle the request, report an event to Supertab Connect and return a response\n */\n private async baseHandleRequest(\n token: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n // 1. Verify token\n const verification = await this.verifyToken(token);\n\n // Record event helper\n async function recordEvent(\n stc: SupertabConnect,\n eventName: string,\n ctx: any\n ) {\n const eventProperties = {\n page_url: url,\n user_agent: user_agent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n if (ctx) {\n const eventPromise = stc.recordEvent(eventName, token, eventProperties);\n ctx.waitUntil(eventPromise);\n return eventPromise;\n } else {\n return await stc.recordEvent(eventName, token, eventProperties);\n }\n }\n\n // 2. Handle based on verification result\n if (!verification.valid) {\n await recordEvent(\n this,\n verification.reason || \"token_verification_failed\",\n ctx\n );\n const message =\n \"Payment required: you need to present a valid Supertab Connect token to access this content. \" +\n \"Check out the provided url for details\";\n const details =\n \"❌ Content access denied\" +\n (verification.reason ? `: ${verification.reason}` : \"\");\n const contentAccessUrl = `${this.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`;\n\n const responseBody = {\n url: contentAccessUrl,\n message: message,\n details: details,\n };\n\n return new Response(JSON.stringify(responseBody), {\n status: 402,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Success\n await recordEvent(this, \"page_viewed\", ctx);\n return new Response(\"✅ Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n private extractDataFromRequest(request: Request): {\n token: string;\n url: string;\n user_agent: string;\n } {\n // Parse token\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"Bearer \") ? auth.slice(7) : \"\";\n\n // Extract URL and user agent\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n return { token, url, user_agent };\n }\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string\n ): Promise<Response> {\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: merchantApiKey,\n merchantSystemUrn: merchantSystemUrn,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n null\n );\n }\n\n async handleRequest(\n request: Request,\n botDetectionHandler?: (request: Request, ctx?: any) => boolean,\n ctx?: any\n ): Promise<Response> {\n // 1. Extract token, URL, and user agent from the request\n const { token, url, user_agent } = this.extractDataFromRequest(request);\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Call the base handle request method and return the result\n return this.baseHandleRequest(token, url, user_agent, ctx);\n }\n\n /** Generate a customer JWT\n * @param customerURN The customer's unique resource name (URN).\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem The private key in PEM format used to sign the JWT.\n * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).\n * @returns A promise that resolves to the generated JWT as a string.\n */\n static async generateCustomerJWT(\n customerURN: string,\n kid: string,\n privateKeyPem: string,\n expirationSeconds: number = 3600\n ): Promise<string> {\n const alg = \"RS256\";\n const key = await importPKCS8(privateKeyPem, alg);\n\n const now = Math.floor(Date.now() / 1000);\n\n return new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(customerURN)\n .setIssuedAt(now)\n .setExpirationTime(now + expirationSeconds)\n .sign(key);\n }\n}\n"],"mappings":"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","names":["index_exports","__export","SupertabConnect","__toCommonJS","import_jose","jwksCache","debug","_SupertabConnect","config","reset","issuer","jwksUrl","options","jwksResponse","jwksData","error","token","header","payload","jwks","jwk","key","eventName","customerToken","properties","response","url","user_agent","ctx","verification","recordEvent","stc","eventProperties","eventPromise","message","details","responseBody","request","auth","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","botDetectionHandler","customerURN","kid","privateKeyPem","expirationSeconds","alg","now","SupertabConnect"]}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{jwtVerify as w,decodeProtectedHeader as v,decodeJwt as _,importPKCS8 as R,SignJWT as E}from"jose";var y=new Map,p=!0,a=class a{constructor(e,n=!1){if(!n&&a._instance){if(!(e.apiKey===a._instance.apiKey&&e.merchantSystemUrn===a._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return a._instance}if(n&&a._instance&&a.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,this.baseUrl="https://api-connect.sbx.supertab.co",a._instance=this}static resetInstance(){a._instance=null}async getJwksForIssuer(e){if(!y.has(e)){let n=`${this.baseUrl}/.well-known/jwks.json/${encodeURIComponent(e)}`;try{let s=await fetch(n);if(!s.ok)throw new Error(`Failed to fetch JWKS: ${s.status}`);let r=await s.json();y.set(e,r)}catch(s){throw p&&console.error("Error fetching JWKS:",s),s}}return y.get(e)}async verifyToken(e){if(!e)return{valid:!1,reason:"missing_token"};let n;try{n=v(e)}catch(t){return p&&console.error("Invalid JWT header:",t),{valid:!1,reason:"invalid_header"}}if(n.alg!=="RS256")return{valid:!1,reason:"invalid_algorithm"};let s;try{s=_(e)}catch(t){return p&&console.error("Invalid JWT payload:",t),{valid:!1,reason:"invalid_payload"}}let r=s.iss;if(!r||!r.startsWith("urn:stc:customer:"))return{valid:!1,reason:"invalid_issuer"};try{let t=await this.getJwksForIssuer(r);return{valid:!0,payload:(await w(e,async i=>{let u=t.keys.find(l=>l.kid===i.kid);if(!u)throw new Error(`No matching key found: ${i.kid}`);return u},{issuer:r,algorithms:["RS256"],clockTolerance:"1m"})).payload}}catch(t){return p&&console.error("JWT verification failed:",t),t.message?.includes("exp")?{valid:!1,reason:"token_expired"}:{valid:!1,reason:"signature_verification_failed"}}}async recordEvent(e,n,s={}){let r={event_name:e,customer_system_token:n,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",properties:s};try{let t=await fetch(`${this.baseUrl}/events`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(r)});t.ok||console.log(`Failed to record event: ${t.status}`)}catch(t){console.log("Error recording event:",t)}}async baseHandleRequest(e,n,s,r){let t=await this.verifyToken(e);async function o(c,i,u){let l={page_url:n,user_agent:s,verification_status:t.valid?"valid":"invalid",verification_reason:t.reason||"success"};if(u){let d=c.recordEvent(i,e,l);return u.waitUntil(d),d}else return await c.recordEvent(i,e,l)}if(!t.valid){await o(this,t.reason||"token_verification_failed",r);let c="Payment required: you need to present a valid Supertab Connect token to access this content. Check out the provided url for details",i="\u274C Content access denied"+(t.reason?`: ${t.reason}`:""),l={url:`${this.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`,message:c,details:i};return new Response(JSON.stringify(l),{status:402,headers:new Headers({"Content-Type":"application/json"})})}return await o(this,"page_viewed",r),new Response("\u2705 Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}extractDataFromRequest(e){let n=e.headers.get("Authorization")||"",s=n.startsWith("Bearer ")?n.slice(7):"",r=e.url,t=e.headers.get("User-Agent")||"unknown";return{token:s,url:r,user_agent:t}}static checkIfBotRequest(e){let n=e.headers.get("User-Agent")||"",s=e.headers.get("accept")||"",r=e.headers.get("sec-ch-ua"),t=e.headers.get("accept-language"),o=e.cf?.botManagement?.score,c=["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"],i=n.toLowerCase(),u=c.some(f=>i.includes(f)),l=n.toLowerCase().includes("headless")||n.toLowerCase().includes("puppeteer")||!r,d=!n.toLowerCase().includes("headless")||!n.toLowerCase().includes("puppeteer")||!r,h=!s||!t,g=typeof o=="number"&&o<30;return console.log("Bot Detection Details:",{botUaMatch:u,headlessIndicators:l,missingHeaders:h,lowBotScore:g,botScore:o}),(i.includes("safari")||i.includes("mozilla"))&&l&&d?!1:u||l||h||g}static async cloudflareHandleRequests(e,n,s){let{MERCHANT_SYSTEM_URN:r,MERCHANT_API_KEY:t}=n;return new a({apiKey:t,merchantSystemUrn:r}).handleRequest(e,a.checkIfBotRequest,s)}static async fastlyHandleRequests(e,n,s){return new a({apiKey:s,merchantSystemUrn:n}).handleRequest(e,a.checkIfBotRequest,null)}async handleRequest(e,n,s){let{token:r,url:t,user_agent:o}=this.extractDataFromRequest(e);return n&&!n(e,s)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):this.baseHandleRequest(r,t,o,s)}static async generateCustomerJWT(e,n,s,r=3600){let t="RS256",o=await R(s,t),c=Math.floor(Date.now()/1e3);return new E({}).setProtectedHeader({alg:t,kid:n}).setIssuer(e).setIssuedAt(c).setExpirationTime(c+r).sign(o)}};a._instance=null;var m=a;export{m as SupertabConnect};
1
+ import{jwtVerify as w,decodeProtectedHeader as b,decodeJwt as v,importPKCS8 as _,SignJWT as E}from"jose";var y=new Map,p=!0,o=class o{constructor(e,n=!1){if(!n&&o._instance){if(!(e.apiKey===o._instance.apiKey&&e.merchantSystemUrn===o._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return o._instance}if(n&&o._instance&&o.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,this.baseUrl="https://api-connect.sbx.supertab.co",o._instance=this}static resetInstance(){o._instance=null}async getJwksForIssuer(e){if(!y.has(e)){let n=`${this.baseUrl}/.well-known/jwks.json/${encodeURIComponent(e)}`;try{let s={method:"GET"};globalThis?.fastly&&(s={...s,backend:"sbx-backend"});let r=await fetch(n,s);if(!r.ok)throw new Error(`Failed to fetch JWKS: ${r.status}`);let t=await r.json();y.set(e,t)}catch(s){throw p&&console.error("Error fetching JWKS:",s),s}}return y.get(e)}async verifyToken(e){if(!e)return{valid:!1,reason:"missing_token"};let n;try{n=b(e)}catch(t){return p&&console.error("Invalid JWT header:",t),{valid:!1,reason:"invalid_header"}}if(n.alg!=="RS256")return{valid:!1,reason:"invalid_algorithm"};let s;try{s=v(e)}catch(t){return p&&console.error("Invalid JWT payload:",t),{valid:!1,reason:"invalid_payload"}}let r=s.iss;if(!r||!r.startsWith("urn:stc:customer:"))return{valid:!1,reason:"invalid_issuer"};try{let t=await this.getJwksForIssuer(r);return{valid:!0,payload:(await w(e,async i=>{let d=t.keys.find(l=>l.kid===i.kid);if(!d)throw new Error(`No matching key found: ${i.kid}`);return d},{issuer:r,algorithms:["RS256"],clockTolerance:"1m"})).payload}}catch(t){return p&&console.error("JWT verification failed:",t),t.message?.includes("exp")?{valid:!1,reason:"token_expired"}:{valid:!1,reason:"signature_verification_failed"}}}async recordEvent(e,n,s={}){let r={event_name:e,customer_system_token:n,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",properties:s};try{let t={method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(r)};globalThis?.fastly&&(t={...t,backend:"sbx-backend"});let a=await fetch(`${this.baseUrl}/events`,t);a.ok||console.log(`Failed to record event: ${a.status}`)}catch(t){console.log("Error recording event:",t)}}async baseHandleRequest(e,n,s,r){let t=await this.verifyToken(e);async function a(c,i,d){let l={page_url:n,user_agent:s,verification_status:t.valid?"valid":"invalid",verification_reason:t.reason||"success"};if(d){let u=c.recordEvent(i,e,l);return d.waitUntil(u),u}else return await c.recordEvent(i,e,l)}if(!t.valid){await a(this,t.reason||"token_verification_failed",r);let c="Payment required: you need to present a valid Supertab Connect token to access this content. Check out the provided url for details",i="\u274C Content access denied"+(t.reason?`: ${t.reason}`:""),l={url:`${this.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`,message:c,details:i};return new Response(JSON.stringify(l),{status:402,headers:new Headers({"Content-Type":"application/json"})})}return await a(this,"page_viewed",r),new Response("\u2705 Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}extractDataFromRequest(e){let n=e.headers.get("Authorization")||"",s=n.startsWith("Bearer ")?n.slice(7):"",r=e.url,t=e.headers.get("User-Agent")||"unknown";return{token:s,url:r,user_agent:t}}static checkIfBotRequest(e){let n=e.headers.get("User-Agent")||"",s=e.headers.get("accept")||"",r=e.headers.get("sec-ch-ua"),t=e.headers.get("accept-language"),a=e.cf?.botManagement?.score,c=["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"],i=n.toLowerCase(),d=c.some(f=>i.includes(f)),l=n.toLowerCase().includes("headless")||n.toLowerCase().includes("puppeteer")||!r,u=!n.toLowerCase().includes("headless")||!n.toLowerCase().includes("puppeteer")||!r,h=!s||!t,g=typeof a=="number"&&a<30;return console.log("Bot Detection Details:",{botUaMatch:d,headlessIndicators:l,missingHeaders:h,lowBotScore:g,botScore:a}),(i.includes("safari")||i.includes("mozilla"))&&l&&u?!1:d||l||h||g}static async cloudflareHandleRequests(e,n,s){let{MERCHANT_SYSTEM_URN:r,MERCHANT_API_KEY:t}=n;return new o({apiKey:t,merchantSystemUrn:r}).handleRequest(e,o.checkIfBotRequest,s)}static async fastlyHandleRequests(e,n,s){return new o({apiKey:s,merchantSystemUrn:n}).handleRequest(e,o.checkIfBotRequest,null)}async handleRequest(e,n,s){let{token:r,url:t,user_agent:a}=this.extractDataFromRequest(e);return n&&!n(e,s)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):this.baseHandleRequest(r,t,a,s)}static async generateCustomerJWT(e,n,s,r=3600){let t="RS256",a=await _(s,t),c=Math.floor(Date.now()/1e3);return new E({}).setProtectedHeader({alg:t,kid:n}).setIssuer(e).setIssuedAt(c).setExpirationTime(c+r).sign(a)}};o._instance=null;var m=o;export{m as SupertabConnect};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n SupertabConnectConfig,\n Env,\n EventPayload,\n TokenVerificationResult,\n TokenInvalidReason,\n} from \"./types\";\nimport {\n jwtVerify,\n decodeProtectedHeader,\n decodeJwt,\n JWTHeaderParameters,\n JWTPayload,\n importPKCS8,\n SignJWT,\n} from \"jose\";\n\nexport type { Env } from \"./types\";\n\n// In-memory cache for JWK sets\nconst jwksCache = new Map<string, any>();\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 baseUrl?: string;\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 this.baseUrl = \"https://api-connect.sbx.supertab.co\";\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 * Get the JWKS for a given issuer, using cache if available\n * @private\n */\n private async getJwksForIssuer(issuer: string): Promise<any> {\n if (!jwksCache.has(issuer)) {\n const jwksUrl = `${\n this.baseUrl\n }/.well-known/jwks.json/${encodeURIComponent(issuer)}`;\n\n try {\n const jwksResponse = await fetch(jwksUrl);\n if (!jwksResponse.ok) {\n throw new Error(`Failed to fetch JWKS: ${jwksResponse.status}`);\n }\n\n const jwksData = await jwksResponse.json();\n jwksCache.set(issuer, jwksData);\n } catch (error) {\n if (debug) {\n console.error(\"Error fetching JWKS:\", error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(issuer);\n }\n\n /**\n * Verify a JWT token\n * @param token The JWT token to verify\n * @returns A promise that resolves with the verification result\n */\n async verifyToken(token: string): Promise<TokenVerificationResult> {\n // 1. Check if token exists\n if (!token) {\n return {\n valid: false,\n reason: TokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n // 2. Verify header and algorithm\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(token) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT header:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"RS256\") {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ALG,\n };\n }\n\n // 3. Verify payload and issuer\n let payload: JWTPayload;\n try {\n payload = decodeJwt(token);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT payload:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(\"urn:stc:customer:\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ISSUER,\n };\n }\n\n // 4. Verify signature\n try {\n const jwks = await this.getJwksForIssuer(issuer);\n\n // Create a key finder function for verification\n const getKey = async (header: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === header.kid);\n if (!jwk) throw new Error(`No matching key found: ${header.kid}`);\n return jwk;\n };\n\n const result = await jwtVerify(token, getKey, {\n issuer,\n algorithms: [\"RS256\"],\n clockTolerance: \"1m\",\n });\n\n // Success case - token is valid\n return {\n valid: true,\n payload: result.payload,\n };\n } catch (error: any) {\n if (debug) {\n console.error(\"JWT verification failed:\", error);\n }\n\n // Check if token is expired\n if (error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.EXPIRED,\n };\n }\n\n return {\n valid: false,\n reason: TokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n };\n }\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param customerToken Optional customer token for the event\n * @param properties Additional properties to include with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n customerToken?: string,\n properties: Record<string, any> = {}\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n customer_system_token: customerToken,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\n properties,\n };\n\n try {\n const response = await fetch(`${this.baseUrl}/events`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n /**\n * Handle the request, report an event to Supertab Connect and return a response\n */\n private async baseHandleRequest(\n token: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n // 1. Verify token\n const verification = await this.verifyToken(token);\n\n // Record event helper\n async function recordEvent(\n stc: SupertabConnect,\n eventName: string,\n ctx: any\n ) {\n const eventProperties = {\n page_url: url,\n user_agent: user_agent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n if (ctx) {\n const eventPromise = stc.recordEvent(eventName, token, eventProperties);\n ctx.waitUntil(eventPromise);\n return eventPromise;\n } else {\n return await stc.recordEvent(eventName, token, eventProperties);\n }\n }\n\n // 2. Handle based on verification result\n if (!verification.valid) {\n await recordEvent(\n this,\n verification.reason || \"token_verification_failed\",\n ctx\n );\n const message =\n \"Payment required: you need to present a valid Supertab Connect token to access this content. \" +\n \"Check out the provided url for details\";\n const details =\n \"❌ Content access denied\" +\n (verification.reason ? `: ${verification.reason}` : \"\");\n const contentAccessUrl = `${this.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`;\n\n const responseBody = {\n url: contentAccessUrl,\n message: message,\n details: details,\n };\n\n return new Response(JSON.stringify(responseBody), {\n status: 402,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Success\n await recordEvent(this, \"page_viewed\", ctx);\n return new Response(\"✅ Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n private extractDataFromRequest(request: Request): {\n token: string;\n url: string;\n user_agent: string;\n } {\n // Parse token\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"Bearer \") ? auth.slice(7) : \"\";\n\n // Extract URL and user agent\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n return { token, url, user_agent };\n }\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string\n ): Promise<Response> {\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: merchantApiKey,\n merchantSystemUrn: merchantSystemUrn,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n null\n );\n }\n\n async handleRequest(\n request: Request,\n botDetectionHandler?: (request: Request, ctx?: any) => boolean,\n ctx?: any\n ): Promise<Response> {\n // 1. Extract token, URL, and user agent from the request\n const { token, url, user_agent } = this.extractDataFromRequest(request);\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Call the base handle request method and return the result\n return this.baseHandleRequest(token, url, user_agent, ctx);\n }\n\n /** Generate a customer JWT\n * @param customerURN The customer's unique resource name (URN).\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem The private key in PEM format used to sign the JWT.\n * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).\n * @returns A promise that resolves to the generated JWT as a string.\n */\n static async generateCustomerJWT(\n customerURN: string,\n kid: string,\n privateKeyPem: string,\n expirationSeconds: number = 3600\n ): Promise<string> {\n const alg = \"RS256\";\n const key = await importPKCS8(privateKeyPem, alg);\n\n const now = Math.floor(Date.now() / 1000);\n\n return new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(customerURN)\n .setIssuedAt(now)\n .setExpirationTime(now + expirationSeconds)\n .sign(key);\n }\n}\n"],"mappings":"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","names":["jwtVerify","decodeProtectedHeader","decodeJwt","importPKCS8","SignJWT","jwksCache","debug","_SupertabConnect","config","reset","issuer","jwksUrl","jwksResponse","jwksData","error","token","header","payload","jwks","jwk","key","eventName","customerToken","properties","response","url","user_agent","ctx","verification","recordEvent","stc","eventProperties","eventPromise","message","details","responseBody","request","auth","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","botDetectionHandler","customerURN","kid","privateKeyPem","expirationSeconds","alg","now","SupertabConnect"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n SupertabConnectConfig,\n Env,\n EventPayload,\n TokenVerificationResult,\n TokenInvalidReason,\n} from \"./types\";\nimport {\n jwtVerify,\n decodeProtectedHeader,\n decodeJwt,\n JWTHeaderParameters,\n JWTPayload,\n importPKCS8,\n SignJWT,\n} from \"jose\";\n\nexport type { Env } from \"./types\";\n\n// In-memory cache for JWK sets\nconst jwksCache = new Map<string, any>();\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 baseUrl?: string;\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 this.baseUrl = \"https://api-connect.sbx.supertab.co\";\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 * Get the JWKS for a given issuer, using cache if available\n * @private\n */\n private async getJwksForIssuer(issuer: string): Promise<any> {\n if (!jwksCache.has(issuer)) {\n const jwksUrl = `${\n this.baseUrl\n }/.well-known/jwks.json/${encodeURIComponent(issuer)}`;\n\n try {\n let options: any = { method: \"GET\" };\n // @ts-ignore\n if (globalThis?.fastly) {\n options = { ...options, backend: \"sbx-backend\" };\n }\n const jwksResponse = await fetch(jwksUrl, options);\n\n if (!jwksResponse.ok) {\n throw new Error(`Failed to fetch JWKS: ${jwksResponse.status}`);\n }\n\n const jwksData = await jwksResponse.json();\n jwksCache.set(issuer, jwksData);\n } catch (error) {\n if (debug) {\n console.error(\"Error fetching JWKS:\", error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(issuer);\n }\n\n /**\n * Verify a JWT token\n * @param token The JWT token to verify\n * @returns A promise that resolves with the verification result\n */\n async verifyToken(token: string): Promise<TokenVerificationResult> {\n // 1. Check if token exists\n if (!token) {\n return {\n valid: false,\n reason: TokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n // 2. Verify header and algorithm\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(token) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT header:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"RS256\") {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ALG,\n };\n }\n\n // 3. Verify payload and issuer\n let payload: JWTPayload;\n try {\n payload = decodeJwt(token);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT payload:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(\"urn:stc:customer:\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ISSUER,\n };\n }\n\n // 4. Verify signature\n try {\n const jwks = await this.getJwksForIssuer(issuer);\n\n // Create a key finder function for verification\n const getKey = async (header: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === header.kid);\n if (!jwk) throw new Error(`No matching key found: ${header.kid}`);\n return jwk;\n };\n\n const result = await jwtVerify(token, getKey, {\n issuer,\n algorithms: [\"RS256\"],\n clockTolerance: \"1m\",\n });\n\n // Success case - token is valid\n return {\n valid: true,\n payload: result.payload,\n };\n } catch (error: any) {\n if (debug) {\n console.error(\"JWT verification failed:\", error);\n }\n\n // Check if token is expired\n if (error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.EXPIRED,\n };\n }\n\n return {\n valid: false,\n reason: TokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n };\n }\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param customerToken Optional customer token for the event\n * @param properties Additional properties to include with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n customerToken?: string,\n properties: Record<string, any> = {}\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n customer_system_token: customerToken,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\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: \"sbx-backend\" };\n }\n const response = await fetch(`${this.baseUrl}/events`, options);\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n /**\n * Handle the request, report an event to Supertab Connect and return a response\n */\n private async baseHandleRequest(\n token: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n // 1. Verify token\n const verification = await this.verifyToken(token);\n\n // Record event helper\n async function recordEvent(\n stc: SupertabConnect,\n eventName: string,\n ctx: any\n ) {\n const eventProperties = {\n page_url: url,\n user_agent: user_agent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n if (ctx) {\n const eventPromise = stc.recordEvent(eventName, token, eventProperties);\n ctx.waitUntil(eventPromise);\n return eventPromise;\n } else {\n return await stc.recordEvent(eventName, token, eventProperties);\n }\n }\n\n // 2. Handle based on verification result\n if (!verification.valid) {\n await recordEvent(\n this,\n verification.reason || \"token_verification_failed\",\n ctx\n );\n const message =\n \"Payment required: you need to present a valid Supertab Connect token to access this content. \" +\n \"Check out the provided url for details\";\n const details =\n \"❌ Content access denied\" +\n (verification.reason ? `: ${verification.reason}` : \"\");\n const contentAccessUrl = `${this.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`;\n\n const responseBody = {\n url: contentAccessUrl,\n message: message,\n details: details,\n };\n\n return new Response(JSON.stringify(responseBody), {\n status: 402,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Success\n await recordEvent(this, \"page_viewed\", ctx);\n return new Response(\"✅ Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n private extractDataFromRequest(request: Request): {\n token: string;\n url: string;\n user_agent: string;\n } {\n // Parse token\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"Bearer \") ? auth.slice(7) : \"\";\n\n // Extract URL and user agent\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n return { token, url, user_agent };\n }\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string\n ): Promise<Response> {\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: merchantApiKey,\n merchantSystemUrn: merchantSystemUrn,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n null\n );\n }\n\n async handleRequest(\n request: Request,\n botDetectionHandler?: (request: Request, ctx?: any) => boolean,\n ctx?: any\n ): Promise<Response> {\n // 1. Extract token, URL, and user agent from the request\n const { token, url, user_agent } = this.extractDataFromRequest(request);\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Call the base handle request method and return the result\n return this.baseHandleRequest(token, url, user_agent, ctx);\n }\n\n /** Generate a customer JWT\n * @param customerURN The customer's unique resource name (URN).\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem The private key in PEM format used to sign the JWT.\n * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).\n * @returns A promise that resolves to the generated JWT as a string.\n */\n static async generateCustomerJWT(\n customerURN: string,\n kid: string,\n privateKeyPem: string,\n expirationSeconds: number = 3600\n ): Promise<string> {\n const alg = \"RS256\";\n const key = await importPKCS8(privateKeyPem, alg);\n\n const now = Math.floor(Date.now() / 1000);\n\n return new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(customerURN)\n .setIssuedAt(now)\n .setExpirationTime(now + expirationSeconds)\n .sign(key);\n }\n}\n"],"mappings":"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","names":["jwtVerify","decodeProtectedHeader","decodeJwt","importPKCS8","SignJWT","jwksCache","debug","_SupertabConnect","config","reset","issuer","jwksUrl","options","jwksResponse","jwksData","error","token","header","payload","jwks","jwk","key","eventName","customerToken","properties","response","url","user_agent","ctx","verification","recordEvent","stc","eventProperties","eventPromise","message","details","responseBody","request","auth","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","botDetectionHandler","customerURN","kid","privateKeyPem","expirationSeconds","alg","now","SupertabConnect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getsupertab/supertab-connect-sdk",
3
- "version": "0.1.0-beta.18",
3
+ "version": "0.1.0-beta.19",
4
4
  "description": "Supertab Connect SDK (beta)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",