postex-auth-sdk-stage 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -6
- package/dist/auth.d.ts +4 -6
- package/dist/postex-auth-sdk-stage.es.js +368 -365
- package/dist/postex-auth-sdk-stage.iife.js +1 -1
- package/dist/postex-auth-sdk-stage.umd.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
var PostexAuthSDKStage=(function(d){"use strict";function T(){return typeof window<"u"&&typeof window.PublicKeyCredential<"u"&&typeof navigator<"u"&&typeof navigator.credentials<"u"}async function O(){if(!T())return!1;try{return await PublicKeyCredential.isConditionalMediationAvailable?.()??!1}catch{return!1}}function _(a){const e=new Uint8Array(a);let r="";for(let t=0;t<e.byteLength;t++)r+=String.fromCharCode(e[t]);return btoa(r)}function E(a){if(typeof a!="string"||!a)throw new Error("Invalid base64: expected non-empty string");const e=a.replace(/\s/g,"").replace(/-/g,"+").replace(/_/g,"/"),r=e.length%4,t=r>0?e+"=".repeat(4-r):e;try{const o=atob(t),n=new Uint8Array(o.length);for(let s=0;s<o.length;s++)n[s]=o.charCodeAt(s);return n.buffer}catch{throw new Error("Invalid base64: string is not correctly encoded. Check challenge/credentialId from server.")}}function p(a){return _(a).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function Z(a){return/^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i.test(a)}function Q(a){const e=a.replace(/-/g,""),r=new Uint8Array(e.length/2);for(let t=0;t<r.length;t++)r[t]=parseInt(e.substr(t*2,2),16);return r.buffer}function P(a){if(!a||typeof a!="string")throw new Error("Invalid input: expected non-empty string");return Z(a)?Q(a):E(a)}function k(a){return new TextEncoder().encode(a).buffer}function N(a){return String.fromCharCode(...a)}const ee="xpay_webauthn",te=1,u="passkey_data",S="passkey_email",A="passkey_mobile_number";function f(){return new Promise((a,e)=>{const r=indexedDB.open(ee,te);r.onerror=()=>e(r.error),r.onsuccess=()=>a(r.result),r.onupgradeneeded=()=>{const t=r.result;t.objectStoreNames.contains(u)||t.createObjectStore(u)}})}async function K(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readonly"),n=t.objectStore(u).get(S);n.onerror=()=>r(n.error),n.onsuccess=()=>e(n.result??null),t.oncomplete=()=>a.close()})}catch{return null}}async function C(a){try{const e=await f();return new Promise((r,t)=>{const o=e.transaction(u,"readwrite"),s=o.objectStore(u).put(a,S);s.onerror=()=>t(s.error),s.onsuccess=()=>r(),o.oncomplete=()=>e.close()})}catch{}}async function q(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readwrite"),n=t.objectStore(u).delete(S);n.onerror=()=>r(n.error),n.onsuccess=()=>e(),t.oncomplete=()=>a.close()})}catch{}}async function B(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readonly"),n=t.objectStore(u).get(A);n.onerror=()=>r(n.error),n.onsuccess=()=>e(n.result??null),t.oncomplete=()=>a.close()})}catch{return null}}async function $(a){try{const e=await f();return new Promise((r,t)=>{const o=e.transaction(u,"readwrite"),s=o.objectStore(u).put(a,A);s.onerror=()=>t(s.error),s.onsuccess=()=>r(),o.oncomplete=()=>e.close()})}catch{}}async function M(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readwrite"),n=t.objectStore(u).delete(A);n.onerror=()=>r(n.error),n.onsuccess=()=>e(),t.oncomplete=()=>a.close()})}catch{}}const I="dpop_private_key",v="dpop_public_key_jwk";function U(a){if(!a||typeof a!="object")return!1;const e=a;return e.kty==="EC"&&e.crv==="P-256"&&typeof e.x=="string"&&typeof e.y=="string"}class D extends Error{constructor(e){super(e),this.name="DPoPProofGenerationError"}}function re(a){return{kty:"EC",crv:"P-256",x:a.x,y:a.y}}async function L(a){const e=JSON.stringify({crv:a.crv,kty:a.kty,x:a.x,y:a.y}),r=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e));return p(r)}async function ne(a){try{const e=await f();return new Promise((r,t)=>{const o=e.transaction(u,"readwrite"),s=o.objectStore(u).put(a,I);s.onerror=()=>t(s.error),s.onsuccess=()=>r(),o.oncomplete=()=>e.close()})}catch(e){console.error("Failed to store DPoP private key:",e)}}async function oe(a){try{const e=await f();return new Promise((r,t)=>{const o=e.transaction(u,"readwrite"),s=o.objectStore(u).put(a,v);s.onerror=()=>t(s.error),s.onsuccess=()=>r(),o.oncomplete=()=>e.close()})}catch(e){console.error("Failed to store DPoP public key JWK:",e)}}async function z(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readonly"),n=t.objectStore(u).get(I);n.onerror=()=>r(n.error),n.onsuccess=()=>e(n.result??null),t.oncomplete=()=>a.close()})}catch{return null}}async function j(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readonly"),n=t.objectStore(u).get(v);n.onerror=()=>r(n.error),n.onsuccess=()=>e(n.result??null),t.oncomplete=()=>a.close()})}catch{return null}}async function H(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readwrite"),o=t.objectStore(u);o.delete(I),o.delete(v),t.onerror=()=>r(t.error),t.oncomplete=()=>{a.close(),e()}})}catch{}}async function ae(){const a=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign","verify"]),e=await crypto.subtle.exportKey("jwk",a.publicKey),r=re(e),t=await L(r);return await ne(a.privateKey),await oe(r),{publicKey:r,thumbprint:t}}async function se(){const a=await z(),e=await j();return a&&U(e)?{publicKey:e,thumbprint:await L(e)}:((a||e)&&await H(),ae())}async function ie(a,e,r){try{const t=await z(),o=await j();if(!t||!U(o))throw new D("DPoP key material is unavailable or invalid");const n={typ:"dpop+jwt",alg:"ES256",jwk:o},s=new URL(e,typeof window<"u"?window.location.origin:void 0),l=`${s.origin}${s.pathname}`,i={jti:crypto.randomUUID(),htm:a.toUpperCase(),htu:l,iat:Math.floor(Date.now()/1e3)};if(r){const R=new TextEncoder().encode(r),Pe=await crypto.subtle.digest("SHA-256",R);i.ath=p(Pe)}const h=p(new TextEncoder().encode(JSON.stringify(n)).buffer),c=p(new TextEncoder().encode(JSON.stringify(i)).buffer),y=`${h}.${c}`,b=await crypto.subtle.sign({name:"ECDSA",hash:{name:"SHA-256"}},t,new TextEncoder().encode(y)),w=p(b);return`${h}.${c}.${w}`}catch(t){throw console.error("Failed to generate DPoP proof:",t),t instanceof D?t:new D("Failed to generate DPoP proof")}}const J={isWebAuthnSupported:T,isConditionalUISupported:O,arrayBufferToBase64:_,base64ToArrayBuffer:E,arrayBufferToBase64url:p,base64urlToArrayBuffer:P,stringToArrayBuffer:k,uint8ArrayToString:N,getPasskeyEmail:K,setPasskeyEmail:C,clearPasskeyEmail:q,getPasskeyMobileNumber:B,setPasskeyMobileNumber:$,clearPasskeyMobileNumber:M};typeof window<"u"&&(window.WebAuthn=J);const ce=typeof window<"u"?window:globalThis;ce.WebAuthn=J;const g="postex-auth-token",le="postexglobal",ue={xstak:"https://auth-stage.xstak.com/public/v1",postex:"https://auth-stage.postex.pk/public/v1",callcourier:"https://auth-stage.callcourier.com.pk/public/v1",postexglobal:"https://auth-stage.postexglobal.com/public/v1",postexsa:"https://auth-stage.postex.sa/public/v1"},de=/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/,he=/^\+[1-9]\d{6,14}$/,ye=/^\d{6}$/,pe=/^[A-Za-z0-9_-]{16,512}$/,me=/[\u0000-\u001F\u007F-\u009F]/,fe=/[A-Za-z]/,we=/[\u0400-\u04FF]/,F=254,W=16,G=64;class Y extends Error{constructor(e,r,t){super(t??`Request failed with status ${e}`),this.response={status:e,data:r},this.name="AuthSDKFetchError"}}class m extends Error{constructor(e,r,t="invalid_input"){super(r),this.field=e,this.code=t,this.name="SDKValidationError"}}const V="auth_sdk_id_token",X="auth_sdk_refresh_token";class x{constructor(e){this.config=e,this.dpopInitializationPromise=this.createDPoPInitializationPromise()}getBaseUrl(){const e=this.config.appId??le;return ue[e]}async initializeDPoPState(){await se()}async ensureDPoPInitialized(){await this.dpopInitializationPromise}createDPoPInitializationPromise(){const e=this.initializeDPoPState();return e.catch(()=>{}),e}resetDPoPInitialization(){this.dpopInitializationPromise=this.createDPoPInitializationPromise()}async getRequiredDPoPProof(e,r,t){return await this.ensureDPoPInitialized(),ie(e.toUpperCase(),r,t)}containsControlChars(e){return me.test(e)}hasMixedLatinAndCyrillic(e){return fe.test(e)&&we.test(e)}assertNoControlChars(e,r){if(this.containsControlChars(r))throw new m(e,`${e} must not contain control characters`)}validateEmail(e,r="email"){const t=e.trim();if(!t)throw new m(r,`${r} is required`);if(t.length>F)throw new m(r,`${r} must be ${F} characters or fewer`);this.assertNoControlChars(r,t);const[o=""]=t.split("@");if(this.hasMixedLatinAndCyrillic(o))throw new m(r,`${r} must not mix Latin and Cyrillic characters in the local part`);if(!de.test(t))throw new m(r,`${r} must be a valid email`);return t}validateMobileNumber(e,r="mobileNumber"){const t=e.trim();if(!t)throw new m(r,`${r} is required`);if(t.length>W)throw new m(r,`${r} must be ${W} characters or fewer`);if(this.assertNoControlChars(r,t),!he.test(t))throw new m(r,`${r} must be in E.164 format`);return t}validateOTP(e,r="otp"){const t=e.trim();if(this.assertNoControlChars(r,t),!ye.test(t))throw new m(r,`${r} must be a 6-digit code`);return t}validateMagicLinkToken(e,r="token"){const t=e.trim();if(this.assertNoControlChars(r,t),!pe.test(t))throw new m(r,`${r} must be 16-512 URL-safe characters`);return t}validateRealm(e){const r=e?.trim();if(r){if(this.assertNoControlChars("realm",r),r.length>G)throw new m("realm",`realm must be ${G} characters or fewer`);return r}}normalizeAndValidateIdentifier(e){return typeof e=="string"?{email:this.validateEmail(e)}:{email:e.email?this.validateEmail(e.email):void 0,mobileNumber:e.mobileNumber?this.validateMobileNumber(e.mobileNumber):void 0}}normalizeAuthIdentifier(e){return this.normalizeAndValidateIdentifier(e)}extractRealm(e,r){const t=r?.realm??(e&&typeof e!="string"?e.realm:void 0);return this.validateRealm(t)}buildAuthRequestBody(e,r){const t=this.normalizeAuthIdentifier(e),o=this.extractRealm(e,r);return{...t,...o?{realm:o}:{}}}buildUrl(e,r){const t=this.getBaseUrl().replace(/\/$/,""),o=e.startsWith("/")?e:`/${e}`,n=`${t}${o}`;if(!r||Object.keys(r).length===0)return n;const s=new URLSearchParams(r).toString();return`${n}?${s}`}async request(e,r,t){const o=this.buildUrl(r,t?.params),n={"Content-Type":"application/json",Accept:"application/json","X-API-Key":this.config.apiKey??"",...t?.headers},s={method:e,credentials:"include",headers:n};t?.body!==void 0&&t?.body!==null&&(s.body=JSON.stringify(t.body));const l=await fetch(o,s);if(!l.ok){let c;try{const y=await l.text();c=y?JSON.parse(y):void 0}catch{c=void 0}throw l.status===401&&await this.clearTokens(),new Y(l.status,c)}const i=await l.text();return{data:i?JSON.parse(i):{}}}async getRequestAuthHeaders(e,r){const t=localStorage.getItem(g);if(!t)return{};const o=r.startsWith("http")?r:`${this.getBaseUrl()}${r}`,n=await this.getRequiredDPoPProof(e,o,t);return{Authorization:`Bearer ${t}`,DPoP:n}}async getStatus(e,r){const t=this.normalizeAuthIdentifier(e),o=this.extractRealm(e,r),n={};t.email&&(n.email=t.email),t.mobileNumber&&(n.mobileNumber=t.mobileNumber),o&&(n.realm=o);const s=await this.request("GET","/auth/status",{params:n});return s.data.data??s.data}async initiateAuth(e,r){const t=this.buildAuthRequestBody(e,r),o=await this.request("POST","/auth/initiate",{body:t}),n=o.data.data??o.data;return{status:n.status,challenge:n.challenge,credentialIds:n.credentialIds,rp:n.rp}}async initiateOTP(e,r){const t=this.normalizeAuthIdentifier(e),o=this.extractRealm(e,r),n=t.email,s=t.mobileNumber;if(!n&&!s)throw new m("identifier","Either mobileNumber or email is required");const l={};n&&(l.email=n),s&&(l.mobileNumber=s),o&&(l.realm=o);const h=(await this.request("POST","/otp/initiate",{body:l})).data;return{message:h.message??h.data?.message}}async verifyOTP(e){const r=this.validateOTP(e),t=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/otp/verify`),o=await this.request("POST","/otp/verify",{body:{otp:r},headers:{DPoP:t}}),n=o.data.data??o.data,s=n.AuthenticationResult??n,l=s.access_token??s.accessToken??s.AccessToken,i=s.refresh_token??s.refreshToken??s.RefreshToken,h=s.id_token??s.idToken??s.IdToken,c=s.expires_in??s.expiresIn??s.ExpiresIn??3600,y=s.token_type??s.tokenType??s.TokenType??"Bearer";return l&&await this.storeTokens({accessToken:l,refreshToken:i??"",idToken:h??"",expiresIn:c??3600,tokenType:y??"Bearer"}),{access_token:l,refresh_token:i??"",id_token:h??"",expires_in:c,token_type:y,verified:s.verified,email:s.email??s.Email??"",...s}}async resendOTP(){const e=await this.request("POST","/otp/resend",{}),r=e.data.data??e.data;return{success:r.success,message:r.message,...r}}async verifySignupOTP({mobileNumber:e,otp:r}){const t=this.validateMobileNumber(e),o=this.validateOTP(r),n=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/otp/signup/verify`),s=await this.request("POST","/otp/signup/verify",{body:{mobileNumber:t,otp:o},headers:{DPoP:n}}),l=s.data.data??s.data,i=l.AuthenticationResult??l,h=i.access_token??i.accessToken??i.AccessToken,c=i.refresh_token??i.refreshToken??i.RefreshToken,y=i.id_token??i.idToken??i.IdToken,b=i.expires_in??i.expiresIn??i.ExpiresIn??3600,w=i.token_type??i.tokenType??i.TokenType??"Bearer";return h&&await this.storeTokens({accessToken:h,refreshToken:c??"",idToken:y??"",expiresIn:b??3600,tokenType:w??"Bearer"}),{access_token:h,refresh_token:c??"",id_token:y??"",expires_in:b,token_type:w,verified:i.verified,email:i.email??i.Email??"",...i}}async resendSignupOTP({mobileNumber:e,email:r=""}){const t=this.validateMobileNumber(e),o=r?this.validateEmail(r):"",n=await this.request("POST","/otp/signup/resend",{body:{email:o,mobileNumber:t}}),s=n.data.data??n.data;return{success:s.success,message:s.message,...s}}async verifyMagicLink(e){const r=this.validateMagicLinkToken(e);await this.request("GET","/verify/magic-link",{params:{token:r,redirect_mode:"frontend"}})}async completeMagicLink(){const e=await this.request("POST","/verify/magic-link/complete",{body:{}}),r=e.data.data??e.data,t=r.AuthenticationResult??r,o=t.access_token??t.accessToken??t.AccessToken,n=t.refresh_token??t.refreshToken??t.RefreshToken,s=t.id_token??t.idToken??t.IdToken,l=t.expires_in??t.expiresIn??t.ExpiresIn??3600,i=t.token_type??t.tokenType??t.TokenType??"Bearer";return o&&await this.storeTokens({accessToken:o,refreshToken:n??"",idToken:s??"",expiresIn:l??3600,tokenType:i??"Bearer"}),{access_token:o,refresh_token:n??"",id_token:s??"",expires_in:l,token_type:i,verified:t.verified,email:t.email??t.Email??"",...t}}async initiatePasskeyRegistration(){const e=await this.request("POST","/webauthn/initiate/challenge",{body:{}});return e.data.data??e.data}async registerPasskey(e){const r=this.validateEmail(e),t=await this.initiatePasskeyRegistration();let o;if(t?.user?.id)try{o=P(t.user.id)}catch{o=k(t.user.id)}else o=k(r);const n=(t.pubKeyCredParams??[{type:"public-key",alg:-7},{type:"public-key",alg:-257}]).map(w=>({type:"public-key",alg:w.alg})),s=t.excludeCredentials?.map(w=>({type:"public-key",id:P(w)})),l={challenge:P(t.challenge),rp:{name:t.rp?.name??"XPay",id:t.rp?.id??window.location.hostname},user:{id:o,name:t.user?.name??r,displayName:t.user?.displayName??r},pubKeyCredParams:n,excludeCredentials:s,timeout:t.timeout??6e4,attestation:"direct",authenticatorSelection:{residentKey:"required",userVerification:"required",authenticatorAttachment:"platform"}},i=await navigator.credentials.create({publicKey:l});if(!i)throw new Error("Credential creation failed");const h=i.response,c={clientDataJSON:p(h.clientDataJSON),attestationObject:p(h.attestationObject),rawId:p(i.rawId)};if(!c.rawId)throw new Error("Raw ID is required");const y=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/webauthn/register/challenge`),b=await this.request("POST","/webauthn/register/challenge",{body:c,headers:{DPoP:y}});return b.data.data??b.data}async authenticateWithPasskey({challenge:e,rp:r,credentialIds:t}){const o=await navigator.credentials.get({publicKey:{challenge:P(e),rpId:r?.host??void 0,allowCredentials:t.map(R=>({type:"public-key",id:P(R),transports:["internal"]})),timeout:6e4,userVerification:"required"}});if(!o)throw new Error("Authentication failed");const n=o.response,s={clientDataJSON:p(n.clientDataJSON),authenticatorData:p(n.authenticatorData),signature:p(n.signature),rawId:p(o.rawId),userHandle:n.userHandle?p(n.userHandle):""},l=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/webauthn/authenticate/challenge`),i=await this.request("POST","/webauthn/authenticate/challenge",{body:s,headers:{DPoP:l}}),h=i.data.data??i.data,c=h.AuthenticationResult??h,y=c.AccessToken??c.accessToken??c.access_token,b=c.RefreshToken??c.refreshToken??c.refresh_token,w=c.IdToken??c.idToken??c.id_token;return y&&await this.storeTokens({accessToken:y,refreshToken:b??"",idToken:w??"",expiresIn:c.expiresIn??c.ExpiresIn??3600,tokenType:c.tokenType??c.token_type??"Bearer"}),{access_token:y,refresh_token:b??"",id_token:w,email:c.email,name:c.name??c.userName,expiresIn:c.expiresIn??c.ExpiresIn,tokenType:c.tokenType??c.token_type,...c}}async getPasskeyStatus(e){const t=`/webauthn/credentials/${encodeURIComponent(e)}`,o=await this.signRequest("GET",t),n=await this.request("GET",t,{headers:o.headers}),s=n.data.data??n.data;return{...s,hasCredentials:!!(s.hasCredentials??s.credentialId)}}async removePasskey(e){const t=`/webauthn/credentials/${encodeURIComponent(e)}`,o=await this.signRequest("DELETE",t);await this.request("DELETE",t,{headers:o.headers})}async signRequest(e,r,t={}){const o=r.startsWith("http")?r:`${this.getBaseUrl()}${r}`,n=await this.getRequestAuthHeaders(e,o);return{...t,headers:{...t.headers,...n}}}async authenticatedFetch(e,r){const t=typeof e=="string"?e:e instanceof URL?e.toString():e.url,o=r?.method||"GET",n=await this.signRequest(o,t,{headers:r?.headers});return fetch(e,{...r,headers:n.headers})}async storeTokens(e){localStorage.setItem(g,e.accessToken),e.refreshToken&&localStorage.setItem(X,e.refreshToken),e.idToken&&localStorage.setItem(V,e.idToken)}async clearTokens(){localStorage.removeItem(g),localStorage.removeItem(V),localStorage.removeItem(X)}async refreshToken(e){const r=this.extractRealm(void 0,e),t=await this.request("POST","/auth/refresh",{body:r?{realm:r}:{}}),o=t.data.data??t.data,n=o.access_token,s=o.id_token??"",l=o.token_type??"Bearer",i=o.expires_in??3600;return n&&await this.storeTokens({accessToken:n,idToken:s,expiresIn:i,tokenType:l}),{access_token:n,id_token:s,token_type:l,expires_in:i}}async logout(){try{const r=await this.getAccessToken()?await this.signRequest("POST","/auth/logout",{body:{}}):{body:{}};await this.request("POST","/auth/logout",r),await H(),this.resetDPoPInitialization()}catch{}await this.clearTokens()}async getAccessToken(){return localStorage.getItem(g)}}typeof window<"u"&&(window.AuthSDK=x);const be=typeof window<"u"?window:globalThis;return be.AuthSDK=x,d.AuthSDK=x,d.AuthSDKFetchError=Y,d.SDKValidationError=m,d.arrayBufferToBase64=_,d.arrayBufferToBase64url=p,d.base64ToArrayBuffer=E,d.base64urlToArrayBuffer=P,d.clearPasskeyEmail=q,d.clearPasskeyMobileNumber=M,d.getPasskeyEmail=K,d.getPasskeyMobileNumber=B,d.isConditionalUISupported=O,d.isWebAuthnSupported=T,d.setPasskeyEmail=C,d.setPasskeyMobileNumber=$,d.stringToArrayBuffer=k,d.uint8ArrayToString=N,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"}),d})({});
|
|
1
|
+
var PostexAuthSDKStage=(function(d){"use strict";function T(){return typeof window<"u"&&typeof window.PublicKeyCredential<"u"&&typeof navigator<"u"&&typeof navigator.credentials<"u"}async function O(){if(!T())return!1;try{return await PublicKeyCredential.isConditionalMediationAvailable?.()??!1}catch{return!1}}function E(s){const e=new Uint8Array(s);let r="";for(let t=0;t<e.byteLength;t++)r+=String.fromCharCode(e[t]);return btoa(r)}function _(s){if(typeof s!="string"||!s)throw new Error("Invalid base64: expected non-empty string");const e=s.replace(/\s/g,"").replace(/-/g,"+").replace(/_/g,"/"),r=e.length%4,t=r>0?e+"=".repeat(4-r):e;try{const n=atob(t),a=new Uint8Array(n.length);for(let o=0;o<n.length;o++)a[o]=n.charCodeAt(o);return a.buffer}catch{throw new Error("Invalid base64: string is not correctly encoded. Check challenge/credentialId from server.")}}function y(s){return E(s).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function X(s){return/^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i.test(s)}function Z(s){const e=s.replace(/-/g,""),r=new Uint8Array(e.length/2);for(let t=0;t<r.length;t++)r[t]=parseInt(e.substr(t*2,2),16);return r.buffer}function b(s){if(!s||typeof s!="string")throw new Error("Invalid input: expected non-empty string");return X(s)?Z(s):_(s)}function g(s){return new TextEncoder().encode(s).buffer}function C(s){return String.fromCharCode(...s)}const Q="xpay_webauthn",ee=1,u="passkey_data",S="passkey_email",A="passkey_mobile_number";function f(){return new Promise((s,e)=>{const r=indexedDB.open(Q,ee);r.onerror=()=>e(r.error),r.onsuccess=()=>s(r.result),r.onupgradeneeded=()=>{const t=r.result;t.objectStoreNames.contains(u)||t.createObjectStore(u)}})}async function x(){try{const s=await f();return new Promise((e,r)=>{const t=s.transaction(u,"readonly"),a=t.objectStore(u).get(S);a.onerror=()=>r(a.error),a.onsuccess=()=>e(a.result??null),t.oncomplete=()=>s.close()})}catch{return null}}async function K(s){try{const e=await f();return new Promise((r,t)=>{const n=e.transaction(u,"readwrite"),o=n.objectStore(u).put(s,S);o.onerror=()=>t(o.error),o.onsuccess=()=>r(),n.oncomplete=()=>e.close()})}catch{}}async function q(){try{const s=await f();return new Promise((e,r)=>{const t=s.transaction(u,"readwrite"),a=t.objectStore(u).delete(S);a.onerror=()=>r(a.error),a.onsuccess=()=>e(),t.oncomplete=()=>s.close()})}catch{}}async function $(){try{const s=await f();return new Promise((e,r)=>{const t=s.transaction(u,"readonly"),a=t.objectStore(u).get(A);a.onerror=()=>r(a.error),a.onsuccess=()=>e(a.result??null),t.oncomplete=()=>s.close()})}catch{return null}}async function B(s){try{const e=await f();return new Promise((r,t)=>{const n=e.transaction(u,"readwrite"),o=n.objectStore(u).put(s,A);o.onerror=()=>t(o.error),o.onsuccess=()=>r(),n.oncomplete=()=>e.close()})}catch{}}async function U(){try{const s=await f();return new Promise((e,r)=>{const t=s.transaction(u,"readwrite"),a=t.objectStore(u).delete(A);a.onerror=()=>r(a.error),a.onsuccess=()=>e(),t.oncomplete=()=>s.close()})}catch{}}const v="dpop_private_key",I="dpop_public_key_jwk";function M(s){if(!s||typeof s!="object")return!1;const e=s;return e.kty==="EC"&&e.crv==="P-256"&&typeof e.x=="string"&&typeof e.y=="string"}class D extends Error{constructor(e){super(e),this.name="DPoPProofGenerationError"}}function te(s){return{kty:"EC",crv:"P-256",x:s.x,y:s.y}}async function z(s){const e=JSON.stringify({crv:s.crv,kty:s.kty,x:s.x,y:s.y}),r=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e));return y(r)}async function re(s){try{const e=await f();return new Promise((r,t)=>{const n=e.transaction(u,"readwrite"),o=n.objectStore(u).put(s,v);o.onerror=()=>t(o.error),o.onsuccess=()=>r(),n.oncomplete=()=>e.close()})}catch(e){console.error("Failed to store DPoP private key:",e)}}async function ae(s){try{const e=await f();return new Promise((r,t)=>{const n=e.transaction(u,"readwrite"),o=n.objectStore(u).put(s,I);o.onerror=()=>t(o.error),o.onsuccess=()=>r(),n.oncomplete=()=>e.close()})}catch(e){console.error("Failed to store DPoP public key JWK:",e)}}async function L(){try{const s=await f();return new Promise((e,r)=>{const t=s.transaction(u,"readonly"),a=t.objectStore(u).get(v);a.onerror=()=>r(a.error),a.onsuccess=()=>e(a.result??null),t.oncomplete=()=>s.close()})}catch{return null}}async function j(){try{const s=await f();return new Promise((e,r)=>{const t=s.transaction(u,"readonly"),a=t.objectStore(u).get(I);a.onerror=()=>r(a.error),a.onsuccess=()=>e(a.result??null),t.oncomplete=()=>s.close()})}catch{return null}}async function R(){try{const s=await f();return new Promise((e,r)=>{const t=s.transaction(u,"readwrite"),n=t.objectStore(u);n.delete(v),n.delete(I),t.onerror=()=>r(t.error),t.oncomplete=()=>{s.close(),e()}})}catch{}}async function ne(){const s=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign","verify"]),e=await crypto.subtle.exportKey("jwk",s.publicKey),r=te(e),t=await z(r);return await re(s.privateKey),await ae(r),{publicKey:r,thumbprint:t}}async function se(){const s=await L(),e=await j();return s&&M(e)?{publicKey:e,thumbprint:await z(e)}:((s||e)&&await R(),ne())}async function oe(s,e,r){try{const t=await L(),n=await j();if(!t||!M(n))throw new D("DPoP key material is unavailable or invalid");const a={typ:"dpop+jwt",alg:"ES256",jwk:n},o=new URL(e,typeof window<"u"?window.location.origin:void 0),l=`${o.origin}${o.pathname}`,c={jti:crypto.randomUUID(),htm:s.toUpperCase(),htu:l,iat:Math.floor(Date.now()/1e3)};if(r){const be=new TextEncoder().encode(r),Pe=await crypto.subtle.digest("SHA-256",be);c.ath=y(Pe)}const h=y(new TextEncoder().encode(JSON.stringify(a)).buffer),i=y(new TextEncoder().encode(JSON.stringify(c)).buffer),p=`${h}.${i}`,w=await crypto.subtle.sign({name:"ECDSA",hash:{name:"SHA-256"}},t,new TextEncoder().encode(p)),P=y(w);return`${h}.${i}.${P}`}catch(t){throw console.error("Failed to generate DPoP proof:",t),t instanceof D?t:new D("Failed to generate DPoP proof")}}const H={isWebAuthnSupported:T,isConditionalUISupported:O,arrayBufferToBase64:E,base64ToArrayBuffer:_,arrayBufferToBase64url:y,base64urlToArrayBuffer:b,stringToArrayBuffer:g,uint8ArrayToString:C,getPasskeyEmail:x,setPasskeyEmail:K,clearPasskeyEmail:q,getPasskeyMobileNumber:$,setPasskeyMobileNumber:B,clearPasskeyMobileNumber:U};typeof window<"u"&&(window.WebAuthn=H);const ie=typeof window<"u"?window:globalThis;ie.WebAuthn=H;const k="postex-auth-token",ce="postexglobal",le={xstak:"https://auth-stage.xstak.com/public/v1",postex:"https://auth-stage.postex.pk/public/v1",callcourier:"https://auth-stage.callcourier.com.pk/public/v1",postexglobal:"https://auth-stage.postexglobal.com/public/v1",postexsa:"https://auth-stage.postex.sa/public/v1"},ue=/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/,de=/^\+[1-9]\d{6,14}$/,he=/^\d{6}$/,ye=/^[A-Za-z0-9_-]{16,512}$/,me=/[\u0000-\u001F\u007F-\u009F]/,pe=/[A-Za-z]/,fe=/[\u0400-\u04FF]/,J=254,F=16,W=64,G=64;class Y extends Error{constructor(e,r,t){super(t??`Request failed with status ${e}`),this.response={status:e,data:r},this.name="AuthSDKFetchError"}}class m extends Error{constructor(e,r,t="invalid_input"){super(r),this.field=e,this.code=t,this.name="SDKValidationError"}}const V="auth_sdk_refresh_token";class N{constructor(e){this.config=e,this.dpopInitializationPromise=this.createDPoPInitializationPromise()}getBaseUrl(){const e=this.config.appId??ce;return le[e]}async initializeDPoPState(){await se()}async ensureDPoPInitialized(){await this.dpopInitializationPromise}createDPoPInitializationPromise(){const e=this.initializeDPoPState();return e.catch(r=>{console.warn("[AuthSDK] DPoP initialization failed:",r)}),e}resetDPoPInitialization(){this.dpopInitializationPromise=this.createDPoPInitializationPromise()}async getRequiredDPoPProof(e,r,t){return await this.ensureDPoPInitialized(),oe(e.toUpperCase(),r,t)}containsControlChars(e){return me.test(e)}hasMixedLatinAndCyrillic(e){return pe.test(e)&&fe.test(e)}assertNoControlChars(e,r){if(this.containsControlChars(r))throw new m(e,`${e} must not contain control characters`)}validateEmail(e,r="email"){const t=e.trim();if(!t)throw new m(r,`${r} is required`);if(t.length>J)throw new m(r,`${r} must be ${J} characters or fewer`);this.assertNoControlChars(r,t);const[n=""]=t.split("@");if(this.hasMixedLatinAndCyrillic(n))throw new m(r,`${r} must not mix Latin and Cyrillic characters in the local part`);if(!ue.test(t))throw new m(r,`${r} must be a valid email`);return t}validateMobileNumber(e,r="mobileNumber"){const t=e.trim();if(!t)throw new m(r,`${r} is required`);if(t.length>F)throw new m(r,`${r} must be ${F} characters or fewer`);if(this.assertNoControlChars(r,t),!de.test(t))throw new m(r,`${r} must be in E.164 format`);return t}validateUsername(e,r="username"){const t=e.trim();if(!t)throw new m(r,`${r} is required`);if(t.length>W)throw new m(r,`${r} must be ${W} characters or fewer`);return this.assertNoControlChars(r,t),t}validateOTP(e,r="otp"){const t=e.trim();if(this.assertNoControlChars(r,t),!he.test(t))throw new m(r,`${r} must be a 6-digit code`);return t}validateMagicLinkToken(e,r="token"){const t=e.trim();if(this.assertNoControlChars(r,t),!ye.test(t))throw new m(r,`${r} must be 16-512 URL-safe characters`);return t}validateRealm(e){const r=e?.trim();if(r){if(this.assertNoControlChars("realm",r),r.length>G)throw new m("realm",`realm must be ${G} characters or fewer`);return r}}normalizeAndValidateIdentifier(e){return typeof e=="string"?{email:this.validateEmail(e)}:{email:e.email?this.validateEmail(e.email):void 0,mobileNumber:e.mobileNumber?this.validateMobileNumber(e.mobileNumber):void 0,username:e.username?this.validateUsername(e.username):void 0}}normalizeAuthIdentifier(e){return this.normalizeAndValidateIdentifier(e)}extractRealm(e,r){const t=r?.realm??(e&&typeof e!="string"?e.realm:void 0);return this.validateRealm(t)}buildAuthRequestBody(e,r){const t=this.normalizeAuthIdentifier(e),n=this.extractRealm(e,r);return{...t,...n?{realm:n}:{}}}buildUrl(e,r){const t=this.getBaseUrl().replace(/\/$/,""),n=e.startsWith("/")?e:`/${e}`,a=`${t}${n}`;if(!r||Object.keys(r).length===0)return a;const o=new URLSearchParams(r).toString();return`${a}?${o}`}async request(e,r,t){const n=this.buildUrl(r,t?.params),a={"Content-Type":"application/json",Accept:"application/json","X-API-Key":this.config.apiKey??"",...t?.headers},o={method:e,credentials:"include",headers:a};t?.body!==void 0&&t?.body!==null&&(o.body=JSON.stringify(t.body));const l=await fetch(n,o);if(!l.ok){let i;try{const p=await l.text();i=p?JSON.parse(p):void 0}catch{i=void 0}throw l.status===401&&(await this.clearTokens(),await R(),this.resetDPoPInitialization()),new Y(l.status,i)}const c=await l.text();return{data:c?JSON.parse(c):{}}}async getRequestAuthHeaders(e,r){const t=localStorage.getItem(k);if(!t)return{};const n=r.startsWith("http")?r:`${this.getBaseUrl()}${r}`,a=await this.getRequiredDPoPProof(e,n,t);return{Authorization:`Bearer ${t}`,DPoP:a}}async getStatus(e,r){const t=this.normalizeAuthIdentifier(e),n=this.extractRealm(e,r),a={};t.email&&(a.email=t.email),t.mobileNumber&&(a.mobileNumber=t.mobileNumber),t.username&&(a.username=t.username),n&&(a.realm=n);const o=await this.request("GET","/auth/status",{params:a});return o.data.data??o.data}async initiateAuth(e,r){const t=this.buildAuthRequestBody(e,r),n=await this.request("POST","/auth/initiate",{body:t}),a=n.data.data??n.data;return{status:a.status,challenge:a.challenge,credentialIds:a.credentialIds,rp:a.rp}}async initiateOTP(e,r){const t=this.normalizeAuthIdentifier(e),n=this.extractRealm(e,r),a=t.email,o=t.mobileNumber,l=t.username;if(!a&&!o&&!l)throw new m("identifier","Either mobileNumber, email, or username is required");const c={};a&&(c.email=a),o&&(c.mobileNumber=o),l&&(c.username=l),n&&(c.realm=n);const i=(await this.request("POST","/otp/initiate",{body:c})).data;return{message:i.message??i.data?.message}}async verifyOTP(e){const r=this.validateOTP(e),t=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/otp/verify`),n=await this.request("POST","/otp/verify",{body:{otp:r},headers:{DPoP:t}}),a=n.data.data??n.data,o=a.AuthenticationResult??a,l=o.access_token??o.accessToken??o.AccessToken,c=o.refresh_token??o.refreshToken??o.RefreshToken,h=o.expires_in??o.expiresIn,i=o.token_type??o.tokenType;return l&&await this.storeTokens({accessToken:l,refreshToken:c,expiresIn:h,tokenType:i}),{access_token:l,refresh_token:c,expires_in:h,token_type:i,verified:o.verified,email:o.email??o.Email,...o}}async resendOTP(){const e=await this.request("POST","/otp/resend",{}),r=e.data.data??e.data;return{success:r.success,message:r.message,...r}}async verifySignupOTP({mobileNumber:e,otp:r}){const t=this.validateMobileNumber(e),n=this.validateOTP(r),a=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/otp/signup/verify`),o=await this.request("POST","/otp/signup/verify",{body:{mobileNumber:t,otp:n},headers:{DPoP:a}}),l=o.data.data??o.data,c=l.AuthenticationResult??l,h=c.access_token??c.accessToken??c.AccessToken,i=c.refresh_token??c.refreshToken??c.RefreshToken,p=c.expires_in??c.expiresIn,w=c.token_type??c.tokenType;return h&&await this.storeTokens({accessToken:h,refreshToken:i,expiresIn:p,tokenType:w}),{access_token:h,refresh_token:i,expires_in:p,token_type:w,verified:c.verified,email:c.email??c.Email,...c}}async resendSignupOTP({mobileNumber:e,email:r=""}){const t=this.validateMobileNumber(e),n=r?this.validateEmail(r):"",a=await this.request("POST","/otp/signup/resend",{body:{email:n,mobileNumber:t}}),o=a.data.data??a.data;return{success:o.success,message:o.message,...o}}async verifyMagicLink(e){const r=this.validateMagicLinkToken(e);await this.request("GET","/verify/magic-link",{params:{token:r,redirect_mode:"frontend"}})}async completeMagicLink(){const e=await this.request("POST","/verify/magic-link/complete",{body:{}}),r=e.data.data??e.data,t=r.AuthenticationResult??r,n=t.access_token??t.accessToken??t.AccessToken,a=t.refresh_token??t.refreshToken??t.RefreshToken,o=t.expires_in??t.expiresIn,l=t.token_type??t.tokenType;return n&&await this.storeTokens({accessToken:n,refreshToken:a,expiresIn:o,tokenType:l}),{access_token:n,refresh_token:a,expires_in:o,token_type:l,verified:t.verified,email:t.email??t.Email,...t}}async initiatePasskeyRegistration(){const e=await this.request("POST","/webauthn/initiate/challenge",{body:{}});return e.data.data??e.data}async registerPasskey(e){const r=this.validateEmail(e),t=await this.initiatePasskeyRegistration();let n;if(t?.user?.id)try{n=b(t.user.id)}catch{n=g(t.user.id)}else n=g(r);const a=(t.pubKeyCredParams??[{type:"public-key",alg:-7},{type:"public-key",alg:-257}]).map(P=>({type:"public-key",alg:P.alg})),o=t.excludeCredentials?.map(P=>({type:"public-key",id:b(P)})),l={challenge:b(t.challenge),rp:{name:t.rp?.name??"XPay",id:t.rp?.id??window.location.hostname},user:{id:n,name:t.user?.name??r,displayName:t.user?.displayName??r},pubKeyCredParams:a,excludeCredentials:o,timeout:t.timeout??6e4,attestation:"direct",authenticatorSelection:{residentKey:"required",userVerification:"required",authenticatorAttachment:"platform"}},c=await navigator.credentials.create({publicKey:l});if(!c)throw new Error("Credential creation failed");const h=c.response,i={clientDataJSON:y(h.clientDataJSON),attestationObject:y(h.attestationObject),rawId:y(c.rawId)};if(!i.rawId)throw new Error("Raw ID is required");const p=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/webauthn/register/challenge`),w=await this.request("POST","/webauthn/register/challenge",{body:i,headers:{DPoP:p}});return w.data.data??w.data}async authenticateWithPasskey({challenge:e,rp:r,credentialIds:t}){const n=await navigator.credentials.get({publicKey:{challenge:b(e),rpId:r?.host??void 0,allowCredentials:t.map(P=>({type:"public-key",id:b(P),transports:["internal"]})),timeout:6e4,userVerification:"required"}});if(!n)throw new Error("Authentication failed");const a=n.response,o={clientDataJSON:y(a.clientDataJSON),authenticatorData:y(a.authenticatorData),signature:y(a.signature),rawId:y(n.rawId),userHandle:a.userHandle?y(a.userHandle):""},l=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/webauthn/authenticate/challenge`),c=await this.request("POST","/webauthn/authenticate/challenge",{body:o,headers:{DPoP:l}}),h=c.data.data??c.data,i=h.AuthenticationResult??h,p=i.AccessToken??i.accessToken??i.access_token,w=i.RefreshToken??i.refreshToken??i.refresh_token;return p&&await this.storeTokens({accessToken:p,refreshToken:w,expiresIn:i.expiresIn??i.ExpiresIn,tokenType:i.tokenType??i.token_type}),{access_token:p,refresh_token:w,email:i.email,name:i.name??i.userName,expiresIn:i.expiresIn??i.ExpiresIn,tokenType:i.tokenType??i.token_type,...i}}async getPasskeyStatus(e){const t=`/webauthn/credentials/${encodeURIComponent(e)}`,n=await this.signRequest("GET",t),a=await this.request("GET",t,{headers:n.headers}),o=a.data.data??a.data;return{...o,hasCredentials:!!(o.hasCredentials??o.credentialId)}}async removePasskey(e){const t=`/webauthn/credentials/${encodeURIComponent(e)}`,n=await this.signRequest("DELETE",t);await this.request("DELETE",t,{headers:n.headers})}async signRequest(e,r,t={}){const n=r.startsWith("http")?r:`${this.getBaseUrl()}${r}`,a=await this.getRequestAuthHeaders(e,n);return{...t,headers:{...t.headers,...a}}}async authenticatedFetch(e,r){const t=typeof e=="string"?e:e instanceof URL?e.toString():e.url,n=r?.method||"GET",a=await this.signRequest(n,t,{headers:r?.headers});return fetch(e,{...r,headers:a.headers})}async storeTokens(e){localStorage.setItem(k,e.accessToken),e.refreshToken&&localStorage.setItem(V,e.refreshToken)}async clearTokens(){localStorage.removeItem(k),localStorage.removeItem(V)}async refreshToken(e){const r=this.extractRealm(void 0,e),t=await this.request("POST","/auth/refresh",{body:r?{realm:r}:{}}),n=t.data.data??t.data,a=n.access_token,o=n.token_type,l=n.expires_in;return a&&await this.storeTokens({accessToken:a,expiresIn:l,tokenType:o}),{access_token:a,token_type:o,expires_in:l}}async logout(){try{const r=await this.getAccessToken()?await this.signRequest("POST","/auth/logout",{body:{}}):{body:{}};await this.request("POST","/auth/logout",r),await R(),this.resetDPoPInitialization()}catch{}await this.clearTokens()}async getAccessToken(){return localStorage.getItem(k)}}typeof window<"u"&&(window.AuthSDK=N);const we=typeof window<"u"?window:globalThis;return we.AuthSDK=N,d.AuthSDK=N,d.AuthSDKFetchError=Y,d.SDKValidationError=m,d.arrayBufferToBase64=E,d.arrayBufferToBase64url=y,d.base64ToArrayBuffer=_,d.base64urlToArrayBuffer=b,d.clearPasskeyEmail=q,d.clearPasskeyMobileNumber=U,d.getPasskeyEmail=x,d.getPasskeyMobileNumber=$,d.isConditionalUISupported=O,d.isWebAuthnSupported=T,d.setPasskeyEmail=K,d.setPasskeyMobileNumber=B,d.stringToArrayBuffer=g,d.uint8ArrayToString=C,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"}),d})({});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(d,P){typeof exports=="object"&&typeof module<"u"?P(exports):typeof define=="function"&&define.amd?define(["exports"],P):(d=typeof globalThis<"u"?globalThis:d||self,P(d.PostexAuthSDKStage={}))})(this,(function(d){"use strict";function P(){return typeof window<"u"&&typeof window.PublicKeyCredential<"u"&&typeof navigator<"u"&&typeof navigator.credentials<"u"}async function O(){if(!P())return!1;try{return await PublicKeyCredential.isConditionalMediationAvailable?.()??!1}catch{return!1}}function _(s){const e=new Uint8Array(s);let n="";for(let t=0;t<e.byteLength;t++)n+=String.fromCharCode(e[t]);return btoa(n)}function E(s){if(typeof s!="string"||!s)throw new Error("Invalid base64: expected non-empty string");const e=s.replace(/\s/g,"").replace(/-/g,"+").replace(/_/g,"/"),n=e.length%4,t=n>0?e+"=".repeat(4-n):e;try{const o=atob(t),r=new Uint8Array(o.length);for(let a=0;a<o.length;a++)r[a]=o.charCodeAt(a);return r.buffer}catch{throw new Error("Invalid base64: string is not correctly encoded. Check challenge/credentialId from server.")}}function y(s){return _(s).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function Z(s){return/^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i.test(s)}function Q(s){const e=s.replace(/-/g,""),n=new Uint8Array(e.length/2);for(let t=0;t<n.length;t++)n[t]=parseInt(e.substr(t*2,2),16);return n.buffer}function k(s){if(!s||typeof s!="string")throw new Error("Invalid input: expected non-empty string");return Z(s)?Q(s):E(s)}function g(s){return new TextEncoder().encode(s).buffer}function N(s){return String.fromCharCode(...s)}const ee="xpay_webauthn",te=1,u="passkey_data",S="passkey_email",A="passkey_mobile_number";function f(){return new Promise((s,e)=>{const n=indexedDB.open(ee,te);n.onerror=()=>e(n.error),n.onsuccess=()=>s(n.result),n.onupgradeneeded=()=>{const t=n.result;t.objectStoreNames.contains(u)||t.createObjectStore(u)}})}async function K(){try{const s=await f();return new Promise((e,n)=>{const t=s.transaction(u,"readonly"),r=t.objectStore(u).get(S);r.onerror=()=>n(r.error),r.onsuccess=()=>e(r.result??null),t.oncomplete=()=>s.close()})}catch{return null}}async function C(s){try{const e=await f();return new Promise((n,t)=>{const o=e.transaction(u,"readwrite"),a=o.objectStore(u).put(s,S);a.onerror=()=>t(a.error),a.onsuccess=()=>n(),o.oncomplete=()=>e.close()})}catch{}}async function q(){try{const s=await f();return new Promise((e,n)=>{const t=s.transaction(u,"readwrite"),r=t.objectStore(u).delete(S);r.onerror=()=>n(r.error),r.onsuccess=()=>e(),t.oncomplete=()=>s.close()})}catch{}}async function B(){try{const s=await f();return new Promise((e,n)=>{const t=s.transaction(u,"readonly"),r=t.objectStore(u).get(A);r.onerror=()=>n(r.error),r.onsuccess=()=>e(r.result??null),t.oncomplete=()=>s.close()})}catch{return null}}async function $(s){try{const e=await f();return new Promise((n,t)=>{const o=e.transaction(u,"readwrite"),a=o.objectStore(u).put(s,A);a.onerror=()=>t(a.error),a.onsuccess=()=>n(),o.oncomplete=()=>e.close()})}catch{}}async function M(){try{const s=await f();return new Promise((e,n)=>{const t=s.transaction(u,"readwrite"),r=t.objectStore(u).delete(A);r.onerror=()=>n(r.error),r.onsuccess=()=>e(),t.oncomplete=()=>s.close()})}catch{}}const I="dpop_private_key",v="dpop_public_key_jwk";function U(s){if(!s||typeof s!="object")return!1;const e=s;return e.kty==="EC"&&e.crv==="P-256"&&typeof e.x=="string"&&typeof e.y=="string"}class D extends Error{constructor(e){super(e),this.name="DPoPProofGenerationError"}}function ne(s){return{kty:"EC",crv:"P-256",x:s.x,y:s.y}}async function L(s){const e=JSON.stringify({crv:s.crv,kty:s.kty,x:s.x,y:s.y}),n=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e));return y(n)}async function re(s){try{const e=await f();return new Promise((n,t)=>{const o=e.transaction(u,"readwrite"),a=o.objectStore(u).put(s,I);a.onerror=()=>t(a.error),a.onsuccess=()=>n(),o.oncomplete=()=>e.close()})}catch(e){console.error("Failed to store DPoP private key:",e)}}async function oe(s){try{const e=await f();return new Promise((n,t)=>{const o=e.transaction(u,"readwrite"),a=o.objectStore(u).put(s,v);a.onerror=()=>t(a.error),a.onsuccess=()=>n(),o.oncomplete=()=>e.close()})}catch(e){console.error("Failed to store DPoP public key JWK:",e)}}async function z(){try{const s=await f();return new Promise((e,n)=>{const t=s.transaction(u,"readonly"),r=t.objectStore(u).get(I);r.onerror=()=>n(r.error),r.onsuccess=()=>e(r.result??null),t.oncomplete=()=>s.close()})}catch{return null}}async function j(){try{const s=await f();return new Promise((e,n)=>{const t=s.transaction(u,"readonly"),r=t.objectStore(u).get(v);r.onerror=()=>n(r.error),r.onsuccess=()=>e(r.result??null),t.oncomplete=()=>s.close()})}catch{return null}}async function H(){try{const s=await f();return new Promise((e,n)=>{const t=s.transaction(u,"readwrite"),o=t.objectStore(u);o.delete(I),o.delete(v),t.onerror=()=>n(t.error),t.oncomplete=()=>{s.close(),e()}})}catch{}}async function se(){const s=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign","verify"]),e=await crypto.subtle.exportKey("jwk",s.publicKey),n=ne(e),t=await L(n);return await re(s.privateKey),await oe(n),{publicKey:n,thumbprint:t}}async function ae(){const s=await z(),e=await j();return s&&U(e)?{publicKey:e,thumbprint:await L(e)}:((s||e)&&await H(),se())}async function ie(s,e,n){try{const t=await z(),o=await j();if(!t||!U(o))throw new D("DPoP key material is unavailable or invalid");const r={typ:"dpop+jwt",alg:"ES256",jwk:o},a=new URL(e,typeof window<"u"?window.location.origin:void 0),l=`${a.origin}${a.pathname}`,i={jti:crypto.randomUUID(),htm:s.toUpperCase(),htu:l,iat:Math.floor(Date.now()/1e3)};if(n){const R=new TextEncoder().encode(n),Pe=await crypto.subtle.digest("SHA-256",R);i.ath=y(Pe)}const h=y(new TextEncoder().encode(JSON.stringify(r)).buffer),c=y(new TextEncoder().encode(JSON.stringify(i)).buffer),p=`${h}.${c}`,b=await crypto.subtle.sign({name:"ECDSA",hash:{name:"SHA-256"}},t,new TextEncoder().encode(p)),w=y(b);return`${h}.${c}.${w}`}catch(t){throw console.error("Failed to generate DPoP proof:",t),t instanceof D?t:new D("Failed to generate DPoP proof")}}const J={isWebAuthnSupported:P,isConditionalUISupported:O,arrayBufferToBase64:_,base64ToArrayBuffer:E,arrayBufferToBase64url:y,base64urlToArrayBuffer:k,stringToArrayBuffer:g,uint8ArrayToString:N,getPasskeyEmail:K,setPasskeyEmail:C,clearPasskeyEmail:q,getPasskeyMobileNumber:B,setPasskeyMobileNumber:$,clearPasskeyMobileNumber:M};typeof window<"u"&&(window.WebAuthn=J);const ce=typeof window<"u"?window:globalThis;ce.WebAuthn=J;const T="postex-auth-token",le="postexglobal",ue={xstak:"https://auth-stage.xstak.com/public/v1",postex:"https://auth-stage.postex.pk/public/v1",callcourier:"https://auth-stage.callcourier.com.pk/public/v1",postexglobal:"https://auth-stage.postexglobal.com/public/v1",postexsa:"https://auth-stage.postex.sa/public/v1"},de=/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/,he=/^\+[1-9]\d{6,14}$/,pe=/^\d{6}$/,ye=/^[A-Za-z0-9_-]{16,512}$/,me=/[\u0000-\u001F\u007F-\u009F]/,fe=/[A-Za-z]/,we=/[\u0400-\u04FF]/,F=254,W=16,G=64;class Y extends Error{constructor(e,n,t){super(t??`Request failed with status ${e}`),this.response={status:e,data:n},this.name="AuthSDKFetchError"}}class m extends Error{constructor(e,n,t="invalid_input"){super(n),this.field=e,this.code=t,this.name="SDKValidationError"}}const V="auth_sdk_id_token",X="auth_sdk_refresh_token";class x{constructor(e){this.config=e,this.dpopInitializationPromise=this.createDPoPInitializationPromise()}getBaseUrl(){const e=this.config.appId??le;return ue[e]}async initializeDPoPState(){await ae()}async ensureDPoPInitialized(){await this.dpopInitializationPromise}createDPoPInitializationPromise(){const e=this.initializeDPoPState();return e.catch(()=>{}),e}resetDPoPInitialization(){this.dpopInitializationPromise=this.createDPoPInitializationPromise()}async getRequiredDPoPProof(e,n,t){return await this.ensureDPoPInitialized(),ie(e.toUpperCase(),n,t)}containsControlChars(e){return me.test(e)}hasMixedLatinAndCyrillic(e){return fe.test(e)&&we.test(e)}assertNoControlChars(e,n){if(this.containsControlChars(n))throw new m(e,`${e} must not contain control characters`)}validateEmail(e,n="email"){const t=e.trim();if(!t)throw new m(n,`${n} is required`);if(t.length>F)throw new m(n,`${n} must be ${F} characters or fewer`);this.assertNoControlChars(n,t);const[o=""]=t.split("@");if(this.hasMixedLatinAndCyrillic(o))throw new m(n,`${n} must not mix Latin and Cyrillic characters in the local part`);if(!de.test(t))throw new m(n,`${n} must be a valid email`);return t}validateMobileNumber(e,n="mobileNumber"){const t=e.trim();if(!t)throw new m(n,`${n} is required`);if(t.length>W)throw new m(n,`${n} must be ${W} characters or fewer`);if(this.assertNoControlChars(n,t),!he.test(t))throw new m(n,`${n} must be in E.164 format`);return t}validateOTP(e,n="otp"){const t=e.trim();if(this.assertNoControlChars(n,t),!pe.test(t))throw new m(n,`${n} must be a 6-digit code`);return t}validateMagicLinkToken(e,n="token"){const t=e.trim();if(this.assertNoControlChars(n,t),!ye.test(t))throw new m(n,`${n} must be 16-512 URL-safe characters`);return t}validateRealm(e){const n=e?.trim();if(n){if(this.assertNoControlChars("realm",n),n.length>G)throw new m("realm",`realm must be ${G} characters or fewer`);return n}}normalizeAndValidateIdentifier(e){return typeof e=="string"?{email:this.validateEmail(e)}:{email:e.email?this.validateEmail(e.email):void 0,mobileNumber:e.mobileNumber?this.validateMobileNumber(e.mobileNumber):void 0}}normalizeAuthIdentifier(e){return this.normalizeAndValidateIdentifier(e)}extractRealm(e,n){const t=n?.realm??(e&&typeof e!="string"?e.realm:void 0);return this.validateRealm(t)}buildAuthRequestBody(e,n){const t=this.normalizeAuthIdentifier(e),o=this.extractRealm(e,n);return{...t,...o?{realm:o}:{}}}buildUrl(e,n){const t=this.getBaseUrl().replace(/\/$/,""),o=e.startsWith("/")?e:`/${e}`,r=`${t}${o}`;if(!n||Object.keys(n).length===0)return r;const a=new URLSearchParams(n).toString();return`${r}?${a}`}async request(e,n,t){const o=this.buildUrl(n,t?.params),r={"Content-Type":"application/json",Accept:"application/json","X-API-Key":this.config.apiKey??"",...t?.headers},a={method:e,credentials:"include",headers:r};t?.body!==void 0&&t?.body!==null&&(a.body=JSON.stringify(t.body));const l=await fetch(o,a);if(!l.ok){let c;try{const p=await l.text();c=p?JSON.parse(p):void 0}catch{c=void 0}throw l.status===401&&await this.clearTokens(),new Y(l.status,c)}const i=await l.text();return{data:i?JSON.parse(i):{}}}async getRequestAuthHeaders(e,n){const t=localStorage.getItem(T);if(!t)return{};const o=n.startsWith("http")?n:`${this.getBaseUrl()}${n}`,r=await this.getRequiredDPoPProof(e,o,t);return{Authorization:`Bearer ${t}`,DPoP:r}}async getStatus(e,n){const t=this.normalizeAuthIdentifier(e),o=this.extractRealm(e,n),r={};t.email&&(r.email=t.email),t.mobileNumber&&(r.mobileNumber=t.mobileNumber),o&&(r.realm=o);const a=await this.request("GET","/auth/status",{params:r});return a.data.data??a.data}async initiateAuth(e,n){const t=this.buildAuthRequestBody(e,n),o=await this.request("POST","/auth/initiate",{body:t}),r=o.data.data??o.data;return{status:r.status,challenge:r.challenge,credentialIds:r.credentialIds,rp:r.rp}}async initiateOTP(e,n){const t=this.normalizeAuthIdentifier(e),o=this.extractRealm(e,n),r=t.email,a=t.mobileNumber;if(!r&&!a)throw new m("identifier","Either mobileNumber or email is required");const l={};r&&(l.email=r),a&&(l.mobileNumber=a),o&&(l.realm=o);const h=(await this.request("POST","/otp/initiate",{body:l})).data;return{message:h.message??h.data?.message}}async verifyOTP(e){const n=this.validateOTP(e),t=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/otp/verify`),o=await this.request("POST","/otp/verify",{body:{otp:n},headers:{DPoP:t}}),r=o.data.data??o.data,a=r.AuthenticationResult??r,l=a.access_token??a.accessToken??a.AccessToken,i=a.refresh_token??a.refreshToken??a.RefreshToken,h=a.id_token??a.idToken??a.IdToken,c=a.expires_in??a.expiresIn??a.ExpiresIn??3600,p=a.token_type??a.tokenType??a.TokenType??"Bearer";return l&&await this.storeTokens({accessToken:l,refreshToken:i??"",idToken:h??"",expiresIn:c??3600,tokenType:p??"Bearer"}),{access_token:l,refresh_token:i??"",id_token:h??"",expires_in:c,token_type:p,verified:a.verified,email:a.email??a.Email??"",...a}}async resendOTP(){const e=await this.request("POST","/otp/resend",{}),n=e.data.data??e.data;return{success:n.success,message:n.message,...n}}async verifySignupOTP({mobileNumber:e,otp:n}){const t=this.validateMobileNumber(e),o=this.validateOTP(n),r=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/otp/signup/verify`),a=await this.request("POST","/otp/signup/verify",{body:{mobileNumber:t,otp:o},headers:{DPoP:r}}),l=a.data.data??a.data,i=l.AuthenticationResult??l,h=i.access_token??i.accessToken??i.AccessToken,c=i.refresh_token??i.refreshToken??i.RefreshToken,p=i.id_token??i.idToken??i.IdToken,b=i.expires_in??i.expiresIn??i.ExpiresIn??3600,w=i.token_type??i.tokenType??i.TokenType??"Bearer";return h&&await this.storeTokens({accessToken:h,refreshToken:c??"",idToken:p??"",expiresIn:b??3600,tokenType:w??"Bearer"}),{access_token:h,refresh_token:c??"",id_token:p??"",expires_in:b,token_type:w,verified:i.verified,email:i.email??i.Email??"",...i}}async resendSignupOTP({mobileNumber:e,email:n=""}){const t=this.validateMobileNumber(e),o=n?this.validateEmail(n):"",r=await this.request("POST","/otp/signup/resend",{body:{email:o,mobileNumber:t}}),a=r.data.data??r.data;return{success:a.success,message:a.message,...a}}async verifyMagicLink(e){const n=this.validateMagicLinkToken(e);await this.request("GET","/verify/magic-link",{params:{token:n,redirect_mode:"frontend"}})}async completeMagicLink(){const e=await this.request("POST","/verify/magic-link/complete",{body:{}}),n=e.data.data??e.data,t=n.AuthenticationResult??n,o=t.access_token??t.accessToken??t.AccessToken,r=t.refresh_token??t.refreshToken??t.RefreshToken,a=t.id_token??t.idToken??t.IdToken,l=t.expires_in??t.expiresIn??t.ExpiresIn??3600,i=t.token_type??t.tokenType??t.TokenType??"Bearer";return o&&await this.storeTokens({accessToken:o,refreshToken:r??"",idToken:a??"",expiresIn:l??3600,tokenType:i??"Bearer"}),{access_token:o,refresh_token:r??"",id_token:a??"",expires_in:l,token_type:i,verified:t.verified,email:t.email??t.Email??"",...t}}async initiatePasskeyRegistration(){const e=await this.request("POST","/webauthn/initiate/challenge",{body:{}});return e.data.data??e.data}async registerPasskey(e){const n=this.validateEmail(e),t=await this.initiatePasskeyRegistration();let o;if(t?.user?.id)try{o=k(t.user.id)}catch{o=g(t.user.id)}else o=g(n);const r=(t.pubKeyCredParams??[{type:"public-key",alg:-7},{type:"public-key",alg:-257}]).map(w=>({type:"public-key",alg:w.alg})),a=t.excludeCredentials?.map(w=>({type:"public-key",id:k(w)})),l={challenge:k(t.challenge),rp:{name:t.rp?.name??"XPay",id:t.rp?.id??window.location.hostname},user:{id:o,name:t.user?.name??n,displayName:t.user?.displayName??n},pubKeyCredParams:r,excludeCredentials:a,timeout:t.timeout??6e4,attestation:"direct",authenticatorSelection:{residentKey:"required",userVerification:"required",authenticatorAttachment:"platform"}},i=await navigator.credentials.create({publicKey:l});if(!i)throw new Error("Credential creation failed");const h=i.response,c={clientDataJSON:y(h.clientDataJSON),attestationObject:y(h.attestationObject),rawId:y(i.rawId)};if(!c.rawId)throw new Error("Raw ID is required");const p=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/webauthn/register/challenge`),b=await this.request("POST","/webauthn/register/challenge",{body:c,headers:{DPoP:p}});return b.data.data??b.data}async authenticateWithPasskey({challenge:e,rp:n,credentialIds:t}){const o=await navigator.credentials.get({publicKey:{challenge:k(e),rpId:n?.host??void 0,allowCredentials:t.map(R=>({type:"public-key",id:k(R),transports:["internal"]})),timeout:6e4,userVerification:"required"}});if(!o)throw new Error("Authentication failed");const r=o.response,a={clientDataJSON:y(r.clientDataJSON),authenticatorData:y(r.authenticatorData),signature:y(r.signature),rawId:y(o.rawId),userHandle:r.userHandle?y(r.userHandle):""},l=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/webauthn/authenticate/challenge`),i=await this.request("POST","/webauthn/authenticate/challenge",{body:a,headers:{DPoP:l}}),h=i.data.data??i.data,c=h.AuthenticationResult??h,p=c.AccessToken??c.accessToken??c.access_token,b=c.RefreshToken??c.refreshToken??c.refresh_token,w=c.IdToken??c.idToken??c.id_token;return p&&await this.storeTokens({accessToken:p,refreshToken:b??"",idToken:w??"",expiresIn:c.expiresIn??c.ExpiresIn??3600,tokenType:c.tokenType??c.token_type??"Bearer"}),{access_token:p,refresh_token:b??"",id_token:w,email:c.email,name:c.name??c.userName,expiresIn:c.expiresIn??c.ExpiresIn,tokenType:c.tokenType??c.token_type,...c}}async getPasskeyStatus(e){const t=`/webauthn/credentials/${encodeURIComponent(e)}`,o=await this.signRequest("GET",t),r=await this.request("GET",t,{headers:o.headers}),a=r.data.data??r.data;return{...a,hasCredentials:!!(a.hasCredentials??a.credentialId)}}async removePasskey(e){const t=`/webauthn/credentials/${encodeURIComponent(e)}`,o=await this.signRequest("DELETE",t);await this.request("DELETE",t,{headers:o.headers})}async signRequest(e,n,t={}){const o=n.startsWith("http")?n:`${this.getBaseUrl()}${n}`,r=await this.getRequestAuthHeaders(e,o);return{...t,headers:{...t.headers,...r}}}async authenticatedFetch(e,n){const t=typeof e=="string"?e:e instanceof URL?e.toString():e.url,o=n?.method||"GET",r=await this.signRequest(o,t,{headers:n?.headers});return fetch(e,{...n,headers:r.headers})}async storeTokens(e){localStorage.setItem(T,e.accessToken),e.refreshToken&&localStorage.setItem(X,e.refreshToken),e.idToken&&localStorage.setItem(V,e.idToken)}async clearTokens(){localStorage.removeItem(T),localStorage.removeItem(V),localStorage.removeItem(X)}async refreshToken(e){const n=this.extractRealm(void 0,e),t=await this.request("POST","/auth/refresh",{body:n?{realm:n}:{}}),o=t.data.data??t.data,r=o.access_token,a=o.id_token??"",l=o.token_type??"Bearer",i=o.expires_in??3600;return r&&await this.storeTokens({accessToken:r,idToken:a,expiresIn:i,tokenType:l}),{access_token:r,id_token:a,token_type:l,expires_in:i}}async logout(){try{const n=await this.getAccessToken()?await this.signRequest("POST","/auth/logout",{body:{}}):{body:{}};await this.request("POST","/auth/logout",n),await H(),this.resetDPoPInitialization()}catch{}await this.clearTokens()}async getAccessToken(){return localStorage.getItem(T)}}typeof window<"u"&&(window.AuthSDK=x);const be=typeof window<"u"?window:globalThis;be.AuthSDK=x,d.AuthSDK=x,d.AuthSDKFetchError=Y,d.SDKValidationError=m,d.arrayBufferToBase64=_,d.arrayBufferToBase64url=y,d.base64ToArrayBuffer=E,d.base64urlToArrayBuffer=k,d.clearPasskeyEmail=q,d.clearPasskeyMobileNumber=M,d.getPasskeyEmail=K,d.getPasskeyMobileNumber=B,d.isConditionalUISupported=O,d.isWebAuthnSupported=P,d.setPasskeyEmail=C,d.setPasskeyMobileNumber=$,d.stringToArrayBuffer=g,d.uint8ArrayToString=N,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(d,b){typeof exports=="object"&&typeof module<"u"?b(exports):typeof define=="function"&&define.amd?define(["exports"],b):(d=typeof globalThis<"u"?globalThis:d||self,b(d.PostexAuthSDKStage={}))})(this,(function(d){"use strict";function b(){return typeof window<"u"&&typeof window.PublicKeyCredential<"u"&&typeof navigator<"u"&&typeof navigator.credentials<"u"}async function N(){if(!b())return!1;try{return await PublicKeyCredential.isConditionalMediationAvailable?.()??!1}catch{return!1}}function E(a){const e=new Uint8Array(a);let r="";for(let t=0;t<e.byteLength;t++)r+=String.fromCharCode(e[t]);return btoa(r)}function _(a){if(typeof a!="string"||!a)throw new Error("Invalid base64: expected non-empty string");const e=a.replace(/\s/g,"").replace(/-/g,"+").replace(/_/g,"/"),r=e.length%4,t=r>0?e+"=".repeat(4-r):e;try{const s=atob(t),n=new Uint8Array(s.length);for(let o=0;o<s.length;o++)n[o]=s.charCodeAt(o);return n.buffer}catch{throw new Error("Invalid base64: string is not correctly encoded. Check challenge/credentialId from server.")}}function y(a){return E(a).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function X(a){return/^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i.test(a)}function Z(a){const e=a.replace(/-/g,""),r=new Uint8Array(e.length/2);for(let t=0;t<r.length;t++)r[t]=parseInt(e.substr(t*2,2),16);return r.buffer}function P(a){if(!a||typeof a!="string")throw new Error("Invalid input: expected non-empty string");return X(a)?Z(a):_(a)}function k(a){return new TextEncoder().encode(a).buffer}function O(a){return String.fromCharCode(...a)}const Q="xpay_webauthn",ee=1,u="passkey_data",S="passkey_email",A="passkey_mobile_number";function f(){return new Promise((a,e)=>{const r=indexedDB.open(Q,ee);r.onerror=()=>e(r.error),r.onsuccess=()=>a(r.result),r.onupgradeneeded=()=>{const t=r.result;t.objectStoreNames.contains(u)||t.createObjectStore(u)}})}async function C(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readonly"),n=t.objectStore(u).get(S);n.onerror=()=>r(n.error),n.onsuccess=()=>e(n.result??null),t.oncomplete=()=>a.close()})}catch{return null}}async function K(a){try{const e=await f();return new Promise((r,t)=>{const s=e.transaction(u,"readwrite"),o=s.objectStore(u).put(a,S);o.onerror=()=>t(o.error),o.onsuccess=()=>r(),s.oncomplete=()=>e.close()})}catch{}}async function q(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readwrite"),n=t.objectStore(u).delete(S);n.onerror=()=>r(n.error),n.onsuccess=()=>e(),t.oncomplete=()=>a.close()})}catch{}}async function $(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readonly"),n=t.objectStore(u).get(A);n.onerror=()=>r(n.error),n.onsuccess=()=>e(n.result??null),t.oncomplete=()=>a.close()})}catch{return null}}async function B(a){try{const e=await f();return new Promise((r,t)=>{const s=e.transaction(u,"readwrite"),o=s.objectStore(u).put(a,A);o.onerror=()=>t(o.error),o.onsuccess=()=>r(),s.oncomplete=()=>e.close()})}catch{}}async function U(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readwrite"),n=t.objectStore(u).delete(A);n.onerror=()=>r(n.error),n.onsuccess=()=>e(),t.oncomplete=()=>a.close()})}catch{}}const v="dpop_private_key",I="dpop_public_key_jwk";function M(a){if(!a||typeof a!="object")return!1;const e=a;return e.kty==="EC"&&e.crv==="P-256"&&typeof e.x=="string"&&typeof e.y=="string"}class D extends Error{constructor(e){super(e),this.name="DPoPProofGenerationError"}}function te(a){return{kty:"EC",crv:"P-256",x:a.x,y:a.y}}async function z(a){const e=JSON.stringify({crv:a.crv,kty:a.kty,x:a.x,y:a.y}),r=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e));return y(r)}async function re(a){try{const e=await f();return new Promise((r,t)=>{const s=e.transaction(u,"readwrite"),o=s.objectStore(u).put(a,v);o.onerror=()=>t(o.error),o.onsuccess=()=>r(),s.oncomplete=()=>e.close()})}catch(e){console.error("Failed to store DPoP private key:",e)}}async function ne(a){try{const e=await f();return new Promise((r,t)=>{const s=e.transaction(u,"readwrite"),o=s.objectStore(u).put(a,I);o.onerror=()=>t(o.error),o.onsuccess=()=>r(),s.oncomplete=()=>e.close()})}catch(e){console.error("Failed to store DPoP public key JWK:",e)}}async function L(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readonly"),n=t.objectStore(u).get(v);n.onerror=()=>r(n.error),n.onsuccess=()=>e(n.result??null),t.oncomplete=()=>a.close()})}catch{return null}}async function j(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readonly"),n=t.objectStore(u).get(I);n.onerror=()=>r(n.error),n.onsuccess=()=>e(n.result??null),t.oncomplete=()=>a.close()})}catch{return null}}async function R(){try{const a=await f();return new Promise((e,r)=>{const t=a.transaction(u,"readwrite"),s=t.objectStore(u);s.delete(v),s.delete(I),t.onerror=()=>r(t.error),t.oncomplete=()=>{a.close(),e()}})}catch{}}async function se(){const a=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign","verify"]),e=await crypto.subtle.exportKey("jwk",a.publicKey),r=te(e),t=await z(r);return await re(a.privateKey),await ne(r),{publicKey:r,thumbprint:t}}async function ae(){const a=await L(),e=await j();return a&&M(e)?{publicKey:e,thumbprint:await z(e)}:((a||e)&&await R(),se())}async function oe(a,e,r){try{const t=await L(),s=await j();if(!t||!M(s))throw new D("DPoP key material is unavailable or invalid");const n={typ:"dpop+jwt",alg:"ES256",jwk:s},o=new URL(e,typeof window<"u"?window.location.origin:void 0),l=`${o.origin}${o.pathname}`,c={jti:crypto.randomUUID(),htm:a.toUpperCase(),htu:l,iat:Math.floor(Date.now()/1e3)};if(r){const be=new TextEncoder().encode(r),Pe=await crypto.subtle.digest("SHA-256",be);c.ath=y(Pe)}const h=y(new TextEncoder().encode(JSON.stringify(n)).buffer),i=y(new TextEncoder().encode(JSON.stringify(c)).buffer),p=`${h}.${i}`,w=await crypto.subtle.sign({name:"ECDSA",hash:{name:"SHA-256"}},t,new TextEncoder().encode(p)),g=y(w);return`${h}.${i}.${g}`}catch(t){throw console.error("Failed to generate DPoP proof:",t),t instanceof D?t:new D("Failed to generate DPoP proof")}}const H={isWebAuthnSupported:b,isConditionalUISupported:N,arrayBufferToBase64:E,base64ToArrayBuffer:_,arrayBufferToBase64url:y,base64urlToArrayBuffer:P,stringToArrayBuffer:k,uint8ArrayToString:O,getPasskeyEmail:C,setPasskeyEmail:K,clearPasskeyEmail:q,getPasskeyMobileNumber:$,setPasskeyMobileNumber:B,clearPasskeyMobileNumber:U};typeof window<"u"&&(window.WebAuthn=H);const ie=typeof window<"u"?window:globalThis;ie.WebAuthn=H;const T="postex-auth-token",ce="postexglobal",le={xstak:"https://auth-stage.xstak.com/public/v1",postex:"https://auth-stage.postex.pk/public/v1",callcourier:"https://auth-stage.callcourier.com.pk/public/v1",postexglobal:"https://auth-stage.postexglobal.com/public/v1",postexsa:"https://auth-stage.postex.sa/public/v1"},ue=/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/,de=/^\+[1-9]\d{6,14}$/,he=/^\d{6}$/,ye=/^[A-Za-z0-9_-]{16,512}$/,me=/[\u0000-\u001F\u007F-\u009F]/,pe=/[A-Za-z]/,fe=/[\u0400-\u04FF]/,J=254,F=16,W=64,G=64;class Y extends Error{constructor(e,r,t){super(t??`Request failed with status ${e}`),this.response={status:e,data:r},this.name="AuthSDKFetchError"}}class m extends Error{constructor(e,r,t="invalid_input"){super(r),this.field=e,this.code=t,this.name="SDKValidationError"}}const V="auth_sdk_refresh_token";class x{constructor(e){this.config=e,this.dpopInitializationPromise=this.createDPoPInitializationPromise()}getBaseUrl(){const e=this.config.appId??ce;return le[e]}async initializeDPoPState(){await ae()}async ensureDPoPInitialized(){await this.dpopInitializationPromise}createDPoPInitializationPromise(){const e=this.initializeDPoPState();return e.catch(r=>{console.warn("[AuthSDK] DPoP initialization failed:",r)}),e}resetDPoPInitialization(){this.dpopInitializationPromise=this.createDPoPInitializationPromise()}async getRequiredDPoPProof(e,r,t){return await this.ensureDPoPInitialized(),oe(e.toUpperCase(),r,t)}containsControlChars(e){return me.test(e)}hasMixedLatinAndCyrillic(e){return pe.test(e)&&fe.test(e)}assertNoControlChars(e,r){if(this.containsControlChars(r))throw new m(e,`${e} must not contain control characters`)}validateEmail(e,r="email"){const t=e.trim();if(!t)throw new m(r,`${r} is required`);if(t.length>J)throw new m(r,`${r} must be ${J} characters or fewer`);this.assertNoControlChars(r,t);const[s=""]=t.split("@");if(this.hasMixedLatinAndCyrillic(s))throw new m(r,`${r} must not mix Latin and Cyrillic characters in the local part`);if(!ue.test(t))throw new m(r,`${r} must be a valid email`);return t}validateMobileNumber(e,r="mobileNumber"){const t=e.trim();if(!t)throw new m(r,`${r} is required`);if(t.length>F)throw new m(r,`${r} must be ${F} characters or fewer`);if(this.assertNoControlChars(r,t),!de.test(t))throw new m(r,`${r} must be in E.164 format`);return t}validateUsername(e,r="username"){const t=e.trim();if(!t)throw new m(r,`${r} is required`);if(t.length>W)throw new m(r,`${r} must be ${W} characters or fewer`);return this.assertNoControlChars(r,t),t}validateOTP(e,r="otp"){const t=e.trim();if(this.assertNoControlChars(r,t),!he.test(t))throw new m(r,`${r} must be a 6-digit code`);return t}validateMagicLinkToken(e,r="token"){const t=e.trim();if(this.assertNoControlChars(r,t),!ye.test(t))throw new m(r,`${r} must be 16-512 URL-safe characters`);return t}validateRealm(e){const r=e?.trim();if(r){if(this.assertNoControlChars("realm",r),r.length>G)throw new m("realm",`realm must be ${G} characters or fewer`);return r}}normalizeAndValidateIdentifier(e){return typeof e=="string"?{email:this.validateEmail(e)}:{email:e.email?this.validateEmail(e.email):void 0,mobileNumber:e.mobileNumber?this.validateMobileNumber(e.mobileNumber):void 0,username:e.username?this.validateUsername(e.username):void 0}}normalizeAuthIdentifier(e){return this.normalizeAndValidateIdentifier(e)}extractRealm(e,r){const t=r?.realm??(e&&typeof e!="string"?e.realm:void 0);return this.validateRealm(t)}buildAuthRequestBody(e,r){const t=this.normalizeAuthIdentifier(e),s=this.extractRealm(e,r);return{...t,...s?{realm:s}:{}}}buildUrl(e,r){const t=this.getBaseUrl().replace(/\/$/,""),s=e.startsWith("/")?e:`/${e}`,n=`${t}${s}`;if(!r||Object.keys(r).length===0)return n;const o=new URLSearchParams(r).toString();return`${n}?${o}`}async request(e,r,t){const s=this.buildUrl(r,t?.params),n={"Content-Type":"application/json",Accept:"application/json","X-API-Key":this.config.apiKey??"",...t?.headers},o={method:e,credentials:"include",headers:n};t?.body!==void 0&&t?.body!==null&&(o.body=JSON.stringify(t.body));const l=await fetch(s,o);if(!l.ok){let i;try{const p=await l.text();i=p?JSON.parse(p):void 0}catch{i=void 0}throw l.status===401&&(await this.clearTokens(),await R(),this.resetDPoPInitialization()),new Y(l.status,i)}const c=await l.text();return{data:c?JSON.parse(c):{}}}async getRequestAuthHeaders(e,r){const t=localStorage.getItem(T);if(!t)return{};const s=r.startsWith("http")?r:`${this.getBaseUrl()}${r}`,n=await this.getRequiredDPoPProof(e,s,t);return{Authorization:`Bearer ${t}`,DPoP:n}}async getStatus(e,r){const t=this.normalizeAuthIdentifier(e),s=this.extractRealm(e,r),n={};t.email&&(n.email=t.email),t.mobileNumber&&(n.mobileNumber=t.mobileNumber),t.username&&(n.username=t.username),s&&(n.realm=s);const o=await this.request("GET","/auth/status",{params:n});return o.data.data??o.data}async initiateAuth(e,r){const t=this.buildAuthRequestBody(e,r),s=await this.request("POST","/auth/initiate",{body:t}),n=s.data.data??s.data;return{status:n.status,challenge:n.challenge,credentialIds:n.credentialIds,rp:n.rp}}async initiateOTP(e,r){const t=this.normalizeAuthIdentifier(e),s=this.extractRealm(e,r),n=t.email,o=t.mobileNumber,l=t.username;if(!n&&!o&&!l)throw new m("identifier","Either mobileNumber, email, or username is required");const c={};n&&(c.email=n),o&&(c.mobileNumber=o),l&&(c.username=l),s&&(c.realm=s);const i=(await this.request("POST","/otp/initiate",{body:c})).data;return{message:i.message??i.data?.message}}async verifyOTP(e){const r=this.validateOTP(e),t=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/otp/verify`),s=await this.request("POST","/otp/verify",{body:{otp:r},headers:{DPoP:t}}),n=s.data.data??s.data,o=n.AuthenticationResult??n,l=o.access_token??o.accessToken??o.AccessToken,c=o.refresh_token??o.refreshToken??o.RefreshToken,h=o.expires_in??o.expiresIn,i=o.token_type??o.tokenType;return l&&await this.storeTokens({accessToken:l,refreshToken:c,expiresIn:h,tokenType:i}),{access_token:l,refresh_token:c,expires_in:h,token_type:i,verified:o.verified,email:o.email??o.Email,...o}}async resendOTP(){const e=await this.request("POST","/otp/resend",{}),r=e.data.data??e.data;return{success:r.success,message:r.message,...r}}async verifySignupOTP({mobileNumber:e,otp:r}){const t=this.validateMobileNumber(e),s=this.validateOTP(r),n=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/otp/signup/verify`),o=await this.request("POST","/otp/signup/verify",{body:{mobileNumber:t,otp:s},headers:{DPoP:n}}),l=o.data.data??o.data,c=l.AuthenticationResult??l,h=c.access_token??c.accessToken??c.AccessToken,i=c.refresh_token??c.refreshToken??c.RefreshToken,p=c.expires_in??c.expiresIn,w=c.token_type??c.tokenType;return h&&await this.storeTokens({accessToken:h,refreshToken:i,expiresIn:p,tokenType:w}),{access_token:h,refresh_token:i,expires_in:p,token_type:w,verified:c.verified,email:c.email??c.Email,...c}}async resendSignupOTP({mobileNumber:e,email:r=""}){const t=this.validateMobileNumber(e),s=r?this.validateEmail(r):"",n=await this.request("POST","/otp/signup/resend",{body:{email:s,mobileNumber:t}}),o=n.data.data??n.data;return{success:o.success,message:o.message,...o}}async verifyMagicLink(e){const r=this.validateMagicLinkToken(e);await this.request("GET","/verify/magic-link",{params:{token:r,redirect_mode:"frontend"}})}async completeMagicLink(){const e=await this.request("POST","/verify/magic-link/complete",{body:{}}),r=e.data.data??e.data,t=r.AuthenticationResult??r,s=t.access_token??t.accessToken??t.AccessToken,n=t.refresh_token??t.refreshToken??t.RefreshToken,o=t.expires_in??t.expiresIn,l=t.token_type??t.tokenType;return s&&await this.storeTokens({accessToken:s,refreshToken:n,expiresIn:o,tokenType:l}),{access_token:s,refresh_token:n,expires_in:o,token_type:l,verified:t.verified,email:t.email??t.Email,...t}}async initiatePasskeyRegistration(){const e=await this.request("POST","/webauthn/initiate/challenge",{body:{}});return e.data.data??e.data}async registerPasskey(e){const r=this.validateEmail(e),t=await this.initiatePasskeyRegistration();let s;if(t?.user?.id)try{s=P(t.user.id)}catch{s=k(t.user.id)}else s=k(r);const n=(t.pubKeyCredParams??[{type:"public-key",alg:-7},{type:"public-key",alg:-257}]).map(g=>({type:"public-key",alg:g.alg})),o=t.excludeCredentials?.map(g=>({type:"public-key",id:P(g)})),l={challenge:P(t.challenge),rp:{name:t.rp?.name??"XPay",id:t.rp?.id??window.location.hostname},user:{id:s,name:t.user?.name??r,displayName:t.user?.displayName??r},pubKeyCredParams:n,excludeCredentials:o,timeout:t.timeout??6e4,attestation:"direct",authenticatorSelection:{residentKey:"required",userVerification:"required",authenticatorAttachment:"platform"}},c=await navigator.credentials.create({publicKey:l});if(!c)throw new Error("Credential creation failed");const h=c.response,i={clientDataJSON:y(h.clientDataJSON),attestationObject:y(h.attestationObject),rawId:y(c.rawId)};if(!i.rawId)throw new Error("Raw ID is required");const p=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/webauthn/register/challenge`),w=await this.request("POST","/webauthn/register/challenge",{body:i,headers:{DPoP:p}});return w.data.data??w.data}async authenticateWithPasskey({challenge:e,rp:r,credentialIds:t}){const s=await navigator.credentials.get({publicKey:{challenge:P(e),rpId:r?.host??void 0,allowCredentials:t.map(g=>({type:"public-key",id:P(g),transports:["internal"]})),timeout:6e4,userVerification:"required"}});if(!s)throw new Error("Authentication failed");const n=s.response,o={clientDataJSON:y(n.clientDataJSON),authenticatorData:y(n.authenticatorData),signature:y(n.signature),rawId:y(s.rawId),userHandle:n.userHandle?y(n.userHandle):""},l=await this.getRequiredDPoPProof("POST",`${this.getBaseUrl()}/webauthn/authenticate/challenge`),c=await this.request("POST","/webauthn/authenticate/challenge",{body:o,headers:{DPoP:l}}),h=c.data.data??c.data,i=h.AuthenticationResult??h,p=i.AccessToken??i.accessToken??i.access_token,w=i.RefreshToken??i.refreshToken??i.refresh_token;return p&&await this.storeTokens({accessToken:p,refreshToken:w,expiresIn:i.expiresIn??i.ExpiresIn,tokenType:i.tokenType??i.token_type}),{access_token:p,refresh_token:w,email:i.email,name:i.name??i.userName,expiresIn:i.expiresIn??i.ExpiresIn,tokenType:i.tokenType??i.token_type,...i}}async getPasskeyStatus(e){const t=`/webauthn/credentials/${encodeURIComponent(e)}`,s=await this.signRequest("GET",t),n=await this.request("GET",t,{headers:s.headers}),o=n.data.data??n.data;return{...o,hasCredentials:!!(o.hasCredentials??o.credentialId)}}async removePasskey(e){const t=`/webauthn/credentials/${encodeURIComponent(e)}`,s=await this.signRequest("DELETE",t);await this.request("DELETE",t,{headers:s.headers})}async signRequest(e,r,t={}){const s=r.startsWith("http")?r:`${this.getBaseUrl()}${r}`,n=await this.getRequestAuthHeaders(e,s);return{...t,headers:{...t.headers,...n}}}async authenticatedFetch(e,r){const t=typeof e=="string"?e:e instanceof URL?e.toString():e.url,s=r?.method||"GET",n=await this.signRequest(s,t,{headers:r?.headers});return fetch(e,{...r,headers:n.headers})}async storeTokens(e){localStorage.setItem(T,e.accessToken),e.refreshToken&&localStorage.setItem(V,e.refreshToken)}async clearTokens(){localStorage.removeItem(T),localStorage.removeItem(V)}async refreshToken(e){const r=this.extractRealm(void 0,e),t=await this.request("POST","/auth/refresh",{body:r?{realm:r}:{}}),s=t.data.data??t.data,n=s.access_token,o=s.token_type,l=s.expires_in;return n&&await this.storeTokens({accessToken:n,expiresIn:l,tokenType:o}),{access_token:n,token_type:o,expires_in:l}}async logout(){try{const r=await this.getAccessToken()?await this.signRequest("POST","/auth/logout",{body:{}}):{body:{}};await this.request("POST","/auth/logout",r),await R(),this.resetDPoPInitialization()}catch{}await this.clearTokens()}async getAccessToken(){return localStorage.getItem(T)}}typeof window<"u"&&(window.AuthSDK=x);const we=typeof window<"u"?window:globalThis;we.AuthSDK=x,d.AuthSDK=x,d.AuthSDKFetchError=Y,d.SDKValidationError=m,d.arrayBufferToBase64=E,d.arrayBufferToBase64url=y,d.base64ToArrayBuffer=_,d.base64urlToArrayBuffer=P,d.clearPasskeyEmail=q,d.clearPasskeyMobileNumber=U,d.getPasskeyEmail=C,d.getPasskeyMobileNumber=$,d.isConditionalUISupported=N,d.isWebAuthnSupported=b,d.setPasskeyEmail=K,d.setPasskeyMobileNumber=B,d.stringToArrayBuffer=k,d.uint8ArrayToString=O,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postex-auth-sdk-stage",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "PostEx Auth SDK for authentication flows: OTP, magic links, passkeys, and token management",
|
|
5
5
|
"main": "./dist/postex-auth-sdk-stage.umd.js",
|
|
6
6
|
"module": "./dist/postex-auth-sdk-stage.es.js",
|