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

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.d.mts CHANGED
@@ -26,16 +26,15 @@ interface TokenVerificationResult {
26
26
  */
27
27
  declare class SupertabConnect {
28
28
  private apiKey?;
29
- private baseUrl?;
29
+ private static baseUrl;
30
30
  private merchantSystemUrn?;
31
31
  private static _instance;
32
32
  constructor(config: SupertabConnectConfig, reset?: boolean);
33
33
  static resetInstance(): void;
34
34
  /**
35
- * Get the JWKS for a given issuer, using cache if available
36
- * @private
35
+ * Override the default base URL for API requests (intended for local development/testing).
37
36
  */
38
- private getJwksForIssuer;
37
+ static setBaseUrl(url: string): void;
39
38
  /**
40
39
  * Verify a JWT token
41
40
  * @param token The JWT token to verify
@@ -45,20 +44,35 @@ declare class SupertabConnect {
45
44
  /**
46
45
  * Records an analytics event
47
46
  * @param eventName Name of the event to record
48
- * @param customerToken Optional customer token for the event
49
47
  * @param properties Additional properties to include with the event
48
+ * @param licenseId Optional license ID associated with the event
50
49
  * @returns Promise that resolves when the event is recorded
51
50
  */
52
- recordEvent(eventName: string, customerToken?: string, properties?: Record<string, any>): Promise<void>;
51
+ recordEvent(eventName: string, properties?: Record<string, any>, licenseId?: string): Promise<void>;
53
52
  /**
54
53
  * Handle the request, report an event to Supertab Connect and return a response
55
54
  */
56
55
  private baseHandleRequest;
56
+ /**
57
+ * Handle the request for license tokens, report an event to Supertab Connect and return a response
58
+ */
59
+ private baseLicenseHandleRequest;
57
60
  private extractDataFromRequest;
58
61
  static checkIfBotRequest(request: Request): boolean;
59
62
  static cloudflareHandleRequests(request: Request, env: Env, ctx: any): Promise<Response>;
60
63
  static fastlyHandleRequests(request: Request, merchantSystemUrn: string, merchantApiKey: string): Promise<Response>;
61
64
  handleRequest(request: Request, botDetectionHandler?: (request: Request, ctx?: any) => boolean, ctx?: any): Promise<Response>;
65
+ /**
66
+ * Request a license token from the Supertab Connect token endpoint.
67
+ * @param clientId OAuth client identifier used for the assertion issuer/subject claims.
68
+ * @param kid The key ID to include in the JWT header.
69
+ * @param privateKeyPem Private key in PEM format used to sign the client assertion.
70
+ * @param tokenEndpoint Token endpoint URL.
71
+ * @param resourceUrl Resource URL attempting to access with a License.
72
+ * @param licenseXml XML license document to include in the request payload.
73
+ * @returns Promise resolving to the issued license access token string.
74
+ */
75
+ static generateLicenseToken(clientId: string, kid: string, privateKeyPem: string, tokenEndpoint: string, resourceUrl: string, licenseXml: string): Promise<string>;
62
76
  /** Generate a customer JWT
63
77
  * @param customerURN The customer's unique resource name (URN).
64
78
  * @param kid The key ID to include in the JWT header.
package/dist/index.d.ts CHANGED
@@ -26,16 +26,15 @@ interface TokenVerificationResult {
26
26
  */
27
27
  declare class SupertabConnect {
28
28
  private apiKey?;
29
- private baseUrl?;
29
+ private static baseUrl;
30
30
  private merchantSystemUrn?;
31
31
  private static _instance;
32
32
  constructor(config: SupertabConnectConfig, reset?: boolean);
33
33
  static resetInstance(): void;
34
34
  /**
35
- * Get the JWKS for a given issuer, using cache if available
36
- * @private
35
+ * Override the default base URL for API requests (intended for local development/testing).
37
36
  */
38
- private getJwksForIssuer;
37
+ static setBaseUrl(url: string): void;
39
38
  /**
40
39
  * Verify a JWT token
41
40
  * @param token The JWT token to verify
@@ -45,20 +44,35 @@ declare class SupertabConnect {
45
44
  /**
46
45
  * Records an analytics event
47
46
  * @param eventName Name of the event to record
48
- * @param customerToken Optional customer token for the event
49
47
  * @param properties Additional properties to include with the event
48
+ * @param licenseId Optional license ID associated with the event
50
49
  * @returns Promise that resolves when the event is recorded
51
50
  */
52
- recordEvent(eventName: string, customerToken?: string, properties?: Record<string, any>): Promise<void>;
51
+ recordEvent(eventName: string, properties?: Record<string, any>, licenseId?: string): Promise<void>;
53
52
  /**
54
53
  * Handle the request, report an event to Supertab Connect and return a response
55
54
  */
56
55
  private baseHandleRequest;
56
+ /**
57
+ * Handle the request for license tokens, report an event to Supertab Connect and return a response
58
+ */
59
+ private baseLicenseHandleRequest;
57
60
  private extractDataFromRequest;
58
61
  static checkIfBotRequest(request: Request): boolean;
59
62
  static cloudflareHandleRequests(request: Request, env: Env, ctx: any): Promise<Response>;
60
63
  static fastlyHandleRequests(request: Request, merchantSystemUrn: string, merchantApiKey: string): Promise<Response>;
61
64
  handleRequest(request: Request, botDetectionHandler?: (request: Request, ctx?: any) => boolean, ctx?: any): Promise<Response>;
65
+ /**
66
+ * Request a license token from the Supertab Connect token endpoint.
67
+ * @param clientId OAuth client identifier used for the assertion issuer/subject claims.
68
+ * @param kid The key ID to include in the JWT header.
69
+ * @param privateKeyPem Private key in PEM format used to sign the client assertion.
70
+ * @param tokenEndpoint Token endpoint URL.
71
+ * @param resourceUrl Resource URL attempting to access with a License.
72
+ * @param licenseXml XML license document to include in the request payload.
73
+ * @returns Promise resolving to the issued license access token string.
74
+ */
75
+ static generateLicenseToken(clientId: string, kid: string, privateKeyPem: string, tokenEndpoint: string, resourceUrl: string, licenseXml: string): Promise<string>;
62
76
  /** Generate a customer JWT
63
77
  * @param customerURN The customer's unique resource name (URN).
64
78
  * @param kid The key ID to include in the JWT header.
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
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});
1
+ "use strict";var A=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var J=(a,e)=>{for(var t in e)A(a,t,{get:e[t],enumerable:!0})},x=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of C(e))!D.call(a,n)&&n!==t&&A(a,n,{get:()=>e[n],enumerable:!(s=N(e,n))||s.enumerable});return a};var H=a=>x(A({},"__esModule",{value:!0}),a);var $={};J($,{SupertabConnect:()=>v});module.exports=H($);var I=require("jose");var h=require("jose");async function q(a,e){let t=["RS256","ES256"];for(let s of t)try{return{key:await(0,h.importPKCS8)(a,s),alg:s}}catch(n){e&&console.debug(`Private key did not import using ${s}, retrying...`,n)}throw new Error("Unsupported private key format. Expected RSA or P-256 EC private key.")}async function S({clientId:a,kid:e,privateKeyPem:t,tokenEndpoint:s,resourceUrl:n,licenseXml:r,debug:i}){let{key:d,alg:l}=await q(t,i),p=Math.floor(Date.now()/1e3),c=await new h.SignJWT({}).setProtectedHeader({alg:l,kid:e}).setIssuer(a).setSubject(a).setIssuedAt(p).setExpirationTime(p+300).setAudience(s).sign(d),o=new URLSearchParams({grant_type:"rsl",client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:c,license:r,resource:n}),y={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:o.toString()};try{let g=await fetch(s,y);if(!g.ok){let m=await g.text().catch(()=>""),E=`Failed to obtain license token: ${g.status} ${g.statusText}${m?` - ${m}`:""}`;throw new Error(E)}let f;try{f=await g.json()}catch(m){throw i&&console.error("Failed to parse license token response as JSON:",m),new Error("Failed to parse license token response as JSON")}if(!f?.access_token)throw new Error("License token response missing access_token");return f.access_token}catch(g){throw i&&console.error("Error generating license token:",g),g}}async function R({customerURN:a,kid:e,privateKeyPem:t,expirationSeconds:s=3600}){let n="RS256",r=await(0,h.importPKCS8)(t,n),i=Math.floor(Date.now()/1e3);return new h.SignJWT({}).setProtectedHeader({alg:n,kid:e}).setIssuer(a).setIssuedAt(i).setExpirationTime(i+s).sign(r)}var w=require("jose");var k=new Map,V="sbx-backend";function W(){let a={method:"GET"};return globalThis?.fastly&&(a={...a,backend:V}),a}async function T({cacheKey:a,url:e,debug:t,failureMessage:s,logLabel:n}){if(!k.has(a))try{let r=await fetch(e,W());if(!r.ok)throw new Error(`${s}: ${r.status}`);let i=await r.json();k.set(a,i)}catch(r){throw t&&console.error(n,r),r}return k.get(a)}async function b(a,e,t){let s=`${a}/.well-known/jwks.json/${encodeURIComponent(e)}`;return T({cacheKey:e,url:s,debug:t,failureMessage:"Failed to fetch JWKS",logLabel:"Error fetching JWKS:"})}async function P(a,e){let t=`${a}/.well-known/.well-known/jwks.json`;return T({cacheKey:"platform_jwks",url:t,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}var L=a=>a.replace(/\/+$/,"");async function K({licenseToken:a,requestUrl:e,supertabBaseUrl:t,debug:s}){if(!a)return{valid:!1,reason:"missing_license_token"};let n;try{n=(0,w.decodeProtectedHeader)(a)}catch(o){return s&&console.error("Invalid license JWT header:",o),{valid:!1,reason:"invalid_license_header"}}if(n.alg!=="ES256")return s&&console.error("Unsupported license JWT alg:",n.alg),{valid:!1,reason:"invalid_license_algorithm"};let r;try{r=(0,w.decodeJwt)(a)}catch(o){return s&&console.error("Invalid license JWT payload:",o),{valid:!1,reason:"invalid_license_payload"}}let i=r.license_id,d=r.iss;if(!d||!d.startsWith(t))return s&&console.error("Invalid license JWT issuer:",d),{valid:!1,reason:"invalid_license_issuer",licenseId:i};let l=Array.isArray(r.aud)?r.aud.filter(o=>typeof o=="string"):typeof r.aud=="string"?[r.aud]:[],p=L(e);if(!l.some(o=>{let y=L(o);return y?p.startsWith(y):!1}))return s&&console.error("License JWT audience does not match request URL:",r.aud),{valid:!1,reason:"invalid_license_audience",licenseId:i};try{let o=await P(t,s),g=await(0,w.jwtVerify)(a,async f=>{let m=o.keys.find(E=>E.kid===f.kid);if(!m)throw new Error(`No matching platform key found: ${f.kid}`);return m},{issuer:d,algorithms:[n.alg],clockTolerance:"1m"});return{valid:!0,licenseId:i,payload:g.payload}}catch(o){return s&&console.error("License JWT verification failed:",o),o instanceof Error&&o.message?.includes("exp")?{valid:!1,reason:"license_token_expired",licenseId:i}:{valid:!1,reason:"license_signature_verification_failed",licenseId:i}}}function F({supertabBaseUrl:a,merchantSystemUrn:e}){return`${a}/merchants/systems/${e}/license.xml`}async function U({licenseToken:a,url:e,userAgent:t,ctx:s,supertabBaseUrl:n,merchantSystemUrn:r,debug:i,recordEvent:d}){let l=await K({licenseToken:a,requestUrl:e,supertabBaseUrl:n,debug:i});async function p(c){let o={page_url:e,user_agent:t,verification_status:l.valid?"valid":"invalid",verification_reason:l.reason||"success"},y=d(c,o,l.licenseId);return s?.waitUntil&&s.waitUntil(y),y}if(!l.valid){await p(l.reason||"license_token_verification_failed");let c="invalid_request",o="Access to this resource requires a license";switch(l.reason){case"missing_license_token":c="invalid_request",o="Access to this resource requires a license";break;case"license_token_expired":c="invalid_token",o="The license token has expired";break;case"license_signature_verification_failed":case"invalid_license_header":case"invalid_license_payload":c="invalid_token",o="The license token is invalid";break;case"invalid_license_issuer":case"invalid_license_audience":c="insufficient_scope",o="The license token is not valid for this resource";break;default:c="invalid_request",o="Access to this resource requires a license"}let y=F({supertabBaseUrl:n,merchantSystemUrn:r}),g=`${n}/docs/errors#${c}`,f=new Headers({"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${c}", error_description="${o}", error_uri="${g}"`,Link:`${y}; rel="license"; type="application/rsl+xml"`}),m=`Access to this resource requires a valid license token. Error: ${c} - ${o}`;return new Response(m,{status:401,headers:f})}return await p("license_used"),new Response("\u2705 License Token Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}var _=!0,u=class u{constructor(e,t=!1){if(!t&&u._instance){if(!(e.apiKey===u._instance.apiKey&&e.merchantSystemUrn===u._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return u._instance}if(t&&u._instance&&u.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,u._instance=this}static resetInstance(){u._instance=null}static setBaseUrl(e){u.baseUrl=e}async verifyToken(e){if(!e)return{valid:!1,reason:"missing_token"};let t;try{t=(0,I.decodeProtectedHeader)(e)}catch(r){return _&&console.error("Invalid JWT header:",r),{valid:!1,reason:"invalid_header"}}if(t.alg!=="RS256")return{valid:!1,reason:"invalid_algorithm"};let s;try{s=(0,I.decodeJwt)(e)}catch(r){return _&&console.error("Invalid JWT payload:",r),{valid:!1,reason:"invalid_payload"}}let n=s.iss;if(!n||!n.startsWith("urn:stc:customer:"))return{valid:!1,reason:"invalid_issuer"};try{let r=await b(u.baseUrl,n,_);return{valid:!0,payload:(await(0,I.jwtVerify)(e,async l=>{let p=r.keys.find(c=>c.kid===l.kid);if(!p)throw new Error(`No matching key found: ${l.kid}`);return p},{issuer:n,algorithms:["RS256"],clockTolerance:"1m"})).payload}}catch(r){return _&&console.error("JWT verification failed:",r),r.message?.includes("exp")?{valid:!1,reason:"token_expired"}:{valid:!1,reason:"signature_verification_failed"}}}async recordEvent(e,t={},s){let n={event_name:e,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",license_id:s,properties:t};try{let r={method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(n)};globalThis?.fastly&&(r={...r,backend:"sbx-backend"});let i=await fetch(`${u.baseUrl}/events`,r);i.ok||console.log(`Failed to record event: ${i.status}`)}catch(r){console.log("Error recording event:",r)}}async baseHandleRequest(e,t,s,n){let r=await this.verifyToken(e);async function i(d,l,p){let c={page_url:t,user_agent:s,verification_status:r.valid?"valid":"invalid",verification_reason:r.reason||"success"};if(p){let o=d.recordEvent(l,c);return p.waitUntil(o),o}else return await d.recordEvent(l,c)}if(!r.valid){await i(this,r.reason||"token_verification_failed",n);let d="Payment required: you need to present a valid Supertab Connect token to access this content. Check out the provided url for details",l="\u274C Content access denied"+(r.reason?`: ${r.reason}`:""),c={url:`${u.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`,message:d,details:l};return new Response(JSON.stringify(c),{status:402,headers:new Headers({"Content-Type":"application/json"})})}return await i(this,"page_viewed",n),new Response("\u2705 Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}async baseLicenseHandleRequest(e,t,s,n){return U({licenseToken:e,url:t,userAgent:s,ctx:n,supertabBaseUrl:u.baseUrl,merchantSystemUrn:this.merchantSystemUrn,debug:_,recordEvent:(r,i,d)=>this.recordEvent(r,i,d)})}extractDataFromRequest(e){let t=e.headers.get("Authorization")||"",s=t.startsWith("Bearer ")?t.slice(7):"",n=t.startsWith("License ")?t.slice(8):"",r=e.url,i=e.headers.get("User-Agent")||"unknown";return{token:s,licenseToken:n,url:r,user_agent:i}}static checkIfBotRequest(e){let t=e.headers.get("User-Agent")||"",s=e.headers.get("accept")||"",n=e.headers.get("sec-ch-ua"),r=e.headers.get("accept-language"),i=e.cf?.botManagement?.score,d=["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"],l=t.toLowerCase(),p=d.some(f=>l.includes(f)),c=t.toLowerCase().includes("headless")||t.toLowerCase().includes("puppeteer")||!n,o=!t.toLowerCase().includes("headless")||!t.toLowerCase().includes("puppeteer")||!n,y=!s||!r,g=typeof i=="number"&&i<30;return console.log("Bot Detection Details:",{botUaMatch:p,headlessIndicators:c,missingHeaders:y,lowBotScore:g,botScore:i}),(l.includes("safari")||l.includes("mozilla"))&&c&&o?!1:p||c||y||g}static async cloudflareHandleRequests(e,t,s){let{MERCHANT_SYSTEM_URN:n,MERCHANT_API_KEY:r}=t;return new u({apiKey:r,merchantSystemUrn:n}).handleRequest(e,u.checkIfBotRequest,s)}static async fastlyHandleRequests(e,t,s){return new u({apiKey:s,merchantSystemUrn:t}).handleRequest(e,u.checkIfBotRequest,null)}async handleRequest(e,t,s){let{token:n,licenseToken:r,url:i,user_agent:d}=this.extractDataFromRequest(e);return t&&!t(e,s)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):n?this.baseHandleRequest(n,i,d,s):this.baseLicenseHandleRequest(r,i,d,s)}static async generateLicenseToken(e,t,s,n,r,i){return S({clientId:e,kid:t,privateKeyPem:s,tokenEndpoint:n,resourceUrl:r,licenseXml:i,debug:_})}static async generateCustomerJWT(e,t,s,n=3600){return R({customerURN:e,kid:t,privateKeyPem:s,expirationSeconds:n})}};u.baseUrl="https://api-connect.sbx.supertab.co",u._instance=null;var v=u;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 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"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/customer.ts","../src/license.ts","../src/jwks.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} from \"jose\";\nimport {\n generateLicenseToken as generateLicenseTokenHelper,\n generateCustomerJWT as generateCustomerJWTHelper,\n} from \"./customer\";\nimport {\n baseLicenseHandleRequest as baseLicenseHandleRequestHelper,\n} from \"./license\";\nimport { fetchIssuerJwks, fetchPlatformJwks } from \"./jwks\";\n\nexport type { Env } from \"./types\";\n\nconst debug = true; // Set to true for debugging purposes\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.sbx.supertab.co\";\n private merchantSystemUrn?: string;\n\n private static _instance: SupertabConnect | null = null;\n\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (\n !(\n config.apiKey === SupertabConnect._instance.apiKey &&\n config.merchantSystemUrn ===\n SupertabConnect._instance.merchantSystemUrn\n )\n ) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey || !config.merchantSystemUrn) {\n throw new Error(\n \"Missing required configuration: apiKey and merchantSystemUrn are required\"\n );\n }\n this.apiKey = config.apiKey;\n this.merchantSystemUrn = config.merchantSystemUrn;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Verify a JWT token\n * @param token The JWT token to verify\n * @returns A promise that resolves with the verification result\n */\n async verifyToken(token: string): Promise<TokenVerificationResult> {\n // 1. Check if token exists\n if (!token) {\n return {\n valid: false,\n reason: TokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n // 2. Verify header and algorithm\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(token) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT header:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"RS256\") {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ALG,\n };\n }\n\n // 3. Verify payload and issuer\n let payload: JWTPayload;\n try {\n payload = decodeJwt(token);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT payload:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(\"urn:stc:customer:\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ISSUER,\n };\n }\n\n // 4. Verify signature\n try {\n const jwks = await fetchIssuerJwks(\n SupertabConnect.baseUrl,\n issuer,\n debug\n );\n\n // Create a key finder function for verification\n const getKey = async (header: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === header.kid);\n if (!jwk) throw new Error(`No matching key found: ${header.kid}`);\n return jwk;\n };\n\n const result = await jwtVerify(token, getKey, {\n issuer,\n algorithms: [\"RS256\"],\n clockTolerance: \"1m\",\n });\n\n // Success case - token is valid\n return {\n valid: true,\n payload: result.payload,\n };\n } catch (error: any) {\n if (debug) {\n console.error(\"JWT verification failed:\", error);\n }\n\n // Check if token is expired\n if (error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.EXPIRED,\n };\n }\n\n return {\n valid: false,\n reason: TokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n };\n }\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param properties Additional properties to include with the event\n * @param licenseId Optional license ID associated with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n properties: Record<string, any> = {},\n licenseId?: string\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: any = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n };\n // @ts-ignore\n if (globalThis?.fastly) {\n options = { ...options, backend: \"sbx-backend\" };\n }\n const response = await fetch(\n `${SupertabConnect.baseUrl}/events`,\n options\n );\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n /**\n * Handle the request, report an event to Supertab Connect and return a response\n */\n private async baseHandleRequest(\n token: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n // 1. Verify token\n const verification = await this.verifyToken(token);\n\n // Record event helper\n async function recordEvent(\n stc: SupertabConnect,\n eventName: string,\n ctx: any\n ) {\n const eventProperties = {\n page_url: url,\n user_agent: user_agent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n if (ctx) {\n const eventPromise = stc.recordEvent(\n eventName,\n eventProperties\n );\n ctx.waitUntil(eventPromise);\n return eventPromise;\n } else {\n return await stc.recordEvent(\n eventName,\n eventProperties\n );\n }\n }\n\n // 2. Handle based on verification result\n if (!verification.valid) {\n await recordEvent(\n this,\n verification.reason || \"token_verification_failed\",\n ctx\n );\n const message =\n \"Payment required: you need to present a valid Supertab Connect token to access this content. \" +\n \"Check out the provided url for details\";\n const details =\n \"❌ Content access denied\" +\n (verification.reason ? `: ${verification.reason}` : \"\");\n const contentAccessUrl = `${SupertabConnect.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`;\n\n const responseBody = {\n url: contentAccessUrl,\n message: message,\n details: details,\n };\n\n return new Response(JSON.stringify(responseBody), {\n status: 402,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Success\n await recordEvent(this, \"page_viewed\", ctx);\n return new Response(\"✅ Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n /**\n * Handle the request for license tokens, report an event to Supertab Connect and return a response\n */\n private async baseLicenseHandleRequest(\n licenseToken: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n return baseLicenseHandleRequestHelper({\n licenseToken,\n url,\n userAgent: user_agent,\n ctx,\n supertabBaseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: this.merchantSystemUrn,\n debug,\n recordEvent: (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string,\n ) => this.recordEvent(eventName, properties, licenseId),\n });\n }\n\n private extractDataFromRequest(request: Request): {\n token: string;\n licenseToken: string;\n url: string;\n user_agent: string;\n } {\n // Parse token\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"Bearer \") ? auth.slice(7) : \"\";\n const licenseToken = auth.startsWith(\"License \") ? auth.slice(8) : \"\";\n\n // Extract URL and user agent\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n return { token, licenseToken, url, user_agent };\n }\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string\n ): 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, license token, URL, and user agent from the request\n const { token, licenseToken, url, user_agent } =\n this.extractDataFromRequest(request);\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Check for bearer token first, then fallback to license token\n if (token) {\n return this.baseHandleRequest(token, url, user_agent, ctx);\n }\n\n // 4. Call the base licenhandle request method and return the result\n return this.baseLicenseHandleRequest(licenseToken, url, user_agent, ctx);\n }\n\n /**\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 tokenEndpoint: string,\n resourceUrl: string,\n licenseXml: string\n ): Promise<string> {\n return generateLicenseTokenHelper({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n });\n }\n\n /** Generate a customer JWT\n * @param customerURN The customer's unique resource name (URN).\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem The private key in PEM format used to sign the JWT.\n * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).\n * @returns A promise that resolves to the generated JWT as a string.\n */\n static async generateCustomerJWT(\n customerURN: string,\n kid: string,\n privateKeyPem: string,\n expirationSeconds: number = 3600\n ): Promise<string> {\n return generateCustomerJWTHelper({\n customerURN,\n kid,\n privateKeyPem,\n expirationSeconds,\n });\n }\n}\n","import { importPKCS8, SignJWT } from \"jose\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\ntype GenerateCustomerJwtParams = {\n customerURN: string;\n kid: string;\n privateKeyPem: string;\n expirationSeconds?: number;\n};\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const supportedAlgs: SupportedAlg[] = [\"RS256\", \"ES256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\nexport async function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: payload.toString(),\n };\n\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: any;\n try {\n data = await response.json();\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nexport async function generateCustomerJWT({\n customerURN,\n kid,\n privateKeyPem,\n expirationSeconds = 3600,\n}: GenerateCustomerJwtParams): Promise<string> {\n const alg: SupportedAlg = \"RS256\";\n const key = await importPKCS8(privateKeyPem, alg);\n\n const now = Math.floor(Date.now() / 1000);\n\n return new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(customerURN)\n .setIssuedAt(now)\n .setExpirationTime(now + expirationSeconds)\n .sign(key);\n}\n\nexport type { GenerateLicenseTokenParams, GenerateCustomerJwtParams };\n","import {\n decodeProtectedHeader,\n decodeJwt,\n JWTPayload,\n JWTHeaderParameters,\n jwtVerify,\n} from \"jose\";\nimport {\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n} 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 supertabBaseUrl,\n merchantSystemUrn,\n}: {\n supertabBaseUrl: string;\n merchantSystemUrn?: string;\n}): string {\n return `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/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 case LicenseTokenInvalidReason.INVALID_HEADER:\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n rslError = \"invalid_token\";\n errorDescription = \"The license token is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n rslError = \"insufficient_scope\";\n errorDescription = \"The license token is not valid for this resource\";\n break;\n default:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n }\n\n const licenseLink = generateLicenseLink({\n supertabBaseUrl,\n merchantSystemUrn,\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","const jwksCache = new Map<string, any>();\nconst FASTLY_BACKEND = \"sbx-backend\";\n\ntype JwksCacheKey = string;\n\ntype FetchOptions = RequestInit & { backend?: string };\n\ntype FetchJwksParams = {\n cacheKey: JwksCacheKey;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<any> {\n if (!jwksCache.has(cacheKey)) {\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json();\n jwksCache.set(cacheKey, jwksData);\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(cacheKey);\n}\n\nexport async function fetchIssuerJwks(\n baseUrl: string,\n issuer: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/${encodeURIComponent(\n issuer\n )}`;\n\n return fetchAndCacheJwks({\n cacheKey: issuer,\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch JWKS\",\n logLabel: \"Error fetching JWKS:\",\n });\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/.well-known/jwks.json`;\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","import_jose","import_jose","importKeyForAlgs","privateKeyPem","debug","supportedAlgs","algorithm","importError","generateLicenseToken","clientId","kid","tokenEndpoint","resourceUrl","licenseXml","key","alg","now","clientAssertion","payload","requestOptions","response","errorBody","errorMessage","data","parseError","error","generateCustomerJWT","customerURN","expirationSeconds","import_jose","jwksCache","FASTLY_BACKEND","buildFetchOptions","options","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","response","jwksData","error","fetchIssuerJwks","baseUrl","issuer","jwksUrl","fetchPlatformJwks","stripTrailingSlash","value","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","header","error","payload","licenseId","issuer","audienceValues","entry","requestUrlNormalized","normalizedAudience","jwks","fetchPlatformJwks","result","jwtHeader","jwk","key","generateLicenseLink","merchantSystemUrn","baseLicenseHandleRequest","url","userAgent","ctx","recordEvent","verification","recordLicenseEvent","eventName","eventProperties","eventPromise","rslError","errorDescription","licenseLink","errorUri","headers","responseBody","debug","_SupertabConnect","config","reset","url","token","header","error","payload","issuer","jwks","fetchIssuerJwks","jwk","key","eventName","properties","licenseId","options","response","user_agent","ctx","verification","recordEvent","stc","eventProperties","eventPromise","message","details","responseBody","licenseToken","baseLicenseHandleRequest","request","auth","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","botDetectionHandler","clientId","kid","privateKeyPem","tokenEndpoint","resourceUrl","licenseXml","generateLicenseToken","customerURN","expirationSeconds","generateCustomerJWT","SupertabConnect"]}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
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};
1
+ import{jwtVerify as q,decodeProtectedHeader as V,decodeJwt as W}from"jose";import{importPKCS8 as I,SignJWT as E}from"jose";async function L(o,e){let r=["RS256","ES256"];for(let s of r)try{return{key:await I(o,s),alg:s}}catch(n){e&&console.debug(`Private key did not import using ${s}, retrying...`,n)}throw new Error("Unsupported private key format. Expected RSA or P-256 EC private key.")}async function A({clientId:o,kid:e,privateKeyPem:r,tokenEndpoint:s,resourceUrl:n,licenseXml:t,debug:a}){let{key:d,alg:l}=await L(r,a),p=Math.floor(Date.now()/1e3),c=await new E({}).setProtectedHeader({alg:l,kid:e}).setIssuer(o).setSubject(o).setIssuedAt(p).setExpirationTime(p+300).setAudience(s).sign(d),i=new URLSearchParams({grant_type:"rsl",client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:c,license:t,resource:n}),y={method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:i.toString()};try{let g=await fetch(s,y);if(!g.ok){let m=await g.text().catch(()=>""),w=`Failed to obtain license token: ${g.status} ${g.statusText}${m?` - ${m}`:""}`;throw new Error(w)}let f;try{f=await g.json()}catch(m){throw a&&console.error("Failed to parse license token response as JSON:",m),new Error("Failed to parse license token response as JSON")}if(!f?.access_token)throw new Error("License token response missing access_token");return f.access_token}catch(g){throw a&&console.error("Error generating license token:",g),g}}async function k({customerURN:o,kid:e,privateKeyPem:r,expirationSeconds:s=3600}){let n="RS256",t=await I(r,n),a=Math.floor(Date.now()/1e3);return new E({}).setProtectedHeader({alg:n,kid:e}).setIssuer(o).setIssuedAt(a).setExpirationTime(a+s).sign(t)}import{decodeProtectedHeader as C,decodeJwt as D,jwtVerify as J}from"jose";var _=new Map,U="sbx-backend";function N(){let o={method:"GET"};return globalThis?.fastly&&(o={...o,backend:U}),o}async function v({cacheKey:o,url:e,debug:r,failureMessage:s,logLabel:n}){if(!_.has(o))try{let t=await fetch(e,N());if(!t.ok)throw new Error(`${s}: ${t.status}`);let a=await t.json();_.set(o,a)}catch(t){throw r&&console.error(n,t),t}return _.get(o)}async function S(o,e,r){let s=`${o}/.well-known/jwks.json/${encodeURIComponent(e)}`;return v({cacheKey:e,url:s,debug:r,failureMessage:"Failed to fetch JWKS",logLabel:"Error fetching JWKS:"})}async function R(o,e){let r=`${o}/.well-known/.well-known/jwks.json`;return v({cacheKey:"platform_jwks",url:r,debug:e,failureMessage:"Failed to fetch platform JWKS",logLabel:"Error fetching platform JWKS:"})}var T=o=>o.replace(/\/+$/,"");async function x({licenseToken:o,requestUrl:e,supertabBaseUrl:r,debug:s}){if(!o)return{valid:!1,reason:"missing_license_token"};let n;try{n=C(o)}catch(i){return s&&console.error("Invalid license JWT header:",i),{valid:!1,reason:"invalid_license_header"}}if(n.alg!=="ES256")return s&&console.error("Unsupported license JWT alg:",n.alg),{valid:!1,reason:"invalid_license_algorithm"};let t;try{t=D(o)}catch(i){return s&&console.error("Invalid license JWT payload:",i),{valid:!1,reason:"invalid_license_payload"}}let a=t.license_id,d=t.iss;if(!d||!d.startsWith(r))return s&&console.error("Invalid license JWT issuer:",d),{valid:!1,reason:"invalid_license_issuer",licenseId:a};let l=Array.isArray(t.aud)?t.aud.filter(i=>typeof i=="string"):typeof t.aud=="string"?[t.aud]:[],p=T(e);if(!l.some(i=>{let y=T(i);return y?p.startsWith(y):!1}))return s&&console.error("License JWT audience does not match request URL:",t.aud),{valid:!1,reason:"invalid_license_audience",licenseId:a};try{let i=await R(r,s),g=await J(o,async f=>{let m=i.keys.find(w=>w.kid===f.kid);if(!m)throw new Error(`No matching platform key found: ${f.kid}`);return m},{issuer:d,algorithms:[n.alg],clockTolerance:"1m"});return{valid:!0,licenseId:a,payload:g.payload}}catch(i){return s&&console.error("License JWT verification failed:",i),i instanceof Error&&i.message?.includes("exp")?{valid:!1,reason:"license_token_expired",licenseId:a}:{valid:!1,reason:"license_signature_verification_failed",licenseId:a}}}function H({supertabBaseUrl:o,merchantSystemUrn:e}){return`${o}/merchants/systems/${e}/license.xml`}async function b({licenseToken:o,url:e,userAgent:r,ctx:s,supertabBaseUrl:n,merchantSystemUrn:t,debug:a,recordEvent:d}){let l=await x({licenseToken:o,requestUrl:e,supertabBaseUrl:n,debug:a});async function p(c){let i={page_url:e,user_agent:r,verification_status:l.valid?"valid":"invalid",verification_reason:l.reason||"success"},y=d(c,i,l.licenseId);return s?.waitUntil&&s.waitUntil(y),y}if(!l.valid){await p(l.reason||"license_token_verification_failed");let c="invalid_request",i="Access to this resource requires a license";switch(l.reason){case"missing_license_token":c="invalid_request",i="Access to this resource requires a license";break;case"license_token_expired":c="invalid_token",i="The license token has expired";break;case"license_signature_verification_failed":case"invalid_license_header":case"invalid_license_payload":c="invalid_token",i="The license token is invalid";break;case"invalid_license_issuer":case"invalid_license_audience":c="insufficient_scope",i="The license token is not valid for this resource";break;default:c="invalid_request",i="Access to this resource requires a license"}let y=H({supertabBaseUrl:n,merchantSystemUrn:t}),g=`${n}/docs/errors#${c}`,f=new Headers({"Content-Type":"text/plain; charset=UTF-8","WWW-Authenticate":`License error="${c}", error_description="${i}", error_uri="${g}"`,Link:`${y}; rel="license"; type="application/rsl+xml"`}),m=`Access to this resource requires a valid license token. Error: ${c} - ${i}`;return new Response(m,{status:401,headers:f})}return await p("license_used"),new Response("\u2705 License Token Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}var h=!0,u=class u{constructor(e,r=!1){if(!r&&u._instance){if(!(e.apiKey===u._instance.apiKey&&e.merchantSystemUrn===u._instance.merchantSystemUrn))throw new Error("Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.");return u._instance}if(r&&u._instance&&u.resetInstance(),!e.apiKey||!e.merchantSystemUrn)throw new Error("Missing required configuration: apiKey and merchantSystemUrn are required");this.apiKey=e.apiKey,this.merchantSystemUrn=e.merchantSystemUrn,u._instance=this}static resetInstance(){u._instance=null}static setBaseUrl(e){u.baseUrl=e}async verifyToken(e){if(!e)return{valid:!1,reason:"missing_token"};let r;try{r=V(e)}catch(t){return h&&console.error("Invalid JWT header:",t),{valid:!1,reason:"invalid_header"}}if(r.alg!=="RS256")return{valid:!1,reason:"invalid_algorithm"};let s;try{s=W(e)}catch(t){return h&&console.error("Invalid JWT payload:",t),{valid:!1,reason:"invalid_payload"}}let n=s.iss;if(!n||!n.startsWith("urn:stc:customer:"))return{valid:!1,reason:"invalid_issuer"};try{let t=await S(u.baseUrl,n,h);return{valid:!0,payload:(await q(e,async l=>{let p=t.keys.find(c=>c.kid===l.kid);if(!p)throw new Error(`No matching key found: ${l.kid}`);return p},{issuer:n,algorithms:["RS256"],clockTolerance:"1m"})).payload}}catch(t){return h&&console.error("JWT verification failed:",t),t.message?.includes("exp")?{valid:!1,reason:"token_expired"}:{valid:!1,reason:"signature_verification_failed"}}}async recordEvent(e,r={},s){let n={event_name:e,merchant_system_urn:this.merchantSystemUrn?this.merchantSystemUrn:"",license_id:s,properties:r};try{let t={method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(n)};globalThis?.fastly&&(t={...t,backend:"sbx-backend"});let a=await fetch(`${u.baseUrl}/events`,t);a.ok||console.log(`Failed to record event: ${a.status}`)}catch(t){console.log("Error recording event:",t)}}async baseHandleRequest(e,r,s,n){let t=await this.verifyToken(e);async function a(d,l,p){let c={page_url:r,user_agent:s,verification_status:t.valid?"valid":"invalid",verification_reason:t.reason||"success"};if(p){let i=d.recordEvent(l,c);return p.waitUntil(i),i}else return await d.recordEvent(l,c)}if(!t.valid){await a(this,t.reason||"token_verification_failed",n);let d="Payment required: you need to present a valid Supertab Connect token to access this content. Check out the provided url for details",l="\u274C Content access denied"+(t.reason?`: ${t.reason}`:""),c={url:`${u.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`,message:d,details:l};return new Response(JSON.stringify(c),{status:402,headers:new Headers({"Content-Type":"application/json"})})}return await a(this,"page_viewed",n),new Response("\u2705 Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})})}async baseLicenseHandleRequest(e,r,s,n){return b({licenseToken:e,url:r,userAgent:s,ctx:n,supertabBaseUrl:u.baseUrl,merchantSystemUrn:this.merchantSystemUrn,debug:h,recordEvent:(t,a,d)=>this.recordEvent(t,a,d)})}extractDataFromRequest(e){let r=e.headers.get("Authorization")||"",s=r.startsWith("Bearer ")?r.slice(7):"",n=r.startsWith("License ")?r.slice(8):"",t=e.url,a=e.headers.get("User-Agent")||"unknown";return{token:s,licenseToken:n,url:t,user_agent:a}}static checkIfBotRequest(e){let r=e.headers.get("User-Agent")||"",s=e.headers.get("accept")||"",n=e.headers.get("sec-ch-ua"),t=e.headers.get("accept-language"),a=e.cf?.botManagement?.score,d=["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"],l=r.toLowerCase(),p=d.some(f=>l.includes(f)),c=r.toLowerCase().includes("headless")||r.toLowerCase().includes("puppeteer")||!n,i=!r.toLowerCase().includes("headless")||!r.toLowerCase().includes("puppeteer")||!n,y=!s||!t,g=typeof a=="number"&&a<30;return console.log("Bot Detection Details:",{botUaMatch:p,headlessIndicators:c,missingHeaders:y,lowBotScore:g,botScore:a}),(l.includes("safari")||l.includes("mozilla"))&&c&&i?!1:p||c||y||g}static async cloudflareHandleRequests(e,r,s){let{MERCHANT_SYSTEM_URN:n,MERCHANT_API_KEY:t}=r;return new u({apiKey:t,merchantSystemUrn:n}).handleRequest(e,u.checkIfBotRequest,s)}static async fastlyHandleRequests(e,r,s){return new u({apiKey:s,merchantSystemUrn:r}).handleRequest(e,u.checkIfBotRequest,null)}async handleRequest(e,r,s){let{token:n,licenseToken:t,url:a,user_agent:d}=this.extractDataFromRequest(e);return r&&!r(e,s)?new Response("\u2705 Non-Bot Content Access granted",{status:200,headers:new Headers({"Content-Type":"application/json"})}):n?this.baseHandleRequest(n,a,d,s):this.baseLicenseHandleRequest(t,a,d,s)}static async generateLicenseToken(e,r,s,n,t,a){return A({clientId:e,kid:r,privateKeyPem:s,tokenEndpoint:n,resourceUrl:t,licenseXml:a,debug:h})}static async generateCustomerJWT(e,r,s,n=3600){return k({customerURN:e,kid:r,privateKeyPem:s,expirationSeconds:n})}};u.baseUrl="https://api-connect.sbx.supertab.co",u._instance=null;var P=u;export{P 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 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"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/customer.ts","../src/license.ts","../src/jwks.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} from \"jose\";\nimport {\n generateLicenseToken as generateLicenseTokenHelper,\n generateCustomerJWT as generateCustomerJWTHelper,\n} from \"./customer\";\nimport {\n baseLicenseHandleRequest as baseLicenseHandleRequestHelper,\n} from \"./license\";\nimport { fetchIssuerJwks, fetchPlatformJwks } from \"./jwks\";\n\nexport type { Env } from \"./types\";\n\nconst debug = true; // Set to true for debugging purposes\n\n/**\n * SupertabConnect class provides higher level methods\n * for using Supertab Connect within supported CDN integrations\n * as well as more specialized methods to customarily verify JWT tokens and record events.\n */\nexport class SupertabConnect {\n private apiKey?: string;\n private static baseUrl: string = \"https://api-connect.sbx.supertab.co\";\n private merchantSystemUrn?: string;\n\n private static _instance: SupertabConnect | null = null;\n\n public constructor(config: SupertabConnectConfig, reset: boolean = false) {\n if (!reset && SupertabConnect._instance) {\n // If reset was not requested and an instance conflicts with the provided config, throw an error\n if (\n !(\n config.apiKey === SupertabConnect._instance.apiKey &&\n config.merchantSystemUrn ===\n SupertabConnect._instance.merchantSystemUrn\n )\n ) {\n throw new Error(\n \"Cannot create a new instance with different configuration. Use resetInstance to clear the existing instance.\"\n );\n }\n\n // If an instance already exists and reset is not requested, just return the existing instance\n return SupertabConnect._instance;\n }\n if (reset && SupertabConnect._instance) {\n // ...and if reset is requested and required, clear the existing instance first\n SupertabConnect.resetInstance();\n }\n\n if (!config.apiKey || !config.merchantSystemUrn) {\n throw new Error(\n \"Missing required configuration: apiKey and merchantSystemUrn are required\"\n );\n }\n this.apiKey = config.apiKey;\n this.merchantSystemUrn = config.merchantSystemUrn;\n\n // Register this as the singleton instance\n SupertabConnect._instance = this;\n }\n\n public static resetInstance(): void {\n SupertabConnect._instance = null;\n }\n\n /**\n * Override the default base URL for API requests (intended for local development/testing).\n */\n public static setBaseUrl(url: string): void {\n SupertabConnect.baseUrl = url;\n }\n\n /**\n * Verify a JWT token\n * @param token The JWT token to verify\n * @returns A promise that resolves with the verification result\n */\n async verifyToken(token: string): Promise<TokenVerificationResult> {\n // 1. Check if token exists\n if (!token) {\n return {\n valid: false,\n reason: TokenInvalidReason.MISSING_TOKEN,\n };\n }\n\n // 2. Verify header and algorithm\n let header: JWTHeaderParameters;\n try {\n header = decodeProtectedHeader(token) as JWTHeaderParameters;\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT header:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_HEADER,\n };\n }\n\n if (header.alg !== \"RS256\") {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ALG,\n };\n }\n\n // 3. Verify payload and issuer\n let payload: JWTPayload;\n try {\n payload = decodeJwt(token);\n } catch (error) {\n if (debug) {\n console.error(\"Invalid JWT payload:\", error);\n }\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_PAYLOAD,\n };\n }\n\n const issuer: string | undefined = payload.iss;\n if (!issuer || !issuer.startsWith(\"urn:stc:customer:\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.INVALID_ISSUER,\n };\n }\n\n // 4. Verify signature\n try {\n const jwks = await fetchIssuerJwks(\n SupertabConnect.baseUrl,\n issuer,\n debug\n );\n\n // Create a key finder function for verification\n const getKey = async (header: JWTHeaderParameters) => {\n const jwk = jwks.keys.find((key: any) => key.kid === header.kid);\n if (!jwk) throw new Error(`No matching key found: ${header.kid}`);\n return jwk;\n };\n\n const result = await jwtVerify(token, getKey, {\n issuer,\n algorithms: [\"RS256\"],\n clockTolerance: \"1m\",\n });\n\n // Success case - token is valid\n return {\n valid: true,\n payload: result.payload,\n };\n } catch (error: any) {\n if (debug) {\n console.error(\"JWT verification failed:\", error);\n }\n\n // Check if token is expired\n if (error.message?.includes(\"exp\")) {\n return {\n valid: false,\n reason: TokenInvalidReason.EXPIRED,\n };\n }\n\n return {\n valid: false,\n reason: TokenInvalidReason.SIGNATURE_VERIFICATION_FAILED,\n };\n }\n }\n\n /**\n * Records an analytics event\n * @param eventName Name of the event to record\n * @param properties Additional properties to include with the event\n * @param licenseId Optional license ID associated with the event\n * @returns Promise that resolves when the event is recorded\n */\n async recordEvent(\n eventName: string,\n properties: Record<string, any> = {},\n licenseId?: string\n ): Promise<void> {\n const payload: EventPayload = {\n event_name: eventName,\n merchant_system_urn: this.merchantSystemUrn ? this.merchantSystemUrn : \"\",\n license_id: licenseId,\n properties,\n };\n\n try {\n let options: any = {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n };\n // @ts-ignore\n if (globalThis?.fastly) {\n options = { ...options, backend: \"sbx-backend\" };\n }\n const response = await fetch(\n `${SupertabConnect.baseUrl}/events`,\n options\n );\n\n if (!response.ok) {\n console.log(`Failed to record event: ${response.status}`);\n }\n } catch (error) {\n console.log(\"Error recording event:\", error);\n }\n }\n\n /**\n * Handle the request, report an event to Supertab Connect and return a response\n */\n private async baseHandleRequest(\n token: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n // 1. Verify token\n const verification = await this.verifyToken(token);\n\n // Record event helper\n async function recordEvent(\n stc: SupertabConnect,\n eventName: string,\n ctx: any\n ) {\n const eventProperties = {\n page_url: url,\n user_agent: user_agent,\n verification_status: verification.valid ? \"valid\" : \"invalid\",\n verification_reason: verification.reason || \"success\",\n };\n if (ctx) {\n const eventPromise = stc.recordEvent(\n eventName,\n eventProperties\n );\n ctx.waitUntil(eventPromise);\n return eventPromise;\n } else {\n return await stc.recordEvent(\n eventName,\n eventProperties\n );\n }\n }\n\n // 2. Handle based on verification result\n if (!verification.valid) {\n await recordEvent(\n this,\n verification.reason || \"token_verification_failed\",\n ctx\n );\n const message =\n \"Payment required: you need to present a valid Supertab Connect token to access this content. \" +\n \"Check out the provided url for details\";\n const details =\n \"❌ Content access denied\" +\n (verification.reason ? `: ${verification.reason}` : \"\");\n const contentAccessUrl = `${SupertabConnect.baseUrl}/merchants/systems/${this.merchantSystemUrn}/content-access.json`;\n\n const responseBody = {\n url: contentAccessUrl,\n message: message,\n details: details,\n };\n\n return new Response(JSON.stringify(responseBody), {\n status: 402,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Success\n await recordEvent(this, \"page_viewed\", ctx);\n return new Response(\"✅ Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n /**\n * Handle the request for license tokens, report an event to Supertab Connect and return a response\n */\n private async baseLicenseHandleRequest(\n licenseToken: string,\n url: string,\n user_agent: string,\n ctx: any\n ): Promise<Response> {\n return baseLicenseHandleRequestHelper({\n licenseToken,\n url,\n userAgent: user_agent,\n ctx,\n supertabBaseUrl: SupertabConnect.baseUrl,\n merchantSystemUrn: this.merchantSystemUrn,\n debug,\n recordEvent: (\n eventName: string,\n properties?: Record<string, any>,\n licenseId?: string,\n ) => this.recordEvent(eventName, properties, licenseId),\n });\n }\n\n private extractDataFromRequest(request: Request): {\n token: string;\n licenseToken: string;\n url: string;\n user_agent: string;\n } {\n // Parse token\n const auth = request.headers.get(\"Authorization\") || \"\";\n const token = auth.startsWith(\"Bearer \") ? auth.slice(7) : \"\";\n const licenseToken = auth.startsWith(\"License \") ? auth.slice(8) : \"\";\n\n // Extract URL and user agent\n const url = request.url;\n const user_agent = request.headers.get(\"User-Agent\") || \"unknown\";\n\n return { token, licenseToken, url, user_agent };\n }\n\n static checkIfBotRequest(request: Request): boolean {\n const userAgent = request.headers.get(\"User-Agent\") || \"\";\n const accept = request.headers.get(\"accept\") || \"\";\n const secChUa = request.headers.get(\"sec-ch-ua\");\n const acceptLanguage = request.headers.get(\"accept-language\");\n const botScore = (request as any).cf?.botManagement?.score;\n\n const botList = [\n \"chatgpt-user\",\n \"perplexitybot\",\n \"gptbot\",\n \"anthropic-ai\",\n \"ccbot\",\n \"claude-web\",\n \"claudebot\",\n \"cohere-ai\",\n \"youbot\",\n \"diffbot\",\n \"oai-searchbot\",\n \"meta-externalagent\",\n \"timpibot\",\n \"amazonbot\",\n \"bytespider\",\n \"perplexity-user\",\n \"googlebot\",\n \"bot\",\n \"curl\",\n \"wget\",\n ];\n // 1. Basic substring check from known list\n const lowerCaseUserAgent = userAgent.toLowerCase();\n const botUaMatch = botList.some((bot) => lowerCaseUserAgent.includes(bot));\n\n // 2. Headless browser detection\n const headlessIndicators =\n userAgent.toLowerCase().includes(\"headless\") ||\n userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n const only_sec_ch_ua_missing =\n !userAgent.toLowerCase().includes(\"headless\") ||\n !userAgent.toLowerCase().includes(\"puppeteer\") ||\n !secChUa;\n\n // 3. Suspicious header gaps — many bots omit these\n const missingHeaders = !accept || !acceptLanguage;\n\n // 4. Cloudflare bot score check (if available)\n const lowBotScore = typeof botScore === \"number\" && botScore < 30;\n console.log(\"Bot Detection Details:\", {\n botUaMatch,\n headlessIndicators,\n missingHeaders,\n lowBotScore,\n botScore,\n });\n\n // Safari and Mozilla special case\n if (\n lowerCaseUserAgent.includes(\"safari\") ||\n lowerCaseUserAgent.includes(\"mozilla\")\n ) {\n // Safari is not a bot, but it may be headless\n if (headlessIndicators && only_sec_ch_ua_missing) {\n return false; // Likely not a bot, but missing a Sec-CH-UA header\n }\n }\n\n // Final decision\n return botUaMatch || headlessIndicators || missingHeaders || lowBotScore;\n }\n\n static async cloudflareHandleRequests(\n request: Request,\n env: Env,\n ctx: any\n ): Promise<Response> {\n // Validate required env variables\n const { MERCHANT_SYSTEM_URN, MERCHANT_API_KEY } = env;\n\n // Prepare or get the SupertabConnect instance\n const supertabConnect = new SupertabConnect({\n apiKey: MERCHANT_API_KEY,\n merchantSystemUrn: MERCHANT_SYSTEM_URN,\n });\n\n // Handle the request, including bot detection, token verification and recording the event\n return supertabConnect.handleRequest(\n request,\n SupertabConnect.checkIfBotRequest,\n ctx\n );\n }\n\n static async fastlyHandleRequests(\n request: Request,\n merchantSystemUrn: string,\n merchantApiKey: string\n ): 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, license token, URL, and user agent from the request\n const { token, licenseToken, url, user_agent } =\n this.extractDataFromRequest(request);\n\n // 2. Handle bot detection if provided\n if (botDetectionHandler && !botDetectionHandler(request, ctx)) {\n return new Response(\"✅ Non-Bot Content Access granted\", {\n status: 200,\n headers: new Headers({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n // 3. Check for bearer token first, then fallback to license token\n if (token) {\n return this.baseHandleRequest(token, url, user_agent, ctx);\n }\n\n // 4. Call the base licenhandle request method and return the result\n return this.baseLicenseHandleRequest(licenseToken, url, user_agent, ctx);\n }\n\n /**\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 tokenEndpoint: string,\n resourceUrl: string,\n licenseXml: string\n ): Promise<string> {\n return generateLicenseTokenHelper({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n });\n }\n\n /** Generate a customer JWT\n * @param customerURN The customer's unique resource name (URN).\n * @param kid The key ID to include in the JWT header.\n * @param privateKeyPem The private key in PEM format used to sign the JWT.\n * @param expirationSeconds The token's expiration time in seconds (default is 3600 seconds).\n * @returns A promise that resolves to the generated JWT as a string.\n */\n static async generateCustomerJWT(\n customerURN: string,\n kid: string,\n privateKeyPem: string,\n expirationSeconds: number = 3600\n ): Promise<string> {\n return generateCustomerJWTHelper({\n customerURN,\n kid,\n privateKeyPem,\n expirationSeconds,\n });\n }\n}\n","import { importPKCS8, SignJWT } from \"jose\";\n\ntype SupportedAlg = \"RS256\" | \"ES256\";\n\ntype GenerateLicenseTokenParams = {\n clientId: string;\n kid: string;\n privateKeyPem: string;\n tokenEndpoint: string;\n resourceUrl: string;\n licenseXml: string;\n debug?: boolean;\n};\n\ntype GenerateCustomerJwtParams = {\n customerURN: string;\n kid: string;\n privateKeyPem: string;\n expirationSeconds?: number;\n};\n\nasync function importKeyForAlgs(\n privateKeyPem: string,\n debug: boolean | undefined\n): Promise<{ key: CryptoKey; alg: SupportedAlg }> {\n const supportedAlgs: SupportedAlg[] = [\"RS256\", \"ES256\"];\n\n for (const algorithm of supportedAlgs) {\n try {\n const key = await importPKCS8(privateKeyPem, algorithm);\n return { key, alg: algorithm };\n } catch (importError) {\n if (debug) {\n console.debug(\n `Private key did not import using ${algorithm}, retrying...`,\n importError\n );\n }\n }\n }\n\n throw new Error(\n \"Unsupported private key format. Expected RSA or P-256 EC private key.\"\n );\n}\n\nexport async function generateLicenseToken({\n clientId,\n kid,\n privateKeyPem,\n tokenEndpoint,\n resourceUrl,\n licenseXml,\n debug,\n}: GenerateLicenseTokenParams): Promise<string> {\n const { key, alg } = await importKeyForAlgs(privateKeyPem, debug);\n const now = Math.floor(Date.now() / 1000);\n\n const clientAssertion = await new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(clientId)\n .setSubject(clientId)\n .setIssuedAt(now)\n .setExpirationTime(now + 300)\n .setAudience(tokenEndpoint)\n .sign(key);\n\n const payload = new URLSearchParams({\n grant_type: \"rsl\",\n client_assertion_type:\n \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\n client_assertion: clientAssertion,\n license: licenseXml,\n resource: resourceUrl,\n });\n\n const requestOptions: RequestInit = {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: payload.toString(),\n };\n\n try {\n const response = await fetch(tokenEndpoint, requestOptions);\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n const errorMessage = `Failed to obtain license token: ${\n response.status\n } ${response.statusText}${errorBody ? ` - ${errorBody}` : \"\"}`;\n throw new Error(errorMessage);\n }\n\n let data: any;\n try {\n data = await response.json();\n } catch (parseError) {\n if (debug) {\n console.error(\n \"Failed to parse license token response as JSON:\",\n parseError\n );\n }\n throw new Error(\"Failed to parse license token response as JSON\");\n }\n\n if (!data?.access_token) {\n throw new Error(\"License token response missing access_token\");\n }\n\n return data.access_token;\n } catch (error) {\n if (debug) {\n console.error(\"Error generating license token:\", error);\n }\n throw error;\n }\n}\n\nexport async function generateCustomerJWT({\n customerURN,\n kid,\n privateKeyPem,\n expirationSeconds = 3600,\n}: GenerateCustomerJwtParams): Promise<string> {\n const alg: SupportedAlg = \"RS256\";\n const key = await importPKCS8(privateKeyPem, alg);\n\n const now = Math.floor(Date.now() / 1000);\n\n return new SignJWT({})\n .setProtectedHeader({ alg, kid })\n .setIssuer(customerURN)\n .setIssuedAt(now)\n .setExpirationTime(now + expirationSeconds)\n .sign(key);\n}\n\nexport type { GenerateLicenseTokenParams, GenerateCustomerJwtParams };\n","import {\n decodeProtectedHeader,\n decodeJwt,\n JWTPayload,\n JWTHeaderParameters,\n jwtVerify,\n} from \"jose\";\nimport {\n LicenseTokenInvalidReason,\n LicenseTokenVerificationResult,\n} 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 supertabBaseUrl,\n merchantSystemUrn,\n}: {\n supertabBaseUrl: string;\n merchantSystemUrn?: string;\n}): string {\n return `${supertabBaseUrl}/merchants/systems/${merchantSystemUrn}/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 case LicenseTokenInvalidReason.INVALID_HEADER:\n case LicenseTokenInvalidReason.INVALID_PAYLOAD:\n rslError = \"invalid_token\";\n errorDescription = \"The license token is invalid\";\n break;\n case LicenseTokenInvalidReason.INVALID_ISSUER:\n case LicenseTokenInvalidReason.INVALID_AUDIENCE:\n rslError = \"insufficient_scope\";\n errorDescription = \"The license token is not valid for this resource\";\n break;\n default:\n rslError = \"invalid_request\";\n errorDescription = \"Access to this resource requires a license\";\n }\n\n const licenseLink = generateLicenseLink({\n supertabBaseUrl,\n merchantSystemUrn,\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","const jwksCache = new Map<string, any>();\nconst FASTLY_BACKEND = \"sbx-backend\";\n\ntype JwksCacheKey = string;\n\ntype FetchOptions = RequestInit & { backend?: string };\n\ntype FetchJwksParams = {\n cacheKey: JwksCacheKey;\n url: string;\n debug: boolean;\n failureMessage: string;\n logLabel: string;\n};\n\nfunction buildFetchOptions(): FetchOptions {\n let options: FetchOptions = { method: \"GET\" };\n // @ts-ignore - backend is a Fastly-specific extension\n if (globalThis?.fastly) {\n options = { ...options, backend: FASTLY_BACKEND };\n }\n return options;\n}\n\nasync function fetchAndCacheJwks({\n cacheKey,\n url,\n debug,\n failureMessage,\n logLabel,\n}: FetchJwksParams): Promise<any> {\n if (!jwksCache.has(cacheKey)) {\n try {\n const response = await fetch(url, buildFetchOptions());\n\n if (!response.ok) {\n throw new Error(`${failureMessage}: ${response.status}`);\n }\n\n const jwksData = await response.json();\n jwksCache.set(cacheKey, jwksData);\n } catch (error) {\n if (debug) {\n console.error(logLabel, error);\n }\n throw error;\n }\n }\n\n return jwksCache.get(cacheKey);\n}\n\nexport async function fetchIssuerJwks(\n baseUrl: string,\n issuer: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/jwks.json/${encodeURIComponent(\n issuer\n )}`;\n\n return fetchAndCacheJwks({\n cacheKey: issuer,\n url: jwksUrl,\n debug,\n failureMessage: \"Failed to fetch JWKS\",\n logLabel: \"Error fetching JWKS:\",\n });\n}\n\nexport async function fetchPlatformJwks(\n baseUrl: string,\n debug: boolean\n): Promise<any> {\n const jwksUrl = `${baseUrl}/.well-known/.well-known/jwks.json`;\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":"AAOA,OACE,aAAAA,EACA,yBAAAC,EACA,aAAAC,MAGK,OCbP,OAAS,eAAAC,EAAa,WAAAC,MAAe,OAqBrC,eAAeC,EACbC,EACAC,EACgD,CAChD,IAAMC,EAAgC,CAAC,QAAS,OAAO,EAEvD,QAAWC,KAAaD,EACtB,GAAI,CAEF,MAAO,CAAE,IADG,MAAML,EAAYG,EAAeG,CAAS,EACxC,IAAKA,CAAU,CAC/B,OAASC,EAAa,CAChBH,GACF,QAAQ,MACN,oCAAoCE,CAAS,gBAC7CC,CACF,CAEJ,CAGF,MAAM,IAAI,MACR,uEACF,CACF,CAEA,eAAsBC,EAAqB,CACzC,SAAAC,EACA,IAAAC,EACA,cAAAP,EACA,cAAAQ,EACA,YAAAC,EACA,WAAAC,EACA,MAAAT,CACF,EAAgD,CAC9C,GAAM,CAAE,IAAAU,EAAK,IAAAC,CAAI,EAAI,MAAMb,EAAiBC,EAAeC,CAAK,EAC1DY,EAAM,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EAElCC,EAAkB,MAAM,IAAIhB,EAAQ,CAAC,CAAC,EACzC,mBAAmB,CAAE,IAAAc,EAAK,IAAAL,CAAI,CAAC,EAC/B,UAAUD,CAAQ,EAClB,WAAWA,CAAQ,EACnB,YAAYO,CAAG,EACf,kBAAkBA,EAAM,GAAG,EAC3B,YAAYL,CAAa,EACzB,KAAKG,CAAG,EAELI,EAAU,IAAI,gBAAgB,CAClC,WAAY,MACZ,sBACE,yDACF,iBAAkBD,EAClB,QAASJ,EACT,SAAUD,CACZ,CAAC,EAEKO,EAA8B,CAClC,OAAQ,OACR,QAAS,CACP,eAAgB,oCAChB,OAAQ,kBACV,EACA,KAAMD,EAAQ,SAAS,CACzB,EAEA,GAAI,CACF,IAAME,EAAW,MAAM,MAAMT,EAAeQ,CAAc,EAE1D,GAAI,CAACC,EAAS,GAAI,CAChB,IAAMC,EAAY,MAAMD,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,EAChDE,EAAe,mCACnBF,EAAS,MACX,IAAIA,EAAS,UAAU,GAAGC,EAAY,MAAMA,CAAS,GAAK,EAAE,GAC5D,MAAM,IAAI,MAAMC,CAAY,CAC9B,CAEA,IAAIC,EACJ,GAAI,CACFA,EAAO,MAAMH,EAAS,KAAK,CAC7B,OAASI,EAAY,CACnB,MAAIpB,GACF,QAAQ,MACN,kDACAoB,CACF,EAEI,IAAI,MAAM,gDAAgD,CAClE,CAEA,GAAI,CAACD,GAAM,aACT,MAAM,IAAI,MAAM,6CAA6C,EAG/D,OAAOA,EAAK,YACd,OAASE,EAAO,CACd,MAAIrB,GACF,QAAQ,MAAM,kCAAmCqB,CAAK,EAElDA,CACR,CACF,CAEA,eAAsBC,EAAoB,CACxC,YAAAC,EACA,IAAAjB,EACA,cAAAP,EACA,kBAAAyB,EAAoB,IACtB,EAA+C,CAC7C,IAAMb,EAAoB,QACpBD,EAAM,MAAMd,EAAYG,EAAeY,CAAG,EAE1CC,EAAM,KAAK,MAAM,KAAK,IAAI,EAAI,GAAI,EAExC,OAAO,IAAIf,EAAQ,CAAC,CAAC,EAClB,mBAAmB,CAAE,IAAAc,EAAK,IAAAL,CAAI,CAAC,EAC/B,UAAUiB,CAAW,EACrB,YAAYX,CAAG,EACf,kBAAkBA,EAAMY,CAAiB,EACzC,KAAKd,CAAG,CACb,CC3IA,OACE,yBAAAe,EACA,aAAAC,EAGA,aAAAC,MACK,OCNP,IAAMC,EAAY,IAAI,IAChBC,EAAiB,cAcvB,SAASC,GAAkC,CACzC,IAAIC,EAAwB,CAAE,OAAQ,KAAM,EAE5C,OAAI,YAAY,SACdA,EAAU,CAAE,GAAGA,EAAS,QAASF,CAAe,GAE3CE,CACT,CAEA,eAAeC,EAAkB,CAC/B,SAAAC,EACA,IAAAC,EACA,MAAAC,EACA,eAAAC,EACA,SAAAC,CACF,EAAkC,CAChC,GAAI,CAACT,EAAU,IAAIK,CAAQ,EACzB,GAAI,CACF,IAAMK,EAAW,MAAM,MAAMJ,EAAKJ,EAAkB,CAAC,EAErD,GAAI,CAACQ,EAAS,GACZ,MAAM,IAAI,MAAM,GAAGF,CAAc,KAAKE,EAAS,MAAM,EAAE,EAGzD,IAAMC,EAAW,MAAMD,EAAS,KAAK,EACrCV,EAAU,IAAIK,EAAUM,CAAQ,CAClC,OAASC,EAAO,CACd,MAAIL,GACF,QAAQ,MAAME,EAAUG,CAAK,EAEzBA,CACR,CAGF,OAAOZ,EAAU,IAAIK,CAAQ,CAC/B,CAEA,eAAsBQ,EACpBC,EACAC,EACAR,EACc,CACd,IAAMS,EAAU,GAAGF,CAAO,0BAA0B,mBAClDC,CACF,CAAC,GAED,OAAOX,EAAkB,CACvB,SAAUW,EACV,IAAKC,EACL,MAAAT,EACA,eAAgB,uBAChB,SAAU,sBACZ,CAAC,CACH,CAEA,eAAsBU,EACpBH,EACAP,EACc,CACd,IAAMS,EAAU,GAAGF,CAAO,qCAE1B,OAAOV,EAAkB,CACvB,SAAU,gBACV,IAAKY,EACL,MAAAT,EACA,eAAgB,gCAChB,SAAU,+BACZ,CAAC,CACH,CDtEA,IAAMW,EAAsBC,GAAkBA,EAAM,QAAQ,OAAQ,EAAE,EAStE,eAAsBC,EAAmB,CACvC,aAAAC,EACA,WAAAC,EACA,gBAAAC,EACA,MAAAC,CACF,EAAsE,CACpE,GAAI,CAACH,EACH,MAAO,CACL,MAAO,GACP,8BACF,EAGF,IAAII,EACJ,GAAI,CACFA,EAASC,EAAsBL,CAAY,CAC7C,OAASM,EAAO,CACd,OAAIH,GACF,QAAQ,MAAM,8BAA+BG,CAAK,EAE7C,CACL,MAAO,GACP,+BACF,CACF,CAEA,GAAIF,EAAO,MAAQ,QACjB,OAAID,GACF,QAAQ,MAAM,+BAAgCC,EAAO,GAAG,EAEnD,CACL,MAAO,GACP,kCACF,EAGF,IAAIG,EACJ,GAAI,CACFA,EAAUC,EAAUR,CAAY,CAClC,OAASM,EAAO,CACd,OAAIH,GACF,QAAQ,MAAM,+BAAgCG,CAAK,EAE9C,CACL,MAAO,GACP,gCACF,CACF,CAGA,IAAMG,EAAgCF,EAAQ,WAExCG,EAA6BH,EAAQ,IAC3C,GAAI,CAACG,GAAU,CAACA,EAAO,WAAWR,CAAe,EAC/C,OAAIC,GACF,QAAQ,MAAM,8BAA+BO,CAAM,EAE9C,CACL,MAAO,GACP,gCACA,UAAAD,CACF,EAGF,IAAME,EAAiB,MAAM,QAAQJ,EAAQ,GAAG,EAC5CA,EAAQ,IAAI,OAAQK,GAA2B,OAAOA,GAAU,QAAQ,EACxE,OAAOL,EAAQ,KAAQ,SACrB,CAACA,EAAQ,GAAG,EACZ,CAAC,EAEDM,EAAuBhB,EAAmBI,CAAU,EAO1D,GAAI,CANsBU,EAAe,KAAMb,GAAU,CACvD,IAAMgB,EAAqBjB,EAAmBC,CAAK,EACnD,OAAKgB,EACED,EAAqB,WAAWC,CAAkB,EADzB,EAElC,CAAC,EAGC,OAAIX,GACF,QAAQ,MACN,mDACAI,EAAQ,GACV,EAEK,CACL,MAAO,GACP,kCACA,UAAAE,CACF,EAGF,GAAI,CACF,IAAMM,EAAO,MAAMC,EAAkBd,EAAiBC,CAAK,EAUrDc,EAAS,MAAMC,EAAUlB,EARhB,MAAOmB,GAAmC,CACvD,IAAMC,EAAML,EAAK,KAAK,KAAMM,GAAaA,EAAI,MAAQF,EAAU,GAAG,EAClE,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,mCAAmCD,EAAU,GAAG,EAAE,EAEpE,OAAOC,CACT,EAEqD,CACnD,OAAAV,EACA,WAAY,CAACN,EAAO,GAAG,EACvB,eAAgB,IAClB,CAAC,EAED,MAAO,CACL,MAAO,GACP,UAAAK,EACA,QAASQ,EAAO,OAClB,CACF,OAASX,EAAO,CAKd,OAJIH,GACF,QAAQ,MAAM,mCAAoCG,CAAK,EAGrDA,aAAiB,OAASA,EAAM,SAAS,SAAS,KAAK,EAClD,CACL,MAAO,GACP,+BACA,UAAAG,CACF,EAGK,CACL,MAAO,GACP,+CACA,UAAAA,CACF,CACF,CACF,CAEO,SAASa,EAAoB,CAClC,gBAAApB,EACA,kBAAAqB,CACF,EAGW,CACT,MAAO,GAAGrB,CAAe,sBAAsBqB,CAAiB,cAClE,CAmBA,eAAsBC,EAAyB,CAC7C,aAAAxB,EACA,IAAAyB,EACA,UAAAC,EACA,IAAAC,EACA,gBAAAzB,EACA,kBAAAqB,EACA,MAAApB,EACA,YAAAyB,CACF,EAAsD,CACpD,IAAMC,EAAe,MAAM9B,EAAmB,CAC5C,aAAAC,EACA,WAAYyB,EACZ,gBAAAvB,EACA,MAAAC,CACF,CAAC,EAED,eAAe2B,EAAmBC,EAAmB,CACnD,IAAMC,EAAkB,CACtB,SAAUP,EACV,WAAYC,EACZ,oBAAqBG,EAAa,MAAQ,QAAU,UACpD,oBAAqBA,EAAa,QAAU,SAC9C,EAEMI,EAAeL,EACnBG,EACAC,EACAH,EAAa,SACf,EAEA,OAAIF,GAAK,WACPA,EAAI,UAAUM,CAAY,EAGrBA,CACT,CAEA,GAAI,CAACJ,EAAa,MAAO,CACvB,MAAMC,EACJD,EAAa,QAAU,mCACzB,EAEA,IAAIK,EAAW,kBACXC,EAAmB,6CAEvB,OAAQN,EAAa,OAAQ,CAC3B,4BACEK,EAAW,kBACXC,EAAmB,6CACnB,MACF,4BACED,EAAW,gBACXC,EAAmB,gCACnB,MACF,4CACA,6BACA,8BACED,EAAW,gBACXC,EAAmB,+BACnB,MACF,6BACA,+BACED,EAAW,qBACXC,EAAmB,mDACnB,MACF,QACED,EAAW,kBACXC,EAAmB,4CACvB,CAEA,IAAMC,EAAcd,EAAoB,CACtC,gBAAApB,EACA,kBAAAqB,CACF,CAAC,EACKc,EAAW,GAAGnC,CAAe,gBAAgBgC,CAAQ,GAErDI,EAAU,IAAI,QAAQ,CAC1B,eAAgB,4BAChB,mBAAoB,kBAAkBJ,CAAQ,yBAAyBC,CAAgB,iBAAiBE,CAAQ,IAChH,KAAM,GAAGD,CAAW,6CACtB,CAAC,EAEKG,EAAe,kEAAkEL,CAAQ,MAAMC,CAAgB,GAErH,OAAO,IAAI,SAASI,EAAc,CAChC,OAAQ,IACR,QAAAD,CACF,CAAC,CACH,CAEA,aAAMR,EAAmB,cAAc,EAChC,IAAI,SAAS,sCAAkC,CACpD,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,CAC7D,CAAC,CACH,CF9PA,IAAMU,EAAQ,GAODC,EAAN,MAAMA,CAAgB,CAOpB,YAAYC,EAA+BC,EAAiB,GAAO,CACxE,GAAI,CAACA,GAASF,EAAgB,UAAW,CAEvC,GACE,EACEC,EAAO,SAAWD,EAAgB,UAAU,QAC5CC,EAAO,oBACLD,EAAgB,UAAU,mBAG9B,MAAM,IAAI,MACR,8GACF,EAIF,OAAOA,EAAgB,SACzB,CAMA,GALIE,GAASF,EAAgB,WAE3BA,EAAgB,cAAc,EAG5B,CAACC,EAAO,QAAU,CAACA,EAAO,kBAC5B,MAAM,IAAI,MACR,2EACF,EAEF,KAAK,OAASA,EAAO,OACrB,KAAK,kBAAoBA,EAAO,kBAGhCD,EAAgB,UAAY,IAC9B,CAEA,OAAc,eAAsB,CAClCA,EAAgB,UAAY,IAC9B,CAKA,OAAc,WAAWG,EAAmB,CAC1CH,EAAgB,QAAUG,CAC5B,CAOA,MAAM,YAAYC,EAAiD,CAEjE,GAAI,CAACA,EACH,MAAO,CACL,MAAO,GACP,sBACF,EAIF,IAAIC,EACJ,GAAI,CACFA,EAASC,EAAsBF,CAAK,CACtC,OAASG,EAAO,CACd,OAAIR,GACF,QAAQ,MAAM,sBAAuBQ,CAAK,EAErC,CACL,MAAO,GACP,uBACF,CACF,CAEA,GAAIF,EAAO,MAAQ,QACjB,MAAO,CACL,MAAO,GACP,0BACF,EAIF,IAAIG,EACJ,GAAI,CACFA,EAAUC,EAAUL,CAAK,CAC3B,OAASG,EAAO,CACd,OAAIR,GACF,QAAQ,MAAM,uBAAwBQ,CAAK,EAEtC,CACL,MAAO,GACP,wBACF,CACF,CAEA,IAAMG,EAA6BF,EAAQ,IAC3C,GAAI,CAACE,GAAU,CAACA,EAAO,WAAW,mBAAmB,EACnD,MAAO,CACL,MAAO,GACP,uBACF,EAIF,GAAI,CACF,IAAMC,EAAO,MAAMC,EACjBZ,EAAgB,QAChBU,EACAX,CACF,EAgBA,MAAO,CACL,MAAO,GACP,SATa,MAAMc,EAAUT,EANhB,MAAOC,GAAgC,CACpD,IAAMS,EAAMH,EAAK,KAAK,KAAMI,GAAaA,EAAI,MAAQV,EAAO,GAAG,EAC/D,GAAI,CAACS,EAAK,MAAM,IAAI,MAAM,0BAA0BT,EAAO,GAAG,EAAE,EAChE,OAAOS,CACT,EAE8C,CAC5C,OAAAJ,EACA,WAAY,CAAC,OAAO,EACpB,eAAgB,IAClB,CAAC,GAKiB,OAClB,CACF,OAASH,EAAY,CAMnB,OALIR,GACF,QAAQ,MAAM,2BAA4BQ,CAAK,EAI7CA,EAAM,SAAS,SAAS,KAAK,EACxB,CACL,MAAO,GACP,sBACF,EAGK,CACL,MAAO,GACP,sCACF,CACF,CACF,CASA,MAAM,YACJS,EACAC,EAAkC,CAAC,EACnCC,EACe,CACf,IAAMV,EAAwB,CAC5B,WAAYQ,EACZ,oBAAqB,KAAK,kBAAoB,KAAK,kBAAoB,GACvE,WAAYE,EACZ,WAAAD,CACF,EAEA,GAAI,CACF,IAAIE,EAAe,CACjB,OAAQ,OACR,QAAS,CACP,cAAe,UAAU,KAAK,MAAM,GACpC,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAUX,CAAO,CAC9B,EAEI,YAAY,SACdW,EAAU,CAAE,GAAGA,EAAS,QAAS,aAAc,GAEjD,IAAMC,EAAW,MAAM,MACrB,GAAGpB,EAAgB,OAAO,UAC1BmB,CACF,EAEKC,EAAS,IACZ,QAAQ,IAAI,2BAA2BA,EAAS,MAAM,EAAE,CAE5D,OAASb,EAAO,CACd,QAAQ,IAAI,yBAA0BA,CAAK,CAC7C,CACF,CAKA,MAAc,kBACZH,EACAD,EACAkB,EACAC,EACmB,CAEnB,IAAMC,EAAe,MAAM,KAAK,YAAYnB,CAAK,EAGjD,eAAeoB,EACbC,EACAT,EACAM,EACA,CACA,IAAMI,EAAkB,CACtB,SAAUvB,EACV,WAAYkB,EACZ,oBAAqBE,EAAa,MAAQ,QAAU,UACpD,oBAAqBA,EAAa,QAAU,SAC9C,EACA,GAAID,EAAK,CACP,IAAMK,EAAeF,EAAI,YACvBT,EACAU,CACF,EACA,OAAAJ,EAAI,UAAUK,CAAY,EACnBA,CACT,KACE,QAAO,MAAMF,EAAI,YACfT,EACAU,CACF,CAEJ,CAGA,GAAI,CAACH,EAAa,MAAO,CACvB,MAAMC,EACJ,KACAD,EAAa,QAAU,4BACvBD,CACF,EACA,IAAMM,EACJ,sIAEIC,EACJ,gCACCN,EAAa,OAAS,KAAKA,EAAa,MAAM,GAAK,IAGhDO,EAAe,CACnB,IAHuB,GAAG9B,EAAgB,OAAO,sBAAsB,KAAK,iBAAiB,uBAI7F,QAAS4B,EACT,QAASC,CACX,EAEA,OAAO,IAAI,SAAS,KAAK,UAAUC,CAAY,EAAG,CAChD,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,CAC7D,CAAC,CACH,CAGA,aAAMN,EAAY,KAAM,cAAeF,CAAG,EACnC,IAAI,SAAS,gCAA4B,CAC9C,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,CAC7D,CAAC,CACH,CAKA,MAAc,yBACZS,EACA5B,EACAkB,EACAC,EACmB,CACnB,OAAOU,EAA+B,CACpC,aAAAD,EACA,IAAA5B,EACA,UAAWkB,EACX,IAAAC,EACA,gBAAiBtB,EAAgB,QACjC,kBAAmB,KAAK,kBACxB,MAAAD,EACA,YAAa,CACXiB,EACAC,EACAC,IACG,KAAK,YAAYF,EAAWC,EAAYC,CAAS,CACxD,CAAC,CACH,CAEQ,uBAAuBe,EAK7B,CAEA,IAAMC,EAAOD,EAAQ,QAAQ,IAAI,eAAe,GAAK,GAC/C7B,EAAQ8B,EAAK,WAAW,SAAS,EAAIA,EAAK,MAAM,CAAC,EAAI,GACrDH,EAAeG,EAAK,WAAW,UAAU,EAAIA,EAAK,MAAM,CAAC,EAAI,GAG7D/B,EAAM8B,EAAQ,IACdZ,EAAaY,EAAQ,QAAQ,IAAI,YAAY,GAAK,UAExD,MAAO,CAAE,MAAA7B,EAAO,aAAA2B,EAAc,IAAA5B,EAAK,WAAAkB,CAAW,CAChD,CAEA,OAAO,kBAAkBY,EAA2B,CAClD,IAAME,EAAYF,EAAQ,QAAQ,IAAI,YAAY,GAAK,GACjDG,EAASH,EAAQ,QAAQ,IAAI,QAAQ,GAAK,GAC1CI,EAAUJ,EAAQ,QAAQ,IAAI,WAAW,EACzCK,EAAiBL,EAAQ,QAAQ,IAAI,iBAAiB,EACtDM,EAAYN,EAAgB,IAAI,eAAe,MAE/CO,EAAU,CACd,eACA,gBACA,SACA,eACA,QACA,aACA,YACA,YACA,SACA,UACA,gBACA,qBACA,WACA,YACA,aACA,kBACA,YACA,MACA,OACA,MACF,EAEMC,EAAqBN,EAAU,YAAY,EAC3CO,EAAaF,EAAQ,KAAMG,GAAQF,EAAmB,SAASE,CAAG,CAAC,EAGnEC,EACJT,EAAU,YAAY,EAAE,SAAS,UAAU,GAC3CA,EAAU,YAAY,EAAE,SAAS,WAAW,GAC5C,CAACE,EAEGQ,EACJ,CAACV,EAAU,YAAY,EAAE,SAAS,UAAU,GAC5C,CAACA,EAAU,YAAY,EAAE,SAAS,WAAW,GAC7C,CAACE,EAGGS,EAAiB,CAACV,GAAU,CAACE,EAG7BS,EAAc,OAAOR,GAAa,UAAYA,EAAW,GAU/D,OATA,QAAQ,IAAI,yBAA0B,CACpC,WAAAG,EACA,mBAAAE,EACA,eAAAE,EACA,YAAAC,EACA,SAAAR,CACF,CAAC,GAICE,EAAmB,SAAS,QAAQ,GACpCA,EAAmB,SAAS,SAAS,IAGjCG,GAAsBC,EACjB,GAKJH,GAAcE,GAAsBE,GAAkBC,CAC/D,CAEA,aAAa,yBACXd,EACAe,EACA1B,EACmB,CAEnB,GAAM,CAAE,oBAAA2B,EAAqB,iBAAAC,CAAiB,EAAIF,EASlD,OANwB,IAAIhD,EAAgB,CAC1C,OAAQkD,EACR,kBAAmBD,CACrB,CAAC,EAGsB,cACrBhB,EACAjC,EAAgB,kBAChBsB,CACF,CACF,CAEA,aAAa,qBACXW,EACAkB,EACAC,EACmB,CAQnB,OANwB,IAAIpD,EAAgB,CAC1C,OAAQoD,EACR,kBAAmBD,CACrB,CAAC,EAGsB,cACrBlB,EACAjC,EAAgB,kBAChB,IACF,CACF,CAEA,MAAM,cACJiC,EACAoB,EACA/B,EACmB,CAEnB,GAAM,CAAE,MAAAlB,EAAO,aAAA2B,EAAc,IAAA5B,EAAK,WAAAkB,CAAW,EAC3C,KAAK,uBAAuBY,CAAO,EAGrC,OAAIoB,GAAuB,CAACA,EAAoBpB,EAASX,CAAG,EACnD,IAAI,SAAS,wCAAoC,CACtD,OAAQ,IACR,QAAS,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,CAC7D,CAAC,EAIClB,EACK,KAAK,kBAAkBA,EAAOD,EAAKkB,EAAYC,CAAG,EAIpD,KAAK,yBAAyBS,EAAc5B,EAAKkB,EAAYC,CAAG,CACzE,CAYA,aAAa,qBACXgC,EACAC,EACAC,EACAC,EACAC,EACAC,EACiB,CACjB,OAAOC,EAA2B,CAChC,SAAAN,EACA,IAAAC,EACA,cAAAC,EACA,cAAAC,EACA,YAAAC,EACA,WAAAC,EACA,MAAA5D,CACF,CAAC,CACH,CASA,aAAa,oBACX8D,EACAN,EACAC,EACAM,EAA4B,KACX,CACjB,OAAOC,EAA0B,CAC/B,YAAAF,EACA,IAAAN,EACA,cAAAC,EACA,kBAAAM,CACF,CAAC,CACH,CACF,EA1fa9D,EAEI,QAAkB,sCAFtBA,EAKI,UAAoC,KAL9C,IAAMgE,EAANhE","names":["jwtVerify","decodeProtectedHeader","decodeJwt","importPKCS8","SignJWT","importKeyForAlgs","privateKeyPem","debug","supportedAlgs","algorithm","importError","generateLicenseToken","clientId","kid","tokenEndpoint","resourceUrl","licenseXml","key","alg","now","clientAssertion","payload","requestOptions","response","errorBody","errorMessage","data","parseError","error","generateCustomerJWT","customerURN","expirationSeconds","decodeProtectedHeader","decodeJwt","jwtVerify","jwksCache","FASTLY_BACKEND","buildFetchOptions","options","fetchAndCacheJwks","cacheKey","url","debug","failureMessage","logLabel","response","jwksData","error","fetchIssuerJwks","baseUrl","issuer","jwksUrl","fetchPlatformJwks","stripTrailingSlash","value","verifyLicenseToken","licenseToken","requestUrl","supertabBaseUrl","debug","header","decodeProtectedHeader","error","payload","decodeJwt","licenseId","issuer","audienceValues","entry","requestUrlNormalized","normalizedAudience","jwks","fetchPlatformJwks","result","jwtVerify","jwtHeader","jwk","key","generateLicenseLink","merchantSystemUrn","baseLicenseHandleRequest","url","userAgent","ctx","recordEvent","verification","recordLicenseEvent","eventName","eventProperties","eventPromise","rslError","errorDescription","licenseLink","errorUri","headers","responseBody","debug","_SupertabConnect","config","reset","url","token","header","decodeProtectedHeader","error","payload","decodeJwt","issuer","jwks","fetchIssuerJwks","jwtVerify","jwk","key","eventName","properties","licenseId","options","response","user_agent","ctx","verification","recordEvent","stc","eventProperties","eventPromise","message","details","responseBody","licenseToken","baseLicenseHandleRequest","request","auth","userAgent","accept","secChUa","acceptLanguage","botScore","botList","lowerCaseUserAgent","botUaMatch","bot","headlessIndicators","only_sec_ch_ua_missing","missingHeaders","lowBotScore","env","MERCHANT_SYSTEM_URN","MERCHANT_API_KEY","merchantSystemUrn","merchantApiKey","botDetectionHandler","clientId","kid","privateKeyPem","tokenEndpoint","resourceUrl","licenseXml","generateLicenseToken","customerURN","expirationSeconds","generateCustomerJWT","SupertabConnect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getsupertab/supertab-connect-sdk",
3
- "version": "0.1.0-beta.19",
3
+ "version": "0.1.0-beta.21",
4
4
  "description": "Supertab Connect SDK (beta)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",