@isaias_pv/custos-sdk 1.2.0 → 1.3.0
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/Custos.d.ts.map +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/Custos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Custos.d.ts","sourceRoot":"","sources":["../src/Custos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAWlF,qBAAa,MAAM;IAClB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,SAAS,CAAsD;IACvE,OAAO,CAAC,gBAAgB,CAAa;gBAEzB,MAAM,EAAE,YAAY;IA8B1B,KAAK,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B/D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBvB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"Custos.d.ts","sourceRoot":"","sources":["../src/Custos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAWlF,qBAAa,MAAM;IAClB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,SAAS,CAAsD;IACvE,OAAO,CAAC,gBAAgB,CAAa;gBAEzB,MAAM,EAAE,YAAY;IA8B1B,KAAK,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B/D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBvB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAqErC,OAAO,IAAI,IAAI,GAAG,IAAI;IAItB,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,eAAe,IAAI,OAAO;IAI1B,QAAQ,IAAI,SAAS;IAQf,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAavC,OAAO,CAAC,0BAA0B;IAuBlC,OAAO,CAAC,qBAAqB;IAOvB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBnC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAOpE,GAAG,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAIrE,OAAO,CAAC,IAAI;IAOZ,YAAY,IAAI,IAAI;IAIpB,OAAO,IAAI,IAAI;CAIf"}
|
package/dist/index.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e="custos_";class t{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(t){this.storage.setItem(`${e}tokens`,JSON.stringify(t)),this.storage.setItem(`${e}token_issued_at`,Date.now().toString())}getTokens(){const t=this.storage.getItem(`${e}tokens`);return t?JSON.parse(t):null}getTokenIssuedAt(){const t=this.storage.getItem(`${e}token_issued_at`);return t?parseInt(t,10):null}setUser(t){this.storage.setItem(`${e}user`,JSON.stringify(t))}getUser(){const t=this.storage.getItem(`${e}user`);return t?JSON.parse(t):null}setState(t,r){this.storage.setItem(`${e}${t}`,r)}getState(t){return this.storage.getItem(`${e}${t}`)}removeState(t){this.storage.removeItem(`${e}${t}`)}setCodeVerifier(e){this.setState("code_verifier",e)}getCodeVerifier(){return this.getState("code_verifier")}removeCodeVerifier(){this.removeState("code_verifier")}setCodeChallenge(e){this.setState("code_challenge",e)}getCodeChallenge(){return this.getState("code_challenge")}removeCodeChallenge(){this.removeState("code_challenge")}clear(){this.storage.removeItem(`${e}tokens`),this.storage.removeItem(`${e}token_issued_at`),this.storage.removeItem(`${e}user`),this.removeState("oauth_state"),this.removeCodeVerifier(),this.removeCodeChallenge()}hasValidToken(){const e=this.getTokens(),t=this.getTokenIssuedAt();if(!e||!t)return!1;return Date.now()<t+1e3*e.expiresIn}}class r{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,r,s,o){const i={grant_type:"authorization_code",code:e,client_id:t,redirect_uri:r};s&&(i.code_verifier=s),o&&(i.client_secret=o);const n=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(i).toString()});if(!n.ok){const e=await n.json().catch(()=>({error:"unknown_error",error_description:"Failed to exchange code for tokens"}));throw new Error(e.error_description||e.error||"Token exchange failed")}const a=await n.json(),c=a.data||a;return{accessToken:c.access_token,refreshToken:c.refresh_token,expiresIn:c.expires_in,tokenType:c.token_type||"Bearer"}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/api/v1/system/users/profile`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");const r=await t.json();return r.data||r}async refreshAccessToken(e,t,r){const s={grant_type:"refresh_token",refresh_token:e,client_id:t};r&&(s.client_secret=r);const o=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(s).toString()});if(!o.ok)throw new Error("Failed to refresh token");const i=await o.json(),n=i.data||i;return{accessToken:n.access_token,refreshToken:n.refresh_token||e,expiresIn:n.expires_in,tokenType:n.token_type||"Bearer"}}async logout(e){await fetch(`${this.baseUrl}/api/v1/auth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}async validateToken(e){try{return(await fetch(`${this.baseUrl}/api/v1/auth/validate`,{headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}}function s(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}async function o(e){const t=(new TextEncoder).encode(e);return function(e){const t=new Uint8Array(e),r=Array.from(t,e=>String.fromCharCode(e)).join("");return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}(await crypto.subtle.digest("SHA-256",t))}exports.Custos=class{constructor(e){this.tokenExpiryTimer=null;const o=function(e){return e?Array.isArray(e)?e:"string"==typeof e?e.split(" "):["openid","profile","email"]:["openid","profile","email"]}(e.scope);this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:o,responseType:e.responseType||"code",state:e.state||s(),usePKCE:!1!==e.usePKCE,codeChallengeMethod:e.codeChallengeMethod||"S256",grantType:e.grantType||"authorization_code"},this.storage=new t,this.api=new r(this.config.apiUrl),this.listeners=new Map,"undefined"!=typeof window&&(this.handleCallback(),this.setupTokenExpiryMonitoring())}async login(e){const t=this.config.state;this.storage.setState("oauth_state",t);const r={response_type:this.config.responseType,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:Array.isArray(this.config.scope)?this.config.scope.join(" "):this.config.scope,state:t,...e};if(this.config.usePKCE){const e=function(){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=Math.floor(86*Math.random())+43;let r="";for(let s=0;s<t;s++)r+=e.charAt(Math.floor(66*Math.random()));return r}(),t=await o(e);this.storage.setCodeVerifier(e),this.storage.setCodeChallenge(t),r.code_challenge=t,r.code_challenge_method=this.config.codeChallengeMethod}const s=`${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(r)}`;window.location.href=s}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.clearTokenExpiryTimer(),this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,r)=>{t[r]=e}),t}(window.location.href),t=e.error;if(t){const r=e.error_description||t;throw this.emit("error",{error:t,error_description:r}),new Error(r)}const r=e.code;if(!r)return;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e="custos_";class t{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(t){this.storage.setItem(`${e}tokens`,JSON.stringify(t)),this.storage.setItem(`${e}token_issued_at`,Date.now().toString())}getTokens(){const t=this.storage.getItem(`${e}tokens`);return t?JSON.parse(t):null}getTokenIssuedAt(){const t=this.storage.getItem(`${e}token_issued_at`);return t?parseInt(t,10):null}setUser(t){this.storage.setItem(`${e}user`,JSON.stringify(t))}getUser(){const t=this.storage.getItem(`${e}user`);return t?JSON.parse(t):null}setState(t,r){this.storage.setItem(`${e}${t}`,r)}getState(t){return this.storage.getItem(`${e}${t}`)}removeState(t){this.storage.removeItem(`${e}${t}`)}setCodeVerifier(e){this.setState("code_verifier",e)}getCodeVerifier(){return this.getState("code_verifier")}removeCodeVerifier(){this.removeState("code_verifier")}setCodeChallenge(e){this.setState("code_challenge",e)}getCodeChallenge(){return this.getState("code_challenge")}removeCodeChallenge(){this.removeState("code_challenge")}clear(){this.storage.removeItem(`${e}tokens`),this.storage.removeItem(`${e}token_issued_at`),this.storage.removeItem(`${e}user`),this.removeState("oauth_state"),this.removeCodeVerifier(),this.removeCodeChallenge()}hasValidToken(){const e=this.getTokens(),t=this.getTokenIssuedAt();if(!e||!t)return!1;return Date.now()<t+1e3*e.expiresIn}}class r{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,r,s,o){const i={grant_type:"authorization_code",code:e,client_id:t,redirect_uri:r};s&&(i.code_verifier=s),o&&(i.client_secret=o);const n=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(i).toString()});if(!n.ok){const e=await n.json().catch(()=>({error:"unknown_error",error_description:"Failed to exchange code for tokens"}));throw new Error(e.error_description||e.error||"Token exchange failed")}const a=await n.json(),c=a.data||a;return{accessToken:c.access_token,refreshToken:c.refresh_token,expiresIn:c.expires_in,tokenType:c.token_type||"Bearer"}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/api/v1/system/users/profile`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");const r=await t.json();return r.data||r}async refreshAccessToken(e,t,r){const s={grant_type:"refresh_token",refresh_token:e,client_id:t};r&&(s.client_secret=r);const o=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(s).toString()});if(!o.ok)throw new Error("Failed to refresh token");const i=await o.json(),n=i.data||i;return{accessToken:n.access_token,refreshToken:n.refresh_token||e,expiresIn:n.expires_in,tokenType:n.token_type||"Bearer"}}async logout(e){await fetch(`${this.baseUrl}/api/v1/auth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}async validateToken(e){try{return(await fetch(`${this.baseUrl}/api/v1/auth/validate`,{headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}}function s(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}async function o(e){const t=(new TextEncoder).encode(e);return function(e){const t=new Uint8Array(e),r=Array.from(t,e=>String.fromCharCode(e)).join("");return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}(await crypto.subtle.digest("SHA-256",t))}exports.Custos=class{constructor(e){this.tokenExpiryTimer=null;const o=function(e){return e?Array.isArray(e)?e:"string"==typeof e?e.split(" "):["openid","profile","email"]:["openid","profile","email"]}(e.scope);this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:o,responseType:e.responseType||"code",state:e.state||s(),usePKCE:!1!==e.usePKCE,codeChallengeMethod:e.codeChallengeMethod||"S256",grantType:e.grantType||"authorization_code"},this.storage=new t,this.api=new r(this.config.apiUrl),this.listeners=new Map,"undefined"!=typeof window&&(this.handleCallback(),this.setupTokenExpiryMonitoring())}async login(e){const t=this.config.state;this.storage.setState("oauth_state",t);const r={response_type:this.config.responseType,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:Array.isArray(this.config.scope)?this.config.scope.join(" "):this.config.scope,state:t,...e};if(this.config.usePKCE){const e=function(){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=Math.floor(86*Math.random())+43;let r="";for(let s=0;s<t;s++)r+=e.charAt(Math.floor(66*Math.random()));return r}(),t=await o(e);this.storage.setCodeVerifier(e),this.storage.setCodeChallenge(t),r.code_challenge=t,r.code_challenge_method=this.config.codeChallengeMethod}const s=`${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(r)}`;window.location.href=s}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.clearTokenExpiryTimer(),this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,r)=>{t[r]=e}),t}(window.location.href);console.log("Callback params:",e);const t=e.error;if(t){console.error("OAuth error:",t,e.error_description);const r=e.error_description||t;throw this.emit("error",{error:t,error_description:r}),new Error(r)}const r=e.code;if(!r)return;const s=e.state,o=this.storage.getState("oauth_state");if(s!==o)throw console.error("State parameter mismatch:",s,o),this.emit("error",{error:"invalid_state",error_description:"State parameter mismatch"}),new Error("Invalid state parameter");this.storage.removeState("oauth_state");try{const e=this.config.usePKCE&&this.storage.getCodeVerifier()||void 0,t=await this.api.exchangeCodeForTokens(r,this.config.clientId,this.config.redirectUri,e,this.config.clientSecret);this.storage.setTokens(t);const s=await this.api.getUserInfo(t.accessToken);this.storage.setUser(s),this.config.usePKCE&&(this.storage.removeCodeVerifier(),this.storage.removeCodeChallenge()),this.setupTokenExpiryMonitoring(),this.emit("login",{user:s,tokens:t}),window.history.replaceState({},document.title,window.location.pathname+window.location.hash)}catch(t){throw console.error("Callback handling error:",t),this.emit("error",t),t}}getUser(){return this.storage.getUser()}getAccessToken(){return this.storage.getTokens()?.accessToken||null}getRefreshToken(){return this.storage.getTokens()?.refreshToken||null}isAuthenticated(){return this.storage.hasValidToken()&&!!this.storage.getUser()}getState(){return{isAuthenticated:this.isAuthenticated(),user:this.getUser(),tokens:this.storage.getTokens()}}async validateToken(){const e=this.getAccessToken();if(!e)return!1;try{return await this.api.validateToken(e)}catch{return!1}}setupTokenExpiryMonitoring(){this.clearTokenExpiryTimer();const e=this.storage.getTokens(),t=this.storage.getTokenIssuedAt();if(!e||!t)return;const r=1e3*(e.expiresIn-300);r>0&&(this.tokenExpiryTimer=setTimeout(async()=>{try{await this.refreshToken()}catch(e){this.emit("token-expired",e),await this.logout()}},r))}clearTokenExpiryTimer(){this.tokenExpiryTimer&&(clearTimeout(this.tokenExpiryTimer),this.tokenExpiryTimer=null)}async refreshToken(){const e=this.storage.getTokens();if(!e?.refreshToken)throw new Error("No refresh token available");try{const t=await this.api.refreshAccessToken(e.refreshToken,this.config.clientId,this.config.clientSecret);this.storage.setTokens(t),this.setupTokenExpiryMonitoring(),this.emit("token-refresh",t)}catch(e){throw this.emit("error",e),e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){const r={type:e,data:t};this.listeners.get(e)?.forEach(e=>e(r))}clearStorage(){this.storage.clear()}destroy(){this.clearTokenExpiryTimer(),this.listeners.clear()}};
|
|
2
2
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/storage.ts","../src/api.ts","../src/utils.ts","../src/Custos.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\n\nconst STORAGE_PREFIX = 'custos_';\n\nexport class Storage {\n\tprivate storage: globalThis.Storage;\n\n\tconstructor(useSessionStorage = false) {\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\n\t}\n\n\t// Tokens\n\tsetTokens(tokens: AuthTokens): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());\n\t}\n\n\tgetTokens(): AuthTokens | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\tgetTokenIssuedAt(): number | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\treturn data ? parseInt(data, 10) : null;\n\t}\n\n\t// User\n\tsetUser(user: User): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\n\t}\n\n\tgetUser(): User | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\t// State & PKCE\n\tsetState(key: string, value: string): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\n\t}\n\n\tgetState(key: string): string | null {\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\tremoveState(key: string): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\t// PKCE specific\n\tsetCodeVerifier(codeVerifier: string): void {\n\t\tthis.setState('code_verifier', codeVerifier);\n\t}\n\n\tgetCodeVerifier(): string | null {\n\t\treturn this.getState('code_verifier');\n\t}\n\n\tremoveCodeVerifier(): void {\n\t\tthis.removeState('code_verifier');\n\t}\n\n\tsetCodeChallenge(codeChallenge: string): void {\n\t\tthis.setState('code_challenge', codeChallenge);\n\t}\n\n\tgetCodeChallenge(): string | null {\n\t\treturn this.getState('code_challenge');\n\t}\n\n\tremoveCodeChallenge(): void {\n\t\tthis.removeState('code_challenge');\n\t}\n\n\t// Clear all\n\tclear(): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\n\t\tthis.removeState('oauth_state');\n\t\tthis.removeCodeVerifier();\n\t\tthis.removeCodeChallenge();\n\t}\n\n\t// Validation\n\thasValidToken(): boolean {\n\t\tconst tokens = this.getTokens();\n\t\tconst issuedAt = this.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return false;\n\n\t\tconst now = Date.now();\n\t\tconst expirationTime = issuedAt + tokens.expiresIn * 1000;\n\n\t\treturn now < expirationTime;\n\t}\n}\n","import { AuthTokens, User } from './types';\n\nexport class ApiClient {\n\tprivate baseUrl: string;\n\n\tconstructor(baseUrl: string) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tasync exchangeCodeForTokens(\n\t\tcode: string,\n\t\tclientId: string,\n\t\tredirectUri: string,\n\t\tcodeVerifier?: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t};\n\n\t\t// Add PKCE code_verifier if present\n\t\tif (codeVerifier) {\n\t\t\tbody.code_verifier = codeVerifier;\n\t\t}\n\n\t\t// Add client_secret if present (for confidential clients)\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({\n\t\t\t\terror: 'unknown_error',\n\t\t\t\terror_description: 'Failed to exchange code for tokens'\n\t\t\t}));\n\n\t\t\tthrow new Error(errorData.error_description || errorData.error || 'Token exchange failed');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result; // Support both {data: {...}} and direct response\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token,\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync getUserInfo(accessToken: string): Promise<User> {\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/system/users/profile`, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to get user info');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\treturn result.data || result;\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t\tclientId: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: clientId,\n\t\t};\n\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to refresh token');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result;\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync logout(accessToken: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/api/v1/auth/revoke`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync validateToken(accessToken: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn response.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","export function generateState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseQueryString(url: string): Record<string, string> {\n\tconst params: Record<string, string> = {};\n\tconst searchParams = new URL(url).searchParams;\n\tsearchParams.forEach((value, key) => {\n\t\tparams[key] = value;\n\t});\n\treturn params;\n}\n\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\n\tconst now = Date.now();\n\tconst expirationTime = issuedAt + expiresIn * 1000;\n\t// Refresh 5 minutes before expiration\n\treturn now >= expirationTime - 5 * 60 * 1000;\n}\n\n// PKCE utilities\nexport function generateCodeVerifier(): string {\n\tconst charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\tconst length = Math.floor(Math.random() * 86) + 43; // 43-128 characters\n\tlet verifier = '';\n\tfor (let i = 0; i < length; i++) {\n\t\tverifier += charset.charAt(Math.floor(Math.random() * charset.length));\n\t}\n\treturn verifier;\n}\n\nexport async function generateCodeChallenge(codeVerifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(codeVerifier);\n\tconst digest = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(digest);\n}\n\nfunction base64UrlEncode(digest: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(digest);\n\tconst binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nexport function normalizeScope(scope?: string | string[]): string[] {\n\tif (!scope) return ['openid', 'profile', 'email'];\n\tif (Array.isArray(scope)) return scope;\n\tif (typeof scope === 'string') return scope.split(' ');\n\treturn ['openid', 'profile', 'email'];\n}\n","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\nimport { Storage } from './storage';\nimport { ApiClient } from './api';\nimport {\n\tgenerateState,\n\tparseQueryString,\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tnormalizeScope\n} from './utils';\n\nexport class Custos {\n\tprivate config: Required<CustosConfig>;\n\tprivate storage: Storage;\n\tprivate api: ApiClient;\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\n\tprivate tokenExpiryTimer: any = null;\n\n\tconstructor(config: CustosConfig) {\n\t\t// Normalize scope\n\t\tconst scope = normalizeScope(config.scope);\n\n\t\tthis.config = {\n\t\t\tclientId: config.clientId,\n\t\t\tclientSecret: config.clientSecret || '',\n\t\t\tredirectUri: config.redirectUri,\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\n\t\t\tscope,\n\t\t\tresponseType: config.responseType || 'code',\n\t\t\tstate: config.state || generateState(),\n\t\t\tusePKCE: config.usePKCE !== false, // Default to true\n\t\t\tcodeChallengeMethod: config.codeChallengeMethod || 'S256',\n\t\t\tgrantType: config.grantType || 'authorization_code',\n\t\t};\n\n\t\tthis.storage = new Storage();\n\t\tthis.api = new ApiClient(this.config.apiUrl);\n\t\tthis.listeners = new Map();\n\n\t\t// Handle callback automatically\n\t\tif (typeof window !== 'undefined') {\n\t\t\tthis.handleCallback();\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t}\n\t}\n\n\t// ==================== Authentication Methods ====================\n\n\tasync login(additionalParams?: Record<string, string>): Promise<void> {\n\t\tconst state = this.config.state;\n\t\tthis.storage.setState('oauth_state', state);\n\n\t\tconst params: Record<string, string> = {\n\t\t\tresponse_type: this.config.responseType,\n\t\t\tclient_id: this.config.clientId,\n\t\t\tredirect_uri: this.config.redirectUri,\n\t\t\tscope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,\n\t\t\tstate,\n\t\t\t...additionalParams,\n\t\t};\n\n\t\t// Add PKCE if enabled\n\t\tif (this.config.usePKCE) {\n\t\t\tconst codeVerifier = generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t\t\tthis.storage.setCodeVerifier(codeVerifier);\n\t\t\tthis.storage.setCodeChallenge(codeChallenge);\n\n\t\t\tparams.code_challenge = codeChallenge;\n\t\t\tparams.code_challenge_method = this.config.codeChallengeMethod;\n\t\t}\n\n\t\tconst authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;\n\t\twindow.location.href = authUrl;\n\t}\n\n\tasync logout(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\n\t\tif (tokens?.accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.api.logout(tokens.accessToken);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Logout error:', error);\n\t\t\t}\n\t\t}\n\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.storage.clear();\n\t\tthis.emit('logout', null);\n\t}\n\n\tasync handleCallback(): Promise<void> {\n\t\tconst params = parseQueryString(window.location.href);\n\n\t\t// Check for errors\n\t\tconst error = params.error;\n\t\tif (error) {\n\t\t\tconst errorDescription = params.error_description || error;\n\t\t\tthis.emit('error', { error, error_description: errorDescription });\n\t\t\tthrow new Error(errorDescription);\n\t\t}\n\n\t\t// Check for authorization code\n\t\tconst code = params.code;\n\t\tif (!code) return;\n\n\t\t// Validate state\n\t\tconst state = params.state;\n\t\tconst savedState = this.storage.getState('oauth_state');\n\t\tif (state !== savedState) {\n\t\t\tthis.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });\n\t\t\tthrow new Error('Invalid state parameter');\n\t\t}\n\n\t\tthis.storage.removeState('oauth_state');\n\n\t\ttry {\n\t\t\t// Get code_verifier if using PKCE\n\t\t\tconst codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;\n\n\t\t\t// Exchange code for tokens\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\n\t\t\t\tcode,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.redirectUri,\n\t\t\t\tcodeVerifier,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(tokens);\n\n\t\t\t// Get user info\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\n\t\t\tthis.storage.setUser(user);\n\n\t\t\t// Clean up PKCE data\n\t\t\tif (this.config.usePKCE) {\n\t\t\t\tthis.storage.removeCodeVerifier();\n\t\t\t\tthis.storage.removeCodeChallenge();\n\t\t\t}\n\n\t\t\t// Setup token expiry monitoring\n\t\t\tthis.setupTokenExpiryMonitoring();\n\n\t\t\tthis.emit('login', { user, tokens });\n\n\t\t\t// Clean URL (remove query params)\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname + window.location.hash);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== User Methods ====================\n\n\tgetUser(): User | null {\n\t\treturn this.storage.getUser();\n\t}\n\n\tgetAccessToken(): string | null {\n\t\treturn this.storage.getTokens()?.accessToken || null;\n\t}\n\n\tgetRefreshToken(): string | null {\n\t\treturn this.storage.getTokens()?.refreshToken || null;\n\t}\n\n\tisAuthenticated(): boolean {\n\t\treturn this.storage.hasValidToken() && !!this.storage.getUser();\n\t}\n\n\tgetState(): AuthState {\n\t\treturn {\n\t\t\tisAuthenticated: this.isAuthenticated(),\n\t\t\tuser: this.getUser(),\n\t\t\ttokens: this.storage.getTokens(),\n\t\t};\n\t}\n\n\tasync validateToken(): Promise<boolean> {\n\t\tconst accessToken = this.getAccessToken();\n\t\tif (!accessToken) return false;\n\n\t\ttry {\n\t\t\treturn await this.api.validateToken(accessToken);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==================== Token Refresh ====================\n\n\tprivate setupTokenExpiryMonitoring(): void {\n\t\tthis.clearTokenExpiryTimer();\n\n\t\tconst tokens = this.storage.getTokens();\n\t\tconst issuedAt = this.storage.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return;\n\n\t\t// Refresh 5 minutes before expiry\n\t\tconst timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer\n\n\t\tif (timeUntilRefresh > 0) {\n\t\t\tthis.tokenExpiryTimer = setTimeout(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.refreshToken();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.emit('token-expired', error);\n\t\t\t\t\tawait this.logout();\n\t\t\t\t}\n\t\t\t}, timeUntilRefresh);\n\t\t}\n\t}\n\n\tprivate clearTokenExpiryTimer(): void {\n\t\tif (this.tokenExpiryTimer) {\n\t\t\tclearTimeout(this.tokenExpiryTimer);\n\t\t\tthis.tokenExpiryTimer = null;\n\t\t}\n\t}\n\n\tasync refreshToken(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\t\tif (!tokens?.refreshToken) {\n\t\t\tthrow new Error('No refresh token available');\n\t\t}\n\n\t\ttry {\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\n\t\t\t\ttokens.refreshToken,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(newTokens);\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t\tthis.emit('token-refresh', newTokens);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== Event Handling ====================\n\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)!.add(callback);\n\t}\n\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tthis.listeners.get(event)?.delete(callback);\n\t}\n\n\tprivate emit(type: AuthEventType, data?: any): void {\n\t\tconst event: AuthEvent = { type, data };\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\n\t}\n\n\t// ==================== Utility Methods ====================\n\n\tclearStorage(): void {\n\t\tthis.storage.clear();\n\t}\n\n\tdestroy(): void {\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.listeners.clear();\n\t}\n}\n"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","Date","now","toString","getTokens","data","getItem","parse","getTokenIssuedAt","parseInt","setUser","user","getUser","setState","key","value","getState","removeState","removeItem","setCodeVerifier","codeVerifier","getCodeVerifier","removeCodeVerifier","setCodeChallenge","codeChallenge","getCodeChallenge","removeCodeChallenge","clear","hasValidToken","issuedAt","expiresIn","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","body","grant_type","client_id","redirect_uri","code_verifier","client_secret","response","fetch","method","headers","URLSearchParams","ok","errorData","json","catch","error","error_description","Error","result","accessToken","access_token","refreshToken","refresh_token","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","validateToken","generateState","array","Uint8Array","crypto","getRandomValues","Array","from","byte","padStart","join","async","generateCodeChallenge","TextEncoder","encode","digest","bytes","binary","String","fromCharCode","btoa","replace","base64UrlEncode","subtle","config","tokenExpiryTimer","scope","isArray","split","normalizeScope","apiUrl","responseType","state","usePKCE","codeChallengeMethod","grantType","api","listeners","Map","window","handleCallback","setupTokenExpiryMonitoring","login","additionalParams","params","response_type","charset","length","Math","floor","random","verifier","i","charAt","generateCodeVerifier","code_challenge","code_challenge_method","authUrl","location","href","console","clearTokenExpiryTimer","emit","url","URL","searchParams","forEach","parseQueryString","errorDescription","undefined","history","replaceState","document","title","pathname","hash","getAccessToken","getRefreshToken","isAuthenticated","timeUntilRefresh","setTimeout","clearTimeout","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type","clearStorage","destroy"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/storage.ts","../src/api.ts","../src/utils.ts","../src/Custos.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\n\nconst STORAGE_PREFIX = 'custos_';\n\nexport class Storage {\n\tprivate storage: globalThis.Storage;\n\n\tconstructor(useSessionStorage = false) {\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\n\t}\n\n\t// Tokens\n\tsetTokens(tokens: AuthTokens): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());\n\t}\n\n\tgetTokens(): AuthTokens | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\tgetTokenIssuedAt(): number | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\treturn data ? parseInt(data, 10) : null;\n\t}\n\n\t// User\n\tsetUser(user: User): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\n\t}\n\n\tgetUser(): User | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\t// State & PKCE\n\tsetState(key: string, value: string): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\n\t}\n\n\tgetState(key: string): string | null {\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\tremoveState(key: string): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\t// PKCE specific\n\tsetCodeVerifier(codeVerifier: string): void {\n\t\tthis.setState('code_verifier', codeVerifier);\n\t}\n\n\tgetCodeVerifier(): string | null {\n\t\treturn this.getState('code_verifier');\n\t}\n\n\tremoveCodeVerifier(): void {\n\t\tthis.removeState('code_verifier');\n\t}\n\n\tsetCodeChallenge(codeChallenge: string): void {\n\t\tthis.setState('code_challenge', codeChallenge);\n\t}\n\n\tgetCodeChallenge(): string | null {\n\t\treturn this.getState('code_challenge');\n\t}\n\n\tremoveCodeChallenge(): void {\n\t\tthis.removeState('code_challenge');\n\t}\n\n\t// Clear all\n\tclear(): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\n\t\tthis.removeState('oauth_state');\n\t\tthis.removeCodeVerifier();\n\t\tthis.removeCodeChallenge();\n\t}\n\n\t// Validation\n\thasValidToken(): boolean {\n\t\tconst tokens = this.getTokens();\n\t\tconst issuedAt = this.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return false;\n\n\t\tconst now = Date.now();\n\t\tconst expirationTime = issuedAt + tokens.expiresIn * 1000;\n\n\t\treturn now < expirationTime;\n\t}\n}\n","import { AuthTokens, User } from './types';\n\nexport class ApiClient {\n\tprivate baseUrl: string;\n\n\tconstructor(baseUrl: string) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tasync exchangeCodeForTokens(\n\t\tcode: string,\n\t\tclientId: string,\n\t\tredirectUri: string,\n\t\tcodeVerifier?: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t};\n\n\t\t// Add PKCE code_verifier if present\n\t\tif (codeVerifier) {\n\t\t\tbody.code_verifier = codeVerifier;\n\t\t}\n\n\t\t// Add client_secret if present (for confidential clients)\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({\n\t\t\t\terror: 'unknown_error',\n\t\t\t\terror_description: 'Failed to exchange code for tokens'\n\t\t\t}));\n\n\t\t\tthrow new Error(errorData.error_description || errorData.error || 'Token exchange failed');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result; // Support both {data: {...}} and direct response\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token,\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync getUserInfo(accessToken: string): Promise<User> {\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/system/users/profile`, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to get user info');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\treturn result.data || result;\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t\tclientId: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: clientId,\n\t\t};\n\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to refresh token');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result;\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync logout(accessToken: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/api/v1/auth/revoke`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync validateToken(accessToken: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn response.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","export function generateState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseQueryString(url: string): Record<string, string> {\n\tconst params: Record<string, string> = {};\n\tconst searchParams = new URL(url).searchParams;\n\tsearchParams.forEach((value, key) => {\n\t\tparams[key] = value;\n\t});\n\treturn params;\n}\n\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\n\tconst now = Date.now();\n\tconst expirationTime = issuedAt + expiresIn * 1000;\n\t// Refresh 5 minutes before expiration\n\treturn now >= expirationTime - 5 * 60 * 1000;\n}\n\n// PKCE utilities\nexport function generateCodeVerifier(): string {\n\tconst charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\tconst length = Math.floor(Math.random() * 86) + 43; // 43-128 characters\n\tlet verifier = '';\n\tfor (let i = 0; i < length; i++) {\n\t\tverifier += charset.charAt(Math.floor(Math.random() * charset.length));\n\t}\n\treturn verifier;\n}\n\nexport async function generateCodeChallenge(codeVerifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(codeVerifier);\n\tconst digest = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(digest);\n}\n\nfunction base64UrlEncode(digest: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(digest);\n\tconst binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nexport function normalizeScope(scope?: string | string[]): string[] {\n\tif (!scope) return ['openid', 'profile', 'email'];\n\tif (Array.isArray(scope)) return scope;\n\tif (typeof scope === 'string') return scope.split(' ');\n\treturn ['openid', 'profile', 'email'];\n}\n","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\nimport { Storage } from './storage';\nimport { ApiClient } from './api';\nimport {\n\tgenerateState,\n\tparseQueryString,\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tnormalizeScope\n} from './utils';\n\nexport class Custos {\n\tprivate config: Required<CustosConfig>;\n\tprivate storage: Storage;\n\tprivate api: ApiClient;\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\n\tprivate tokenExpiryTimer: any = null;\n\n\tconstructor(config: CustosConfig) {\n\t\t// Normalize scope\n\t\tconst scope = normalizeScope(config.scope);\n\n\t\tthis.config = {\n\t\t\tclientId: config.clientId,\n\t\t\tclientSecret: config.clientSecret || '',\n\t\t\tredirectUri: config.redirectUri,\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\n\t\t\tscope,\n\t\t\tresponseType: config.responseType || 'code',\n\t\t\tstate: config.state || generateState(),\n\t\t\tusePKCE: config.usePKCE !== false, // Default to true\n\t\t\tcodeChallengeMethod: config.codeChallengeMethod || 'S256',\n\t\t\tgrantType: config.grantType || 'authorization_code',\n\t\t};\n\n\t\tthis.storage = new Storage();\n\t\tthis.api = new ApiClient(this.config.apiUrl);\n\t\tthis.listeners = new Map();\n\n\t\t// Handle callback automatically\n\t\tif (typeof window !== 'undefined') {\n\t\t\tthis.handleCallback();\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t}\n\t}\n\n\t// ==================== Authentication Methods ====================\n\n\tasync login(additionalParams?: Record<string, string>): Promise<void> {\n\t\tconst state = this.config.state;\n\t\tthis.storage.setState('oauth_state', state);\n\n\t\tconst params: Record<string, string> = {\n\t\t\tresponse_type: this.config.responseType,\n\t\t\tclient_id: this.config.clientId,\n\t\t\tredirect_uri: this.config.redirectUri,\n\t\t\tscope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,\n\t\t\tstate,\n\t\t\t...additionalParams,\n\t\t};\n\n\t\t// Add PKCE if enabled\n\t\tif (this.config.usePKCE) {\n\t\t\tconst codeVerifier = generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t\t\tthis.storage.setCodeVerifier(codeVerifier);\n\t\t\tthis.storage.setCodeChallenge(codeChallenge);\n\n\t\t\tparams.code_challenge = codeChallenge;\n\t\t\tparams.code_challenge_method = this.config.codeChallengeMethod;\n\t\t}\n\n\t\tconst authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;\n\t\twindow.location.href = authUrl;\n\t}\n\n\tasync logout(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\n\t\tif (tokens?.accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.api.logout(tokens.accessToken);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Logout error:', error);\n\t\t\t}\n\t\t}\n\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.storage.clear();\n\t\tthis.emit('logout', null);\n\t}\n\n\tasync handleCallback(): Promise<void> {\n\t\tconst params = parseQueryString(window.location.href);\n\t\tconsole.log('Callback params:', params);\n\n\t\t// Check for errors\n\t\tconst error = params.error;\n\t\tif (error) {\n\t\t\tconsole.error('OAuth error:', error, params.error_description);\n\t\t\tconst errorDescription = params.error_description || error;\n\t\t\tthis.emit('error', { error, error_description: errorDescription });\n\t\t\tthrow new Error(errorDescription);\n\t\t}\n\n\t\t// Check for authorization code\n\t\tconst code = params.code;\n\t\tif (!code) return;\n\n\t\t// Validate state\n\t\tconst state = params.state;\n\t\tconst savedState = this.storage.getState('oauth_state');\n\t\tif (state !== savedState) {\n\t\t\tconsole.error('State parameter mismatch:', state, savedState);\n\t\t\tthis.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });\n\t\t\tthrow new Error('Invalid state parameter');\n\t\t}\n\n\t\tthis.storage.removeState('oauth_state');\n\n\t\ttry {\n\t\t\t// Get code_verifier if using PKCE\n\t\t\tconst codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;\n\n\t\t\t// Exchange code for tokens\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\n\t\t\t\tcode,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.redirectUri,\n\t\t\t\tcodeVerifier,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(tokens);\n\n\t\t\t// Get user info\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\n\t\t\tthis.storage.setUser(user);\n\n\t\t\t// Clean up PKCE data\n\t\t\tif (this.config.usePKCE) {\n\t\t\t\tthis.storage.removeCodeVerifier();\n\t\t\t\tthis.storage.removeCodeChallenge();\n\t\t\t}\n\n\t\t\t// Setup token expiry monitoring\n\t\t\tthis.setupTokenExpiryMonitoring();\n\n\t\t\tthis.emit('login', { user, tokens });\n\n\t\t\t// Clean URL (remove query params)\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname + window.location.hash);\n\t\t} catch (error) {\n\t\t\tconsole.error('Callback handling error:', error);\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== User Methods ====================\n\n\tgetUser(): User | null {\n\t\treturn this.storage.getUser();\n\t}\n\n\tgetAccessToken(): string | null {\n\t\treturn this.storage.getTokens()?.accessToken || null;\n\t}\n\n\tgetRefreshToken(): string | null {\n\t\treturn this.storage.getTokens()?.refreshToken || null;\n\t}\n\n\tisAuthenticated(): boolean {\n\t\treturn this.storage.hasValidToken() && !!this.storage.getUser();\n\t}\n\n\tgetState(): AuthState {\n\t\treturn {\n\t\t\tisAuthenticated: this.isAuthenticated(),\n\t\t\tuser: this.getUser(),\n\t\t\ttokens: this.storage.getTokens(),\n\t\t};\n\t}\n\n\tasync validateToken(): Promise<boolean> {\n\t\tconst accessToken = this.getAccessToken();\n\t\tif (!accessToken) return false;\n\n\t\ttry {\n\t\t\treturn await this.api.validateToken(accessToken);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==================== Token Refresh ====================\n\n\tprivate setupTokenExpiryMonitoring(): void {\n\t\tthis.clearTokenExpiryTimer();\n\n\t\tconst tokens = this.storage.getTokens();\n\t\tconst issuedAt = this.storage.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return;\n\n\t\t// Refresh 5 minutes before expiry\n\t\tconst timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer\n\n\t\tif (timeUntilRefresh > 0) {\n\t\t\tthis.tokenExpiryTimer = setTimeout(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.refreshToken();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.emit('token-expired', error);\n\t\t\t\t\tawait this.logout();\n\t\t\t\t}\n\t\t\t}, timeUntilRefresh);\n\t\t}\n\t}\n\n\tprivate clearTokenExpiryTimer(): void {\n\t\tif (this.tokenExpiryTimer) {\n\t\t\tclearTimeout(this.tokenExpiryTimer);\n\t\t\tthis.tokenExpiryTimer = null;\n\t\t}\n\t}\n\n\tasync refreshToken(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\t\tif (!tokens?.refreshToken) {\n\t\t\tthrow new Error('No refresh token available');\n\t\t}\n\n\t\ttry {\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\n\t\t\t\ttokens.refreshToken,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(newTokens);\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t\tthis.emit('token-refresh', newTokens);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== Event Handling ====================\n\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)!.add(callback);\n\t}\n\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tthis.listeners.get(event)?.delete(callback);\n\t}\n\n\tprivate emit(type: AuthEventType, data?: any): void {\n\t\tconst event: AuthEvent = { type, data };\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\n\t}\n\n\t// ==================== Utility Methods ====================\n\n\tclearStorage(): void {\n\t\tthis.storage.clear();\n\t}\n\n\tdestroy(): void {\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.listeners.clear();\n\t}\n}\n"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","Date","now","toString","getTokens","data","getItem","parse","getTokenIssuedAt","parseInt","setUser","user","getUser","setState","key","value","getState","removeState","removeItem","setCodeVerifier","codeVerifier","getCodeVerifier","removeCodeVerifier","setCodeChallenge","codeChallenge","getCodeChallenge","removeCodeChallenge","clear","hasValidToken","issuedAt","expiresIn","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","body","grant_type","client_id","redirect_uri","code_verifier","client_secret","response","fetch","method","headers","URLSearchParams","ok","errorData","json","catch","error","error_description","Error","result","accessToken","access_token","refreshToken","refresh_token","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","validateToken","generateState","array","Uint8Array","crypto","getRandomValues","Array","from","byte","padStart","join","async","generateCodeChallenge","TextEncoder","encode","digest","bytes","binary","String","fromCharCode","btoa","replace","base64UrlEncode","subtle","config","tokenExpiryTimer","scope","isArray","split","normalizeScope","apiUrl","responseType","state","usePKCE","codeChallengeMethod","grantType","api","listeners","Map","window","handleCallback","setupTokenExpiryMonitoring","login","additionalParams","params","response_type","charset","length","Math","floor","random","verifier","i","charAt","generateCodeVerifier","code_challenge","code_challenge_method","authUrl","location","href","console","clearTokenExpiryTimer","emit","url","URL","searchParams","forEach","parseQueryString","log","errorDescription","savedState","undefined","history","replaceState","document","title","pathname","hash","getAccessToken","getRefreshToken","isAuthenticated","timeUntilRefresh","setTimeout","clearTimeout","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type","clearStorage","destroy"],"mappings":"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"}
|
package/dist/index.esm.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e="custos_";class t{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(t){this.storage.setItem(`${e}tokens`,JSON.stringify(t)),this.storage.setItem(`${e}token_issued_at`,Date.now().toString())}getTokens(){const t=this.storage.getItem(`${e}tokens`);return t?JSON.parse(t):null}getTokenIssuedAt(){const t=this.storage.getItem(`${e}token_issued_at`);return t?parseInt(t,10):null}setUser(t){this.storage.setItem(`${e}user`,JSON.stringify(t))}getUser(){const t=this.storage.getItem(`${e}user`);return t?JSON.parse(t):null}setState(t,r){this.storage.setItem(`${e}${t}`,r)}getState(t){return this.storage.getItem(`${e}${t}`)}removeState(t){this.storage.removeItem(`${e}${t}`)}setCodeVerifier(e){this.setState("code_verifier",e)}getCodeVerifier(){return this.getState("code_verifier")}removeCodeVerifier(){this.removeState("code_verifier")}setCodeChallenge(e){this.setState("code_challenge",e)}getCodeChallenge(){return this.getState("code_challenge")}removeCodeChallenge(){this.removeState("code_challenge")}clear(){this.storage.removeItem(`${e}tokens`),this.storage.removeItem(`${e}token_issued_at`),this.storage.removeItem(`${e}user`),this.removeState("oauth_state"),this.removeCodeVerifier(),this.removeCodeChallenge()}hasValidToken(){const e=this.getTokens(),t=this.getTokenIssuedAt();if(!e||!t)return!1;return Date.now()<t+1e3*e.expiresIn}}class r{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,r,s
|
|
1
|
+
const e="custos_";class t{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(t){this.storage.setItem(`${e}tokens`,JSON.stringify(t)),this.storage.setItem(`${e}token_issued_at`,Date.now().toString())}getTokens(){const t=this.storage.getItem(`${e}tokens`);return t?JSON.parse(t):null}getTokenIssuedAt(){const t=this.storage.getItem(`${e}token_issued_at`);return t?parseInt(t,10):null}setUser(t){this.storage.setItem(`${e}user`,JSON.stringify(t))}getUser(){const t=this.storage.getItem(`${e}user`);return t?JSON.parse(t):null}setState(t,r){this.storage.setItem(`${e}${t}`,r)}getState(t){return this.storage.getItem(`${e}${t}`)}removeState(t){this.storage.removeItem(`${e}${t}`)}setCodeVerifier(e){this.setState("code_verifier",e)}getCodeVerifier(){return this.getState("code_verifier")}removeCodeVerifier(){this.removeState("code_verifier")}setCodeChallenge(e){this.setState("code_challenge",e)}getCodeChallenge(){return this.getState("code_challenge")}removeCodeChallenge(){this.removeState("code_challenge")}clear(){this.storage.removeItem(`${e}tokens`),this.storage.removeItem(`${e}token_issued_at`),this.storage.removeItem(`${e}user`),this.removeState("oauth_state"),this.removeCodeVerifier(),this.removeCodeChallenge()}hasValidToken(){const e=this.getTokens(),t=this.getTokenIssuedAt();if(!e||!t)return!1;return Date.now()<t+1e3*e.expiresIn}}class r{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,r,o,s){const i={grant_type:"authorization_code",code:e,client_id:t,redirect_uri:r};o&&(i.code_verifier=o),s&&(i.client_secret=s);const n=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(i).toString()});if(!n.ok){const e=await n.json().catch(()=>({error:"unknown_error",error_description:"Failed to exchange code for tokens"}));throw new Error(e.error_description||e.error||"Token exchange failed")}const a=await n.json(),c=a.data||a;return{accessToken:c.access_token,refreshToken:c.refresh_token,expiresIn:c.expires_in,tokenType:c.token_type||"Bearer"}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/api/v1/system/users/profile`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");const r=await t.json();return r.data||r}async refreshAccessToken(e,t,r){const o={grant_type:"refresh_token",refresh_token:e,client_id:t};r&&(o.client_secret=r);const s=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(o).toString()});if(!s.ok)throw new Error("Failed to refresh token");const i=await s.json(),n=i.data||i;return{accessToken:n.access_token,refreshToken:n.refresh_token||e,expiresIn:n.expires_in,tokenType:n.token_type||"Bearer"}}async logout(e){await fetch(`${this.baseUrl}/api/v1/auth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}async validateToken(e){try{return(await fetch(`${this.baseUrl}/api/v1/auth/validate`,{headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}}function o(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}async function s(e){const t=(new TextEncoder).encode(e);return function(e){const t=new Uint8Array(e),r=Array.from(t,e=>String.fromCharCode(e)).join("");return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}(await crypto.subtle.digest("SHA-256",t))}class i{constructor(e){this.tokenExpiryTimer=null;const s=function(e){return e?Array.isArray(e)?e:"string"==typeof e?e.split(" "):["openid","profile","email"]:["openid","profile","email"]}(e.scope);this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:s,responseType:e.responseType||"code",state:e.state||o(),usePKCE:!1!==e.usePKCE,codeChallengeMethod:e.codeChallengeMethod||"S256",grantType:e.grantType||"authorization_code"},this.storage=new t,this.api=new r(this.config.apiUrl),this.listeners=new Map,"undefined"!=typeof window&&(this.handleCallback(),this.setupTokenExpiryMonitoring())}async login(e){const t=this.config.state;this.storage.setState("oauth_state",t);const r={response_type:this.config.responseType,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:Array.isArray(this.config.scope)?this.config.scope.join(" "):this.config.scope,state:t,...e};if(this.config.usePKCE){const e=function(){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=Math.floor(86*Math.random())+43;let r="";for(let o=0;o<t;o++)r+=e.charAt(Math.floor(66*Math.random()));return r}(),t=await s(e);this.storage.setCodeVerifier(e),this.storage.setCodeChallenge(t),r.code_challenge=t,r.code_challenge_method=this.config.codeChallengeMethod}const o=`${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(r)}`;window.location.href=o}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.clearTokenExpiryTimer(),this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,r)=>{t[r]=e}),t}(window.location.href);console.log("Callback params:",e);const t=e.error;if(t){console.error("OAuth error:",t,e.error_description);const r=e.error_description||t;throw this.emit("error",{error:t,error_description:r}),new Error(r)}const r=e.code;if(!r)return;const o=e.state,s=this.storage.getState("oauth_state");if(o!==s)throw console.error("State parameter mismatch:",o,s),this.emit("error",{error:"invalid_state",error_description:"State parameter mismatch"}),new Error("Invalid state parameter");this.storage.removeState("oauth_state");try{const e=this.config.usePKCE&&this.storage.getCodeVerifier()||void 0,t=await this.api.exchangeCodeForTokens(r,this.config.clientId,this.config.redirectUri,e,this.config.clientSecret);this.storage.setTokens(t);const o=await this.api.getUserInfo(t.accessToken);this.storage.setUser(o),this.config.usePKCE&&(this.storage.removeCodeVerifier(),this.storage.removeCodeChallenge()),this.setupTokenExpiryMonitoring(),this.emit("login",{user:o,tokens:t}),window.history.replaceState({},document.title,window.location.pathname+window.location.hash)}catch(t){throw console.error("Callback handling error:",t),this.emit("error",t),t}}getUser(){return this.storage.getUser()}getAccessToken(){return this.storage.getTokens()?.accessToken||null}getRefreshToken(){return this.storage.getTokens()?.refreshToken||null}isAuthenticated(){return this.storage.hasValidToken()&&!!this.storage.getUser()}getState(){return{isAuthenticated:this.isAuthenticated(),user:this.getUser(),tokens:this.storage.getTokens()}}async validateToken(){const e=this.getAccessToken();if(!e)return!1;try{return await this.api.validateToken(e)}catch{return!1}}setupTokenExpiryMonitoring(){this.clearTokenExpiryTimer();const e=this.storage.getTokens(),t=this.storage.getTokenIssuedAt();if(!e||!t)return;const r=1e3*(e.expiresIn-300);r>0&&(this.tokenExpiryTimer=setTimeout(async()=>{try{await this.refreshToken()}catch(e){this.emit("token-expired",e),await this.logout()}},r))}clearTokenExpiryTimer(){this.tokenExpiryTimer&&(clearTimeout(this.tokenExpiryTimer),this.tokenExpiryTimer=null)}async refreshToken(){const e=this.storage.getTokens();if(!e?.refreshToken)throw new Error("No refresh token available");try{const t=await this.api.refreshAccessToken(e.refreshToken,this.config.clientId,this.config.clientSecret);this.storage.setTokens(t),this.setupTokenExpiryMonitoring(),this.emit("token-refresh",t)}catch(e){throw this.emit("error",e),e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){const r={type:e,data:t};this.listeners.get(e)?.forEach(e=>e(r))}clearStorage(){this.storage.clear()}destroy(){this.clearTokenExpiryTimer(),this.listeners.clear()}}export{i as Custos};
|
|
2
2
|
//# sourceMappingURL=index.esm.js.map
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../src/storage.ts","../src/api.ts","../src/utils.ts","../src/Custos.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\n\nconst STORAGE_PREFIX = 'custos_';\n\nexport class Storage {\n\tprivate storage: globalThis.Storage;\n\n\tconstructor(useSessionStorage = false) {\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\n\t}\n\n\t// Tokens\n\tsetTokens(tokens: AuthTokens): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());\n\t}\n\n\tgetTokens(): AuthTokens | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\tgetTokenIssuedAt(): number | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\treturn data ? parseInt(data, 10) : null;\n\t}\n\n\t// User\n\tsetUser(user: User): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\n\t}\n\n\tgetUser(): User | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\t// State & PKCE\n\tsetState(key: string, value: string): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\n\t}\n\n\tgetState(key: string): string | null {\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\tremoveState(key: string): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\t// PKCE specific\n\tsetCodeVerifier(codeVerifier: string): void {\n\t\tthis.setState('code_verifier', codeVerifier);\n\t}\n\n\tgetCodeVerifier(): string | null {\n\t\treturn this.getState('code_verifier');\n\t}\n\n\tremoveCodeVerifier(): void {\n\t\tthis.removeState('code_verifier');\n\t}\n\n\tsetCodeChallenge(codeChallenge: string): void {\n\t\tthis.setState('code_challenge', codeChallenge);\n\t}\n\n\tgetCodeChallenge(): string | null {\n\t\treturn this.getState('code_challenge');\n\t}\n\n\tremoveCodeChallenge(): void {\n\t\tthis.removeState('code_challenge');\n\t}\n\n\t// Clear all\n\tclear(): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\n\t\tthis.removeState('oauth_state');\n\t\tthis.removeCodeVerifier();\n\t\tthis.removeCodeChallenge();\n\t}\n\n\t// Validation\n\thasValidToken(): boolean {\n\t\tconst tokens = this.getTokens();\n\t\tconst issuedAt = this.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return false;\n\n\t\tconst now = Date.now();\n\t\tconst expirationTime = issuedAt + tokens.expiresIn * 1000;\n\n\t\treturn now < expirationTime;\n\t}\n}\n","import { AuthTokens, User } from './types';\n\nexport class ApiClient {\n\tprivate baseUrl: string;\n\n\tconstructor(baseUrl: string) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tasync exchangeCodeForTokens(\n\t\tcode: string,\n\t\tclientId: string,\n\t\tredirectUri: string,\n\t\tcodeVerifier?: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t};\n\n\t\t// Add PKCE code_verifier if present\n\t\tif (codeVerifier) {\n\t\t\tbody.code_verifier = codeVerifier;\n\t\t}\n\n\t\t// Add client_secret if present (for confidential clients)\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({\n\t\t\t\terror: 'unknown_error',\n\t\t\t\terror_description: 'Failed to exchange code for tokens'\n\t\t\t}));\n\n\t\t\tthrow new Error(errorData.error_description || errorData.error || 'Token exchange failed');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result; // Support both {data: {...}} and direct response\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token,\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync getUserInfo(accessToken: string): Promise<User> {\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/system/users/profile`, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to get user info');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\treturn result.data || result;\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t\tclientId: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: clientId,\n\t\t};\n\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to refresh token');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result;\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync logout(accessToken: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/api/v1/auth/revoke`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync validateToken(accessToken: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn response.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","export function generateState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseQueryString(url: string): Record<string, string> {\n\tconst params: Record<string, string> = {};\n\tconst searchParams = new URL(url).searchParams;\n\tsearchParams.forEach((value, key) => {\n\t\tparams[key] = value;\n\t});\n\treturn params;\n}\n\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\n\tconst now = Date.now();\n\tconst expirationTime = issuedAt + expiresIn * 1000;\n\t// Refresh 5 minutes before expiration\n\treturn now >= expirationTime - 5 * 60 * 1000;\n}\n\n// PKCE utilities\nexport function generateCodeVerifier(): string {\n\tconst charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\tconst length = Math.floor(Math.random() * 86) + 43; // 43-128 characters\n\tlet verifier = '';\n\tfor (let i = 0; i < length; i++) {\n\t\tverifier += charset.charAt(Math.floor(Math.random() * charset.length));\n\t}\n\treturn verifier;\n}\n\nexport async function generateCodeChallenge(codeVerifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(codeVerifier);\n\tconst digest = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(digest);\n}\n\nfunction base64UrlEncode(digest: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(digest);\n\tconst binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nexport function normalizeScope(scope?: string | string[]): string[] {\n\tif (!scope) return ['openid', 'profile', 'email'];\n\tif (Array.isArray(scope)) return scope;\n\tif (typeof scope === 'string') return scope.split(' ');\n\treturn ['openid', 'profile', 'email'];\n}\n","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\nimport { Storage } from './storage';\nimport { ApiClient } from './api';\nimport {\n\tgenerateState,\n\tparseQueryString,\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tnormalizeScope\n} from './utils';\n\nexport class Custos {\n\tprivate config: Required<CustosConfig>;\n\tprivate storage: Storage;\n\tprivate api: ApiClient;\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\n\tprivate tokenExpiryTimer: any = null;\n\n\tconstructor(config: CustosConfig) {\n\t\t// Normalize scope\n\t\tconst scope = normalizeScope(config.scope);\n\n\t\tthis.config = {\n\t\t\tclientId: config.clientId,\n\t\t\tclientSecret: config.clientSecret || '',\n\t\t\tredirectUri: config.redirectUri,\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\n\t\t\tscope,\n\t\t\tresponseType: config.responseType || 'code',\n\t\t\tstate: config.state || generateState(),\n\t\t\tusePKCE: config.usePKCE !== false, // Default to true\n\t\t\tcodeChallengeMethod: config.codeChallengeMethod || 'S256',\n\t\t\tgrantType: config.grantType || 'authorization_code',\n\t\t};\n\n\t\tthis.storage = new Storage();\n\t\tthis.api = new ApiClient(this.config.apiUrl);\n\t\tthis.listeners = new Map();\n\n\t\t// Handle callback automatically\n\t\tif (typeof window !== 'undefined') {\n\t\t\tthis.handleCallback();\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t}\n\t}\n\n\t// ==================== Authentication Methods ====================\n\n\tasync login(additionalParams?: Record<string, string>): Promise<void> {\n\t\tconst state = this.config.state;\n\t\tthis.storage.setState('oauth_state', state);\n\n\t\tconst params: Record<string, string> = {\n\t\t\tresponse_type: this.config.responseType,\n\t\t\tclient_id: this.config.clientId,\n\t\t\tredirect_uri: this.config.redirectUri,\n\t\t\tscope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,\n\t\t\tstate,\n\t\t\t...additionalParams,\n\t\t};\n\n\t\t// Add PKCE if enabled\n\t\tif (this.config.usePKCE) {\n\t\t\tconst codeVerifier = generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t\t\tthis.storage.setCodeVerifier(codeVerifier);\n\t\t\tthis.storage.setCodeChallenge(codeChallenge);\n\n\t\t\tparams.code_challenge = codeChallenge;\n\t\t\tparams.code_challenge_method = this.config.codeChallengeMethod;\n\t\t}\n\n\t\tconst authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;\n\t\twindow.location.href = authUrl;\n\t}\n\n\tasync logout(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\n\t\tif (tokens?.accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.api.logout(tokens.accessToken);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Logout error:', error);\n\t\t\t}\n\t\t}\n\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.storage.clear();\n\t\tthis.emit('logout', null);\n\t}\n\n\tasync handleCallback(): Promise<void> {\n\t\tconst params = parseQueryString(window.location.href);\n\n\t\t// Check for errors\n\t\tconst error = params.error;\n\t\tif (error) {\n\t\t\tconst errorDescription = params.error_description || error;\n\t\t\tthis.emit('error', { error, error_description: errorDescription });\n\t\t\tthrow new Error(errorDescription);\n\t\t}\n\n\t\t// Check for authorization code\n\t\tconst code = params.code;\n\t\tif (!code) return;\n\n\t\t// Validate state\n\t\tconst state = params.state;\n\t\tconst savedState = this.storage.getState('oauth_state');\n\t\tif (state !== savedState) {\n\t\t\tthis.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });\n\t\t\tthrow new Error('Invalid state parameter');\n\t\t}\n\n\t\tthis.storage.removeState('oauth_state');\n\n\t\ttry {\n\t\t\t// Get code_verifier if using PKCE\n\t\t\tconst codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;\n\n\t\t\t// Exchange code for tokens\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\n\t\t\t\tcode,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.redirectUri,\n\t\t\t\tcodeVerifier,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(tokens);\n\n\t\t\t// Get user info\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\n\t\t\tthis.storage.setUser(user);\n\n\t\t\t// Clean up PKCE data\n\t\t\tif (this.config.usePKCE) {\n\t\t\t\tthis.storage.removeCodeVerifier();\n\t\t\t\tthis.storage.removeCodeChallenge();\n\t\t\t}\n\n\t\t\t// Setup token expiry monitoring\n\t\t\tthis.setupTokenExpiryMonitoring();\n\n\t\t\tthis.emit('login', { user, tokens });\n\n\t\t\t// Clean URL (remove query params)\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname + window.location.hash);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== User Methods ====================\n\n\tgetUser(): User | null {\n\t\treturn this.storage.getUser();\n\t}\n\n\tgetAccessToken(): string | null {\n\t\treturn this.storage.getTokens()?.accessToken || null;\n\t}\n\n\tgetRefreshToken(): string | null {\n\t\treturn this.storage.getTokens()?.refreshToken || null;\n\t}\n\n\tisAuthenticated(): boolean {\n\t\treturn this.storage.hasValidToken() && !!this.storage.getUser();\n\t}\n\n\tgetState(): AuthState {\n\t\treturn {\n\t\t\tisAuthenticated: this.isAuthenticated(),\n\t\t\tuser: this.getUser(),\n\t\t\ttokens: this.storage.getTokens(),\n\t\t};\n\t}\n\n\tasync validateToken(): Promise<boolean> {\n\t\tconst accessToken = this.getAccessToken();\n\t\tif (!accessToken) return false;\n\n\t\ttry {\n\t\t\treturn await this.api.validateToken(accessToken);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==================== Token Refresh ====================\n\n\tprivate setupTokenExpiryMonitoring(): void {\n\t\tthis.clearTokenExpiryTimer();\n\n\t\tconst tokens = this.storage.getTokens();\n\t\tconst issuedAt = this.storage.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return;\n\n\t\t// Refresh 5 minutes before expiry\n\t\tconst timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer\n\n\t\tif (timeUntilRefresh > 0) {\n\t\t\tthis.tokenExpiryTimer = setTimeout(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.refreshToken();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.emit('token-expired', error);\n\t\t\t\t\tawait this.logout();\n\t\t\t\t}\n\t\t\t}, timeUntilRefresh);\n\t\t}\n\t}\n\n\tprivate clearTokenExpiryTimer(): void {\n\t\tif (this.tokenExpiryTimer) {\n\t\t\tclearTimeout(this.tokenExpiryTimer);\n\t\t\tthis.tokenExpiryTimer = null;\n\t\t}\n\t}\n\n\tasync refreshToken(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\t\tif (!tokens?.refreshToken) {\n\t\t\tthrow new Error('No refresh token available');\n\t\t}\n\n\t\ttry {\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\n\t\t\t\ttokens.refreshToken,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(newTokens);\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t\tthis.emit('token-refresh', newTokens);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== Event Handling ====================\n\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)!.add(callback);\n\t}\n\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tthis.listeners.get(event)?.delete(callback);\n\t}\n\n\tprivate emit(type: AuthEventType, data?: any): void {\n\t\tconst event: AuthEvent = { type, data };\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\n\t}\n\n\t// ==================== Utility Methods ====================\n\n\tclearStorage(): void {\n\t\tthis.storage.clear();\n\t}\n\n\tdestroy(): void {\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.listeners.clear();\n\t}\n}\n"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","Date","now","toString","getTokens","data","getItem","parse","getTokenIssuedAt","parseInt","setUser","user","getUser","setState","key","value","getState","removeState","removeItem","setCodeVerifier","codeVerifier","getCodeVerifier","removeCodeVerifier","setCodeChallenge","codeChallenge","getCodeChallenge","removeCodeChallenge","clear","hasValidToken","issuedAt","expiresIn","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","body","grant_type","client_id","redirect_uri","code_verifier","client_secret","response","fetch","method","headers","URLSearchParams","ok","errorData","json","catch","error","error_description","Error","result","accessToken","access_token","refreshToken","refresh_token","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","validateToken","generateState","array","Uint8Array","crypto","getRandomValues","Array","from","byte","padStart","join","async","generateCodeChallenge","TextEncoder","encode","digest","bytes","binary","String","fromCharCode","btoa","replace","base64UrlEncode","subtle","Custos","config","tokenExpiryTimer","scope","isArray","split","normalizeScope","apiUrl","responseType","state","usePKCE","codeChallengeMethod","grantType","api","listeners","Map","window","handleCallback","setupTokenExpiryMonitoring","login","additionalParams","params","response_type","charset","length","Math","floor","random","verifier","i","charAt","generateCodeVerifier","code_challenge","code_challenge_method","authUrl","location","href","console","clearTokenExpiryTimer","emit","url","URL","searchParams","forEach","parseQueryString","errorDescription","undefined","history","replaceState","document","title","pathname","hash","getAccessToken","getRefreshToken","isAuthenticated","timeUntilRefresh","setTimeout","clearTimeout","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type","clearStorage","destroy"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/storage.ts","../src/api.ts","../src/utils.ts","../src/Custos.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\n\nconst STORAGE_PREFIX = 'custos_';\n\nexport class Storage {\n\tprivate storage: globalThis.Storage;\n\n\tconstructor(useSessionStorage = false) {\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\n\t}\n\n\t// Tokens\n\tsetTokens(tokens: AuthTokens): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());\n\t}\n\n\tgetTokens(): AuthTokens | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\tgetTokenIssuedAt(): number | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\treturn data ? parseInt(data, 10) : null;\n\t}\n\n\t// User\n\tsetUser(user: User): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\n\t}\n\n\tgetUser(): User | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\t// State & PKCE\n\tsetState(key: string, value: string): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\n\t}\n\n\tgetState(key: string): string | null {\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\tremoveState(key: string): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\t// PKCE specific\n\tsetCodeVerifier(codeVerifier: string): void {\n\t\tthis.setState('code_verifier', codeVerifier);\n\t}\n\n\tgetCodeVerifier(): string | null {\n\t\treturn this.getState('code_verifier');\n\t}\n\n\tremoveCodeVerifier(): void {\n\t\tthis.removeState('code_verifier');\n\t}\n\n\tsetCodeChallenge(codeChallenge: string): void {\n\t\tthis.setState('code_challenge', codeChallenge);\n\t}\n\n\tgetCodeChallenge(): string | null {\n\t\treturn this.getState('code_challenge');\n\t}\n\n\tremoveCodeChallenge(): void {\n\t\tthis.removeState('code_challenge');\n\t}\n\n\t// Clear all\n\tclear(): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\n\t\tthis.removeState('oauth_state');\n\t\tthis.removeCodeVerifier();\n\t\tthis.removeCodeChallenge();\n\t}\n\n\t// Validation\n\thasValidToken(): boolean {\n\t\tconst tokens = this.getTokens();\n\t\tconst issuedAt = this.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return false;\n\n\t\tconst now = Date.now();\n\t\tconst expirationTime = issuedAt + tokens.expiresIn * 1000;\n\n\t\treturn now < expirationTime;\n\t}\n}\n","import { AuthTokens, User } from './types';\n\nexport class ApiClient {\n\tprivate baseUrl: string;\n\n\tconstructor(baseUrl: string) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tasync exchangeCodeForTokens(\n\t\tcode: string,\n\t\tclientId: string,\n\t\tredirectUri: string,\n\t\tcodeVerifier?: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t};\n\n\t\t// Add PKCE code_verifier if present\n\t\tif (codeVerifier) {\n\t\t\tbody.code_verifier = codeVerifier;\n\t\t}\n\n\t\t// Add client_secret if present (for confidential clients)\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({\n\t\t\t\terror: 'unknown_error',\n\t\t\t\terror_description: 'Failed to exchange code for tokens'\n\t\t\t}));\n\n\t\t\tthrow new Error(errorData.error_description || errorData.error || 'Token exchange failed');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result; // Support both {data: {...}} and direct response\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token,\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync getUserInfo(accessToken: string): Promise<User> {\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/system/users/profile`, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to get user info');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\treturn result.data || result;\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t\tclientId: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: clientId,\n\t\t};\n\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to refresh token');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result;\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync logout(accessToken: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/api/v1/auth/revoke`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync validateToken(accessToken: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn response.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","export function generateState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseQueryString(url: string): Record<string, string> {\n\tconst params: Record<string, string> = {};\n\tconst searchParams = new URL(url).searchParams;\n\tsearchParams.forEach((value, key) => {\n\t\tparams[key] = value;\n\t});\n\treturn params;\n}\n\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\n\tconst now = Date.now();\n\tconst expirationTime = issuedAt + expiresIn * 1000;\n\t// Refresh 5 minutes before expiration\n\treturn now >= expirationTime - 5 * 60 * 1000;\n}\n\n// PKCE utilities\nexport function generateCodeVerifier(): string {\n\tconst charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\tconst length = Math.floor(Math.random() * 86) + 43; // 43-128 characters\n\tlet verifier = '';\n\tfor (let i = 0; i < length; i++) {\n\t\tverifier += charset.charAt(Math.floor(Math.random() * charset.length));\n\t}\n\treturn verifier;\n}\n\nexport async function generateCodeChallenge(codeVerifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(codeVerifier);\n\tconst digest = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(digest);\n}\n\nfunction base64UrlEncode(digest: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(digest);\n\tconst binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nexport function normalizeScope(scope?: string | string[]): string[] {\n\tif (!scope) return ['openid', 'profile', 'email'];\n\tif (Array.isArray(scope)) return scope;\n\tif (typeof scope === 'string') return scope.split(' ');\n\treturn ['openid', 'profile', 'email'];\n}\n","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\nimport { Storage } from './storage';\nimport { ApiClient } from './api';\nimport {\n\tgenerateState,\n\tparseQueryString,\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tnormalizeScope\n} from './utils';\n\nexport class Custos {\n\tprivate config: Required<CustosConfig>;\n\tprivate storage: Storage;\n\tprivate api: ApiClient;\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\n\tprivate tokenExpiryTimer: any = null;\n\n\tconstructor(config: CustosConfig) {\n\t\t// Normalize scope\n\t\tconst scope = normalizeScope(config.scope);\n\n\t\tthis.config = {\n\t\t\tclientId: config.clientId,\n\t\t\tclientSecret: config.clientSecret || '',\n\t\t\tredirectUri: config.redirectUri,\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\n\t\t\tscope,\n\t\t\tresponseType: config.responseType || 'code',\n\t\t\tstate: config.state || generateState(),\n\t\t\tusePKCE: config.usePKCE !== false, // Default to true\n\t\t\tcodeChallengeMethod: config.codeChallengeMethod || 'S256',\n\t\t\tgrantType: config.grantType || 'authorization_code',\n\t\t};\n\n\t\tthis.storage = new Storage();\n\t\tthis.api = new ApiClient(this.config.apiUrl);\n\t\tthis.listeners = new Map();\n\n\t\t// Handle callback automatically\n\t\tif (typeof window !== 'undefined') {\n\t\t\tthis.handleCallback();\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t}\n\t}\n\n\t// ==================== Authentication Methods ====================\n\n\tasync login(additionalParams?: Record<string, string>): Promise<void> {\n\t\tconst state = this.config.state;\n\t\tthis.storage.setState('oauth_state', state);\n\n\t\tconst params: Record<string, string> = {\n\t\t\tresponse_type: this.config.responseType,\n\t\t\tclient_id: this.config.clientId,\n\t\t\tredirect_uri: this.config.redirectUri,\n\t\t\tscope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,\n\t\t\tstate,\n\t\t\t...additionalParams,\n\t\t};\n\n\t\t// Add PKCE if enabled\n\t\tif (this.config.usePKCE) {\n\t\t\tconst codeVerifier = generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t\t\tthis.storage.setCodeVerifier(codeVerifier);\n\t\t\tthis.storage.setCodeChallenge(codeChallenge);\n\n\t\t\tparams.code_challenge = codeChallenge;\n\t\t\tparams.code_challenge_method = this.config.codeChallengeMethod;\n\t\t}\n\n\t\tconst authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;\n\t\twindow.location.href = authUrl;\n\t}\n\n\tasync logout(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\n\t\tif (tokens?.accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.api.logout(tokens.accessToken);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Logout error:', error);\n\t\t\t}\n\t\t}\n\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.storage.clear();\n\t\tthis.emit('logout', null);\n\t}\n\n\tasync handleCallback(): Promise<void> {\n\t\tconst params = parseQueryString(window.location.href);\n\t\tconsole.log('Callback params:', params);\n\n\t\t// Check for errors\n\t\tconst error = params.error;\n\t\tif (error) {\n\t\t\tconsole.error('OAuth error:', error, params.error_description);\n\t\t\tconst errorDescription = params.error_description || error;\n\t\t\tthis.emit('error', { error, error_description: errorDescription });\n\t\t\tthrow new Error(errorDescription);\n\t\t}\n\n\t\t// Check for authorization code\n\t\tconst code = params.code;\n\t\tif (!code) return;\n\n\t\t// Validate state\n\t\tconst state = params.state;\n\t\tconst savedState = this.storage.getState('oauth_state');\n\t\tif (state !== savedState) {\n\t\t\tconsole.error('State parameter mismatch:', state, savedState);\n\t\t\tthis.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });\n\t\t\tthrow new Error('Invalid state parameter');\n\t\t}\n\n\t\tthis.storage.removeState('oauth_state');\n\n\t\ttry {\n\t\t\t// Get code_verifier if using PKCE\n\t\t\tconst codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;\n\n\t\t\t// Exchange code for tokens\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\n\t\t\t\tcode,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.redirectUri,\n\t\t\t\tcodeVerifier,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(tokens);\n\n\t\t\t// Get user info\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\n\t\t\tthis.storage.setUser(user);\n\n\t\t\t// Clean up PKCE data\n\t\t\tif (this.config.usePKCE) {\n\t\t\t\tthis.storage.removeCodeVerifier();\n\t\t\t\tthis.storage.removeCodeChallenge();\n\t\t\t}\n\n\t\t\t// Setup token expiry monitoring\n\t\t\tthis.setupTokenExpiryMonitoring();\n\n\t\t\tthis.emit('login', { user, tokens });\n\n\t\t\t// Clean URL (remove query params)\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname + window.location.hash);\n\t\t} catch (error) {\n\t\t\tconsole.error('Callback handling error:', error);\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== User Methods ====================\n\n\tgetUser(): User | null {\n\t\treturn this.storage.getUser();\n\t}\n\n\tgetAccessToken(): string | null {\n\t\treturn this.storage.getTokens()?.accessToken || null;\n\t}\n\n\tgetRefreshToken(): string | null {\n\t\treturn this.storage.getTokens()?.refreshToken || null;\n\t}\n\n\tisAuthenticated(): boolean {\n\t\treturn this.storage.hasValidToken() && !!this.storage.getUser();\n\t}\n\n\tgetState(): AuthState {\n\t\treturn {\n\t\t\tisAuthenticated: this.isAuthenticated(),\n\t\t\tuser: this.getUser(),\n\t\t\ttokens: this.storage.getTokens(),\n\t\t};\n\t}\n\n\tasync validateToken(): Promise<boolean> {\n\t\tconst accessToken = this.getAccessToken();\n\t\tif (!accessToken) return false;\n\n\t\ttry {\n\t\t\treturn await this.api.validateToken(accessToken);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==================== Token Refresh ====================\n\n\tprivate setupTokenExpiryMonitoring(): void {\n\t\tthis.clearTokenExpiryTimer();\n\n\t\tconst tokens = this.storage.getTokens();\n\t\tconst issuedAt = this.storage.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return;\n\n\t\t// Refresh 5 minutes before expiry\n\t\tconst timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer\n\n\t\tif (timeUntilRefresh > 0) {\n\t\t\tthis.tokenExpiryTimer = setTimeout(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.refreshToken();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.emit('token-expired', error);\n\t\t\t\t\tawait this.logout();\n\t\t\t\t}\n\t\t\t}, timeUntilRefresh);\n\t\t}\n\t}\n\n\tprivate clearTokenExpiryTimer(): void {\n\t\tif (this.tokenExpiryTimer) {\n\t\t\tclearTimeout(this.tokenExpiryTimer);\n\t\t\tthis.tokenExpiryTimer = null;\n\t\t}\n\t}\n\n\tasync refreshToken(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\t\tif (!tokens?.refreshToken) {\n\t\t\tthrow new Error('No refresh token available');\n\t\t}\n\n\t\ttry {\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\n\t\t\t\ttokens.refreshToken,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(newTokens);\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t\tthis.emit('token-refresh', newTokens);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== Event Handling ====================\n\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)!.add(callback);\n\t}\n\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tthis.listeners.get(event)?.delete(callback);\n\t}\n\n\tprivate emit(type: AuthEventType, data?: any): void {\n\t\tconst event: AuthEvent = { type, data };\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\n\t}\n\n\t// ==================== Utility Methods ====================\n\n\tclearStorage(): void {\n\t\tthis.storage.clear();\n\t}\n\n\tdestroy(): void {\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.listeners.clear();\n\t}\n}\n"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","Date","now","toString","getTokens","data","getItem","parse","getTokenIssuedAt","parseInt","setUser","user","getUser","setState","key","value","getState","removeState","removeItem","setCodeVerifier","codeVerifier","getCodeVerifier","removeCodeVerifier","setCodeChallenge","codeChallenge","getCodeChallenge","removeCodeChallenge","clear","hasValidToken","issuedAt","expiresIn","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","body","grant_type","client_id","redirect_uri","code_verifier","client_secret","response","fetch","method","headers","URLSearchParams","ok","errorData","json","catch","error","error_description","Error","result","accessToken","access_token","refreshToken","refresh_token","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","validateToken","generateState","array","Uint8Array","crypto","getRandomValues","Array","from","byte","padStart","join","async","generateCodeChallenge","TextEncoder","encode","digest","bytes","binary","String","fromCharCode","btoa","replace","base64UrlEncode","subtle","Custos","config","tokenExpiryTimer","scope","isArray","split","normalizeScope","apiUrl","responseType","state","usePKCE","codeChallengeMethod","grantType","api","listeners","Map","window","handleCallback","setupTokenExpiryMonitoring","login","additionalParams","params","response_type","charset","length","Math","floor","random","verifier","i","charAt","generateCodeVerifier","code_challenge","code_challenge_method","authUrl","location","href","console","clearTokenExpiryTimer","emit","url","URL","searchParams","forEach","parseQueryString","log","errorDescription","savedState","undefined","history","replaceState","document","title","pathname","hash","getAccessToken","getRefreshToken","isAuthenticated","timeUntilRefresh","setTimeout","clearTimeout","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type","clearStorage","destroy"],"mappings":"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"}
|
package/dist/index.umd.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Custos={})}(this,function(e){"use strict";const t="custos_";class r{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(e){this.storage.setItem(`${t}tokens`,JSON.stringify(e)),this.storage.setItem(`${t}token_issued_at`,Date.now().toString())}getTokens(){const e=this.storage.getItem(`${t}tokens`);return e?JSON.parse(e):null}getTokenIssuedAt(){const e=this.storage.getItem(`${t}token_issued_at`);return e?parseInt(e,10):null}setUser(e){this.storage.setItem(`${t}user`,JSON.stringify(e))}getUser(){const e=this.storage.getItem(`${t}user`);return e?JSON.parse(e):null}setState(e,r){this.storage.setItem(`${t}${e}`,r)}getState(e){return this.storage.getItem(`${t}${e}`)}removeState(e){this.storage.removeItem(`${t}${e}`)}setCodeVerifier(e){this.setState("code_verifier",e)}getCodeVerifier(){return this.getState("code_verifier")}removeCodeVerifier(){this.removeState("code_verifier")}setCodeChallenge(e){this.setState("code_challenge",e)}getCodeChallenge(){return this.getState("code_challenge")}removeCodeChallenge(){this.removeState("code_challenge")}clear(){this.storage.removeItem(`${t}tokens`),this.storage.removeItem(`${t}token_issued_at`),this.storage.removeItem(`${t}user`),this.removeState("oauth_state"),this.removeCodeVerifier(),this.removeCodeChallenge()}hasValidToken(){const e=this.getTokens(),t=this.getTokenIssuedAt();if(!e||!t)return!1;return Date.now()<t+1e3*e.expiresIn}}class
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Custos={})}(this,function(e){"use strict";const t="custos_";class r{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(e){this.storage.setItem(`${t}tokens`,JSON.stringify(e)),this.storage.setItem(`${t}token_issued_at`,Date.now().toString())}getTokens(){const e=this.storage.getItem(`${t}tokens`);return e?JSON.parse(e):null}getTokenIssuedAt(){const e=this.storage.getItem(`${t}token_issued_at`);return e?parseInt(e,10):null}setUser(e){this.storage.setItem(`${t}user`,JSON.stringify(e))}getUser(){const e=this.storage.getItem(`${t}user`);return e?JSON.parse(e):null}setState(e,r){this.storage.setItem(`${t}${e}`,r)}getState(e){return this.storage.getItem(`${t}${e}`)}removeState(e){this.storage.removeItem(`${t}${e}`)}setCodeVerifier(e){this.setState("code_verifier",e)}getCodeVerifier(){return this.getState("code_verifier")}removeCodeVerifier(){this.removeState("code_verifier")}setCodeChallenge(e){this.setState("code_challenge",e)}getCodeChallenge(){return this.getState("code_challenge")}removeCodeChallenge(){this.removeState("code_challenge")}clear(){this.storage.removeItem(`${t}tokens`),this.storage.removeItem(`${t}token_issued_at`),this.storage.removeItem(`${t}user`),this.removeState("oauth_state"),this.removeCodeVerifier(),this.removeCodeChallenge()}hasValidToken(){const e=this.getTokens(),t=this.getTokenIssuedAt();if(!e||!t)return!1;return Date.now()<t+1e3*e.expiresIn}}class o{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,r,o,s){const i={grant_type:"authorization_code",code:e,client_id:t,redirect_uri:r};o&&(i.code_verifier=o),s&&(i.client_secret=s);const n=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(i).toString()});if(!n.ok){const e=await n.json().catch(()=>({error:"unknown_error",error_description:"Failed to exchange code for tokens"}));throw new Error(e.error_description||e.error||"Token exchange failed")}const a=await n.json(),c=a.data||a;return{accessToken:c.access_token,refreshToken:c.refresh_token,expiresIn:c.expires_in,tokenType:c.token_type||"Bearer"}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/api/v1/system/users/profile`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");const r=await t.json();return r.data||r}async refreshAccessToken(e,t,r){const o={grant_type:"refresh_token",refresh_token:e,client_id:t};r&&(o.client_secret=r);const s=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(o).toString()});if(!s.ok)throw new Error("Failed to refresh token");const i=await s.json(),n=i.data||i;return{accessToken:n.access_token,refreshToken:n.refresh_token||e,expiresIn:n.expires_in,tokenType:n.token_type||"Bearer"}}async logout(e){await fetch(`${this.baseUrl}/api/v1/auth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}async validateToken(e){try{return(await fetch(`${this.baseUrl}/api/v1/auth/validate`,{headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}}function s(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}async function i(e){const t=(new TextEncoder).encode(e);return function(e){const t=new Uint8Array(e),r=Array.from(t,e=>String.fromCharCode(e)).join("");return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}(await crypto.subtle.digest("SHA-256",t))}e.Custos=class{constructor(e){this.tokenExpiryTimer=null;const t=function(e){return e?Array.isArray(e)?e:"string"==typeof e?e.split(" "):["openid","profile","email"]:["openid","profile","email"]}(e.scope);this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:t,responseType:e.responseType||"code",state:e.state||s(),usePKCE:!1!==e.usePKCE,codeChallengeMethod:e.codeChallengeMethod||"S256",grantType:e.grantType||"authorization_code"},this.storage=new r,this.api=new o(this.config.apiUrl),this.listeners=new Map,"undefined"!=typeof window&&(this.handleCallback(),this.setupTokenExpiryMonitoring())}async login(e){const t=this.config.state;this.storage.setState("oauth_state",t);const r={response_type:this.config.responseType,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:Array.isArray(this.config.scope)?this.config.scope.join(" "):this.config.scope,state:t,...e};if(this.config.usePKCE){const e=function(){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=Math.floor(86*Math.random())+43;let r="";for(let o=0;o<t;o++)r+=e.charAt(Math.floor(66*Math.random()));return r}(),t=await i(e);this.storage.setCodeVerifier(e),this.storage.setCodeChallenge(t),r.code_challenge=t,r.code_challenge_method=this.config.codeChallengeMethod}const o=`${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(r)}`;window.location.href=o}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.clearTokenExpiryTimer(),this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,r)=>{t[r]=e}),t}(window.location.href);console.log("Callback params:",e);const t=e.error;if(t){console.error("OAuth error:",t,e.error_description);const r=e.error_description||t;throw this.emit("error",{error:t,error_description:r}),new Error(r)}const r=e.code;if(!r)return;const o=e.state,s=this.storage.getState("oauth_state");if(o!==s)throw console.error("State parameter mismatch:",o,s),this.emit("error",{error:"invalid_state",error_description:"State parameter mismatch"}),new Error("Invalid state parameter");this.storage.removeState("oauth_state");try{const e=this.config.usePKCE&&this.storage.getCodeVerifier()||void 0,t=await this.api.exchangeCodeForTokens(r,this.config.clientId,this.config.redirectUri,e,this.config.clientSecret);this.storage.setTokens(t);const o=await this.api.getUserInfo(t.accessToken);this.storage.setUser(o),this.config.usePKCE&&(this.storage.removeCodeVerifier(),this.storage.removeCodeChallenge()),this.setupTokenExpiryMonitoring(),this.emit("login",{user:o,tokens:t}),window.history.replaceState({},document.title,window.location.pathname+window.location.hash)}catch(t){throw console.error("Callback handling error:",t),this.emit("error",t),t}}getUser(){return this.storage.getUser()}getAccessToken(){return this.storage.getTokens()?.accessToken||null}getRefreshToken(){return this.storage.getTokens()?.refreshToken||null}isAuthenticated(){return this.storage.hasValidToken()&&!!this.storage.getUser()}getState(){return{isAuthenticated:this.isAuthenticated(),user:this.getUser(),tokens:this.storage.getTokens()}}async validateToken(){const e=this.getAccessToken();if(!e)return!1;try{return await this.api.validateToken(e)}catch{return!1}}setupTokenExpiryMonitoring(){this.clearTokenExpiryTimer();const e=this.storage.getTokens(),t=this.storage.getTokenIssuedAt();if(!e||!t)return;const r=1e3*(e.expiresIn-300);r>0&&(this.tokenExpiryTimer=setTimeout(async()=>{try{await this.refreshToken()}catch(e){this.emit("token-expired",e),await this.logout()}},r))}clearTokenExpiryTimer(){this.tokenExpiryTimer&&(clearTimeout(this.tokenExpiryTimer),this.tokenExpiryTimer=null)}async refreshToken(){const e=this.storage.getTokens();if(!e?.refreshToken)throw new Error("No refresh token available");try{const t=await this.api.refreshAccessToken(e.refreshToken,this.config.clientId,this.config.clientSecret);this.storage.setTokens(t),this.setupTokenExpiryMonitoring(),this.emit("token-refresh",t)}catch(e){throw this.emit("error",e),e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){const r={type:e,data:t};this.listeners.get(e)?.forEach(e=>e(r))}clearStorage(){this.storage.clear()}destroy(){this.clearTokenExpiryTimer(),this.listeners.clear()}},Object.defineProperty(e,"__esModule",{value:!0})});
|
|
2
2
|
//# sourceMappingURL=index.umd.js.map
|
package/dist/index.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.umd.js","sources":["../src/storage.ts","../src/api.ts","../src/utils.ts","../src/Custos.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\n\nconst STORAGE_PREFIX = 'custos_';\n\nexport class Storage {\n\tprivate storage: globalThis.Storage;\n\n\tconstructor(useSessionStorage = false) {\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\n\t}\n\n\t// Tokens\n\tsetTokens(tokens: AuthTokens): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());\n\t}\n\n\tgetTokens(): AuthTokens | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\tgetTokenIssuedAt(): number | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\treturn data ? parseInt(data, 10) : null;\n\t}\n\n\t// User\n\tsetUser(user: User): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\n\t}\n\n\tgetUser(): User | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\t// State & PKCE\n\tsetState(key: string, value: string): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\n\t}\n\n\tgetState(key: string): string | null {\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\tremoveState(key: string): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\t// PKCE specific\n\tsetCodeVerifier(codeVerifier: string): void {\n\t\tthis.setState('code_verifier', codeVerifier);\n\t}\n\n\tgetCodeVerifier(): string | null {\n\t\treturn this.getState('code_verifier');\n\t}\n\n\tremoveCodeVerifier(): void {\n\t\tthis.removeState('code_verifier');\n\t}\n\n\tsetCodeChallenge(codeChallenge: string): void {\n\t\tthis.setState('code_challenge', codeChallenge);\n\t}\n\n\tgetCodeChallenge(): string | null {\n\t\treturn this.getState('code_challenge');\n\t}\n\n\tremoveCodeChallenge(): void {\n\t\tthis.removeState('code_challenge');\n\t}\n\n\t// Clear all\n\tclear(): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\n\t\tthis.removeState('oauth_state');\n\t\tthis.removeCodeVerifier();\n\t\tthis.removeCodeChallenge();\n\t}\n\n\t// Validation\n\thasValidToken(): boolean {\n\t\tconst tokens = this.getTokens();\n\t\tconst issuedAt = this.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return false;\n\n\t\tconst now = Date.now();\n\t\tconst expirationTime = issuedAt + tokens.expiresIn * 1000;\n\n\t\treturn now < expirationTime;\n\t}\n}\n","import { AuthTokens, User } from './types';\n\nexport class ApiClient {\n\tprivate baseUrl: string;\n\n\tconstructor(baseUrl: string) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tasync exchangeCodeForTokens(\n\t\tcode: string,\n\t\tclientId: string,\n\t\tredirectUri: string,\n\t\tcodeVerifier?: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t};\n\n\t\t// Add PKCE code_verifier if present\n\t\tif (codeVerifier) {\n\t\t\tbody.code_verifier = codeVerifier;\n\t\t}\n\n\t\t// Add client_secret if present (for confidential clients)\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({\n\t\t\t\terror: 'unknown_error',\n\t\t\t\terror_description: 'Failed to exchange code for tokens'\n\t\t\t}));\n\n\t\t\tthrow new Error(errorData.error_description || errorData.error || 'Token exchange failed');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result; // Support both {data: {...}} and direct response\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token,\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync getUserInfo(accessToken: string): Promise<User> {\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/system/users/profile`, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to get user info');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\treturn result.data || result;\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t\tclientId: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: clientId,\n\t\t};\n\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to refresh token');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result;\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync logout(accessToken: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/api/v1/auth/revoke`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync validateToken(accessToken: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn response.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","export function generateState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseQueryString(url: string): Record<string, string> {\n\tconst params: Record<string, string> = {};\n\tconst searchParams = new URL(url).searchParams;\n\tsearchParams.forEach((value, key) => {\n\t\tparams[key] = value;\n\t});\n\treturn params;\n}\n\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\n\tconst now = Date.now();\n\tconst expirationTime = issuedAt + expiresIn * 1000;\n\t// Refresh 5 minutes before expiration\n\treturn now >= expirationTime - 5 * 60 * 1000;\n}\n\n// PKCE utilities\nexport function generateCodeVerifier(): string {\n\tconst charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\tconst length = Math.floor(Math.random() * 86) + 43; // 43-128 characters\n\tlet verifier = '';\n\tfor (let i = 0; i < length; i++) {\n\t\tverifier += charset.charAt(Math.floor(Math.random() * charset.length));\n\t}\n\treturn verifier;\n}\n\nexport async function generateCodeChallenge(codeVerifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(codeVerifier);\n\tconst digest = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(digest);\n}\n\nfunction base64UrlEncode(digest: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(digest);\n\tconst binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nexport function normalizeScope(scope?: string | string[]): string[] {\n\tif (!scope) return ['openid', 'profile', 'email'];\n\tif (Array.isArray(scope)) return scope;\n\tif (typeof scope === 'string') return scope.split(' ');\n\treturn ['openid', 'profile', 'email'];\n}\n","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\nimport { Storage } from './storage';\nimport { ApiClient } from './api';\nimport {\n\tgenerateState,\n\tparseQueryString,\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tnormalizeScope\n} from './utils';\n\nexport class Custos {\n\tprivate config: Required<CustosConfig>;\n\tprivate storage: Storage;\n\tprivate api: ApiClient;\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\n\tprivate tokenExpiryTimer: any = null;\n\n\tconstructor(config: CustosConfig) {\n\t\t// Normalize scope\n\t\tconst scope = normalizeScope(config.scope);\n\n\t\tthis.config = {\n\t\t\tclientId: config.clientId,\n\t\t\tclientSecret: config.clientSecret || '',\n\t\t\tredirectUri: config.redirectUri,\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\n\t\t\tscope,\n\t\t\tresponseType: config.responseType || 'code',\n\t\t\tstate: config.state || generateState(),\n\t\t\tusePKCE: config.usePKCE !== false, // Default to true\n\t\t\tcodeChallengeMethod: config.codeChallengeMethod || 'S256',\n\t\t\tgrantType: config.grantType || 'authorization_code',\n\t\t};\n\n\t\tthis.storage = new Storage();\n\t\tthis.api = new ApiClient(this.config.apiUrl);\n\t\tthis.listeners = new Map();\n\n\t\t// Handle callback automatically\n\t\tif (typeof window !== 'undefined') {\n\t\t\tthis.handleCallback();\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t}\n\t}\n\n\t// ==================== Authentication Methods ====================\n\n\tasync login(additionalParams?: Record<string, string>): Promise<void> {\n\t\tconst state = this.config.state;\n\t\tthis.storage.setState('oauth_state', state);\n\n\t\tconst params: Record<string, string> = {\n\t\t\tresponse_type: this.config.responseType,\n\t\t\tclient_id: this.config.clientId,\n\t\t\tredirect_uri: this.config.redirectUri,\n\t\t\tscope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,\n\t\t\tstate,\n\t\t\t...additionalParams,\n\t\t};\n\n\t\t// Add PKCE if enabled\n\t\tif (this.config.usePKCE) {\n\t\t\tconst codeVerifier = generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t\t\tthis.storage.setCodeVerifier(codeVerifier);\n\t\t\tthis.storage.setCodeChallenge(codeChallenge);\n\n\t\t\tparams.code_challenge = codeChallenge;\n\t\t\tparams.code_challenge_method = this.config.codeChallengeMethod;\n\t\t}\n\n\t\tconst authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;\n\t\twindow.location.href = authUrl;\n\t}\n\n\tasync logout(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\n\t\tif (tokens?.accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.api.logout(tokens.accessToken);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Logout error:', error);\n\t\t\t}\n\t\t}\n\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.storage.clear();\n\t\tthis.emit('logout', null);\n\t}\n\n\tasync handleCallback(): Promise<void> {\n\t\tconst params = parseQueryString(window.location.href);\n\n\t\t// Check for errors\n\t\tconst error = params.error;\n\t\tif (error) {\n\t\t\tconst errorDescription = params.error_description || error;\n\t\t\tthis.emit('error', { error, error_description: errorDescription });\n\t\t\tthrow new Error(errorDescription);\n\t\t}\n\n\t\t// Check for authorization code\n\t\tconst code = params.code;\n\t\tif (!code) return;\n\n\t\t// Validate state\n\t\tconst state = params.state;\n\t\tconst savedState = this.storage.getState('oauth_state');\n\t\tif (state !== savedState) {\n\t\t\tthis.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });\n\t\t\tthrow new Error('Invalid state parameter');\n\t\t}\n\n\t\tthis.storage.removeState('oauth_state');\n\n\t\ttry {\n\t\t\t// Get code_verifier if using PKCE\n\t\t\tconst codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;\n\n\t\t\t// Exchange code for tokens\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\n\t\t\t\tcode,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.redirectUri,\n\t\t\t\tcodeVerifier,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(tokens);\n\n\t\t\t// Get user info\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\n\t\t\tthis.storage.setUser(user);\n\n\t\t\t// Clean up PKCE data\n\t\t\tif (this.config.usePKCE) {\n\t\t\t\tthis.storage.removeCodeVerifier();\n\t\t\t\tthis.storage.removeCodeChallenge();\n\t\t\t}\n\n\t\t\t// Setup token expiry monitoring\n\t\t\tthis.setupTokenExpiryMonitoring();\n\n\t\t\tthis.emit('login', { user, tokens });\n\n\t\t\t// Clean URL (remove query params)\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname + window.location.hash);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== User Methods ====================\n\n\tgetUser(): User | null {\n\t\treturn this.storage.getUser();\n\t}\n\n\tgetAccessToken(): string | null {\n\t\treturn this.storage.getTokens()?.accessToken || null;\n\t}\n\n\tgetRefreshToken(): string | null {\n\t\treturn this.storage.getTokens()?.refreshToken || null;\n\t}\n\n\tisAuthenticated(): boolean {\n\t\treturn this.storage.hasValidToken() && !!this.storage.getUser();\n\t}\n\n\tgetState(): AuthState {\n\t\treturn {\n\t\t\tisAuthenticated: this.isAuthenticated(),\n\t\t\tuser: this.getUser(),\n\t\t\ttokens: this.storage.getTokens(),\n\t\t};\n\t}\n\n\tasync validateToken(): Promise<boolean> {\n\t\tconst accessToken = this.getAccessToken();\n\t\tif (!accessToken) return false;\n\n\t\ttry {\n\t\t\treturn await this.api.validateToken(accessToken);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==================== Token Refresh ====================\n\n\tprivate setupTokenExpiryMonitoring(): void {\n\t\tthis.clearTokenExpiryTimer();\n\n\t\tconst tokens = this.storage.getTokens();\n\t\tconst issuedAt = this.storage.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return;\n\n\t\t// Refresh 5 minutes before expiry\n\t\tconst timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer\n\n\t\tif (timeUntilRefresh > 0) {\n\t\t\tthis.tokenExpiryTimer = setTimeout(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.refreshToken();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.emit('token-expired', error);\n\t\t\t\t\tawait this.logout();\n\t\t\t\t}\n\t\t\t}, timeUntilRefresh);\n\t\t}\n\t}\n\n\tprivate clearTokenExpiryTimer(): void {\n\t\tif (this.tokenExpiryTimer) {\n\t\t\tclearTimeout(this.tokenExpiryTimer);\n\t\t\tthis.tokenExpiryTimer = null;\n\t\t}\n\t}\n\n\tasync refreshToken(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\t\tif (!tokens?.refreshToken) {\n\t\t\tthrow new Error('No refresh token available');\n\t\t}\n\n\t\ttry {\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\n\t\t\t\ttokens.refreshToken,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(newTokens);\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t\tthis.emit('token-refresh', newTokens);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== Event Handling ====================\n\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)!.add(callback);\n\t}\n\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tthis.listeners.get(event)?.delete(callback);\n\t}\n\n\tprivate emit(type: AuthEventType, data?: any): void {\n\t\tconst event: AuthEvent = { type, data };\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\n\t}\n\n\t// ==================== Utility Methods ====================\n\n\tclearStorage(): void {\n\t\tthis.storage.clear();\n\t}\n\n\tdestroy(): void {\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.listeners.clear();\n\t}\n}\n"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","Date","now","toString","getTokens","data","getItem","parse","getTokenIssuedAt","parseInt","setUser","user","getUser","setState","key","value","getState","removeState","removeItem","setCodeVerifier","codeVerifier","getCodeVerifier","removeCodeVerifier","setCodeChallenge","codeChallenge","getCodeChallenge","removeCodeChallenge","clear","hasValidToken","issuedAt","expiresIn","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","body","grant_type","client_id","redirect_uri","code_verifier","client_secret","response","fetch","method","headers","URLSearchParams","ok","errorData","json","catch","error","error_description","Error","result","accessToken","access_token","refreshToken","refresh_token","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","validateToken","generateState","array","Uint8Array","crypto","getRandomValues","Array","from","byte","padStart","join","async","generateCodeChallenge","TextEncoder","encode","digest","bytes","binary","String","fromCharCode","btoa","replace","base64UrlEncode","subtle","config","tokenExpiryTimer","scope","isArray","split","normalizeScope","apiUrl","responseType","state","usePKCE","codeChallengeMethod","grantType","api","listeners","Map","window","handleCallback","setupTokenExpiryMonitoring","login","additionalParams","params","response_type","charset","length","Math","floor","random","verifier","i","charAt","generateCodeVerifier","code_challenge","code_challenge_method","authUrl","location","href","console","clearTokenExpiryTimer","emit","url","URL","searchParams","forEach","parseQueryString","errorDescription","undefined","history","replaceState","document","title","pathname","hash","getAccessToken","getRefreshToken","isAuthenticated","timeUntilRefresh","setTimeout","clearTimeout","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type","clearStorage","destroy"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../src/storage.ts","../src/api.ts","../src/utils.ts","../src/Custos.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\n\nconst STORAGE_PREFIX = 'custos_';\n\nexport class Storage {\n\tprivate storage: globalThis.Storage;\n\n\tconstructor(useSessionStorage = false) {\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\n\t}\n\n\t// Tokens\n\tsetTokens(tokens: AuthTokens): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());\n\t}\n\n\tgetTokens(): AuthTokens | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\tgetTokenIssuedAt(): number | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\treturn data ? parseInt(data, 10) : null;\n\t}\n\n\t// User\n\tsetUser(user: User): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\n\t}\n\n\tgetUser(): User | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\t// State & PKCE\n\tsetState(key: string, value: string): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\n\t}\n\n\tgetState(key: string): string | null {\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\tremoveState(key: string): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\t// PKCE specific\n\tsetCodeVerifier(codeVerifier: string): void {\n\t\tthis.setState('code_verifier', codeVerifier);\n\t}\n\n\tgetCodeVerifier(): string | null {\n\t\treturn this.getState('code_verifier');\n\t}\n\n\tremoveCodeVerifier(): void {\n\t\tthis.removeState('code_verifier');\n\t}\n\n\tsetCodeChallenge(codeChallenge: string): void {\n\t\tthis.setState('code_challenge', codeChallenge);\n\t}\n\n\tgetCodeChallenge(): string | null {\n\t\treturn this.getState('code_challenge');\n\t}\n\n\tremoveCodeChallenge(): void {\n\t\tthis.removeState('code_challenge');\n\t}\n\n\t// Clear all\n\tclear(): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\n\t\tthis.removeState('oauth_state');\n\t\tthis.removeCodeVerifier();\n\t\tthis.removeCodeChallenge();\n\t}\n\n\t// Validation\n\thasValidToken(): boolean {\n\t\tconst tokens = this.getTokens();\n\t\tconst issuedAt = this.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return false;\n\n\t\tconst now = Date.now();\n\t\tconst expirationTime = issuedAt + tokens.expiresIn * 1000;\n\n\t\treturn now < expirationTime;\n\t}\n}\n","import { AuthTokens, User } from './types';\n\nexport class ApiClient {\n\tprivate baseUrl: string;\n\n\tconstructor(baseUrl: string) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tasync exchangeCodeForTokens(\n\t\tcode: string,\n\t\tclientId: string,\n\t\tredirectUri: string,\n\t\tcodeVerifier?: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t};\n\n\t\t// Add PKCE code_verifier if present\n\t\tif (codeVerifier) {\n\t\t\tbody.code_verifier = codeVerifier;\n\t\t}\n\n\t\t// Add client_secret if present (for confidential clients)\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({\n\t\t\t\terror: 'unknown_error',\n\t\t\t\terror_description: 'Failed to exchange code for tokens'\n\t\t\t}));\n\n\t\t\tthrow new Error(errorData.error_description || errorData.error || 'Token exchange failed');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result; // Support both {data: {...}} and direct response\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token,\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync getUserInfo(accessToken: string): Promise<User> {\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/system/users/profile`, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to get user info');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\treturn result.data || result;\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t\tclientId: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: clientId,\n\t\t};\n\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to refresh token');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result;\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync logout(accessToken: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/api/v1/auth/revoke`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync validateToken(accessToken: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn response.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","export function generateState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseQueryString(url: string): Record<string, string> {\n\tconst params: Record<string, string> = {};\n\tconst searchParams = new URL(url).searchParams;\n\tsearchParams.forEach((value, key) => {\n\t\tparams[key] = value;\n\t});\n\treturn params;\n}\n\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\n\tconst now = Date.now();\n\tconst expirationTime = issuedAt + expiresIn * 1000;\n\t// Refresh 5 minutes before expiration\n\treturn now >= expirationTime - 5 * 60 * 1000;\n}\n\n// PKCE utilities\nexport function generateCodeVerifier(): string {\n\tconst charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\tconst length = Math.floor(Math.random() * 86) + 43; // 43-128 characters\n\tlet verifier = '';\n\tfor (let i = 0; i < length; i++) {\n\t\tverifier += charset.charAt(Math.floor(Math.random() * charset.length));\n\t}\n\treturn verifier;\n}\n\nexport async function generateCodeChallenge(codeVerifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(codeVerifier);\n\tconst digest = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(digest);\n}\n\nfunction base64UrlEncode(digest: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(digest);\n\tconst binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nexport function normalizeScope(scope?: string | string[]): string[] {\n\tif (!scope) return ['openid', 'profile', 'email'];\n\tif (Array.isArray(scope)) return scope;\n\tif (typeof scope === 'string') return scope.split(' ');\n\treturn ['openid', 'profile', 'email'];\n}\n","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\nimport { Storage } from './storage';\nimport { ApiClient } from './api';\nimport {\n\tgenerateState,\n\tparseQueryString,\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tnormalizeScope\n} from './utils';\n\nexport class Custos {\n\tprivate config: Required<CustosConfig>;\n\tprivate storage: Storage;\n\tprivate api: ApiClient;\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\n\tprivate tokenExpiryTimer: any = null;\n\n\tconstructor(config: CustosConfig) {\n\t\t// Normalize scope\n\t\tconst scope = normalizeScope(config.scope);\n\n\t\tthis.config = {\n\t\t\tclientId: config.clientId,\n\t\t\tclientSecret: config.clientSecret || '',\n\t\t\tredirectUri: config.redirectUri,\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\n\t\t\tscope,\n\t\t\tresponseType: config.responseType || 'code',\n\t\t\tstate: config.state || generateState(),\n\t\t\tusePKCE: config.usePKCE !== false, // Default to true\n\t\t\tcodeChallengeMethod: config.codeChallengeMethod || 'S256',\n\t\t\tgrantType: config.grantType || 'authorization_code',\n\t\t};\n\n\t\tthis.storage = new Storage();\n\t\tthis.api = new ApiClient(this.config.apiUrl);\n\t\tthis.listeners = new Map();\n\n\t\t// Handle callback automatically\n\t\tif (typeof window !== 'undefined') {\n\t\t\tthis.handleCallback();\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t}\n\t}\n\n\t// ==================== Authentication Methods ====================\n\n\tasync login(additionalParams?: Record<string, string>): Promise<void> {\n\t\tconst state = this.config.state;\n\t\tthis.storage.setState('oauth_state', state);\n\n\t\tconst params: Record<string, string> = {\n\t\t\tresponse_type: this.config.responseType,\n\t\t\tclient_id: this.config.clientId,\n\t\t\tredirect_uri: this.config.redirectUri,\n\t\t\tscope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,\n\t\t\tstate,\n\t\t\t...additionalParams,\n\t\t};\n\n\t\t// Add PKCE if enabled\n\t\tif (this.config.usePKCE) {\n\t\t\tconst codeVerifier = generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t\t\tthis.storage.setCodeVerifier(codeVerifier);\n\t\t\tthis.storage.setCodeChallenge(codeChallenge);\n\n\t\t\tparams.code_challenge = codeChallenge;\n\t\t\tparams.code_challenge_method = this.config.codeChallengeMethod;\n\t\t}\n\n\t\tconst authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;\n\t\twindow.location.href = authUrl;\n\t}\n\n\tasync logout(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\n\t\tif (tokens?.accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.api.logout(tokens.accessToken);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Logout error:', error);\n\t\t\t}\n\t\t}\n\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.storage.clear();\n\t\tthis.emit('logout', null);\n\t}\n\n\tasync handleCallback(): Promise<void> {\n\t\tconst params = parseQueryString(window.location.href);\n\t\tconsole.log('Callback params:', params);\n\n\t\t// Check for errors\n\t\tconst error = params.error;\n\t\tif (error) {\n\t\t\tconsole.error('OAuth error:', error, params.error_description);\n\t\t\tconst errorDescription = params.error_description || error;\n\t\t\tthis.emit('error', { error, error_description: errorDescription });\n\t\t\tthrow new Error(errorDescription);\n\t\t}\n\n\t\t// Check for authorization code\n\t\tconst code = params.code;\n\t\tif (!code) return;\n\n\t\t// Validate state\n\t\tconst state = params.state;\n\t\tconst savedState = this.storage.getState('oauth_state');\n\t\tif (state !== savedState) {\n\t\t\tconsole.error('State parameter mismatch:', state, savedState);\n\t\t\tthis.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });\n\t\t\tthrow new Error('Invalid state parameter');\n\t\t}\n\n\t\tthis.storage.removeState('oauth_state');\n\n\t\ttry {\n\t\t\t// Get code_verifier if using PKCE\n\t\t\tconst codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;\n\n\t\t\t// Exchange code for tokens\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\n\t\t\t\tcode,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.redirectUri,\n\t\t\t\tcodeVerifier,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(tokens);\n\n\t\t\t// Get user info\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\n\t\t\tthis.storage.setUser(user);\n\n\t\t\t// Clean up PKCE data\n\t\t\tif (this.config.usePKCE) {\n\t\t\t\tthis.storage.removeCodeVerifier();\n\t\t\t\tthis.storage.removeCodeChallenge();\n\t\t\t}\n\n\t\t\t// Setup token expiry monitoring\n\t\t\tthis.setupTokenExpiryMonitoring();\n\n\t\t\tthis.emit('login', { user, tokens });\n\n\t\t\t// Clean URL (remove query params)\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname + window.location.hash);\n\t\t} catch (error) {\n\t\t\tconsole.error('Callback handling error:', error);\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== User Methods ====================\n\n\tgetUser(): User | null {\n\t\treturn this.storage.getUser();\n\t}\n\n\tgetAccessToken(): string | null {\n\t\treturn this.storage.getTokens()?.accessToken || null;\n\t}\n\n\tgetRefreshToken(): string | null {\n\t\treturn this.storage.getTokens()?.refreshToken || null;\n\t}\n\n\tisAuthenticated(): boolean {\n\t\treturn this.storage.hasValidToken() && !!this.storage.getUser();\n\t}\n\n\tgetState(): AuthState {\n\t\treturn {\n\t\t\tisAuthenticated: this.isAuthenticated(),\n\t\t\tuser: this.getUser(),\n\t\t\ttokens: this.storage.getTokens(),\n\t\t};\n\t}\n\n\tasync validateToken(): Promise<boolean> {\n\t\tconst accessToken = this.getAccessToken();\n\t\tif (!accessToken) return false;\n\n\t\ttry {\n\t\t\treturn await this.api.validateToken(accessToken);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==================== Token Refresh ====================\n\n\tprivate setupTokenExpiryMonitoring(): void {\n\t\tthis.clearTokenExpiryTimer();\n\n\t\tconst tokens = this.storage.getTokens();\n\t\tconst issuedAt = this.storage.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return;\n\n\t\t// Refresh 5 minutes before expiry\n\t\tconst timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer\n\n\t\tif (timeUntilRefresh > 0) {\n\t\t\tthis.tokenExpiryTimer = setTimeout(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.refreshToken();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.emit('token-expired', error);\n\t\t\t\t\tawait this.logout();\n\t\t\t\t}\n\t\t\t}, timeUntilRefresh);\n\t\t}\n\t}\n\n\tprivate clearTokenExpiryTimer(): void {\n\t\tif (this.tokenExpiryTimer) {\n\t\t\tclearTimeout(this.tokenExpiryTimer);\n\t\t\tthis.tokenExpiryTimer = null;\n\t\t}\n\t}\n\n\tasync refreshToken(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\t\tif (!tokens?.refreshToken) {\n\t\t\tthrow new Error('No refresh token available');\n\t\t}\n\n\t\ttry {\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\n\t\t\t\ttokens.refreshToken,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(newTokens);\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t\tthis.emit('token-refresh', newTokens);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== Event Handling ====================\n\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)!.add(callback);\n\t}\n\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tthis.listeners.get(event)?.delete(callback);\n\t}\n\n\tprivate emit(type: AuthEventType, data?: any): void {\n\t\tconst event: AuthEvent = { type, data };\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\n\t}\n\n\t// ==================== Utility Methods ====================\n\n\tclearStorage(): void {\n\t\tthis.storage.clear();\n\t}\n\n\tdestroy(): void {\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.listeners.clear();\n\t}\n}\n"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","Date","now","toString","getTokens","data","getItem","parse","getTokenIssuedAt","parseInt","setUser","user","getUser","setState","key","value","getState","removeState","removeItem","setCodeVerifier","codeVerifier","getCodeVerifier","removeCodeVerifier","setCodeChallenge","codeChallenge","getCodeChallenge","removeCodeChallenge","clear","hasValidToken","issuedAt","expiresIn","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","body","grant_type","client_id","redirect_uri","code_verifier","client_secret","response","fetch","method","headers","URLSearchParams","ok","errorData","json","catch","error","error_description","Error","result","accessToken","access_token","refreshToken","refresh_token","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","validateToken","generateState","array","Uint8Array","crypto","getRandomValues","Array","from","byte","padStart","join","async","generateCodeChallenge","TextEncoder","encode","digest","bytes","binary","String","fromCharCode","btoa","replace","base64UrlEncode","subtle","config","tokenExpiryTimer","scope","isArray","split","normalizeScope","apiUrl","responseType","state","usePKCE","codeChallengeMethod","grantType","api","listeners","Map","window","handleCallback","setupTokenExpiryMonitoring","login","additionalParams","params","response_type","charset","length","Math","floor","random","verifier","i","charAt","generateCodeVerifier","code_challenge","code_challenge_method","authUrl","location","href","console","clearTokenExpiryTimer","emit","url","URL","searchParams","forEach","parseQueryString","log","errorDescription","savedState","undefined","history","replaceState","document","title","pathname","hash","getAccessToken","getRefreshToken","isAuthenticated","timeUntilRefresh","setTimeout","clearTimeout","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type","clearStorage","destroy"],"mappings":"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"}
|