@authaction/web-sdk 0.1.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/README.md +239 -0
- package/dist/angular/index.d.mts +116 -0
- package/dist/angular/index.d.ts +116 -0
- package/dist/angular/index.js +2 -0
- package/dist/angular/index.js.map +1 -0
- package/dist/angular/index.mjs +2 -0
- package/dist/angular/index.mjs.map +1 -0
- package/dist/chunk-5RMD5KG6.mjs +2 -0
- package/dist/chunk-5RMD5KG6.mjs.map +1 -0
- package/dist/chunk-DHV7MPBE.mjs +2 -0
- package/dist/chunk-DHV7MPBE.mjs.map +1 -0
- package/dist/index.d.mts +55 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/nextjs/index.d.mts +36 -0
- package/dist/nextjs/index.d.ts +36 -0
- package/dist/nextjs/index.js +2 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/nextjs/index.mjs +2 -0
- package/dist/nextjs/index.mjs.map +1 -0
- package/dist/react/index.d.mts +81 -0
- package/dist/react/index.d.ts +81 -0
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +2 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/utils-BzTlGuFj.d.mts +101 -0
- package/dist/utils-BzTlGuFj.d.ts +101 -0
- package/dist/vue/index.d.mts +102 -0
- package/dist/vue/index.d.ts +102 -0
- package/dist/vue/index.js +2 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/vue/index.mjs +2 -0
- package/dist/vue/index.mjs.map +1 -0
- package/package.json +127 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/context.tsx","../src/react/hooks.ts"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\nimport { AuthActionClient } from '../client';\nimport { AuthActionClientConfig, AuthState } from '../types';\n\n// ── Context ───────────────────────────────────────────────────────────────────\n\ninterface AuthActionContextValue {\n client: AuthActionClient;\n state: AuthState;\n}\n\nconst AuthActionContext = createContext<AuthActionContextValue | null>(null);\n\n// ── Provider ──────────────────────────────────────────────────────────────────\n\nexport interface AuthActionProviderProps extends AuthActionClientConfig {\n children: React.ReactNode;\n /**\n * When true, automatically calls handleRedirectCallback() if the current URL\n * contains ?code= and ?state= query params (i.e. the OAuth2 callback page).\n * On success the callback URL is cleaned from the browser history.\n * Defaults to true.\n */\n onRedirectCallback?: (appState?: Record<string, unknown>) => void;\n}\n\nexport function AuthActionProvider({\n children,\n onRedirectCallback,\n ...config\n}: AuthActionProviderProps) {\n // Stable client instance — never recreated unless config.domain/clientId changes\n const client = useMemo(\n () => new AuthActionClient(config),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [config.domain, config.clientId, config.redirectUri],\n );\n\n const [state, setState] = useState<AuthState>(client.getState());\n\n // Subscribe to auth state changes\n useEffect(() => client.onStateChange(setState), [client]);\n\n // Auto-handle redirect callback when ?code= is present in the URL\n useEffect(() => {\n const params = new URLSearchParams(window.location.search);\n if (!params.has('code') && !params.has('error')) return;\n\n client\n .handleRedirectCallback()\n .then(({ appState }) => onRedirectCallback?.(appState))\n .catch(() => {\n // Ignore errors (e.g. stale callbacks on page refresh)\n });\n // Run only once on mount\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <AuthActionContext.Provider value={{ client, state }}>\n {children}\n </AuthActionContext.Provider>\n );\n}\n\n// ── Internal hook ─────────────────────────────────────────────────────────────\n\nexport function useAuthActionContext(): AuthActionContextValue {\n const ctx = useContext(AuthActionContext);\n if (!ctx) {\n throw new Error(\n 'useAuthAction() must be used inside <AuthActionProvider>. ' +\n 'Wrap your application root with <AuthActionProvider domain=\"...\" clientId=\"...\" redirectUri=\"...\">.',\n );\n }\n return ctx;\n}\n","import { useCallback } from 'react';\nimport { useAuthActionContext } from './context';\nimport {\n AuthState,\n GetAccessTokenOptions,\n LoginOptions,\n LoginWithPopupOptions,\n LogoutOptions,\n RedirectCallbackResult,\n User,\n} from '../types';\n\n// ── Primary hook ──────────────────────────────────────────────────────────────\n\nexport interface UseAuthActionReturn extends AuthState {\n /** Redirect to AuthAction login page (PKCE flow) */\n loginWithRedirect: (options?: LoginOptions) => Promise<void>;\n /** Open AuthAction login in a popup window */\n loginWithPopup: (options?: LoginWithPopupOptions) => Promise<void>;\n /** Handle the OAuth2 redirect callback — call on your /callback page */\n handleRedirectCallback: (url?: string) => Promise<RedirectCallbackResult>;\n /**\n * Get a valid access token, auto-refreshing if expired.\n * Use as Bearer token for API calls including the AuthAction Files service:\n * `fetch(url, { headers: { Authorization: \\`Bearer \\${token}\\` } })`\n */\n getAccessToken: (options?: GetAccessTokenOptions) => Promise<string>;\n /** Log out and optionally end the AuthAction SSO session */\n logout: (options?: LogoutOptions) => Promise<void>;\n /**\n * Clear the local session without hitting the server's end-session endpoint.\n * Use this on 401 API responses where the server already invalidated the session.\n */\n removeUser: () => void;\n}\n\n/**\n * Primary hook — returns auth state + all client methods.\n *\n * @example\n * ```tsx\n * function App() {\n * const { isAuthenticated, isLoading, user, loginWithRedirect, logout } = useAuthAction();\n *\n * if (isLoading) return <Spinner />;\n * if (!isAuthenticated) return <button onClick={() => loginWithRedirect()}>Login</button>;\n * return <div>Hello {user?.name} <button onClick={() => logout()}>Logout</button></div>;\n * }\n * ```\n */\nexport function useAuthAction(): UseAuthActionReturn {\n const { client, state } = useAuthActionContext();\n\n const loginWithRedirect = useCallback(\n (options?: LoginOptions) => client.loginWithRedirect(options),\n [client],\n );\n\n const loginWithPopup = useCallback(\n (options?: LoginWithPopupOptions) => client.loginWithPopup(options),\n [client],\n );\n\n const handleRedirectCallback = useCallback(\n (url?: string) => client.handleRedirectCallback(url),\n [client],\n );\n\n const getAccessToken = useCallback(\n (options?: GetAccessTokenOptions) => client.getAccessToken(options),\n [client],\n );\n\n const logout = useCallback(\n (options?: LogoutOptions) => client.logout(options),\n [client],\n );\n\n const removeUser = useCallback(() => client.removeUser(), [client]);\n\n return {\n ...state,\n loginWithRedirect,\n loginWithPopup,\n handleRedirectCallback,\n getAccessToken,\n logout,\n removeUser,\n };\n}\n\n// ── Convenience hooks ─────────────────────────────────────────────────────────\n\n/** Returns the current user profile, or undefined if not authenticated */\nexport function useUser<T extends User = User>(): T | undefined {\n return useAuthActionContext().state.user as T | undefined;\n}\n\n/** Returns true while the SDK is determining the initial auth state */\nexport function useAuthLoading(): boolean {\n return useAuthActionContext().state.isLoading;\n}\n\n/** Returns true when the user is authenticated */\nexport function useIsAuthenticated(): boolean {\n return useAuthActionContext().state.isAuthenticated;\n}\n\n/**\n * Returns a function that fetches a valid access token on demand.\n * Useful when you need a token inside event handlers or API calls.\n *\n * @example\n * ```tsx\n * function UploadButton() {\n * const getToken = useGetAccessToken();\n *\n * const handleUpload = async (file: File) => {\n * const token = await getToken();\n * await fetch('/api/v1/files/upload', {\n * method: 'POST',\n * headers: { Authorization: `Bearer ${token}` },\n * body: formData,\n * });\n * };\n * }\n * ```\n */\nexport function useGetAccessToken(): (options?: GetAccessTokenOptions) => Promise<string> {\n const { client } = useAuthActionContext();\n return useCallback(\n (options?: GetAccessTokenOptions) => client.getAccessToken(options),\n [client],\n );\n}\n"],"mappings":"yCAAA,OACE,iBAAAA,EACA,cAAAC,EACA,aAAAC,EACA,WAAAC,EACA,YAAAC,MACK,QA2DH,cAAAC,MAAA,oBAhDJ,IAAMC,EAAoBC,EAA6C,IAAI,EAepE,SAASC,EAAmB,CACjC,SAAAC,EACA,mBAAAC,EACA,GAAGC,CACL,EAA4B,CAE1B,IAAMC,EAASC,EACb,IAAM,IAAIC,EAAiBH,CAAM,EAEjC,CAACA,EAAO,OAAQA,EAAO,SAAUA,EAAO,WAAW,CACrD,EAEM,CAACI,EAAOC,CAAQ,EAAIC,EAAoBL,EAAO,SAAS,CAAC,EAG/D,OAAAM,EAAU,IAAMN,EAAO,cAAcI,CAAQ,EAAG,CAACJ,CAAM,CAAC,EAGxDM,EAAU,IAAM,CACd,IAAMC,EAAS,IAAI,gBAAgB,OAAO,SAAS,MAAM,EACrD,CAACA,EAAO,IAAI,MAAM,GAAK,CAACA,EAAO,IAAI,OAAO,GAE9CP,EACG,uBAAuB,EACvB,KAAK,CAAC,CAAE,SAAAQ,CAAS,IAAMV,IAAqBU,CAAQ,CAAC,EACrD,MAAM,IAAM,CAEb,CAAC,CAGL,EAAG,CAAC,CAAC,EAGHf,EAACC,EAAkB,SAAlB,CAA2B,MAAO,CAAE,OAAAM,EAAQ,MAAAG,CAAM,EAChD,SAAAN,EACH,CAEJ,CAIO,SAASY,GAA+C,CAC7D,IAAMC,EAAMC,EAAWjB,CAAiB,EACxC,GAAI,CAACgB,EACH,MAAM,IAAI,MACR,+JAEF,EAEF,OAAOA,CACT,CClFA,OAAS,eAAAE,MAAmB,QAkDrB,SAASC,GAAqC,CACnD,GAAM,CAAE,OAAAC,EAAQ,MAAAC,CAAM,EAAIC,EAAqB,EAEzCC,EAAoBC,EACvBC,GAA2BL,EAAO,kBAAkBK,CAAO,EAC5D,CAACL,CAAM,CACT,EAEMM,EAAiBF,EACpBC,GAAoCL,EAAO,eAAeK,CAAO,EAClE,CAACL,CAAM,CACT,EAEMO,EAAyBH,EAC5BI,GAAiBR,EAAO,uBAAuBQ,CAAG,EACnD,CAACR,CAAM,CACT,EAEMS,EAAiBL,EACpBC,GAAoCL,EAAO,eAAeK,CAAO,EAClE,CAACL,CAAM,CACT,EAEMU,EAASN,EACZC,GAA4BL,EAAO,OAAOK,CAAO,EAClD,CAACL,CAAM,CACT,EAEMW,EAAaP,EAAY,IAAMJ,EAAO,WAAW,EAAG,CAACA,CAAM,CAAC,EAElE,MAAO,CACL,GAAGC,EACH,kBAAAE,EACA,eAAAG,EACA,uBAAAC,EACA,eAAAE,EACA,OAAAC,EACA,WAAAC,CACF,CACF,CAKO,SAASC,GAAgD,CAC9D,OAAOV,EAAqB,EAAE,MAAM,IACtC,CAGO,SAASW,GAA0B,CACxC,OAAOX,EAAqB,EAAE,MAAM,SACtC,CAGO,SAASY,GAA8B,CAC5C,OAAOZ,EAAqB,EAAE,MAAM,eACtC,CAsBO,SAASa,GAA0E,CACxF,GAAM,CAAE,OAAAf,CAAO,EAAIE,EAAqB,EACxC,OAAOE,EACJC,GAAoCL,EAAO,eAAeK,CAAO,EAClE,CAACL,CAAM,CACT,CACF","names":["createContext","useContext","useEffect","useMemo","useState","jsx","AuthActionContext","createContext","AuthActionProvider","children","onRedirectCallback","config","client","useMemo","AuthActionClient","state","setState","useState","useEffect","params","appState","useAuthActionContext","ctx","useContext","useCallback","useAuthAction","client","state","useAuthActionContext","loginWithRedirect","useCallback","options","loginWithPopup","handleRedirectCallback","url","getAccessToken","logout","removeUser","useUser","useAuthLoading","useIsAuthenticated","useGetAccessToken"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var P=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var C=(n,e,t,r)=>{for(var i=r>1?void 0:r?v(e,t):e,o=n.length-1,s;o>=0;o--)(s=n[o])&&(i=(r?s(e,t,i):s(i))||i);return r&&i&&P(e,t,i),i},b=(n,e)=>(t,r)=>e(t,r,n);function c(n=64){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=new Uint8Array(n);return crypto.getRandomValues(t),Array.from(t,r=>e[r%e.length]).join("")}function U(n){return btoa(String.fromCharCode(...new Uint8Array(n))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function g(n){let e=new TextEncoder().encode(n),t=await crypto.subtle.digest("SHA-256",e);return U(t)}function p(n){let e=n.split(".");if(e.length!==3)throw new Error("Invalid JWT format");let t=e[1].replace(/-/g,"+").replace(/_/g,"/");try{return JSON.parse(atob(t))}catch{throw new Error("Failed to decode JWT payload")}}var m="__authaction_pkce__",l=()=>typeof sessionStorage<"u"?sessionStorage:null,d={save(n){l()?.setItem(m,JSON.stringify(n))},get(){let n=l()?.getItem(m)??null;return n?JSON.parse(n):null},clear(){l()?.removeItem(m)}},w="__authaction_tokens__",u=class{constructor(e="memory"){this.memStore=null;this.backend=e}get(){if(this.backend==="memory")return this.memStore;let e=this.getStore()?.getItem(w)??null;return e?JSON.parse(e):null}set(e){if(this.backend==="memory"){this.memStore=e;return}this.getStore()?.setItem(w,JSON.stringify(e))}clear(){this.memStore=null,this.backend!=="memory"&&this.getStore()?.removeItem(w)}getStore(){return this.backend==="localstorage"?typeof localStorage<"u"?localStorage:null:l()}};var _="openid profile email",T=60,y=class{constructor(e){this.listeners=new Set;this.state={isAuthenticated:!1,isLoading:!0,user:void 0,error:void 0};this.cfg=e,this.cache=new u(e.cacheLocation??"memory"),this.loadStateFromCache()}async loginWithRedirect(e={}){this.setState({...this.state,activeNavigator:"loginWithRedirect"});try{let t=c(32),r=c(64),i=await g(r);d.save({state:t,codeVerifier:r,redirectUri:this.cfg.redirectUri,appState:e.appState});let o=new URLSearchParams({response_type:"code",client_id:this.cfg.clientId,redirect_uri:this.cfg.redirectUri,scope:this.cfg.scope??_,state:t,code_challenge:i,code_challenge_method:"S256",...this.cfg.authorizationParams,...e.authorizationParams});this.navigate(`https://${this.cfg.domain}/oauth2/authorize?${o}`)}catch(t){throw this.setState({isAuthenticated:this.state.isAuthenticated,isLoading:!1,user:this.state.user,error:t instanceof Error?t:new Error(String(t))}),t}}async loginWithPopup(e={}){let t=c(32),r=c(64),i=await g(r);d.save({state:t,codeVerifier:r,redirectUri:this.cfg.redirectUri,appState:e.appState});let o=new URLSearchParams({response_type:"code",client_id:this.cfg.clientId,redirect_uri:this.cfg.redirectUri,scope:this.cfg.scope??_,state:t,code_challenge:i,code_challenge_method:"S256",...this.cfg.authorizationParams,...e.authorizationParams});if(!(e.popup??window.open(`https://${this.cfg.domain}/oauth2/authorize?${o}`,"authaction:login","width=480,height=640,left=100,top=100")))throw new Error("Popup was blocked. Allow popups for this site and try again.");await new Promise((h,S)=>{let k=async a=>{if(a.origin===window.location.origin&&a.data?.type?.startsWith("authaction:"))if(window.removeEventListener("message",k),a.data.type==="authaction:callback")try{let f=new URL(a.data.url);await this.exchangeCodeFromUrl(f),h()}catch(f){S(f)}else a.data.type==="authaction:error"&&S(new Error(a.data.error))};window.addEventListener("message",k)})}async handleRedirectCallback(e){let t=new URL(e??window.location.href),r=await this.exchangeCodeFromUrl(t),i=new URL(window.location.href);return i.searchParams.delete("code"),i.searchParams.delete("state"),window.history.replaceState({},document.title,i.toString()),{appState:r}}static handlePopupCallback(e){window.opener&&(window.opener.postMessage({type:"authaction:callback",url:e??window.location.href},window.location.origin),window.close())}async isAuthenticated(){let e=this.cache.get();if(!e)return!1;if(this.isExpired(e))try{await this.refreshTokens()}catch{return!1}return!0}async getUser(){if(!await this.isAuthenticated())return;let e=this.cache.get();if(e?.id_token)try{return p(e.id_token)}catch{return}}async getAccessToken(e={}){let t=this.cache.get();if(!t)throw new Error("Not authenticated \u2014 call loginWithRedirect() first");return(e.forceRefresh||this.isExpired(t))&&(t=await this.refreshTokens()),t.access_token}async logout(e={}){if(this.cache.clear(),e.federated===!1){this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0});return}this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0,activeNavigator:"logout"});let t=e.returnTo??this.cfg.postLogoutRedirectUri,r=new URLSearchParams({client_id:e.clientId??this.cfg.clientId,...t?{post_logout_redirect_uri:t}:{}});this.navigate(`https://${this.cfg.domain}/oidc/logout?${r}`)}removeUser(){this.cache.clear(),this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0})}onStateChange(e){return this.listeners.add(e),e(this.state),()=>this.listeners.delete(e)}getState(){return{...this.state}}async exchangeCodeFromUrl(e){let t=e.searchParams.get("code"),r=e.searchParams.get("state"),i=e.searchParams.get("error"),o=e.searchParams.get("error_description");if(i)throw new Error(o??i);if(!t)throw new Error("No authorization code in callback URL");let s=d.get();if(d.clear(),!s)throw new Error("No PKCE transaction found \u2014 did the login session expire?");if(s.state!==r)throw new Error("State mismatch \u2014 possible CSRF attack");let h=await this.exchangeCode(t,s.codeVerifier,s.redirectUri);return this.cache.set(h),this.setState({isAuthenticated:!0,isLoading:!1,user:this.buildUser(h),error:void 0}),s.appState}async exchangeCode(e,t,r){let i=await fetch(`https://${this.cfg.domain}/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:this.cfg.clientId,code:e,redirect_uri:r,code_verifier:t})});if(!i.ok){let s=await i.json().catch(()=>({}));throw new Error(s.error_description??s.error??"Token exchange failed")}let o=await i.json();return this.normaliseTokenResponse(o)}async refreshTokens(){let e=this.cache.get();if(!e?.refresh_token)throw new Error("No refresh token available");let t=await fetch(`https://${this.cfg.domain}/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"refresh_token",client_id:this.cfg.clientId,refresh_token:e.refresh_token})});if(!t.ok)throw this.cache.clear(),this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0}),new Error("Token refresh failed \u2014 user must re-authenticate");let r=await t.json(),i=this.normaliseTokenResponse(r);return this.cache.set(i),this.setState({isAuthenticated:!0,isLoading:!1,user:this.buildUser(i),error:void 0}),i}normaliseTokenResponse(e){let t=typeof e.expires_in=="number"?e.expires_in:3600;return{access_token:e.access_token,id_token:e.id_token,refresh_token:e.refresh_token,scope:e.scope,expires_at:Math.floor(Date.now()/1e3)+t}}isExpired(e){return e.expires_at-T<Math.floor(Date.now()/1e3)}async loadStateFromCache(){let e=this.cache.get();if(!e){this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0});return}try{this.isExpired(e)&&await this.refreshTokens();let t=this.cache.get();this.setState({isAuthenticated:!0,isLoading:!1,user:t?this.buildUser(t):void 0,error:void 0})}catch{this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0})}}buildUser(e){if(e.id_token)try{let r={...p(e.id_token),access_token:e.access_token},i={sub:r.sub,name:r.name,email:r.email,email_verified:r.email_verified,picture:r.picture};for(let o of Object.keys(r))!(o in i)&&o!=="access_token"&&o!=="profile"&&(i[o]=r[o]);return r.profile=i,r}catch{return}}navigate(e){window.location.assign(e)}setState(e){this.state=e,this.notifyListeners(e)}notifyListeners(e){this.state=e,this.listeners.forEach(t=>t(e))}};function I(n){let e=n?new URL(n).search:window.location.search,t=new URLSearchParams(e);return t.has("code")||t.has("error")}export{C as a,b,y as c,I as d};
|
|
2
|
+
//# sourceMappingURL=chunk-DHV7MPBE.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pkce.ts","../src/storage.ts","../src/client.ts","../src/utils.ts"],"sourcesContent":["/** Generate a cryptographically random string for PKCE code_verifier or state */\nexport function generateRandom(length = 64): string {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => chars[byte % chars.length]).join('');\n}\n\n/** base64url-encode a buffer (no padding, url-safe) */\nfunction base64urlEncode(buffer: ArrayBuffer): string {\n return btoa(String.fromCharCode(...new Uint8Array(buffer)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n}\n\n/** Compute the PKCE code_challenge from a code_verifier using S256 */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoded = new TextEncoder().encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', encoded);\n return base64urlEncode(digest);\n}\n\n/** Decode a JWT payload without verifying the signature */\nexport function decodeJwtPayload(token: string): Record<string, unknown> {\n const parts = token.split('.');\n if (parts.length !== 3) throw new Error('Invalid JWT format');\n const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n try {\n return JSON.parse(atob(payload));\n } catch {\n throw new Error('Failed to decode JWT payload');\n }\n}\n","import { TokenSet } from \"./types\";\n\n// ── PKCE transaction store ────────────────────────────────────────────────────\n// Must survive the redirect to /authorize and back, so always uses sessionStorage.\n\nconst PKCE_KEY = \"__authaction_pkce__\";\n\ninterface PkceTransaction {\n state: string;\n codeVerifier: string;\n redirectUri: string;\n appState?: Record<string, unknown>;\n}\n\nconst ss = (): Storage | null =>\n typeof sessionStorage !== \"undefined\" ? sessionStorage : null;\n\nexport const pkceStore = {\n save(tx: PkceTransaction): void {\n ss()?.setItem(PKCE_KEY, JSON.stringify(tx));\n },\n get(): PkceTransaction | null {\n const raw = ss()?.getItem(PKCE_KEY) ?? null;\n return raw ? (JSON.parse(raw) as PkceTransaction) : null;\n },\n clear(): void {\n ss()?.removeItem(PKCE_KEY);\n },\n};\n\n// ── Token cache ───────────────────────────────────────────────────────────────\n\nconst TOKEN_KEY = \"__authaction_tokens__\";\n\ntype StorageBackend = \"memory\" | \"localstorage\" | \"sessionstorage\";\n\nexport class TokenCache {\n private memStore: TokenSet | null = null;\n private backend: StorageBackend;\n\n constructor(backend: StorageBackend = \"memory\") {\n this.backend = backend;\n }\n\n get(): TokenSet | null {\n if (this.backend === \"memory\") return this.memStore;\n const raw = this.getStore()?.getItem(TOKEN_KEY) ?? null;\n return raw ? (JSON.parse(raw) as TokenSet) : null;\n }\n\n set(tokens: TokenSet): void {\n if (this.backend === \"memory\") {\n this.memStore = tokens;\n return;\n }\n this.getStore()?.setItem(TOKEN_KEY, JSON.stringify(tokens));\n }\n\n clear(): void {\n this.memStore = null;\n if (this.backend !== \"memory\") this.getStore()?.removeItem(TOKEN_KEY);\n }\n\n private getStore(): Storage | null {\n if (this.backend === \"localstorage\") {\n return typeof localStorage !== \"undefined\" ? localStorage : null;\n }\n return ss();\n }\n}\n","import { generateRandom, generateCodeChallenge, decodeJwtPayload } from './pkce';\nimport { pkceStore, TokenCache } from './storage';\nimport {\n AuthActionClientConfig,\n AuthState,\n GetAccessTokenOptions,\n LoginOptions,\n LoginWithPopupOptions,\n LogoutOptions,\n RedirectCallbackResult,\n StateChangeCallback,\n TokenSet,\n UnsubscribeFn,\n User,\n UserProfile,\n} from './types';\n\nconst DEFAULT_SCOPE = 'openid profile email';\n// Refresh the access token 60 seconds before it expires\nconst REFRESH_BUFFER_SECONDS = 60;\n\nexport class AuthActionClient {\n private readonly cfg: Required<Pick<AuthActionClientConfig, 'domain' | 'clientId' | 'redirectUri'>> &\n AuthActionClientConfig;\n private readonly cache: TokenCache;\n private readonly listeners = new Set<StateChangeCallback>();\n\n private state: AuthState = { isAuthenticated: false, isLoading: true, user: undefined, error: undefined };\n\n constructor(config: AuthActionClientConfig) {\n this.cfg = config as typeof this.cfg;\n this.cache = new TokenCache(config.cacheLocation ?? 'memory');\n // Restore state from cache on init (non-blocking)\n void this.loadStateFromCache();\n }\n\n // ── Login ────────────────────────────────────────────────────────────────────\n\n async loginWithRedirect(options: LoginOptions = {}): Promise<void> {\n this.setState({ ...this.state, activeNavigator: 'loginWithRedirect' });\n try {\n const state = generateRandom(32);\n const codeVerifier = generateRandom(64);\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n\n pkceStore.save({\n state,\n codeVerifier,\n redirectUri: this.cfg.redirectUri,\n appState: options.appState,\n });\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.cfg.clientId,\n redirect_uri: this.cfg.redirectUri,\n scope: this.cfg.scope ?? DEFAULT_SCOPE,\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n ...this.cfg.authorizationParams,\n ...options.authorizationParams,\n });\n\n this.navigate(`https://${this.cfg.domain}/oauth2/authorize?${params}`);\n } catch (err) {\n this.setState({\n isAuthenticated: this.state.isAuthenticated,\n isLoading: false,\n user: this.state.user,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n throw err;\n }\n }\n\n /** Opens AuthAction login in a popup window instead of a full redirect */\n async loginWithPopup(options: LoginWithPopupOptions = {}): Promise<void> {\n const state = generateRandom(32);\n const codeVerifier = generateRandom(64);\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n\n pkceStore.save({ state, codeVerifier, redirectUri: this.cfg.redirectUri, appState: options.appState });\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.cfg.clientId,\n redirect_uri: this.cfg.redirectUri,\n scope: this.cfg.scope ?? DEFAULT_SCOPE,\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n ...this.cfg.authorizationParams,\n ...options.authorizationParams,\n });\n\n const popup = options.popup ?? window.open(\n `https://${this.cfg.domain}/oauth2/authorize?${params}`,\n 'authaction:login',\n 'width=480,height=640,left=100,top=100',\n );\n\n if (!popup) throw new Error('Popup was blocked. Allow popups for this site and try again.');\n\n await new Promise<void>((resolve, reject) => {\n const listener = async (event: MessageEvent) => {\n if (event.origin !== window.location.origin) return;\n if (!event.data?.type?.startsWith('authaction:')) return;\n\n window.removeEventListener('message', listener);\n\n if (event.data.type === 'authaction:callback') {\n try {\n const url = new URL(event.data.url);\n await this.exchangeCodeFromUrl(url);\n resolve();\n } catch (err) {\n reject(err);\n }\n } else if (event.data.type === 'authaction:error') {\n reject(new Error(event.data.error));\n }\n };\n window.addEventListener('message', listener);\n });\n }\n\n // ── Callback ─────────────────────────────────────────────────────────────────\n\n /**\n * Call this on the redirect callback page (e.g. /callback).\n * Exchanges the authorization code for tokens and returns the original appState.\n */\n async handleRedirectCallback(url?: string): Promise<RedirectCallbackResult> {\n const callbackUrl = new URL(url ?? window.location.href);\n const appState = await this.exchangeCodeFromUrl(callbackUrl);\n\n // Clean the code/state from the URL without a page reload\n const clean = new URL(window.location.href);\n clean.searchParams.delete('code');\n clean.searchParams.delete('state');\n window.history.replaceState({}, document.title, clean.toString());\n\n return { appState };\n }\n\n /**\n * Call this from the popup callback page to relay the URL back to the opener.\n * Place this call in the script that runs on your redirectUri page when in popup mode.\n */\n static handlePopupCallback(url?: string): void {\n if (!window.opener) return;\n window.opener.postMessage(\n { type: 'authaction:callback', url: url ?? window.location.href },\n window.location.origin,\n );\n window.close();\n }\n\n // ── Auth state ────────────────────────────────────────────────────────────────\n\n async isAuthenticated(): Promise<boolean> {\n const tokens = this.cache.get();\n if (!tokens) return false;\n // Auto-refresh if near expiry\n if (this.isExpired(tokens)) {\n try {\n await this.refreshTokens();\n } catch {\n return false;\n }\n }\n return true;\n }\n\n async getUser<T extends User = User>(): Promise<T | undefined> {\n if (!(await this.isAuthenticated())) return undefined;\n const tokens = this.cache.get();\n if (!tokens?.id_token) return undefined;\n try {\n return decodeJwtPayload(tokens.id_token) as unknown as T;\n } catch {\n return undefined;\n }\n }\n\n // ── Tokens ────────────────────────────────────────────────────────────────────\n\n /**\n * Returns a valid access token, refreshing automatically if needed.\n * Use this as the Bearer token for API calls including the AuthAction Files service.\n *\n * @example\n * const token = await client.getAccessToken();\n * fetch('/api/v1/files', { headers: { Authorization: `Bearer ${token}` } });\n */\n async getAccessToken(options: GetAccessTokenOptions = {}): Promise<string> {\n let tokens = this.cache.get();\n\n if (!tokens) throw new Error('Not authenticated — call loginWithRedirect() first');\n\n if (options.forceRefresh || this.isExpired(tokens)) {\n tokens = await this.refreshTokens();\n }\n\n return tokens.access_token;\n }\n\n // ── Logout ────────────────────────────────────────────────────────────────────\n\n async logout(options: LogoutOptions = {}): Promise<void> {\n this.cache.clear();\n\n if (options.federated === false) {\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n return;\n }\n\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined, activeNavigator: 'logout' });\n\n const returnTo = options.returnTo ?? this.cfg.postLogoutRedirectUri;\n const params = new URLSearchParams({\n client_id: options.clientId ?? this.cfg.clientId,\n ...(returnTo ? { post_logout_redirect_uri: returnTo } : {}),\n });\n\n this.navigate(`https://${this.cfg.domain}/oidc/logout?${params}`);\n }\n\n /**\n * Clears the local session (tokens + state) without hitting the server's end-session endpoint.\n * Use this to handle 401 responses from APIs where the server already considers the session gone.\n */\n removeUser(): void {\n this.cache.clear();\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n }\n\n // ── State subscriptions ────────────────────────────────────────────────────────\n\n onStateChange(callback: StateChangeCallback): UnsubscribeFn {\n this.listeners.add(callback);\n // Emit current state immediately\n callback(this.state);\n return () => this.listeners.delete(callback);\n }\n\n getState(): AuthState {\n return { ...this.state };\n }\n\n // ── Private ───────────────────────────────────────────────────────────────────\n\n private async exchangeCodeFromUrl(url: URL): Promise<Record<string, unknown> | undefined> {\n const code = url.searchParams.get('code');\n const returnedState = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n const errorDescription = url.searchParams.get('error_description');\n\n if (error) throw new Error(errorDescription ?? error);\n if (!code) throw new Error('No authorization code in callback URL');\n\n const tx = pkceStore.get();\n pkceStore.clear();\n\n if (!tx) throw new Error('No PKCE transaction found — did the login session expire?');\n if (tx.state !== returnedState) throw new Error('State mismatch — possible CSRF attack');\n\n const tokens = await this.exchangeCode(code, tx.codeVerifier, tx.redirectUri);\n this.cache.set(tokens);\n\n this.setState({ isAuthenticated: true, isLoading: false, user: this.buildUser(tokens), error: undefined });\n\n return tx.appState;\n }\n\n private async exchangeCode(code: string, codeVerifier: string, redirectUri: string): Promise<TokenSet> {\n const res = await fetch(`https://${this.cfg.domain}/oauth2/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.cfg.clientId,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error(err.error_description ?? err.error ?? 'Token exchange failed');\n }\n\n const data = await res.json();\n return this.normaliseTokenResponse(data);\n }\n\n private async refreshTokens(): Promise<TokenSet> {\n const tokens = this.cache.get();\n if (!tokens?.refresh_token) throw new Error('No refresh token available');\n\n const res = await fetch(`https://${this.cfg.domain}/oauth2/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.cfg.clientId,\n refresh_token: tokens.refresh_token,\n }),\n });\n\n if (!res.ok) {\n this.cache.clear();\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n throw new Error('Token refresh failed — user must re-authenticate');\n }\n\n const data = await res.json();\n const refreshed = this.normaliseTokenResponse(data);\n this.cache.set(refreshed);\n this.setState({ isAuthenticated: true, isLoading: false, user: this.buildUser(refreshed), error: undefined });\n return refreshed;\n }\n\n private normaliseTokenResponse(data: Record<string, unknown>): TokenSet {\n const expiresIn = typeof data.expires_in === 'number' ? data.expires_in : 3600;\n return {\n access_token: data.access_token as string,\n id_token: data.id_token as string | undefined,\n refresh_token: data.refresh_token as string | undefined,\n scope: data.scope as string | undefined,\n expires_at: Math.floor(Date.now() / 1000) + expiresIn,\n };\n }\n\n private isExpired(tokens: TokenSet): boolean {\n return tokens.expires_at - REFRESH_BUFFER_SECONDS < Math.floor(Date.now() / 1000);\n }\n\n private async loadStateFromCache(): Promise<void> {\n const tokens = this.cache.get();\n if (!tokens) {\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n return;\n }\n\n try {\n if (this.isExpired(tokens)) await this.refreshTokens();\n const cached = this.cache.get();\n this.setState({ isAuthenticated: true, isLoading: false, user: cached ? this.buildUser(cached) : undefined, error: undefined });\n } catch {\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n }\n }\n\n private buildUser(tokens: TokenSet): User | undefined {\n if (!tokens.id_token) return undefined;\n try {\n const claims = decodeJwtPayload(tokens.id_token) as User;\n const user: User = { ...claims, access_token: tokens.access_token };\n const profile: UserProfile = {\n sub: user.sub,\n name: user.name,\n email: user.email,\n email_verified: user.email_verified,\n picture: user.picture,\n };\n // Copy any extra claims from the ID token into profile\n for (const key of Object.keys(user)) {\n if (!(key in profile) && key !== 'access_token' && key !== 'profile') {\n profile[key] = user[key];\n }\n }\n user.profile = profile;\n return user;\n } catch {\n return undefined;\n }\n }\n\n /** Extracted for testability — spy on this method to capture redirect URLs */\n protected navigate(url: string): void {\n window.location.assign(url);\n }\n\n private setState(next: AuthState): void {\n this.state = next;\n this.notifyListeners(next);\n }\n\n private notifyListeners(state: AuthState): void {\n this.state = state;\n this.listeners.forEach((cb) => cb(state));\n }\n}\n","/**\n * Returns true if the given URL (or window.location) contains OAuth2 callback\n * params — i.e. the authorization server has redirected back with a code or error.\n * Mirrors react-oidc-context's hasAuthParams() for drop-in compatibility.\n */\nexport function hasAuthParams(url?: string): boolean {\n const search = url ? new URL(url).search : window.location.search;\n const params = new URLSearchParams(search);\n return params.has('code') || params.has('error');\n}\n"],"mappings":"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","names":["generateRandom","length","chars","array","byte","base64urlEncode","buffer","generateCodeChallenge","verifier","encoded","digest","decodeJwtPayload","token","parts","payload","PKCE_KEY","ss","pkceStore","tx","raw","TOKEN_KEY","TokenCache","backend","tokens","DEFAULT_SCOPE","REFRESH_BUFFER_SECONDS","AuthActionClient","config","TokenCache","options","state","generateRandom","codeVerifier","codeChallenge","generateCodeChallenge","pkceStore","params","err","resolve","reject","listener","event","url","callbackUrl","appState","clean","tokens","decodeJwtPayload","returnTo","callback","code","returnedState","error","errorDescription","tx","redirectUri","res","data","refreshed","expiresIn","cached","user","profile","key","next","cb","hasAuthParams","url","search","params"]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { A as AuthActionClientConfig, L as LoginOptions, a as LoginWithPopupOptions, R as RedirectCallbackResult, U as User, G as GetAccessTokenOptions, b as LogoutOptions, S as StateChangeCallback, c as UnsubscribeFn, d as AuthState } from './utils-BzTlGuFj.mjs';
|
|
2
|
+
export { D as DecodedToken, T as TokenSet, e as UserProfile, h as hasAuthParams } from './utils-BzTlGuFj.mjs';
|
|
3
|
+
|
|
4
|
+
declare class AuthActionClient {
|
|
5
|
+
private readonly cfg;
|
|
6
|
+
private readonly cache;
|
|
7
|
+
private readonly listeners;
|
|
8
|
+
private state;
|
|
9
|
+
constructor(config: AuthActionClientConfig);
|
|
10
|
+
loginWithRedirect(options?: LoginOptions): Promise<void>;
|
|
11
|
+
/** Opens AuthAction login in a popup window instead of a full redirect */
|
|
12
|
+
loginWithPopup(options?: LoginWithPopupOptions): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Call this on the redirect callback page (e.g. /callback).
|
|
15
|
+
* Exchanges the authorization code for tokens and returns the original appState.
|
|
16
|
+
*/
|
|
17
|
+
handleRedirectCallback(url?: string): Promise<RedirectCallbackResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Call this from the popup callback page to relay the URL back to the opener.
|
|
20
|
+
* Place this call in the script that runs on your redirectUri page when in popup mode.
|
|
21
|
+
*/
|
|
22
|
+
static handlePopupCallback(url?: string): void;
|
|
23
|
+
isAuthenticated(): Promise<boolean>;
|
|
24
|
+
getUser<T extends User = User>(): Promise<T | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* Returns a valid access token, refreshing automatically if needed.
|
|
27
|
+
* Use this as the Bearer token for API calls including the AuthAction Files service.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const token = await client.getAccessToken();
|
|
31
|
+
* fetch('/api/v1/files', { headers: { Authorization: `Bearer ${token}` } });
|
|
32
|
+
*/
|
|
33
|
+
getAccessToken(options?: GetAccessTokenOptions): Promise<string>;
|
|
34
|
+
logout(options?: LogoutOptions): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Clears the local session (tokens + state) without hitting the server's end-session endpoint.
|
|
37
|
+
* Use this to handle 401 responses from APIs where the server already considers the session gone.
|
|
38
|
+
*/
|
|
39
|
+
removeUser(): void;
|
|
40
|
+
onStateChange(callback: StateChangeCallback): UnsubscribeFn;
|
|
41
|
+
getState(): AuthState;
|
|
42
|
+
private exchangeCodeFromUrl;
|
|
43
|
+
private exchangeCode;
|
|
44
|
+
private refreshTokens;
|
|
45
|
+
private normaliseTokenResponse;
|
|
46
|
+
private isExpired;
|
|
47
|
+
private loadStateFromCache;
|
|
48
|
+
private buildUser;
|
|
49
|
+
/** Extracted for testability — spy on this method to capture redirect URLs */
|
|
50
|
+
protected navigate(url: string): void;
|
|
51
|
+
private setState;
|
|
52
|
+
private notifyListeners;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { AuthActionClient, AuthActionClientConfig, AuthState, GetAccessTokenOptions, LoginOptions, LoginWithPopupOptions, LogoutOptions, RedirectCallbackResult, StateChangeCallback, UnsubscribeFn, User };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { A as AuthActionClientConfig, L as LoginOptions, a as LoginWithPopupOptions, R as RedirectCallbackResult, U as User, G as GetAccessTokenOptions, b as LogoutOptions, S as StateChangeCallback, c as UnsubscribeFn, d as AuthState } from './utils-BzTlGuFj.js';
|
|
2
|
+
export { D as DecodedToken, T as TokenSet, e as UserProfile, h as hasAuthParams } from './utils-BzTlGuFj.js';
|
|
3
|
+
|
|
4
|
+
declare class AuthActionClient {
|
|
5
|
+
private readonly cfg;
|
|
6
|
+
private readonly cache;
|
|
7
|
+
private readonly listeners;
|
|
8
|
+
private state;
|
|
9
|
+
constructor(config: AuthActionClientConfig);
|
|
10
|
+
loginWithRedirect(options?: LoginOptions): Promise<void>;
|
|
11
|
+
/** Opens AuthAction login in a popup window instead of a full redirect */
|
|
12
|
+
loginWithPopup(options?: LoginWithPopupOptions): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Call this on the redirect callback page (e.g. /callback).
|
|
15
|
+
* Exchanges the authorization code for tokens and returns the original appState.
|
|
16
|
+
*/
|
|
17
|
+
handleRedirectCallback(url?: string): Promise<RedirectCallbackResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Call this from the popup callback page to relay the URL back to the opener.
|
|
20
|
+
* Place this call in the script that runs on your redirectUri page when in popup mode.
|
|
21
|
+
*/
|
|
22
|
+
static handlePopupCallback(url?: string): void;
|
|
23
|
+
isAuthenticated(): Promise<boolean>;
|
|
24
|
+
getUser<T extends User = User>(): Promise<T | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* Returns a valid access token, refreshing automatically if needed.
|
|
27
|
+
* Use this as the Bearer token for API calls including the AuthAction Files service.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const token = await client.getAccessToken();
|
|
31
|
+
* fetch('/api/v1/files', { headers: { Authorization: `Bearer ${token}` } });
|
|
32
|
+
*/
|
|
33
|
+
getAccessToken(options?: GetAccessTokenOptions): Promise<string>;
|
|
34
|
+
logout(options?: LogoutOptions): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Clears the local session (tokens + state) without hitting the server's end-session endpoint.
|
|
37
|
+
* Use this to handle 401 responses from APIs where the server already considers the session gone.
|
|
38
|
+
*/
|
|
39
|
+
removeUser(): void;
|
|
40
|
+
onStateChange(callback: StateChangeCallback): UnsubscribeFn;
|
|
41
|
+
getState(): AuthState;
|
|
42
|
+
private exchangeCodeFromUrl;
|
|
43
|
+
private exchangeCode;
|
|
44
|
+
private refreshTokens;
|
|
45
|
+
private normaliseTokenResponse;
|
|
46
|
+
private isExpired;
|
|
47
|
+
private loadStateFromCache;
|
|
48
|
+
private buildUser;
|
|
49
|
+
/** Extracted for testability — spy on this method to capture redirect URLs */
|
|
50
|
+
protected navigate(url: string): void;
|
|
51
|
+
private setState;
|
|
52
|
+
private notifyListeners;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { AuthActionClient, AuthActionClientConfig, AuthState, GetAccessTokenOptions, LoginOptions, LoginWithPopupOptions, LogoutOptions, RedirectCallbackResult, StateChangeCallback, UnsubscribeFn, User };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var p=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var b=(n,e)=>{for(var t in e)p(n,t,{get:e[t],enumerable:!0})},A=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of C(e))!T.call(n,i)&&i!==t&&p(n,i,{get:()=>e[i],enumerable:!(r=U(e,i))||r.enumerable});return n};var R=n=>A(p({},"__esModule",{value:!0}),n);var x={};b(x,{AuthActionClient:()=>f,hasAuthParams:()=>v});module.exports=R(x);function c(n=64){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=new Uint8Array(n);return crypto.getRandomValues(t),Array.from(t,r=>e[r%e.length]).join("")}function L(n){return btoa(String.fromCharCode(...new Uint8Array(n))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function m(n){let e=new TextEncoder().encode(n),t=await crypto.subtle.digest("SHA-256",e);return L(t)}function w(n){let e=n.split(".");if(e.length!==3)throw new Error("Invalid JWT format");let t=e[1].replace(/-/g,"+").replace(/_/g,"/");try{return JSON.parse(atob(t))}catch{throw new Error("Failed to decode JWT payload")}}var S="__authaction_pkce__",l=()=>typeof sessionStorage<"u"?sessionStorage:null,d={save(n){l()?.setItem(S,JSON.stringify(n))},get(){let n=l()?.getItem(S)??null;return n?JSON.parse(n):null},clear(){l()?.removeItem(S)}},k="__authaction_tokens__",u=class{constructor(e="memory"){this.memStore=null;this.backend=e}get(){if(this.backend==="memory")return this.memStore;let e=this.getStore()?.getItem(k)??null;return e?JSON.parse(e):null}set(e){if(this.backend==="memory"){this.memStore=e;return}this.getStore()?.setItem(k,JSON.stringify(e))}clear(){this.memStore=null,this.backend!=="memory"&&this.getStore()?.removeItem(k)}getStore(){return this.backend==="localstorage"?typeof localStorage<"u"?localStorage:null:l()}};var P="openid profile email",E=60,f=class{constructor(e){this.listeners=new Set;this.state={isAuthenticated:!1,isLoading:!0,user:void 0,error:void 0};this.cfg=e,this.cache=new u(e.cacheLocation??"memory"),this.loadStateFromCache()}async loginWithRedirect(e={}){this.setState({...this.state,activeNavigator:"loginWithRedirect"});try{let t=c(32),r=c(64),i=await m(r);d.save({state:t,codeVerifier:r,redirectUri:this.cfg.redirectUri,appState:e.appState});let o=new URLSearchParams({response_type:"code",client_id:this.cfg.clientId,redirect_uri:this.cfg.redirectUri,scope:this.cfg.scope??P,state:t,code_challenge:i,code_challenge_method:"S256",...this.cfg.authorizationParams,...e.authorizationParams});this.navigate(`https://${this.cfg.domain}/oauth2/authorize?${o}`)}catch(t){throw this.setState({isAuthenticated:this.state.isAuthenticated,isLoading:!1,user:this.state.user,error:t instanceof Error?t:new Error(String(t))}),t}}async loginWithPopup(e={}){let t=c(32),r=c(64),i=await m(r);d.save({state:t,codeVerifier:r,redirectUri:this.cfg.redirectUri,appState:e.appState});let o=new URLSearchParams({response_type:"code",client_id:this.cfg.clientId,redirect_uri:this.cfg.redirectUri,scope:this.cfg.scope??P,state:t,code_challenge:i,code_challenge_method:"S256",...this.cfg.authorizationParams,...e.authorizationParams});if(!(e.popup??window.open(`https://${this.cfg.domain}/oauth2/authorize?${o}`,"authaction:login","width=480,height=640,left=100,top=100")))throw new Error("Popup was blocked. Allow popups for this site and try again.");await new Promise((h,_)=>{let y=async a=>{if(a.origin===window.location.origin&&a.data?.type?.startsWith("authaction:"))if(window.removeEventListener("message",y),a.data.type==="authaction:callback")try{let g=new URL(a.data.url);await this.exchangeCodeFromUrl(g),h()}catch(g){_(g)}else a.data.type==="authaction:error"&&_(new Error(a.data.error))};window.addEventListener("message",y)})}async handleRedirectCallback(e){let t=new URL(e??window.location.href),r=await this.exchangeCodeFromUrl(t),i=new URL(window.location.href);return i.searchParams.delete("code"),i.searchParams.delete("state"),window.history.replaceState({},document.title,i.toString()),{appState:r}}static handlePopupCallback(e){window.opener&&(window.opener.postMessage({type:"authaction:callback",url:e??window.location.href},window.location.origin),window.close())}async isAuthenticated(){let e=this.cache.get();if(!e)return!1;if(this.isExpired(e))try{await this.refreshTokens()}catch{return!1}return!0}async getUser(){if(!await this.isAuthenticated())return;let e=this.cache.get();if(e?.id_token)try{return w(e.id_token)}catch{return}}async getAccessToken(e={}){let t=this.cache.get();if(!t)throw new Error("Not authenticated \u2014 call loginWithRedirect() first");return(e.forceRefresh||this.isExpired(t))&&(t=await this.refreshTokens()),t.access_token}async logout(e={}){if(this.cache.clear(),e.federated===!1){this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0});return}this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0,activeNavigator:"logout"});let t=e.returnTo??this.cfg.postLogoutRedirectUri,r=new URLSearchParams({client_id:e.clientId??this.cfg.clientId,...t?{post_logout_redirect_uri:t}:{}});this.navigate(`https://${this.cfg.domain}/oidc/logout?${r}`)}removeUser(){this.cache.clear(),this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0})}onStateChange(e){return this.listeners.add(e),e(this.state),()=>this.listeners.delete(e)}getState(){return{...this.state}}async exchangeCodeFromUrl(e){let t=e.searchParams.get("code"),r=e.searchParams.get("state"),i=e.searchParams.get("error"),o=e.searchParams.get("error_description");if(i)throw new Error(o??i);if(!t)throw new Error("No authorization code in callback URL");let s=d.get();if(d.clear(),!s)throw new Error("No PKCE transaction found \u2014 did the login session expire?");if(s.state!==r)throw new Error("State mismatch \u2014 possible CSRF attack");let h=await this.exchangeCode(t,s.codeVerifier,s.redirectUri);return this.cache.set(h),this.setState({isAuthenticated:!0,isLoading:!1,user:this.buildUser(h),error:void 0}),s.appState}async exchangeCode(e,t,r){let i=await fetch(`https://${this.cfg.domain}/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:this.cfg.clientId,code:e,redirect_uri:r,code_verifier:t})});if(!i.ok){let s=await i.json().catch(()=>({}));throw new Error(s.error_description??s.error??"Token exchange failed")}let o=await i.json();return this.normaliseTokenResponse(o)}async refreshTokens(){let e=this.cache.get();if(!e?.refresh_token)throw new Error("No refresh token available");let t=await fetch(`https://${this.cfg.domain}/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"refresh_token",client_id:this.cfg.clientId,refresh_token:e.refresh_token})});if(!t.ok)throw this.cache.clear(),this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0}),new Error("Token refresh failed \u2014 user must re-authenticate");let r=await t.json(),i=this.normaliseTokenResponse(r);return this.cache.set(i),this.setState({isAuthenticated:!0,isLoading:!1,user:this.buildUser(i),error:void 0}),i}normaliseTokenResponse(e){let t=typeof e.expires_in=="number"?e.expires_in:3600;return{access_token:e.access_token,id_token:e.id_token,refresh_token:e.refresh_token,scope:e.scope,expires_at:Math.floor(Date.now()/1e3)+t}}isExpired(e){return e.expires_at-E<Math.floor(Date.now()/1e3)}async loadStateFromCache(){let e=this.cache.get();if(!e){this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0});return}try{this.isExpired(e)&&await this.refreshTokens();let t=this.cache.get();this.setState({isAuthenticated:!0,isLoading:!1,user:t?this.buildUser(t):void 0,error:void 0})}catch{this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0})}}buildUser(e){if(e.id_token)try{let r={...w(e.id_token),access_token:e.access_token},i={sub:r.sub,name:r.name,email:r.email,email_verified:r.email_verified,picture:r.picture};for(let o of Object.keys(r))!(o in i)&&o!=="access_token"&&o!=="profile"&&(i[o]=r[o]);return r.profile=i,r}catch{return}}navigate(e){window.location.assign(e)}setState(e){this.state=e,this.notifyListeners(e)}notifyListeners(e){this.state=e,this.listeners.forEach(t=>t(e))}};function v(n){let e=n?new URL(n).search:window.location.search,t=new URLSearchParams(e);return t.has("code")||t.has("error")}0&&(module.exports={AuthActionClient,hasAuthParams});
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/pkce.ts","../src/storage.ts","../src/client.ts","../src/utils.ts"],"sourcesContent":["export { AuthActionClient } from './client';\nexport * from './types';\nexport { hasAuthParams } from './utils';\n","/** Generate a cryptographically random string for PKCE code_verifier or state */\nexport function generateRandom(length = 64): string {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => chars[byte % chars.length]).join('');\n}\n\n/** base64url-encode a buffer (no padding, url-safe) */\nfunction base64urlEncode(buffer: ArrayBuffer): string {\n return btoa(String.fromCharCode(...new Uint8Array(buffer)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n}\n\n/** Compute the PKCE code_challenge from a code_verifier using S256 */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoded = new TextEncoder().encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', encoded);\n return base64urlEncode(digest);\n}\n\n/** Decode a JWT payload without verifying the signature */\nexport function decodeJwtPayload(token: string): Record<string, unknown> {\n const parts = token.split('.');\n if (parts.length !== 3) throw new Error('Invalid JWT format');\n const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n try {\n return JSON.parse(atob(payload));\n } catch {\n throw new Error('Failed to decode JWT payload');\n }\n}\n","import { TokenSet } from \"./types\";\n\n// ── PKCE transaction store ────────────────────────────────────────────────────\n// Must survive the redirect to /authorize and back, so always uses sessionStorage.\n\nconst PKCE_KEY = \"__authaction_pkce__\";\n\ninterface PkceTransaction {\n state: string;\n codeVerifier: string;\n redirectUri: string;\n appState?: Record<string, unknown>;\n}\n\nconst ss = (): Storage | null =>\n typeof sessionStorage !== \"undefined\" ? sessionStorage : null;\n\nexport const pkceStore = {\n save(tx: PkceTransaction): void {\n ss()?.setItem(PKCE_KEY, JSON.stringify(tx));\n },\n get(): PkceTransaction | null {\n const raw = ss()?.getItem(PKCE_KEY) ?? null;\n return raw ? (JSON.parse(raw) as PkceTransaction) : null;\n },\n clear(): void {\n ss()?.removeItem(PKCE_KEY);\n },\n};\n\n// ── Token cache ───────────────────────────────────────────────────────────────\n\nconst TOKEN_KEY = \"__authaction_tokens__\";\n\ntype StorageBackend = \"memory\" | \"localstorage\" | \"sessionstorage\";\n\nexport class TokenCache {\n private memStore: TokenSet | null = null;\n private backend: StorageBackend;\n\n constructor(backend: StorageBackend = \"memory\") {\n this.backend = backend;\n }\n\n get(): TokenSet | null {\n if (this.backend === \"memory\") return this.memStore;\n const raw = this.getStore()?.getItem(TOKEN_KEY) ?? null;\n return raw ? (JSON.parse(raw) as TokenSet) : null;\n }\n\n set(tokens: TokenSet): void {\n if (this.backend === \"memory\") {\n this.memStore = tokens;\n return;\n }\n this.getStore()?.setItem(TOKEN_KEY, JSON.stringify(tokens));\n }\n\n clear(): void {\n this.memStore = null;\n if (this.backend !== \"memory\") this.getStore()?.removeItem(TOKEN_KEY);\n }\n\n private getStore(): Storage | null {\n if (this.backend === \"localstorage\") {\n return typeof localStorage !== \"undefined\" ? localStorage : null;\n }\n return ss();\n }\n}\n","import { generateRandom, generateCodeChallenge, decodeJwtPayload } from './pkce';\nimport { pkceStore, TokenCache } from './storage';\nimport {\n AuthActionClientConfig,\n AuthState,\n GetAccessTokenOptions,\n LoginOptions,\n LoginWithPopupOptions,\n LogoutOptions,\n RedirectCallbackResult,\n StateChangeCallback,\n TokenSet,\n UnsubscribeFn,\n User,\n UserProfile,\n} from './types';\n\nconst DEFAULT_SCOPE = 'openid profile email';\n// Refresh the access token 60 seconds before it expires\nconst REFRESH_BUFFER_SECONDS = 60;\n\nexport class AuthActionClient {\n private readonly cfg: Required<Pick<AuthActionClientConfig, 'domain' | 'clientId' | 'redirectUri'>> &\n AuthActionClientConfig;\n private readonly cache: TokenCache;\n private readonly listeners = new Set<StateChangeCallback>();\n\n private state: AuthState = { isAuthenticated: false, isLoading: true, user: undefined, error: undefined };\n\n constructor(config: AuthActionClientConfig) {\n this.cfg = config as typeof this.cfg;\n this.cache = new TokenCache(config.cacheLocation ?? 'memory');\n // Restore state from cache on init (non-blocking)\n void this.loadStateFromCache();\n }\n\n // ── Login ────────────────────────────────────────────────────────────────────\n\n async loginWithRedirect(options: LoginOptions = {}): Promise<void> {\n this.setState({ ...this.state, activeNavigator: 'loginWithRedirect' });\n try {\n const state = generateRandom(32);\n const codeVerifier = generateRandom(64);\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n\n pkceStore.save({\n state,\n codeVerifier,\n redirectUri: this.cfg.redirectUri,\n appState: options.appState,\n });\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.cfg.clientId,\n redirect_uri: this.cfg.redirectUri,\n scope: this.cfg.scope ?? DEFAULT_SCOPE,\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n ...this.cfg.authorizationParams,\n ...options.authorizationParams,\n });\n\n this.navigate(`https://${this.cfg.domain}/oauth2/authorize?${params}`);\n } catch (err) {\n this.setState({\n isAuthenticated: this.state.isAuthenticated,\n isLoading: false,\n user: this.state.user,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n throw err;\n }\n }\n\n /** Opens AuthAction login in a popup window instead of a full redirect */\n async loginWithPopup(options: LoginWithPopupOptions = {}): Promise<void> {\n const state = generateRandom(32);\n const codeVerifier = generateRandom(64);\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n\n pkceStore.save({ state, codeVerifier, redirectUri: this.cfg.redirectUri, appState: options.appState });\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.cfg.clientId,\n redirect_uri: this.cfg.redirectUri,\n scope: this.cfg.scope ?? DEFAULT_SCOPE,\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n ...this.cfg.authorizationParams,\n ...options.authorizationParams,\n });\n\n const popup = options.popup ?? window.open(\n `https://${this.cfg.domain}/oauth2/authorize?${params}`,\n 'authaction:login',\n 'width=480,height=640,left=100,top=100',\n );\n\n if (!popup) throw new Error('Popup was blocked. Allow popups for this site and try again.');\n\n await new Promise<void>((resolve, reject) => {\n const listener = async (event: MessageEvent) => {\n if (event.origin !== window.location.origin) return;\n if (!event.data?.type?.startsWith('authaction:')) return;\n\n window.removeEventListener('message', listener);\n\n if (event.data.type === 'authaction:callback') {\n try {\n const url = new URL(event.data.url);\n await this.exchangeCodeFromUrl(url);\n resolve();\n } catch (err) {\n reject(err);\n }\n } else if (event.data.type === 'authaction:error') {\n reject(new Error(event.data.error));\n }\n };\n window.addEventListener('message', listener);\n });\n }\n\n // ── Callback ─────────────────────────────────────────────────────────────────\n\n /**\n * Call this on the redirect callback page (e.g. /callback).\n * Exchanges the authorization code for tokens and returns the original appState.\n */\n async handleRedirectCallback(url?: string): Promise<RedirectCallbackResult> {\n const callbackUrl = new URL(url ?? window.location.href);\n const appState = await this.exchangeCodeFromUrl(callbackUrl);\n\n // Clean the code/state from the URL without a page reload\n const clean = new URL(window.location.href);\n clean.searchParams.delete('code');\n clean.searchParams.delete('state');\n window.history.replaceState({}, document.title, clean.toString());\n\n return { appState };\n }\n\n /**\n * Call this from the popup callback page to relay the URL back to the opener.\n * Place this call in the script that runs on your redirectUri page when in popup mode.\n */\n static handlePopupCallback(url?: string): void {\n if (!window.opener) return;\n window.opener.postMessage(\n { type: 'authaction:callback', url: url ?? window.location.href },\n window.location.origin,\n );\n window.close();\n }\n\n // ── Auth state ────────────────────────────────────────────────────────────────\n\n async isAuthenticated(): Promise<boolean> {\n const tokens = this.cache.get();\n if (!tokens) return false;\n // Auto-refresh if near expiry\n if (this.isExpired(tokens)) {\n try {\n await this.refreshTokens();\n } catch {\n return false;\n }\n }\n return true;\n }\n\n async getUser<T extends User = User>(): Promise<T | undefined> {\n if (!(await this.isAuthenticated())) return undefined;\n const tokens = this.cache.get();\n if (!tokens?.id_token) return undefined;\n try {\n return decodeJwtPayload(tokens.id_token) as unknown as T;\n } catch {\n return undefined;\n }\n }\n\n // ── Tokens ────────────────────────────────────────────────────────────────────\n\n /**\n * Returns a valid access token, refreshing automatically if needed.\n * Use this as the Bearer token for API calls including the AuthAction Files service.\n *\n * @example\n * const token = await client.getAccessToken();\n * fetch('/api/v1/files', { headers: { Authorization: `Bearer ${token}` } });\n */\n async getAccessToken(options: GetAccessTokenOptions = {}): Promise<string> {\n let tokens = this.cache.get();\n\n if (!tokens) throw new Error('Not authenticated — call loginWithRedirect() first');\n\n if (options.forceRefresh || this.isExpired(tokens)) {\n tokens = await this.refreshTokens();\n }\n\n return tokens.access_token;\n }\n\n // ── Logout ────────────────────────────────────────────────────────────────────\n\n async logout(options: LogoutOptions = {}): Promise<void> {\n this.cache.clear();\n\n if (options.federated === false) {\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n return;\n }\n\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined, activeNavigator: 'logout' });\n\n const returnTo = options.returnTo ?? this.cfg.postLogoutRedirectUri;\n const params = new URLSearchParams({\n client_id: options.clientId ?? this.cfg.clientId,\n ...(returnTo ? { post_logout_redirect_uri: returnTo } : {}),\n });\n\n this.navigate(`https://${this.cfg.domain}/oidc/logout?${params}`);\n }\n\n /**\n * Clears the local session (tokens + state) without hitting the server's end-session endpoint.\n * Use this to handle 401 responses from APIs where the server already considers the session gone.\n */\n removeUser(): void {\n this.cache.clear();\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n }\n\n // ── State subscriptions ────────────────────────────────────────────────────────\n\n onStateChange(callback: StateChangeCallback): UnsubscribeFn {\n this.listeners.add(callback);\n // Emit current state immediately\n callback(this.state);\n return () => this.listeners.delete(callback);\n }\n\n getState(): AuthState {\n return { ...this.state };\n }\n\n // ── Private ───────────────────────────────────────────────────────────────────\n\n private async exchangeCodeFromUrl(url: URL): Promise<Record<string, unknown> | undefined> {\n const code = url.searchParams.get('code');\n const returnedState = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n const errorDescription = url.searchParams.get('error_description');\n\n if (error) throw new Error(errorDescription ?? error);\n if (!code) throw new Error('No authorization code in callback URL');\n\n const tx = pkceStore.get();\n pkceStore.clear();\n\n if (!tx) throw new Error('No PKCE transaction found — did the login session expire?');\n if (tx.state !== returnedState) throw new Error('State mismatch — possible CSRF attack');\n\n const tokens = await this.exchangeCode(code, tx.codeVerifier, tx.redirectUri);\n this.cache.set(tokens);\n\n this.setState({ isAuthenticated: true, isLoading: false, user: this.buildUser(tokens), error: undefined });\n\n return tx.appState;\n }\n\n private async exchangeCode(code: string, codeVerifier: string, redirectUri: string): Promise<TokenSet> {\n const res = await fetch(`https://${this.cfg.domain}/oauth2/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.cfg.clientId,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error(err.error_description ?? err.error ?? 'Token exchange failed');\n }\n\n const data = await res.json();\n return this.normaliseTokenResponse(data);\n }\n\n private async refreshTokens(): Promise<TokenSet> {\n const tokens = this.cache.get();\n if (!tokens?.refresh_token) throw new Error('No refresh token available');\n\n const res = await fetch(`https://${this.cfg.domain}/oauth2/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.cfg.clientId,\n refresh_token: tokens.refresh_token,\n }),\n });\n\n if (!res.ok) {\n this.cache.clear();\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n throw new Error('Token refresh failed — user must re-authenticate');\n }\n\n const data = await res.json();\n const refreshed = this.normaliseTokenResponse(data);\n this.cache.set(refreshed);\n this.setState({ isAuthenticated: true, isLoading: false, user: this.buildUser(refreshed), error: undefined });\n return refreshed;\n }\n\n private normaliseTokenResponse(data: Record<string, unknown>): TokenSet {\n const expiresIn = typeof data.expires_in === 'number' ? data.expires_in : 3600;\n return {\n access_token: data.access_token as string,\n id_token: data.id_token as string | undefined,\n refresh_token: data.refresh_token as string | undefined,\n scope: data.scope as string | undefined,\n expires_at: Math.floor(Date.now() / 1000) + expiresIn,\n };\n }\n\n private isExpired(tokens: TokenSet): boolean {\n return tokens.expires_at - REFRESH_BUFFER_SECONDS < Math.floor(Date.now() / 1000);\n }\n\n private async loadStateFromCache(): Promise<void> {\n const tokens = this.cache.get();\n if (!tokens) {\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n return;\n }\n\n try {\n if (this.isExpired(tokens)) await this.refreshTokens();\n const cached = this.cache.get();\n this.setState({ isAuthenticated: true, isLoading: false, user: cached ? this.buildUser(cached) : undefined, error: undefined });\n } catch {\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n }\n }\n\n private buildUser(tokens: TokenSet): User | undefined {\n if (!tokens.id_token) return undefined;\n try {\n const claims = decodeJwtPayload(tokens.id_token) as User;\n const user: User = { ...claims, access_token: tokens.access_token };\n const profile: UserProfile = {\n sub: user.sub,\n name: user.name,\n email: user.email,\n email_verified: user.email_verified,\n picture: user.picture,\n };\n // Copy any extra claims from the ID token into profile\n for (const key of Object.keys(user)) {\n if (!(key in profile) && key !== 'access_token' && key !== 'profile') {\n profile[key] = user[key];\n }\n }\n user.profile = profile;\n return user;\n } catch {\n return undefined;\n }\n }\n\n /** Extracted for testability — spy on this method to capture redirect URLs */\n protected navigate(url: string): void {\n window.location.assign(url);\n }\n\n private setState(next: AuthState): void {\n this.state = next;\n this.notifyListeners(next);\n }\n\n private notifyListeners(state: AuthState): void {\n this.state = state;\n this.listeners.forEach((cb) => cb(state));\n }\n}\n","/**\n * Returns true if the given URL (or window.location) contains OAuth2 callback\n * params — i.e. the authorization server has redirected back with a code or error.\n * Mirrors react-oidc-context's hasAuthParams() for drop-in compatibility.\n */\nexport function hasAuthParams(url?: string): boolean {\n const search = url ? new URL(url).search : window.location.search;\n const params = new URLSearchParams(search);\n return params.has('code') || params.has('error');\n}\n"],"mappings":"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","names":["src_exports","__export","AuthActionClient","hasAuthParams","__toCommonJS","generateRandom","length","chars","array","byte","base64urlEncode","buffer","generateCodeChallenge","verifier","encoded","digest","decodeJwtPayload","token","parts","payload","PKCE_KEY","ss","pkceStore","tx","raw","TOKEN_KEY","TokenCache","backend","tokens","DEFAULT_SCOPE","REFRESH_BUFFER_SECONDS","AuthActionClient","config","TokenCache","options","state","generateRandom","codeVerifier","codeChallenge","generateCodeChallenge","pkceStore","params","err","resolve","reject","listener","event","url","callbackUrl","appState","clean","tokens","decodeJwtPayload","returnTo","callback","code","returnedState","error","errorDescription","tx","redirectUri","res","data","refreshed","expiresIn","cached","user","profile","key","next","cb","hasAuthParams","url","search","params"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { AuthActionProviderProps } from '../react/index.mjs';
|
|
3
|
+
export { UseAuthActionReturn, useAuthAction, useAuthLoading, useGetAccessToken, useIsAuthenticated, useUser } from '../react/index.mjs';
|
|
4
|
+
export { h as hasAuthParams } from '../utils-BzTlGuFj.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Drop-in replacement for `<AuthActionProvider>` in Next.js projects.
|
|
8
|
+
* The `'use client'` directive at the top of this file marks it as a client
|
|
9
|
+
* boundary for Next.js App Router, so child components can freely use
|
|
10
|
+
* `useAuthAction()` and other hooks without adding their own `'use client'`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* // app/layout.tsx (App Router)
|
|
15
|
+
* import { AuthActionNextProvider } from '@authaction/web-sdk/nextjs';
|
|
16
|
+
*
|
|
17
|
+
* export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
18
|
+
* return (
|
|
19
|
+
* <html>
|
|
20
|
+
* <body>
|
|
21
|
+
* <AuthActionNextProvider
|
|
22
|
+
* domain="myapp.eu.authaction.com"
|
|
23
|
+
* clientId="your-client-id"
|
|
24
|
+
* redirectUri="http://localhost:3000/callback"
|
|
25
|
+
* >
|
|
26
|
+
* {children}
|
|
27
|
+
* </AuthActionNextProvider>
|
|
28
|
+
* </body>
|
|
29
|
+
* </html>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function AuthActionNextProvider(props: AuthActionProviderProps): React.JSX.Element;
|
|
35
|
+
|
|
36
|
+
export { AuthActionNextProvider, AuthActionProviderProps };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { AuthActionProviderProps } from '../react/index.js';
|
|
3
|
+
export { UseAuthActionReturn, useAuthAction, useAuthLoading, useGetAccessToken, useIsAuthenticated, useUser } from '../react/index.js';
|
|
4
|
+
export { h as hasAuthParams } from '../utils-BzTlGuFj.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Drop-in replacement for `<AuthActionProvider>` in Next.js projects.
|
|
8
|
+
* The `'use client'` directive at the top of this file marks it as a client
|
|
9
|
+
* boundary for Next.js App Router, so child components can freely use
|
|
10
|
+
* `useAuthAction()` and other hooks without adding their own `'use client'`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* // app/layout.tsx (App Router)
|
|
15
|
+
* import { AuthActionNextProvider } from '@authaction/web-sdk/nextjs';
|
|
16
|
+
*
|
|
17
|
+
* export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
18
|
+
* return (
|
|
19
|
+
* <html>
|
|
20
|
+
* <body>
|
|
21
|
+
* <AuthActionNextProvider
|
|
22
|
+
* domain="myapp.eu.authaction.com"
|
|
23
|
+
* clientId="your-client-id"
|
|
24
|
+
* redirectUri="http://localhost:3000/callback"
|
|
25
|
+
* >
|
|
26
|
+
* {children}
|
|
27
|
+
* </AuthActionNextProvider>
|
|
28
|
+
* </body>
|
|
29
|
+
* </html>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function AuthActionNextProvider(props: AuthActionProviderProps): React.JSX.Element;
|
|
35
|
+
|
|
36
|
+
export { AuthActionNextProvider, AuthActionProviderProps };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";"use client";var k=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var J=Object.prototype.hasOwnProperty;var z=(r,e)=>{for(var t in e)k(r,t,{get:e[t],enumerable:!0})},V=(r,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of G(e))!J.call(r,n)&&n!==t&&k(r,n,{get:()=>e[n],enumerable:!(o=F(e,n))||o.enumerable});return r};var $=r=>V(k({},"__esModule",{value:!0}),r);var K={};z(K,{AuthActionNextProvider:()=>T,hasAuthParams:()=>N,useAuthAction:()=>L,useAuthLoading:()=>O,useGetAccessToken:()=>W,useIsAuthenticated:()=>I,useUser:()=>E});module.exports=$(K);var c=require("react");function p(r=64){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=new Uint8Array(r);return crypto.getRandomValues(t),Array.from(t,o=>e[o%e.length]).join("")}function B(r){return btoa(String.fromCharCode(...new Uint8Array(r))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function S(r){let e=new TextEncoder().encode(r),t=await crypto.subtle.digest("SHA-256",e);return B(t)}function P(r){let e=r.split(".");if(e.length!==3)throw new Error("Invalid JWT format");let t=e[1].replace(/-/g,"+").replace(/_/g,"/");try{return JSON.parse(atob(t))}catch{throw new Error("Failed to decode JWT payload")}}var v="__authaction_pkce__",g=()=>typeof sessionStorage<"u"?sessionStorage:null,f={save(r){g()?.setItem(v,JSON.stringify(r))},get(){let r=g()?.getItem(v)??null;return r?JSON.parse(r):null},clear(){g()?.removeItem(v)}},y="__authaction_tokens__",m=class{constructor(e="memory"){this.memStore=null;this.backend=e}get(){if(this.backend==="memory")return this.memStore;let e=this.getStore()?.getItem(y)??null;return e?JSON.parse(e):null}set(e){if(this.backend==="memory"){this.memStore=e;return}this.getStore()?.setItem(y,JSON.stringify(e))}clear(){this.memStore=null,this.backend!=="memory"&&this.getStore()?.removeItem(y)}getStore(){return this.backend==="localstorage"?typeof localStorage<"u"?localStorage:null:g()}};var C="openid profile email",D=60,A=class{constructor(e){this.listeners=new Set;this.state={isAuthenticated:!1,isLoading:!0,user:void 0,error:void 0};this.cfg=e,this.cache=new m(e.cacheLocation??"memory"),this.loadStateFromCache()}async loginWithRedirect(e={}){this.setState({...this.state,activeNavigator:"loginWithRedirect"});try{let t=p(32),o=p(64),n=await S(o);f.save({state:t,codeVerifier:o,redirectUri:this.cfg.redirectUri,appState:e.appState});let i=new URLSearchParams({response_type:"code",client_id:this.cfg.clientId,redirect_uri:this.cfg.redirectUri,scope:this.cfg.scope??C,state:t,code_challenge:n,code_challenge_method:"S256",...this.cfg.authorizationParams,...e.authorizationParams});this.navigate(`https://${this.cfg.domain}/oauth2/authorize?${i}`)}catch(t){throw this.setState({isAuthenticated:this.state.isAuthenticated,isLoading:!1,user:this.state.user,error:t instanceof Error?t:new Error(String(t))}),t}}async loginWithPopup(e={}){let t=p(32),o=p(64),n=await S(o);f.save({state:t,codeVerifier:o,redirectUri:this.cfg.redirectUri,appState:e.appState});let i=new URLSearchParams({response_type:"code",client_id:this.cfg.clientId,redirect_uri:this.cfg.redirectUri,scope:this.cfg.scope??C,state:t,code_challenge:n,code_challenge_method:"S256",...this.cfg.authorizationParams,...e.authorizationParams});if(!(e.popup??window.open(`https://${this.cfg.domain}/oauth2/authorize?${i}`,"authaction:login","width=480,height=640,left=100,top=100")))throw new Error("Popup was blocked. Allow popups for this site and try again.");await new Promise((d,a)=>{let _=async u=>{if(u.origin===window.location.origin&&u.data?.type?.startsWith("authaction:"))if(window.removeEventListener("message",_),u.data.type==="authaction:callback")try{let w=new URL(u.data.url);await this.exchangeCodeFromUrl(w),d()}catch(w){a(w)}else u.data.type==="authaction:error"&&a(new Error(u.data.error))};window.addEventListener("message",_)})}async handleRedirectCallback(e){let t=new URL(e??window.location.href),o=await this.exchangeCodeFromUrl(t),n=new URL(window.location.href);return n.searchParams.delete("code"),n.searchParams.delete("state"),window.history.replaceState({},document.title,n.toString()),{appState:o}}static handlePopupCallback(e){window.opener&&(window.opener.postMessage({type:"authaction:callback",url:e??window.location.href},window.location.origin),window.close())}async isAuthenticated(){let e=this.cache.get();if(!e)return!1;if(this.isExpired(e))try{await this.refreshTokens()}catch{return!1}return!0}async getUser(){if(!await this.isAuthenticated())return;let e=this.cache.get();if(e?.id_token)try{return P(e.id_token)}catch{return}}async getAccessToken(e={}){let t=this.cache.get();if(!t)throw new Error("Not authenticated \u2014 call loginWithRedirect() first");return(e.forceRefresh||this.isExpired(t))&&(t=await this.refreshTokens()),t.access_token}async logout(e={}){if(this.cache.clear(),e.federated===!1){this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0});return}this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0,activeNavigator:"logout"});let t=e.returnTo??this.cfg.postLogoutRedirectUri,o=new URLSearchParams({client_id:e.clientId??this.cfg.clientId,...t?{post_logout_redirect_uri:t}:{}});this.navigate(`https://${this.cfg.domain}/oidc/logout?${o}`)}removeUser(){this.cache.clear(),this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0})}onStateChange(e){return this.listeners.add(e),e(this.state),()=>this.listeners.delete(e)}getState(){return{...this.state}}async exchangeCodeFromUrl(e){let t=e.searchParams.get("code"),o=e.searchParams.get("state"),n=e.searchParams.get("error"),i=e.searchParams.get("error_description");if(n)throw new Error(i??n);if(!t)throw new Error("No authorization code in callback URL");let s=f.get();if(f.clear(),!s)throw new Error("No PKCE transaction found \u2014 did the login session expire?");if(s.state!==o)throw new Error("State mismatch \u2014 possible CSRF attack");let d=await this.exchangeCode(t,s.codeVerifier,s.redirectUri);return this.cache.set(d),this.setState({isAuthenticated:!0,isLoading:!1,user:this.buildUser(d),error:void 0}),s.appState}async exchangeCode(e,t,o){let n=await fetch(`https://${this.cfg.domain}/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:this.cfg.clientId,code:e,redirect_uri:o,code_verifier:t})});if(!n.ok){let s=await n.json().catch(()=>({}));throw new Error(s.error_description??s.error??"Token exchange failed")}let i=await n.json();return this.normaliseTokenResponse(i)}async refreshTokens(){let e=this.cache.get();if(!e?.refresh_token)throw new Error("No refresh token available");let t=await fetch(`https://${this.cfg.domain}/oauth2/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"refresh_token",client_id:this.cfg.clientId,refresh_token:e.refresh_token})});if(!t.ok)throw this.cache.clear(),this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0}),new Error("Token refresh failed \u2014 user must re-authenticate");let o=await t.json(),n=this.normaliseTokenResponse(o);return this.cache.set(n),this.setState({isAuthenticated:!0,isLoading:!1,user:this.buildUser(n),error:void 0}),n}normaliseTokenResponse(e){let t=typeof e.expires_in=="number"?e.expires_in:3600;return{access_token:e.access_token,id_token:e.id_token,refresh_token:e.refresh_token,scope:e.scope,expires_at:Math.floor(Date.now()/1e3)+t}}isExpired(e){return e.expires_at-D<Math.floor(Date.now()/1e3)}async loadStateFromCache(){let e=this.cache.get();if(!e){this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0});return}try{this.isExpired(e)&&await this.refreshTokens();let t=this.cache.get();this.setState({isAuthenticated:!0,isLoading:!1,user:t?this.buildUser(t):void 0,error:void 0})}catch{this.setState({isAuthenticated:!1,isLoading:!1,user:void 0,error:void 0})}}buildUser(e){if(e.id_token)try{let o={...P(e.id_token),access_token:e.access_token},n={sub:o.sub,name:o.name,email:o.email,email_verified:o.email_verified,picture:o.picture};for(let i of Object.keys(o))!(i in n)&&i!=="access_token"&&i!=="profile"&&(n[i]=o[i]);return o.profile=n,o}catch{return}}navigate(e){window.location.assign(e)}setState(e){this.state=e,this.notifyListeners(e)}notifyListeners(e){this.state=e,this.listeners.forEach(t=>t(e))}};var x=require("react/jsx-runtime"),U=(0,c.createContext)(null);function R({children:r,onRedirectCallback:e,...t}){let o=(0,c.useMemo)(()=>new A(t),[t.domain,t.clientId,t.redirectUri]),[n,i]=(0,c.useState)(o.getState());return(0,c.useEffect)(()=>o.onStateChange(i),[o]),(0,c.useEffect)(()=>{let s=new URLSearchParams(window.location.search);!s.has("code")&&!s.has("error")||o.handleRedirectCallback().then(({appState:d})=>e?.(d)).catch(()=>{})},[]),(0,x.jsx)(U.Provider,{value:{client:o,state:n},children:r})}function l(){let r=(0,c.useContext)(U);if(!r)throw new Error('useAuthAction() must be used inside <AuthActionProvider>. Wrap your application root with <AuthActionProvider domain="..." clientId="..." redirectUri="...">.');return r}var b=require("react/jsx-runtime");function T(r){return(0,b.jsx)(R,{...r})}var h=require("react");function L(){let{client:r,state:e}=l(),t=(0,h.useCallback)(a=>r.loginWithRedirect(a),[r]),o=(0,h.useCallback)(a=>r.loginWithPopup(a),[r]),n=(0,h.useCallback)(a=>r.handleRedirectCallback(a),[r]),i=(0,h.useCallback)(a=>r.getAccessToken(a),[r]),s=(0,h.useCallback)(a=>r.logout(a),[r]),d=(0,h.useCallback)(()=>r.removeUser(),[r]);return{...e,loginWithRedirect:t,loginWithPopup:o,handleRedirectCallback:n,getAccessToken:i,logout:s,removeUser:d}}function E(){return l().state.user}function O(){return l().state.isLoading}function I(){return l().state.isAuthenticated}function W(){let{client:r}=l();return(0,h.useCallback)(e=>r.getAccessToken(e),[r])}function N(r){let e=r?new URL(r).search:window.location.search,t=new URLSearchParams(e);return t.has("code")||t.has("error")}0&&(module.exports={AuthActionNextProvider,hasAuthParams,useAuthAction,useAuthLoading,useGetAccessToken,useIsAuthenticated,useUser});
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/nextjs/index.ts","../../src/react/context.tsx","../../src/pkce.ts","../../src/storage.ts","../../src/client.ts","../../src/nextjs/provider.tsx","../../src/react/hooks.ts","../../src/utils.ts"],"sourcesContent":["'use client';\n\nexport { AuthActionNextProvider } from './provider';\nexport type { AuthActionProviderProps } from './provider';\n\n// Re-export React hooks — all are client-only and safe to use in 'use client' components\nexport {\n useAuthAction,\n useUser,\n useAuthLoading,\n useIsAuthenticated,\n useGetAccessToken,\n} from '../react/hooks';\nexport type { UseAuthActionReturn } from '../react/hooks';\nexport { hasAuthParams } from '../utils';\n","import React, {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\nimport { AuthActionClient } from '../client';\nimport { AuthActionClientConfig, AuthState } from '../types';\n\n// ── Context ───────────────────────────────────────────────────────────────────\n\ninterface AuthActionContextValue {\n client: AuthActionClient;\n state: AuthState;\n}\n\nconst AuthActionContext = createContext<AuthActionContextValue | null>(null);\n\n// ── Provider ──────────────────────────────────────────────────────────────────\n\nexport interface AuthActionProviderProps extends AuthActionClientConfig {\n children: React.ReactNode;\n /**\n * When true, automatically calls handleRedirectCallback() if the current URL\n * contains ?code= and ?state= query params (i.e. the OAuth2 callback page).\n * On success the callback URL is cleaned from the browser history.\n * Defaults to true.\n */\n onRedirectCallback?: (appState?: Record<string, unknown>) => void;\n}\n\nexport function AuthActionProvider({\n children,\n onRedirectCallback,\n ...config\n}: AuthActionProviderProps) {\n // Stable client instance — never recreated unless config.domain/clientId changes\n const client = useMemo(\n () => new AuthActionClient(config),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [config.domain, config.clientId, config.redirectUri],\n );\n\n const [state, setState] = useState<AuthState>(client.getState());\n\n // Subscribe to auth state changes\n useEffect(() => client.onStateChange(setState), [client]);\n\n // Auto-handle redirect callback when ?code= is present in the URL\n useEffect(() => {\n const params = new URLSearchParams(window.location.search);\n if (!params.has('code') && !params.has('error')) return;\n\n client\n .handleRedirectCallback()\n .then(({ appState }) => onRedirectCallback?.(appState))\n .catch(() => {\n // Ignore errors (e.g. stale callbacks on page refresh)\n });\n // Run only once on mount\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return (\n <AuthActionContext.Provider value={{ client, state }}>\n {children}\n </AuthActionContext.Provider>\n );\n}\n\n// ── Internal hook ─────────────────────────────────────────────────────────────\n\nexport function useAuthActionContext(): AuthActionContextValue {\n const ctx = useContext(AuthActionContext);\n if (!ctx) {\n throw new Error(\n 'useAuthAction() must be used inside <AuthActionProvider>. ' +\n 'Wrap your application root with <AuthActionProvider domain=\"...\" clientId=\"...\" redirectUri=\"...\">.',\n );\n }\n return ctx;\n}\n","/** Generate a cryptographically random string for PKCE code_verifier or state */\nexport function generateRandom(length = 64): string {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => chars[byte % chars.length]).join('');\n}\n\n/** base64url-encode a buffer (no padding, url-safe) */\nfunction base64urlEncode(buffer: ArrayBuffer): string {\n return btoa(String.fromCharCode(...new Uint8Array(buffer)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n}\n\n/** Compute the PKCE code_challenge from a code_verifier using S256 */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoded = new TextEncoder().encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', encoded);\n return base64urlEncode(digest);\n}\n\n/** Decode a JWT payload without verifying the signature */\nexport function decodeJwtPayload(token: string): Record<string, unknown> {\n const parts = token.split('.');\n if (parts.length !== 3) throw new Error('Invalid JWT format');\n const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n try {\n return JSON.parse(atob(payload));\n } catch {\n throw new Error('Failed to decode JWT payload');\n }\n}\n","import { TokenSet } from \"./types\";\n\n// ── PKCE transaction store ────────────────────────────────────────────────────\n// Must survive the redirect to /authorize and back, so always uses sessionStorage.\n\nconst PKCE_KEY = \"__authaction_pkce__\";\n\ninterface PkceTransaction {\n state: string;\n codeVerifier: string;\n redirectUri: string;\n appState?: Record<string, unknown>;\n}\n\nconst ss = (): Storage | null =>\n typeof sessionStorage !== \"undefined\" ? sessionStorage : null;\n\nexport const pkceStore = {\n save(tx: PkceTransaction): void {\n ss()?.setItem(PKCE_KEY, JSON.stringify(tx));\n },\n get(): PkceTransaction | null {\n const raw = ss()?.getItem(PKCE_KEY) ?? null;\n return raw ? (JSON.parse(raw) as PkceTransaction) : null;\n },\n clear(): void {\n ss()?.removeItem(PKCE_KEY);\n },\n};\n\n// ── Token cache ───────────────────────────────────────────────────────────────\n\nconst TOKEN_KEY = \"__authaction_tokens__\";\n\ntype StorageBackend = \"memory\" | \"localstorage\" | \"sessionstorage\";\n\nexport class TokenCache {\n private memStore: TokenSet | null = null;\n private backend: StorageBackend;\n\n constructor(backend: StorageBackend = \"memory\") {\n this.backend = backend;\n }\n\n get(): TokenSet | null {\n if (this.backend === \"memory\") return this.memStore;\n const raw = this.getStore()?.getItem(TOKEN_KEY) ?? null;\n return raw ? (JSON.parse(raw) as TokenSet) : null;\n }\n\n set(tokens: TokenSet): void {\n if (this.backend === \"memory\") {\n this.memStore = tokens;\n return;\n }\n this.getStore()?.setItem(TOKEN_KEY, JSON.stringify(tokens));\n }\n\n clear(): void {\n this.memStore = null;\n if (this.backend !== \"memory\") this.getStore()?.removeItem(TOKEN_KEY);\n }\n\n private getStore(): Storage | null {\n if (this.backend === \"localstorage\") {\n return typeof localStorage !== \"undefined\" ? localStorage : null;\n }\n return ss();\n }\n}\n","import { generateRandom, generateCodeChallenge, decodeJwtPayload } from './pkce';\nimport { pkceStore, TokenCache } from './storage';\nimport {\n AuthActionClientConfig,\n AuthState,\n GetAccessTokenOptions,\n LoginOptions,\n LoginWithPopupOptions,\n LogoutOptions,\n RedirectCallbackResult,\n StateChangeCallback,\n TokenSet,\n UnsubscribeFn,\n User,\n UserProfile,\n} from './types';\n\nconst DEFAULT_SCOPE = 'openid profile email';\n// Refresh the access token 60 seconds before it expires\nconst REFRESH_BUFFER_SECONDS = 60;\n\nexport class AuthActionClient {\n private readonly cfg: Required<Pick<AuthActionClientConfig, 'domain' | 'clientId' | 'redirectUri'>> &\n AuthActionClientConfig;\n private readonly cache: TokenCache;\n private readonly listeners = new Set<StateChangeCallback>();\n\n private state: AuthState = { isAuthenticated: false, isLoading: true, user: undefined, error: undefined };\n\n constructor(config: AuthActionClientConfig) {\n this.cfg = config as typeof this.cfg;\n this.cache = new TokenCache(config.cacheLocation ?? 'memory');\n // Restore state from cache on init (non-blocking)\n void this.loadStateFromCache();\n }\n\n // ── Login ────────────────────────────────────────────────────────────────────\n\n async loginWithRedirect(options: LoginOptions = {}): Promise<void> {\n this.setState({ ...this.state, activeNavigator: 'loginWithRedirect' });\n try {\n const state = generateRandom(32);\n const codeVerifier = generateRandom(64);\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n\n pkceStore.save({\n state,\n codeVerifier,\n redirectUri: this.cfg.redirectUri,\n appState: options.appState,\n });\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.cfg.clientId,\n redirect_uri: this.cfg.redirectUri,\n scope: this.cfg.scope ?? DEFAULT_SCOPE,\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n ...this.cfg.authorizationParams,\n ...options.authorizationParams,\n });\n\n this.navigate(`https://${this.cfg.domain}/oauth2/authorize?${params}`);\n } catch (err) {\n this.setState({\n isAuthenticated: this.state.isAuthenticated,\n isLoading: false,\n user: this.state.user,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n throw err;\n }\n }\n\n /** Opens AuthAction login in a popup window instead of a full redirect */\n async loginWithPopup(options: LoginWithPopupOptions = {}): Promise<void> {\n const state = generateRandom(32);\n const codeVerifier = generateRandom(64);\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n\n pkceStore.save({ state, codeVerifier, redirectUri: this.cfg.redirectUri, appState: options.appState });\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: this.cfg.clientId,\n redirect_uri: this.cfg.redirectUri,\n scope: this.cfg.scope ?? DEFAULT_SCOPE,\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n ...this.cfg.authorizationParams,\n ...options.authorizationParams,\n });\n\n const popup = options.popup ?? window.open(\n `https://${this.cfg.domain}/oauth2/authorize?${params}`,\n 'authaction:login',\n 'width=480,height=640,left=100,top=100',\n );\n\n if (!popup) throw new Error('Popup was blocked. Allow popups for this site and try again.');\n\n await new Promise<void>((resolve, reject) => {\n const listener = async (event: MessageEvent) => {\n if (event.origin !== window.location.origin) return;\n if (!event.data?.type?.startsWith('authaction:')) return;\n\n window.removeEventListener('message', listener);\n\n if (event.data.type === 'authaction:callback') {\n try {\n const url = new URL(event.data.url);\n await this.exchangeCodeFromUrl(url);\n resolve();\n } catch (err) {\n reject(err);\n }\n } else if (event.data.type === 'authaction:error') {\n reject(new Error(event.data.error));\n }\n };\n window.addEventListener('message', listener);\n });\n }\n\n // ── Callback ─────────────────────────────────────────────────────────────────\n\n /**\n * Call this on the redirect callback page (e.g. /callback).\n * Exchanges the authorization code for tokens and returns the original appState.\n */\n async handleRedirectCallback(url?: string): Promise<RedirectCallbackResult> {\n const callbackUrl = new URL(url ?? window.location.href);\n const appState = await this.exchangeCodeFromUrl(callbackUrl);\n\n // Clean the code/state from the URL without a page reload\n const clean = new URL(window.location.href);\n clean.searchParams.delete('code');\n clean.searchParams.delete('state');\n window.history.replaceState({}, document.title, clean.toString());\n\n return { appState };\n }\n\n /**\n * Call this from the popup callback page to relay the URL back to the opener.\n * Place this call in the script that runs on your redirectUri page when in popup mode.\n */\n static handlePopupCallback(url?: string): void {\n if (!window.opener) return;\n window.opener.postMessage(\n { type: 'authaction:callback', url: url ?? window.location.href },\n window.location.origin,\n );\n window.close();\n }\n\n // ── Auth state ────────────────────────────────────────────────────────────────\n\n async isAuthenticated(): Promise<boolean> {\n const tokens = this.cache.get();\n if (!tokens) return false;\n // Auto-refresh if near expiry\n if (this.isExpired(tokens)) {\n try {\n await this.refreshTokens();\n } catch {\n return false;\n }\n }\n return true;\n }\n\n async getUser<T extends User = User>(): Promise<T | undefined> {\n if (!(await this.isAuthenticated())) return undefined;\n const tokens = this.cache.get();\n if (!tokens?.id_token) return undefined;\n try {\n return decodeJwtPayload(tokens.id_token) as unknown as T;\n } catch {\n return undefined;\n }\n }\n\n // ── Tokens ────────────────────────────────────────────────────────────────────\n\n /**\n * Returns a valid access token, refreshing automatically if needed.\n * Use this as the Bearer token for API calls including the AuthAction Files service.\n *\n * @example\n * const token = await client.getAccessToken();\n * fetch('/api/v1/files', { headers: { Authorization: `Bearer ${token}` } });\n */\n async getAccessToken(options: GetAccessTokenOptions = {}): Promise<string> {\n let tokens = this.cache.get();\n\n if (!tokens) throw new Error('Not authenticated — call loginWithRedirect() first');\n\n if (options.forceRefresh || this.isExpired(tokens)) {\n tokens = await this.refreshTokens();\n }\n\n return tokens.access_token;\n }\n\n // ── Logout ────────────────────────────────────────────────────────────────────\n\n async logout(options: LogoutOptions = {}): Promise<void> {\n this.cache.clear();\n\n if (options.federated === false) {\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n return;\n }\n\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined, activeNavigator: 'logout' });\n\n const returnTo = options.returnTo ?? this.cfg.postLogoutRedirectUri;\n const params = new URLSearchParams({\n client_id: options.clientId ?? this.cfg.clientId,\n ...(returnTo ? { post_logout_redirect_uri: returnTo } : {}),\n });\n\n this.navigate(`https://${this.cfg.domain}/oidc/logout?${params}`);\n }\n\n /**\n * Clears the local session (tokens + state) without hitting the server's end-session endpoint.\n * Use this to handle 401 responses from APIs where the server already considers the session gone.\n */\n removeUser(): void {\n this.cache.clear();\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n }\n\n // ── State subscriptions ────────────────────────────────────────────────────────\n\n onStateChange(callback: StateChangeCallback): UnsubscribeFn {\n this.listeners.add(callback);\n // Emit current state immediately\n callback(this.state);\n return () => this.listeners.delete(callback);\n }\n\n getState(): AuthState {\n return { ...this.state };\n }\n\n // ── Private ───────────────────────────────────────────────────────────────────\n\n private async exchangeCodeFromUrl(url: URL): Promise<Record<string, unknown> | undefined> {\n const code = url.searchParams.get('code');\n const returnedState = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n const errorDescription = url.searchParams.get('error_description');\n\n if (error) throw new Error(errorDescription ?? error);\n if (!code) throw new Error('No authorization code in callback URL');\n\n const tx = pkceStore.get();\n pkceStore.clear();\n\n if (!tx) throw new Error('No PKCE transaction found — did the login session expire?');\n if (tx.state !== returnedState) throw new Error('State mismatch — possible CSRF attack');\n\n const tokens = await this.exchangeCode(code, tx.codeVerifier, tx.redirectUri);\n this.cache.set(tokens);\n\n this.setState({ isAuthenticated: true, isLoading: false, user: this.buildUser(tokens), error: undefined });\n\n return tx.appState;\n }\n\n private async exchangeCode(code: string, codeVerifier: string, redirectUri: string): Promise<TokenSet> {\n const res = await fetch(`https://${this.cfg.domain}/oauth2/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.cfg.clientId,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({}));\n throw new Error(err.error_description ?? err.error ?? 'Token exchange failed');\n }\n\n const data = await res.json();\n return this.normaliseTokenResponse(data);\n }\n\n private async refreshTokens(): Promise<TokenSet> {\n const tokens = this.cache.get();\n if (!tokens?.refresh_token) throw new Error('No refresh token available');\n\n const res = await fetch(`https://${this.cfg.domain}/oauth2/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.cfg.clientId,\n refresh_token: tokens.refresh_token,\n }),\n });\n\n if (!res.ok) {\n this.cache.clear();\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n throw new Error('Token refresh failed — user must re-authenticate');\n }\n\n const data = await res.json();\n const refreshed = this.normaliseTokenResponse(data);\n this.cache.set(refreshed);\n this.setState({ isAuthenticated: true, isLoading: false, user: this.buildUser(refreshed), error: undefined });\n return refreshed;\n }\n\n private normaliseTokenResponse(data: Record<string, unknown>): TokenSet {\n const expiresIn = typeof data.expires_in === 'number' ? data.expires_in : 3600;\n return {\n access_token: data.access_token as string,\n id_token: data.id_token as string | undefined,\n refresh_token: data.refresh_token as string | undefined,\n scope: data.scope as string | undefined,\n expires_at: Math.floor(Date.now() / 1000) + expiresIn,\n };\n }\n\n private isExpired(tokens: TokenSet): boolean {\n return tokens.expires_at - REFRESH_BUFFER_SECONDS < Math.floor(Date.now() / 1000);\n }\n\n private async loadStateFromCache(): Promise<void> {\n const tokens = this.cache.get();\n if (!tokens) {\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n return;\n }\n\n try {\n if (this.isExpired(tokens)) await this.refreshTokens();\n const cached = this.cache.get();\n this.setState({ isAuthenticated: true, isLoading: false, user: cached ? this.buildUser(cached) : undefined, error: undefined });\n } catch {\n this.setState({ isAuthenticated: false, isLoading: false, user: undefined, error: undefined });\n }\n }\n\n private buildUser(tokens: TokenSet): User | undefined {\n if (!tokens.id_token) return undefined;\n try {\n const claims = decodeJwtPayload(tokens.id_token) as User;\n const user: User = { ...claims, access_token: tokens.access_token };\n const profile: UserProfile = {\n sub: user.sub,\n name: user.name,\n email: user.email,\n email_verified: user.email_verified,\n picture: user.picture,\n };\n // Copy any extra claims from the ID token into profile\n for (const key of Object.keys(user)) {\n if (!(key in profile) && key !== 'access_token' && key !== 'profile') {\n profile[key] = user[key];\n }\n }\n user.profile = profile;\n return user;\n } catch {\n return undefined;\n }\n }\n\n /** Extracted for testability — spy on this method to capture redirect URLs */\n protected navigate(url: string): void {\n window.location.assign(url);\n }\n\n private setState(next: AuthState): void {\n this.state = next;\n this.notifyListeners(next);\n }\n\n private notifyListeners(state: AuthState): void {\n this.state = state;\n this.listeners.forEach((cb) => cb(state));\n }\n}\n","'use client';\n\n// Marks all exports in this module as client components for Next.js App Router.\n// For Pages Router the 'use client' directive is a harmless string literal.\n//\n// SSR note: only cacheLocation: 'memory' (the default) is safe for server-side\n// rendering. 'localstorage' and 'sessionstorage' require browser APIs and will\n// throw during SSR if passed explicitly.\n\nimport React from 'react';\nimport { AuthActionProvider } from '../react/context';\nimport type { AuthActionProviderProps } from '../react/context';\n\nexport type { AuthActionProviderProps };\n\n/**\n * Drop-in replacement for `<AuthActionProvider>` in Next.js projects.\n * The `'use client'` directive at the top of this file marks it as a client\n * boundary for Next.js App Router, so child components can freely use\n * `useAuthAction()` and other hooks without adding their own `'use client'`.\n *\n * @example\n * ```tsx\n * // app/layout.tsx (App Router)\n * import { AuthActionNextProvider } from '@authaction/web-sdk/nextjs';\n *\n * export default function RootLayout({ children }: { children: React.ReactNode }) {\n * return (\n * <html>\n * <body>\n * <AuthActionNextProvider\n * domain=\"myapp.eu.authaction.com\"\n * clientId=\"your-client-id\"\n * redirectUri=\"http://localhost:3000/callback\"\n * >\n * {children}\n * </AuthActionNextProvider>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\nexport function AuthActionNextProvider(props: AuthActionProviderProps) {\n return <AuthActionProvider {...props} />;\n}\n","import { useCallback } from 'react';\nimport { useAuthActionContext } from './context';\nimport {\n AuthState,\n GetAccessTokenOptions,\n LoginOptions,\n LoginWithPopupOptions,\n LogoutOptions,\n RedirectCallbackResult,\n User,\n} from '../types';\n\n// ── Primary hook ──────────────────────────────────────────────────────────────\n\nexport interface UseAuthActionReturn extends AuthState {\n /** Redirect to AuthAction login page (PKCE flow) */\n loginWithRedirect: (options?: LoginOptions) => Promise<void>;\n /** Open AuthAction login in a popup window */\n loginWithPopup: (options?: LoginWithPopupOptions) => Promise<void>;\n /** Handle the OAuth2 redirect callback — call on your /callback page */\n handleRedirectCallback: (url?: string) => Promise<RedirectCallbackResult>;\n /**\n * Get a valid access token, auto-refreshing if expired.\n * Use as Bearer token for API calls including the AuthAction Files service:\n * `fetch(url, { headers: { Authorization: \\`Bearer \\${token}\\` } })`\n */\n getAccessToken: (options?: GetAccessTokenOptions) => Promise<string>;\n /** Log out and optionally end the AuthAction SSO session */\n logout: (options?: LogoutOptions) => Promise<void>;\n /**\n * Clear the local session without hitting the server's end-session endpoint.\n * Use this on 401 API responses where the server already invalidated the session.\n */\n removeUser: () => void;\n}\n\n/**\n * Primary hook — returns auth state + all client methods.\n *\n * @example\n * ```tsx\n * function App() {\n * const { isAuthenticated, isLoading, user, loginWithRedirect, logout } = useAuthAction();\n *\n * if (isLoading) return <Spinner />;\n * if (!isAuthenticated) return <button onClick={() => loginWithRedirect()}>Login</button>;\n * return <div>Hello {user?.name} <button onClick={() => logout()}>Logout</button></div>;\n * }\n * ```\n */\nexport function useAuthAction(): UseAuthActionReturn {\n const { client, state } = useAuthActionContext();\n\n const loginWithRedirect = useCallback(\n (options?: LoginOptions) => client.loginWithRedirect(options),\n [client],\n );\n\n const loginWithPopup = useCallback(\n (options?: LoginWithPopupOptions) => client.loginWithPopup(options),\n [client],\n );\n\n const handleRedirectCallback = useCallback(\n (url?: string) => client.handleRedirectCallback(url),\n [client],\n );\n\n const getAccessToken = useCallback(\n (options?: GetAccessTokenOptions) => client.getAccessToken(options),\n [client],\n );\n\n const logout = useCallback(\n (options?: LogoutOptions) => client.logout(options),\n [client],\n );\n\n const removeUser = useCallback(() => client.removeUser(), [client]);\n\n return {\n ...state,\n loginWithRedirect,\n loginWithPopup,\n handleRedirectCallback,\n getAccessToken,\n logout,\n removeUser,\n };\n}\n\n// ── Convenience hooks ─────────────────────────────────────────────────────────\n\n/** Returns the current user profile, or undefined if not authenticated */\nexport function useUser<T extends User = User>(): T | undefined {\n return useAuthActionContext().state.user as T | undefined;\n}\n\n/** Returns true while the SDK is determining the initial auth state */\nexport function useAuthLoading(): boolean {\n return useAuthActionContext().state.isLoading;\n}\n\n/** Returns true when the user is authenticated */\nexport function useIsAuthenticated(): boolean {\n return useAuthActionContext().state.isAuthenticated;\n}\n\n/**\n * Returns a function that fetches a valid access token on demand.\n * Useful when you need a token inside event handlers or API calls.\n *\n * @example\n * ```tsx\n * function UploadButton() {\n * const getToken = useGetAccessToken();\n *\n * const handleUpload = async (file: File) => {\n * const token = await getToken();\n * await fetch('/api/v1/files/upload', {\n * method: 'POST',\n * headers: { Authorization: `Bearer ${token}` },\n * body: formData,\n * });\n * };\n * }\n * ```\n */\nexport function useGetAccessToken(): (options?: GetAccessTokenOptions) => Promise<string> {\n const { client } = useAuthActionContext();\n return useCallback(\n (options?: GetAccessTokenOptions) => client.getAccessToken(options),\n [client],\n );\n}\n","/**\n * Returns true if the given URL (or window.location) contains OAuth2 callback\n * params — i.e. the authorization server has redirected back with a code or error.\n * Mirrors react-oidc-context's hasAuthParams() for drop-in compatibility.\n */\nexport function hasAuthParams(url?: string): boolean {\n const search = url ? new URL(url).search : window.location.search;\n const params = new URLSearchParams(search);\n return params.has('code') || params.has('error');\n}\n"],"mappings":"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","names":["nextjs_exports","__export","AuthActionNextProvider","hasAuthParams","useAuthAction","useAuthLoading","useGetAccessToken","useIsAuthenticated","useUser","__toCommonJS","import_react","generateRandom","length","chars","array","byte","base64urlEncode","buffer","generateCodeChallenge","verifier","encoded","digest","decodeJwtPayload","token","parts","payload","PKCE_KEY","ss","pkceStore","tx","raw","TOKEN_KEY","TokenCache","backend","tokens","DEFAULT_SCOPE","REFRESH_BUFFER_SECONDS","AuthActionClient","config","TokenCache","options","state","generateRandom","codeVerifier","codeChallenge","generateCodeChallenge","pkceStore","params","err","resolve","reject","listener","event","url","callbackUrl","appState","clean","tokens","decodeJwtPayload","returnTo","callback","code","returnedState","error","errorDescription","tx","redirectUri","res","data","refreshed","expiresIn","cached","user","profile","key","next","cb","import_jsx_runtime","AuthActionContext","AuthActionProvider","children","onRedirectCallback","config","client","AuthActionClient","state","setState","params","appState","useAuthActionContext","ctx","import_jsx_runtime","AuthActionNextProvider","props","AuthActionProvider","import_react","useAuthAction","client","state","useAuthActionContext","loginWithRedirect","options","loginWithPopup","handleRedirectCallback","url","getAccessToken","logout","removeUser","useUser","useAuthLoading","useIsAuthenticated","useGetAccessToken","hasAuthParams","url","search","params"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import{a as t,b as e,c as i,d as A,e as u,f as p}from"../chunk-5RMD5KG6.mjs";import{d as r}from"../chunk-DHV7MPBE.mjs";import{jsx as n}from"react/jsx-runtime";function c(o){return n(t,{...o})}export{c as AuthActionNextProvider,r as hasAuthParams,e as useAuthAction,A as useAuthLoading,p as useGetAccessToken,u as useIsAuthenticated,i as useUser};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/nextjs/provider.tsx"],"sourcesContent":["'use client';\n\n// Marks all exports in this module as client components for Next.js App Router.\n// For Pages Router the 'use client' directive is a harmless string literal.\n//\n// SSR note: only cacheLocation: 'memory' (the default) is safe for server-side\n// rendering. 'localstorage' and 'sessionstorage' require browser APIs and will\n// throw during SSR if passed explicitly.\n\nimport React from 'react';\nimport { AuthActionProvider } from '../react/context';\nimport type { AuthActionProviderProps } from '../react/context';\n\nexport type { AuthActionProviderProps };\n\n/**\n * Drop-in replacement for `<AuthActionProvider>` in Next.js projects.\n * The `'use client'` directive at the top of this file marks it as a client\n * boundary for Next.js App Router, so child components can freely use\n * `useAuthAction()` and other hooks without adding their own `'use client'`.\n *\n * @example\n * ```tsx\n * // app/layout.tsx (App Router)\n * import { AuthActionNextProvider } from '@authaction/web-sdk/nextjs';\n *\n * export default function RootLayout({ children }: { children: React.ReactNode }) {\n * return (\n * <html>\n * <body>\n * <AuthActionNextProvider\n * domain=\"myapp.eu.authaction.com\"\n * clientId=\"your-client-id\"\n * redirectUri=\"http://localhost:3000/callback\"\n * >\n * {children}\n * </AuthActionNextProvider>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\nexport function AuthActionNextProvider(props: AuthActionProviderProps) {\n return <AuthActionProvider {...props} />;\n}\n"],"mappings":"oIA4CS,cAAAA,MAAA,oBADF,SAASC,EAAuBC,EAAgC,CACrE,OAAOF,EAACG,EAAA,CAAoB,GAAGD,EAAO,CACxC","names":["jsx","AuthActionNextProvider","props","AuthActionProvider"]}
|