@chemmangat/msal-next 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,2 +1,1595 @@
1
- import {MsalProvider,useMsal,useAccount}from'@azure/msal-react';export{useAccount,useIsAuthenticated,useMsal}from'@azure/msal-react';import {LogLevel,PublicClientApplication,EventType,InteractionStatus}from'@azure/msal-browser';import {useState,useRef,useEffect,useMemo,useCallback,Component}from'react';import {jsx,Fragment,jsxs}from'react/jsx-runtime';import {NextResponse}from'next/server';function B(r,e){try{let t=JSON.parse(r);return e(t)?t:(console.warn("[Validation] JSON validation failed"),null)}catch(t){return console.error("[Validation] JSON parse error:",t),null}}function _(r){return typeof r=="object"&&r!==null&&typeof r.homeAccountId=="string"&&r.homeAccountId.length>0&&typeof r.username=="string"&&r.username.length>0&&(r.name===void 0||typeof r.name=="string")}function S(r){return r instanceof Error?r.message.replace(/[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/g,"[TOKEN_REDACTED]").replace(/[a-f0-9]{32,}/gi,"[SECRET_REDACTED]").replace(/Bearer\s+[^\s]+/gi,"Bearer [REDACTED]"):"An unexpected error occurred"}function $(r,e){try{let t=new URL(r);return e.some(o=>{let n=new URL(o);return t.origin===n.origin})}catch{return false}}function ie(r){return /^[a-zA-Z0-9._-]+$/.test(r)}function Ae(r){return Array.isArray(r)&&r.every(ie)}function H(r){if(r.msalConfig)return r.msalConfig;let{clientId:e,tenantId:t,authorityType:o="common",redirectUri:n,postLogoutRedirectUri:a,cacheLocation:f="sessionStorage",storeAuthStateInCookie:c=false,navigateToLoginRequestUrl:u=true,enableLogging:i=false,loggerCallback:m,allowedRedirectUris:d}=r;if(!e)throw new Error("@chemmangat/msal-next: clientId is required");let p=()=>{if(o==="tenant"){if(!t)throw new Error('@chemmangat/msal-next: tenantId is required when authorityType is "tenant"');return `https://login.microsoftonline.com/${t}`}return `https://login.microsoftonline.com/${o}`},g=typeof window<"u"?window.location.origin:"http://localhost:3000",y=n||g;if(d&&d.length>0){if(!$(y,d))throw new Error(`@chemmangat/msal-next: redirectUri "${y}" is not in the allowed list`);let s=a||y;if(!$(s,d))throw new Error(`@chemmangat/msal-next: postLogoutRedirectUri "${s}" is not in the allowed list`)}return {auth:{clientId:e,authority:p(),redirectUri:y,postLogoutRedirectUri:a||y,navigateToLoginRequestUrl:u},cache:{cacheLocation:f,storeAuthStateInCookie:c},system:{loggerOptions:{loggerCallback:m||((s,l,b)=>{if(!(b||!i))switch(s){case LogLevel.Error:console.error("[MSAL]",l);break;case LogLevel.Warning:console.warn("[MSAL]",l);break;case LogLevel.Info:console.info("[MSAL]",l);break;case LogLevel.Verbose:console.debug("[MSAL]",l);break}}),logLevel:i?LogLevel.Verbose:LogLevel.Error}}}}var ae=null;function Re(){return ae}function V({children:r,loadingComponent:e,onInitialized:t,...o}){let[n,a]=useState(null),f=useRef(null);return useEffect(()=>{if(typeof window>"u"||f.current)return;(async()=>{try{let u=H(o),i=new PublicClientApplication(u);await i.initialize();try{let p=await i.handleRedirectPromise();p&&(o.enableLogging&&console.log("[MSAL] Redirect authentication successful"),p.account&&i.setActiveAccount(p.account));}catch(p){p?.errorCode==="no_token_request_cache_error"?o.enableLogging&&console.log("[MSAL] No pending redirect found (this is normal)"):p?.errorCode==="user_cancelled"?o.enableLogging&&console.log("[MSAL] User cancelled authentication"):console.error("[MSAL] Redirect handling error:",p);}let m=i.getAllAccounts();m.length>0&&!i.getActiveAccount()&&i.setActiveAccount(m[0]);let d=o.enableLogging||!1;i.addEventCallback(p=>{if(p.eventType===EventType.LOGIN_SUCCESS){let g=p.payload;g?.account&&i.setActiveAccount(g.account),d&&console.log("[MSAL] Login successful:",g.account?.username);}if(p.eventType===EventType.LOGIN_FAILURE&&console.error("[MSAL] Login failed:",p.error),p.eventType===EventType.LOGOUT_SUCCESS&&(i.setActiveAccount(null),d&&console.log("[MSAL] Logout successful")),p.eventType===EventType.ACQUIRE_TOKEN_SUCCESS){let g=p.payload;g?.account&&!i.getActiveAccount()&&i.setActiveAccount(g.account);}p.eventType===EventType.ACQUIRE_TOKEN_FAILURE&&d&&console.error("[MSAL] Token acquisition failed:",p.error);}),f.current=i,ae=i,a(i),t&&t(i);}catch(u){throw console.error("[MSAL] Initialization failed:",u),u}})();},[]),typeof window>"u"?jsx(Fragment,{children:e||jsx("div",{children:"Loading authentication..."})}):n?jsx(MsalProvider,{instance:n,children:r}):jsx(Fragment,{children:e||jsx("div",{children:"Loading authentication..."})})}var j=new Map;function A(r=["User.Read"]){let{instance:e,accounts:t,inProgress:o}=useMsal(),n=useAccount(t[0]||null),a=useRef(false),f=useMemo(()=>t.length>0,[t]),c=useCallback(async(s=r)=>{if(o!==InteractionStatus.None){console.warn("[MSAL] Interaction already in progress");return}try{let l={scopes:s,prompt:"select_account"},b=await e.loginPopup(l);b?.account&&e.setActiveAccount(b.account);}catch(l){if(l?.errorCode==="user_cancelled"){console.log("[MSAL] User cancelled login");return}throw console.error("[MSAL] Login popup failed:",l),l}},[e,r,o]),u=useCallback(async(s=r)=>{if(o!==InteractionStatus.None){console.warn("[MSAL] Interaction already in progress");return}try{let l={scopes:s,prompt:"select_account"};await e.loginRedirect(l);}catch(l){if(l?.errorCode==="user_cancelled"){console.log("[MSAL] User cancelled login");return}throw console.error("[MSAL] Login redirect failed:",l),l}},[e,r,o]),i=useCallback(async()=>{try{await e.logoutPopup({account:n||void 0});}catch(s){throw console.error("[MSAL] Logout popup failed:",s),s}},[e,n]),m=useCallback(async()=>{try{await e.logoutRedirect({account:n||void 0});}catch(s){throw console.error("[MSAL] Logout redirect failed:",s),s}},[e,n]),d=useCallback(async(s=r)=>{if(!n)throw new Error("[MSAL] No active account. Please login first.");try{let l={scopes:s,account:n,forceRefresh:!1};return (await e.acquireTokenSilent(l)).accessToken}catch(l){throw console.error("[MSAL] Silent token acquisition failed:",l),l}},[e,n,r]),p=useCallback(async(s=r)=>{if(!n)throw new Error("[MSAL] No active account. Please login first.");if(a.current)throw new Error("[MSAL] Popup already in progress. Please wait.");try{a.current=!0;let l={scopes:s,account:n};return (await e.acquireTokenPopup(l)).accessToken}catch(l){throw console.error("[MSAL] Token popup acquisition failed:",l),l}finally{a.current=false;}},[e,n,r]),g=useCallback(async(s=r)=>{if(!n)throw new Error("[MSAL] No active account. Please login first.");try{let l={scopes:s,account:n};await e.acquireTokenRedirect(l);}catch(l){throw console.error("[MSAL] Token redirect acquisition failed:",l),l}},[e,n,r]),y=useCallback(async(s=r)=>{let l=`${n?.homeAccountId||"anonymous"}-${s.sort().join(",")}`,b=j.get(l);if(b)return b;let w=(async()=>{try{return await d(s)}catch{return console.warn("[MSAL] Silent token acquisition failed, falling back to popup"),await p(s)}finally{j.delete(l);}})();return j.set(l,w),w},[d,p,r,n]),h=useCallback(async()=>{e.setActiveAccount(null),await e.clearCache();},[e]);return {account:n,accounts:t,isAuthenticated:f,inProgress:o!==InteractionStatus.None,loginPopup:c,loginRedirect:u,logoutPopup:i,logoutRedirect:m,acquireToken:y,acquireTokenSilent:d,acquireTokenPopup:p,acquireTokenRedirect:g,clearSession:h}}function Le({text:r="Sign in with Microsoft",variant:e="dark",size:t="medium",useRedirect:o=false,scopes:n,className:a="",style:f,onSuccess:c,onError:u}){let{loginPopup:i,loginRedirect:m,inProgress:d}=A(),p=async()=>{try{o?await m(n):await i(n),c?.();}catch(s){u?.(s);}},g={small:{padding:"8px 16px",fontSize:"14px",height:"36px"},medium:{padding:"10px 20px",fontSize:"15px",height:"41px"},large:{padding:"12px 24px",fontSize:"16px",height:"48px"}},h={display:"inline-flex",alignItems:"center",justifyContent:"center",gap:"12px",fontFamily:'"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',fontWeight:600,borderRadius:"2px",cursor:d?"not-allowed":"pointer",transition:"all 0.2s ease",opacity:d?.6:1,...{dark:{backgroundColor:"#2F2F2F",color:"#FFFFFF",border:"1px solid #8C8C8C"},light:{backgroundColor:"#FFFFFF",color:"#5E5E5E",border:"1px solid #8C8C8C"}}[e],...g[t],...f};return jsxs("button",{onClick:p,disabled:d,className:a,style:h,"aria-label":r,children:[jsx(Me,{}),jsx("span",{children:r})]})}function Me(){return jsxs("svg",{width:"21",height:"21",viewBox:"0 0 21 21",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[jsx("rect",{width:"10",height:"10",fill:"#F25022"}),jsx("rect",{x:"11",width:"10",height:"10",fill:"#7FBA00"}),jsx("rect",{y:"11",width:"10",height:"10",fill:"#00A4EF"}),jsx("rect",{x:"11",y:"11",width:"10",height:"10",fill:"#FFB900"})]})}function Ue({text:r="Sign out",variant:e="dark",size:t="medium",useRedirect:o=false,className:n="",style:a,onSuccess:f,onError:c}){let{logoutPopup:u,logoutRedirect:i,inProgress:m}=A(),d=async()=>{try{o?await i():await u(),f?.();}catch(h){c?.(h);}},p={small:{padding:"8px 16px",fontSize:"14px",height:"36px"},medium:{padding:"10px 20px",fontSize:"15px",height:"41px"},large:{padding:"12px 24px",fontSize:"16px",height:"48px"}},y={display:"inline-flex",alignItems:"center",justifyContent:"center",gap:"12px",fontFamily:'"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',fontWeight:600,borderRadius:"2px",cursor:m?"not-allowed":"pointer",transition:"all 0.2s ease",opacity:m?.6:1,...{dark:{backgroundColor:"#2F2F2F",color:"#FFFFFF",border:"1px solid #8C8C8C"},light:{backgroundColor:"#FFFFFF",color:"#5E5E5E",border:"1px solid #8C8C8C"}}[e],...p[t],...a};return jsxs("button",{onClick:d,disabled:m,className:n,style:y,"aria-label":r,children:[jsx(ke,{}),jsx("span",{children:r})]})}function ke(){return jsxs("svg",{width:"21",height:"21",viewBox:"0 0 21 21",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[jsx("rect",{width:"10",height:"10",fill:"#F25022"}),jsx("rect",{x:"11",width:"10",height:"10",fill:"#7FBA00"}),jsx("rect",{y:"11",width:"10",height:"10",fill:"#00A4EF"}),jsx("rect",{x:"11",y:"11",width:"10",height:"10",fill:"#FFB900"})]})}function k(){let{acquireToken:r}=A(),e=useCallback(async(c,u={})=>{let{scopes:i=["User.Read"],version:m="v1.0",debug:d=false,...p}=u;try{let g=await r(i),y=`https://graph.microsoft.com/${m}`,h=c.startsWith("http")?c:`${y}${c.startsWith("/")?c:`/${c}`}`;d&&console.log("[GraphAPI] Request:",{url:h,method:p.method||"GET"});let s=await fetch(h,{...p,headers:{Authorization:`Bearer ${g}`,"Content-Type":"application/json",...p.headers}});if(!s.ok){let b=await s.text(),w=`Graph API error (${s.status}): ${b}`;throw new Error(w)}if(s.status===204||s.headers.get("content-length")==="0")return null;let l=await s.json();return d&&console.log("[GraphAPI] Response:",l),l}catch(g){let y=S(g);throw console.error("[GraphAPI] Request failed:",y),new Error(y)}},[r]),t=useCallback((c,u={})=>e(c,{...u,method:"GET"}),[e]),o=useCallback((c,u,i={})=>e(c,{...i,method:"POST",body:u?JSON.stringify(u):void 0}),[e]),n=useCallback((c,u,i={})=>e(c,{...i,method:"PUT",body:u?JSON.stringify(u):void 0}),[e]),a=useCallback((c,u,i={})=>e(c,{...i,method:"PATCH",body:u?JSON.stringify(u):void 0}),[e]),f=useCallback((c,u={})=>e(c,{...u,method:"DELETE"}),[e]);return {get:t,post:o,put:n,patch:a,delete:f,request:e}}var P=new Map,Fe=300*1e3,de=100;function Ne(){if(P.size>de){let r=Array.from(P.entries());r.sort((t,o)=>t[1].timestamp-o[1].timestamp),r.slice(0,P.size-de).forEach(([t])=>{let o=P.get(t);o?.data.photo&&URL.revokeObjectURL(o.data.photo),P.delete(t);});}}function K(){let{isAuthenticated:r,account:e}=A(),t=k(),[o,n]=useState(null),[a,f]=useState(false),[c,u]=useState(null),i=useCallback(async()=>{if(!r||!e){n(null);return}let d=e.homeAccountId,p=P.get(d);if(p&&Date.now()-p.timestamp<Fe){n(p.data);return}f(true),u(null);try{let g=await t.get("/me",{scopes:["User.Read"]}),y;try{let s=await t.get("/me/photo/$value",{scopes:["User.Read"],headers:{"Content-Type":"image/jpeg"}});s&&(y=URL.createObjectURL(s));}catch{console.debug("[UserProfile] Photo not available");}let h={id:g.id,displayName:g.displayName,givenName:g.givenName,surname:g.surname,userPrincipalName:g.userPrincipalName,mail:g.mail,jobTitle:g.jobTitle,officeLocation:g.officeLocation,mobilePhone:g.mobilePhone,businessPhones:g.businessPhones,photo:y};P.set(d,{data:h,timestamp:Date.now()}),Ne(),n(h);}catch(g){let h=S(g),s=new Error(h);u(s),console.error("[UserProfile] Failed to fetch profile:",h);}finally{f(false);}},[r,e,t]),m=useCallback(()=>{if(e){let d=P.get(e.homeAccountId);d?.data.photo&&URL.revokeObjectURL(d.data.photo),P.delete(e.homeAccountId);}o?.photo&&URL.revokeObjectURL(o.photo),n(null);},[e,o]);return useEffect(()=>(i(),()=>{o?.photo&&URL.revokeObjectURL(o.photo);}),[i]),useEffect(()=>()=>{o?.photo&&URL.revokeObjectURL(o.photo);},[o?.photo]),{profile:o,loading:a,error:c,refetch:i,clearCache:m}}function Oe({size:r=40,className:e="",style:t,showTooltip:o=true,fallbackImage:n}){let{profile:a,loading:f}=K(),[c,u]=useState(null),[i,m]=useState(false);useEffect(()=>{a?.photo&&u(a.photo);},[a?.photo]);let d=()=>{if(!a)return "?";let{givenName:y,surname:h,displayName:s}=a;if(y&&h)return `${y[0]}${h[0]}`.toUpperCase();if(s){let l=s.split(" ");return l.length>=2?`${l[0][0]}${l[l.length-1][0]}`.toUpperCase():s.substring(0,2).toUpperCase()}return "?"},p={width:`${r}px`,height:`${r}px`,borderRadius:"50%",display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:`${r*.4}px`,fontWeight:600,fontFamily:'"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',backgroundColor:"#0078D4",color:"#FFFFFF",overflow:"hidden",userSelect:"none",...t},g=a?.displayName||"User";return f?jsx("div",{className:e,style:{...p,backgroundColor:"#E1E1E1"},"aria-label":"Loading user avatar",children:jsx("span",{style:{fontSize:`${r*.3}px`},children:"..."})}):c&&!i?jsx("div",{className:e,style:p,title:o?g:void 0,"aria-label":`${g} avatar`,children:jsx("img",{src:c,alt:g,style:{width:"100%",height:"100%",objectFit:"cover"},onError:()=>{m(true),n&&u(n);}})}):jsx("div",{className:e,style:p,title:o?g:void 0,"aria-label":`${g} avatar`,children:d()})}function De({className:r="",style:e,showDetails:t=false,renderLoading:o,renderAuthenticated:n,renderUnauthenticated:a}){let{isAuthenticated:f,inProgress:c,account:u}=A(),i={display:"inline-flex",alignItems:"center",gap:"8px",padding:"8px 12px",borderRadius:"4px",fontFamily:'"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',fontSize:"14px",fontWeight:500,...e};if(c)return o?jsx(Fragment,{children:o()}):jsxs("div",{className:r,style:{...i,backgroundColor:"#FFF4CE",color:"#8A6D3B"},role:"status","aria-live":"polite",children:[jsx(Z,{color:"#FFA500"}),jsx("span",{children:"Loading..."})]});if(f){let m=u?.username||u?.name||"User";return n?jsx(Fragment,{children:n(m)}):jsxs("div",{className:r,style:{...i,backgroundColor:"#D4EDDA",color:"#155724"},role:"status","aria-live":"polite",children:[jsx(Z,{color:"#28A745"}),jsx("span",{children:t?`Authenticated as ${m}`:"Authenticated"})]})}return a?jsx(Fragment,{children:a()}):jsxs("div",{className:r,style:{...i,backgroundColor:"#F8D7DA",color:"#721C24"},role:"status","aria-live":"polite",children:[jsx(Z,{color:"#DC3545"}),jsx("span",{children:"Not authenticated"})]})}function Z({color:r}){return jsx("svg",{width:"8",height:"8",viewBox:"0 0 8 8",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:jsx("circle",{cx:"4",cy:"4",r:"4",fill:r})})}function ee({children:r,loadingComponent:e,fallbackComponent:t,useRedirect:o=true,scopes:n,onAuthRequired:a}){let{isAuthenticated:f,inProgress:c,loginRedirect:u,loginPopup:i}=A();return useEffect(()=>{!f&&!c&&(a?.(),(async()=>{try{o?await u(n):await i(n);}catch(d){console.error("[AuthGuard] Authentication failed:",d);}})());},[f,c,o,n,u,i,a]),c?jsx(Fragment,{children:e||jsx("div",{children:"Authenticating..."})}):f?jsx(Fragment,{children:r}):jsx(Fragment,{children:t||jsx("div",{children:"Redirecting to login..."})})}var te=class extends Component{constructor(t){super(t);this.reset=()=>{this.setState({hasError:false,error:null});};this.state={hasError:false,error:null};}static getDerivedStateFromError(t){return {hasError:true,error:t}}componentDidCatch(t,o){let{onError:n,debug:a}=this.props;a&&(console.error("[ErrorBoundary] Caught error:",t),console.error("[ErrorBoundary] Error info:",o)),n?.(t,o);}render(){let{hasError:t,error:o}=this.state,{children:n,fallback:a}=this.props;return t&&o?a?a(o,this.reset):jsxs("div",{style:{padding:"20px",margin:"20px",border:"1px solid #DC3545",borderRadius:"4px",backgroundColor:"#F8D7DA",color:"#721C24",fontFamily:'"Segoe UI", Tahoma, Geneva, Verdana, sans-serif'},children:[jsx("h2",{style:{margin:"0 0 10px 0",fontSize:"18px"},children:"Authentication Error"}),jsx("p",{style:{margin:"0 0 10px 0"},children:o.message}),jsx("button",{onClick:this.reset,style:{padding:"8px 16px",backgroundColor:"#DC3545",color:"#FFFFFF",border:"none",borderRadius:"4px",cursor:"pointer",fontSize:"14px",fontWeight:600},children:"Try Again"})]}):n}};function ze({children:r,...e}){return jsx(V,{...e,children:r})}var R=new Map,He=300*1e3,fe=100;function Ve(r){r?R.delete(r):R.clear();}function We(){if(R.size>fe){let r=Array.from(R.entries());r.sort((t,o)=>t[1].timestamp-o[1].timestamp),r.slice(0,R.size-fe).forEach(([t])=>R.delete(t));}}function je(){let{isAuthenticated:r,account:e}=A(),t=k(),[o,n]=useState([]),[a,f]=useState([]),[c,u]=useState(false),[i,m]=useState(null),d=useCallback(async()=>{if(!r||!e){n([]),f([]);return}let s=e.homeAccountId,l=R.get(s);if(l&&Date.now()-l.timestamp<He){n(l.roles),f(l.groups);return}u(true),m(null);try{let w=e.idTokenClaims?.roles||[],G=(await t.get("/me/memberOf",{scopes:["User.Read","Directory.Read.All"]})).value.map(ne=>ne.id);R.set(s,{roles:w,groups:G,timestamp:Date.now()}),We(),n(w),f(G);}catch(b){let D=S(b),G=new Error(D);m(G),console.error("[Roles] Failed to fetch roles/groups:",D);let ye=e.idTokenClaims?.roles||[];n(ye);}finally{u(false);}},[r,e,t]),p=useCallback(s=>o.includes(s),[o]),g=useCallback(s=>a.includes(s),[a]),y=useCallback(s=>s.some(l=>o.includes(l)),[o]),h=useCallback(s=>s.every(l=>o.includes(l)),[o]);return useEffect(()=>(d(),()=>{e&&Ve(e.homeAccountId);}),[d,e]),{roles:o,groups:a,loading:c,error:i,hasRole:p,hasGroup:g,hasAnyRole:y,hasAllRoles:h,refetch:d}}function Je(r,e={}){let{displayName:t,...o}=e,n=a=>jsx(ee,{...o,children:jsx(r,{...a})});return n.displayName=t||`withAuth(${r.displayName||r.name||"Component"})`,n}async function me(r,e={}){let{maxRetries:t=3,initialDelay:o=1e3,maxDelay:n=1e4,backoffMultiplier:a=2,debug:f=false}=e,c,u=o;for(let i=0;i<=t;i++)try{return f&&i>0&&console.log(`[TokenRetry] Attempt ${i+1}/${t+1}`),await r()}catch(m){if(c=m,i===t){f&&console.error("[TokenRetry] All retry attempts failed");break}if(!Ke(m))throw f&&console.log("[TokenRetry] Non-retryable error, aborting"),m;f&&console.warn(`[TokenRetry] Attempt ${i+1} failed, retrying in ${u}ms...`),await Ze(u),u=Math.min(u*a,n);}throw c}function Ke(r){let e=r.message.toLowerCase();return !!(e.includes("network")||e.includes("timeout")||e.includes("fetch")||e.includes("connection")||e.includes("500")||e.includes("502")||e.includes("503")||e.includes("429")||e.includes("rate limit")||e.includes("token")&&e.includes("expired"))}function Ze(r){return new Promise(e=>setTimeout(e,r))}function Qe(r,e={}){return (...t)=>me(()=>r(...t),e)}var z=class{constructor(e={}){this.logHistory=[];this.performanceTimings=new Map;this.config={enabled:e.enabled??false,prefix:e.prefix??"[MSAL-Next]",showTimestamp:e.showTimestamp??true,level:e.level??"info",enablePerformance:e.enablePerformance??false,enableNetworkLogs:e.enableNetworkLogs??false,maxHistorySize:e.maxHistorySize??100};}shouldLog(e){if(!this.config.enabled)return false;let t=["error","warn","info","debug"],o=t.indexOf(this.config.level);return t.indexOf(e)<=o}formatMessage(e,t,o){let n=this.config.showTimestamp?`[${new Date().toISOString()}]`:"",a=this.config.prefix,f=`[${e.toUpperCase()}]`,c=`${n} ${a} ${f} ${t}`;return o!==void 0&&(c+=`
2
- `+JSON.stringify(o,null,2)),c}addToHistory(e,t,o){this.logHistory.length>=this.config.maxHistorySize&&this.logHistory.shift(),this.logHistory.push({timestamp:Date.now(),level:e,message:t,data:o});}error(e,t){this.shouldLog("error")&&(console.error(this.formatMessage("error",e,t)),this.addToHistory("error",e,t));}warn(e,t){this.shouldLog("warn")&&(console.warn(this.formatMessage("warn",e,t)),this.addToHistory("warn",e,t));}info(e,t){this.shouldLog("info")&&(console.info(this.formatMessage("info",e,t)),this.addToHistory("info",e,t));}debug(e,t){this.shouldLog("debug")&&(console.debug(this.formatMessage("debug",e,t)),this.addToHistory("debug",e,t));}group(e){this.config.enabled&&console.group(`${this.config.prefix} ${e}`);}groupEnd(){this.config.enabled&&console.groupEnd();}startTiming(e){this.config.enablePerformance&&(this.performanceTimings.set(e,{operation:e,startTime:performance.now()}),this.debug(`\u23F1\uFE0F Started: ${e}`));}endTiming(e){if(this.config.enablePerformance){let t=this.performanceTimings.get(e);if(t)return t.endTime=performance.now(),t.duration=t.endTime-t.startTime,this.info(`\u23F1\uFE0F Completed: ${e} (${t.duration.toFixed(2)}ms)`),t.duration}}logRequest(e,t,o){this.config.enableNetworkLogs&&this.debug(`\u{1F310} ${e} ${t}`,o);}logResponse(e,t,o,n){if(this.config.enableNetworkLogs){let a=o>=200&&o<300?"\u2705":"\u274C";this.debug(`${a} ${e} ${t} - ${o}`,n);}}getHistory(){return [...this.logHistory]}getPerformanceTimings(){return Array.from(this.performanceTimings.values())}clearHistory(){this.logHistory=[];}clearTimings(){this.performanceTimings.clear();}exportLogs(){return JSON.stringify({config:this.config,history:this.logHistory,performanceTimings:Array.from(this.performanceTimings.values()),exportedAt:new Date().toISOString()},null,2)}downloadLogs(e="msal-next-debug-logs.json"){if(typeof window>"u")return;let t=this.exportLogs(),o=new Blob([t],{type:"application/json"}),n=URL.createObjectURL(o),a=document.createElement("a");a.href=n,a.download=e,a.click(),URL.revokeObjectURL(n);}setEnabled(e){this.config.enabled=e;}setLevel(e){e&&(this.config.level=e);}},O=null;function Xe(r){return O?r&&(r.enabled!==void 0&&O.setEnabled(r.enabled),r.level&&O.setLevel(r.level)):O=new z(r),O}function Ye(r,e){return new z({...e,prefix:`[MSAL-Next:${r}]`})}function er(r={}){let{protectedRoutes:e=[],publicOnlyRoutes:t=[],loginPath:o="/login",redirectAfterLogin:n="/",sessionCookie:a="msal.account",isAuthenticated:f,debug:c=false}=r;return async function(i){let{pathname:m}=i.nextUrl;c&&console.log("[AuthMiddleware] Processing:",m);let d=false;f?d=await f(i):d=!!i.cookies.get(a)?.value,c&&console.log("[AuthMiddleware] Authenticated:",d);let p=e.some(h=>m.startsWith(h)),g=t.some(h=>m.startsWith(h));if(p&&!d){c&&console.log("[AuthMiddleware] Redirecting to login");let h=i.nextUrl.clone();return h.pathname=o,h.searchParams.set("returnUrl",m),NextResponse.redirect(h)}if(g&&d){c&&console.log("[AuthMiddleware] Redirecting to home");let h=i.nextUrl.searchParams.get("returnUrl"),s=i.nextUrl.clone();return s.pathname=h||n,s.searchParams.delete("returnUrl"),NextResponse.redirect(s)}let y=NextResponse.next();if(d){y.headers.set("x-msal-authenticated","true");try{let h=i.cookies.get(a);if(h?.value){let s=B(h.value,_);s?.username&&y.headers.set("x-msal-username",s.username);}}catch{c&&console.warn("[AuthMiddleware] Failed to parse session data");}}return y}}export{ee as AuthGuard,De as AuthStatus,te as ErrorBoundary,Le as MicrosoftSignInButton,V as MsalAuthProvider,ze as Providers,Ue as SignOutButton,Oe as UserAvatar,er as createAuthMiddleware,H as createMsalConfig,Qe as createRetryWrapper,Ye as createScopedLogger,Xe as getDebugLogger,Re as getMsalInstance,_ as isValidAccountData,$ as isValidRedirectUri,ie as isValidScope,me as retryWithBackoff,B as safeJsonParse,S as sanitizeError,k as useGraphApi,A as useMsalAuth,je as useRoles,K as useUserProfile,Ae as validateScopes,Je as withAuth};
1
+ import {
2
+ isValidAccountData,
3
+ isValidRedirectUri,
4
+ isValidScope,
5
+ safeJsonParse,
6
+ sanitizeError,
7
+ validateScopes
8
+ } from "./chunk-AD43IVG7.mjs";
9
+
10
+ // src/components/MsalAuthProvider.tsx
11
+ import { MsalProvider } from "@azure/msal-react";
12
+ import { PublicClientApplication, EventType } from "@azure/msal-browser";
13
+ import { useEffect, useState, useRef } from "react";
14
+
15
+ // src/utils/createMsalConfig.ts
16
+ import { LogLevel } from "@azure/msal-browser";
17
+ function createMsalConfig(config) {
18
+ if (config.msalConfig) {
19
+ return config.msalConfig;
20
+ }
21
+ const {
22
+ clientId,
23
+ tenantId,
24
+ authorityType = "common",
25
+ redirectUri,
26
+ postLogoutRedirectUri,
27
+ cacheLocation = "sessionStorage",
28
+ storeAuthStateInCookie = false,
29
+ navigateToLoginRequestUrl = true,
30
+ enableLogging = false,
31
+ loggerCallback,
32
+ allowedRedirectUris
33
+ } = config;
34
+ if (!clientId) {
35
+ throw new Error("@chemmangat/msal-next: clientId is required");
36
+ }
37
+ const getAuthority = () => {
38
+ if (authorityType === "tenant") {
39
+ if (!tenantId) {
40
+ throw new Error('@chemmangat/msal-next: tenantId is required when authorityType is "tenant"');
41
+ }
42
+ return `https://login.microsoftonline.com/${tenantId}`;
43
+ }
44
+ return `https://login.microsoftonline.com/${authorityType}`;
45
+ };
46
+ const defaultRedirectUri = typeof window !== "undefined" ? window.location.origin : "http://localhost:3000";
47
+ const finalRedirectUri = redirectUri || defaultRedirectUri;
48
+ if (allowedRedirectUris && allowedRedirectUris.length > 0) {
49
+ if (!isValidRedirectUri(finalRedirectUri, allowedRedirectUris)) {
50
+ throw new Error(
51
+ `@chemmangat/msal-next: redirectUri "${finalRedirectUri}" is not in the allowed list`
52
+ );
53
+ }
54
+ const finalPostLogoutUri = postLogoutRedirectUri || finalRedirectUri;
55
+ if (!isValidRedirectUri(finalPostLogoutUri, allowedRedirectUris)) {
56
+ throw new Error(
57
+ `@chemmangat/msal-next: postLogoutRedirectUri "${finalPostLogoutUri}" is not in the allowed list`
58
+ );
59
+ }
60
+ }
61
+ const msalConfig = {
62
+ auth: {
63
+ clientId,
64
+ authority: getAuthority(),
65
+ redirectUri: finalRedirectUri,
66
+ postLogoutRedirectUri: postLogoutRedirectUri || finalRedirectUri,
67
+ navigateToLoginRequestUrl
68
+ },
69
+ cache: {
70
+ cacheLocation,
71
+ storeAuthStateInCookie
72
+ },
73
+ system: {
74
+ loggerOptions: {
75
+ loggerCallback: loggerCallback || ((level, message, containsPii) => {
76
+ if (containsPii || !enableLogging) return;
77
+ switch (level) {
78
+ case LogLevel.Error:
79
+ console.error("[MSAL]", message);
80
+ break;
81
+ case LogLevel.Warning:
82
+ console.warn("[MSAL]", message);
83
+ break;
84
+ case LogLevel.Info:
85
+ console.info("[MSAL]", message);
86
+ break;
87
+ case LogLevel.Verbose:
88
+ console.debug("[MSAL]", message);
89
+ break;
90
+ }
91
+ }),
92
+ logLevel: enableLogging ? LogLevel.Verbose : LogLevel.Error
93
+ }
94
+ }
95
+ };
96
+ return msalConfig;
97
+ }
98
+
99
+ // src/components/MsalAuthProvider.tsx
100
+ import { Fragment, jsx } from "react/jsx-runtime";
101
+ var globalMsalInstance = null;
102
+ function getMsalInstance() {
103
+ return globalMsalInstance;
104
+ }
105
+ function MsalAuthProvider({ children, loadingComponent, onInitialized, ...config }) {
106
+ const [msalInstance, setMsalInstance] = useState(null);
107
+ const instanceRef = useRef(null);
108
+ useEffect(() => {
109
+ if (typeof window === "undefined") {
110
+ return;
111
+ }
112
+ if (instanceRef.current) {
113
+ return;
114
+ }
115
+ const initializeMsal = async () => {
116
+ try {
117
+ const msalConfig = createMsalConfig(config);
118
+ const instance = new PublicClientApplication(msalConfig);
119
+ await instance.initialize();
120
+ try {
121
+ const response = await instance.handleRedirectPromise();
122
+ if (response) {
123
+ if (config.enableLogging) {
124
+ console.log("[MSAL] Redirect authentication successful");
125
+ }
126
+ if (response.account) {
127
+ instance.setActiveAccount(response.account);
128
+ }
129
+ }
130
+ } catch (redirectError) {
131
+ if (redirectError?.errorCode === "no_token_request_cache_error") {
132
+ if (config.enableLogging) {
133
+ console.log("[MSAL] No pending redirect found (this is normal)");
134
+ }
135
+ } else if (redirectError?.errorCode === "user_cancelled") {
136
+ if (config.enableLogging) {
137
+ console.log("[MSAL] User cancelled authentication");
138
+ }
139
+ } else {
140
+ console.error("[MSAL] Redirect handling error:", redirectError);
141
+ }
142
+ }
143
+ const accounts = instance.getAllAccounts();
144
+ if (accounts.length > 0 && !instance.getActiveAccount()) {
145
+ instance.setActiveAccount(accounts[0]);
146
+ }
147
+ const enableLogging = config.enableLogging || false;
148
+ instance.addEventCallback((event) => {
149
+ if (event.eventType === EventType.LOGIN_SUCCESS) {
150
+ const payload = event.payload;
151
+ if (payload?.account) {
152
+ instance.setActiveAccount(payload.account);
153
+ }
154
+ if (enableLogging) {
155
+ console.log("[MSAL] Login successful:", payload.account?.username);
156
+ }
157
+ }
158
+ if (event.eventType === EventType.LOGIN_FAILURE) {
159
+ console.error("[MSAL] Login failed:", event.error);
160
+ }
161
+ if (event.eventType === EventType.LOGOUT_SUCCESS) {
162
+ instance.setActiveAccount(null);
163
+ if (enableLogging) {
164
+ console.log("[MSAL] Logout successful");
165
+ }
166
+ }
167
+ if (event.eventType === EventType.ACQUIRE_TOKEN_SUCCESS) {
168
+ const payload = event.payload;
169
+ if (payload?.account && !instance.getActiveAccount()) {
170
+ instance.setActiveAccount(payload.account);
171
+ }
172
+ }
173
+ if (event.eventType === EventType.ACQUIRE_TOKEN_FAILURE) {
174
+ if (enableLogging) {
175
+ console.error("[MSAL] Token acquisition failed:", event.error);
176
+ }
177
+ }
178
+ });
179
+ instanceRef.current = instance;
180
+ globalMsalInstance = instance;
181
+ setMsalInstance(instance);
182
+ if (onInitialized) {
183
+ onInitialized(instance);
184
+ }
185
+ } catch (error) {
186
+ console.error("[MSAL] Initialization failed:", error);
187
+ throw error;
188
+ }
189
+ };
190
+ initializeMsal();
191
+ }, []);
192
+ if (typeof window === "undefined") {
193
+ return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx("div", { children: "Loading authentication..." }) });
194
+ }
195
+ if (!msalInstance) {
196
+ return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx("div", { children: "Loading authentication..." }) });
197
+ }
198
+ return /* @__PURE__ */ jsx(MsalProvider, { instance: msalInstance, children });
199
+ }
200
+
201
+ // src/components/MSALProvider.tsx
202
+ import { jsx as jsx2 } from "react/jsx-runtime";
203
+ function MSALProvider({ children, ...props }) {
204
+ return /* @__PURE__ */ jsx2(MsalAuthProvider, { ...props, children });
205
+ }
206
+
207
+ // src/hooks/useMsalAuth.ts
208
+ import { useMsal, useAccount } from "@azure/msal-react";
209
+ import { InteractionStatus } from "@azure/msal-browser";
210
+ import { useCallback, useMemo, useRef as useRef2 } from "react";
211
+ var pendingTokenRequests = /* @__PURE__ */ new Map();
212
+ function useMsalAuth(defaultScopes = ["User.Read"]) {
213
+ const { instance, accounts, inProgress } = useMsal();
214
+ const account = useAccount(accounts[0] || null);
215
+ const popupInProgressRef = useRef2(false);
216
+ const isAuthenticated = useMemo(() => accounts.length > 0, [accounts]);
217
+ const loginPopup = useCallback(
218
+ async (scopes = defaultScopes) => {
219
+ if (inProgress !== InteractionStatus.None) {
220
+ console.warn("[MSAL] Interaction already in progress");
221
+ return;
222
+ }
223
+ try {
224
+ const request = {
225
+ scopes,
226
+ prompt: "select_account"
227
+ };
228
+ const response = await instance.loginPopup(request);
229
+ if (response?.account) {
230
+ instance.setActiveAccount(response.account);
231
+ }
232
+ } catch (error) {
233
+ if (error?.errorCode === "user_cancelled") {
234
+ console.log("[MSAL] User cancelled login");
235
+ return;
236
+ }
237
+ console.error("[MSAL] Login popup failed:", error);
238
+ throw error;
239
+ }
240
+ },
241
+ [instance, defaultScopes, inProgress]
242
+ );
243
+ const loginRedirect = useCallback(
244
+ async (scopes = defaultScopes) => {
245
+ if (inProgress !== InteractionStatus.None) {
246
+ console.warn("[MSAL] Interaction already in progress");
247
+ return;
248
+ }
249
+ try {
250
+ const request = {
251
+ scopes,
252
+ prompt: "select_account"
253
+ };
254
+ await instance.loginRedirect(request);
255
+ } catch (error) {
256
+ if (error?.errorCode === "user_cancelled") {
257
+ console.log("[MSAL] User cancelled login");
258
+ return;
259
+ }
260
+ console.error("[MSAL] Login redirect failed:", error);
261
+ throw error;
262
+ }
263
+ },
264
+ [instance, defaultScopes, inProgress]
265
+ );
266
+ const logoutPopup = useCallback(async () => {
267
+ try {
268
+ await instance.logoutPopup({
269
+ account: account || void 0
270
+ });
271
+ } catch (error) {
272
+ console.error("[MSAL] Logout popup failed:", error);
273
+ throw error;
274
+ }
275
+ }, [instance, account]);
276
+ const logoutRedirect = useCallback(async () => {
277
+ try {
278
+ await instance.logoutRedirect({
279
+ account: account || void 0
280
+ });
281
+ } catch (error) {
282
+ console.error("[MSAL] Logout redirect failed:", error);
283
+ throw error;
284
+ }
285
+ }, [instance, account]);
286
+ const acquireTokenSilent = useCallback(
287
+ async (scopes = defaultScopes) => {
288
+ if (!account) {
289
+ throw new Error("[MSAL] No active account. Please login first.");
290
+ }
291
+ try {
292
+ const request = {
293
+ scopes,
294
+ account,
295
+ forceRefresh: false
296
+ };
297
+ const response = await instance.acquireTokenSilent(request);
298
+ return response.accessToken;
299
+ } catch (error) {
300
+ console.error("[MSAL] Silent token acquisition failed:", error);
301
+ throw error;
302
+ }
303
+ },
304
+ [instance, account, defaultScopes]
305
+ );
306
+ const acquireTokenPopup = useCallback(
307
+ async (scopes = defaultScopes) => {
308
+ if (!account) {
309
+ throw new Error("[MSAL] No active account. Please login first.");
310
+ }
311
+ if (popupInProgressRef.current) {
312
+ throw new Error("[MSAL] Popup already in progress. Please wait.");
313
+ }
314
+ try {
315
+ popupInProgressRef.current = true;
316
+ const request = {
317
+ scopes,
318
+ account
319
+ };
320
+ const response = await instance.acquireTokenPopup(request);
321
+ return response.accessToken;
322
+ } catch (error) {
323
+ console.error("[MSAL] Token popup acquisition failed:", error);
324
+ throw error;
325
+ } finally {
326
+ popupInProgressRef.current = false;
327
+ }
328
+ },
329
+ [instance, account, defaultScopes]
330
+ );
331
+ const acquireTokenRedirect = useCallback(
332
+ async (scopes = defaultScopes) => {
333
+ if (!account) {
334
+ throw new Error("[MSAL] No active account. Please login first.");
335
+ }
336
+ try {
337
+ const request = {
338
+ scopes,
339
+ account
340
+ };
341
+ await instance.acquireTokenRedirect(request);
342
+ } catch (error) {
343
+ console.error("[MSAL] Token redirect acquisition failed:", error);
344
+ throw error;
345
+ }
346
+ },
347
+ [instance, account, defaultScopes]
348
+ );
349
+ const acquireToken = useCallback(
350
+ async (scopes = defaultScopes) => {
351
+ const requestKey = `${account?.homeAccountId || "anonymous"}-${scopes.sort().join(",")}`;
352
+ const pendingRequest = pendingTokenRequests.get(requestKey);
353
+ if (pendingRequest) {
354
+ return pendingRequest;
355
+ }
356
+ const tokenRequest = (async () => {
357
+ try {
358
+ return await acquireTokenSilent(scopes);
359
+ } catch (error) {
360
+ console.warn("[MSAL] Silent token acquisition failed, falling back to popup");
361
+ return await acquireTokenPopup(scopes);
362
+ } finally {
363
+ pendingTokenRequests.delete(requestKey);
364
+ }
365
+ })();
366
+ pendingTokenRequests.set(requestKey, tokenRequest);
367
+ return tokenRequest;
368
+ },
369
+ [acquireTokenSilent, acquireTokenPopup, defaultScopes, account]
370
+ );
371
+ const clearSession = useCallback(async () => {
372
+ instance.setActiveAccount(null);
373
+ await instance.clearCache();
374
+ }, [instance]);
375
+ return {
376
+ account,
377
+ accounts,
378
+ isAuthenticated,
379
+ inProgress: inProgress !== InteractionStatus.None,
380
+ loginPopup,
381
+ loginRedirect,
382
+ logoutPopup,
383
+ logoutRedirect,
384
+ acquireToken,
385
+ acquireTokenSilent,
386
+ acquireTokenPopup,
387
+ acquireTokenRedirect,
388
+ clearSession
389
+ };
390
+ }
391
+
392
+ // src/components/MicrosoftSignInButton.tsx
393
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
394
+ function MicrosoftSignInButton({
395
+ text = "Sign in with Microsoft",
396
+ variant = "dark",
397
+ size = "medium",
398
+ useRedirect = false,
399
+ scopes,
400
+ className = "",
401
+ style,
402
+ onSuccess,
403
+ onError
404
+ }) {
405
+ const { loginPopup, loginRedirect, inProgress } = useMsalAuth();
406
+ const handleClick = async () => {
407
+ try {
408
+ if (useRedirect) {
409
+ await loginRedirect(scopes);
410
+ } else {
411
+ await loginPopup(scopes);
412
+ }
413
+ onSuccess?.();
414
+ } catch (error) {
415
+ onError?.(error);
416
+ }
417
+ };
418
+ const sizeStyles = {
419
+ small: {
420
+ padding: "8px 16px",
421
+ fontSize: "14px",
422
+ height: "36px"
423
+ },
424
+ medium: {
425
+ padding: "10px 20px",
426
+ fontSize: "15px",
427
+ height: "41px"
428
+ },
429
+ large: {
430
+ padding: "12px 24px",
431
+ fontSize: "16px",
432
+ height: "48px"
433
+ }
434
+ };
435
+ const variantStyles = {
436
+ dark: {
437
+ backgroundColor: "#2F2F2F",
438
+ color: "#FFFFFF",
439
+ border: "1px solid #8C8C8C"
440
+ },
441
+ light: {
442
+ backgroundColor: "#FFFFFF",
443
+ color: "#5E5E5E",
444
+ border: "1px solid #8C8C8C"
445
+ }
446
+ };
447
+ const baseStyles = {
448
+ display: "inline-flex",
449
+ alignItems: "center",
450
+ justifyContent: "center",
451
+ gap: "12px",
452
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
453
+ fontWeight: 600,
454
+ borderRadius: "2px",
455
+ cursor: inProgress ? "not-allowed" : "pointer",
456
+ transition: "all 0.2s ease",
457
+ opacity: inProgress ? 0.6 : 1,
458
+ ...variantStyles[variant],
459
+ ...sizeStyles[size],
460
+ ...style
461
+ };
462
+ return /* @__PURE__ */ jsxs(
463
+ "button",
464
+ {
465
+ onClick: handleClick,
466
+ disabled: inProgress,
467
+ className,
468
+ style: baseStyles,
469
+ "aria-label": text,
470
+ children: [
471
+ /* @__PURE__ */ jsx3(MicrosoftLogo, {}),
472
+ /* @__PURE__ */ jsx3("span", { children: text })
473
+ ]
474
+ }
475
+ );
476
+ }
477
+ function MicrosoftLogo() {
478
+ return /* @__PURE__ */ jsxs("svg", { width: "21", height: "21", viewBox: "0 0 21 21", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
479
+ /* @__PURE__ */ jsx3("rect", { width: "10", height: "10", fill: "#F25022" }),
480
+ /* @__PURE__ */ jsx3("rect", { x: "11", width: "10", height: "10", fill: "#7FBA00" }),
481
+ /* @__PURE__ */ jsx3("rect", { y: "11", width: "10", height: "10", fill: "#00A4EF" }),
482
+ /* @__PURE__ */ jsx3("rect", { x: "11", y: "11", width: "10", height: "10", fill: "#FFB900" })
483
+ ] });
484
+ }
485
+
486
+ // src/components/SignOutButton.tsx
487
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
488
+ function SignOutButton({
489
+ text = "Sign out",
490
+ variant = "dark",
491
+ size = "medium",
492
+ useRedirect = false,
493
+ className = "",
494
+ style,
495
+ onSuccess,
496
+ onError
497
+ }) {
498
+ const { logoutPopup, logoutRedirect, inProgress } = useMsalAuth();
499
+ const handleClick = async () => {
500
+ try {
501
+ if (useRedirect) {
502
+ await logoutRedirect();
503
+ } else {
504
+ await logoutPopup();
505
+ }
506
+ onSuccess?.();
507
+ } catch (error) {
508
+ onError?.(error);
509
+ }
510
+ };
511
+ const sizeStyles = {
512
+ small: {
513
+ padding: "8px 16px",
514
+ fontSize: "14px",
515
+ height: "36px"
516
+ },
517
+ medium: {
518
+ padding: "10px 20px",
519
+ fontSize: "15px",
520
+ height: "41px"
521
+ },
522
+ large: {
523
+ padding: "12px 24px",
524
+ fontSize: "16px",
525
+ height: "48px"
526
+ }
527
+ };
528
+ const variantStyles = {
529
+ dark: {
530
+ backgroundColor: "#2F2F2F",
531
+ color: "#FFFFFF",
532
+ border: "1px solid #8C8C8C"
533
+ },
534
+ light: {
535
+ backgroundColor: "#FFFFFF",
536
+ color: "#5E5E5E",
537
+ border: "1px solid #8C8C8C"
538
+ }
539
+ };
540
+ const baseStyles = {
541
+ display: "inline-flex",
542
+ alignItems: "center",
543
+ justifyContent: "center",
544
+ gap: "12px",
545
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
546
+ fontWeight: 600,
547
+ borderRadius: "2px",
548
+ cursor: inProgress ? "not-allowed" : "pointer",
549
+ transition: "all 0.2s ease",
550
+ opacity: inProgress ? 0.6 : 1,
551
+ ...variantStyles[variant],
552
+ ...sizeStyles[size],
553
+ ...style
554
+ };
555
+ return /* @__PURE__ */ jsxs2(
556
+ "button",
557
+ {
558
+ onClick: handleClick,
559
+ disabled: inProgress,
560
+ className,
561
+ style: baseStyles,
562
+ "aria-label": text,
563
+ children: [
564
+ /* @__PURE__ */ jsx4(MicrosoftLogo2, {}),
565
+ /* @__PURE__ */ jsx4("span", { children: text })
566
+ ]
567
+ }
568
+ );
569
+ }
570
+ function MicrosoftLogo2() {
571
+ return /* @__PURE__ */ jsxs2("svg", { width: "21", height: "21", viewBox: "0 0 21 21", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
572
+ /* @__PURE__ */ jsx4("rect", { width: "10", height: "10", fill: "#F25022" }),
573
+ /* @__PURE__ */ jsx4("rect", { x: "11", width: "10", height: "10", fill: "#7FBA00" }),
574
+ /* @__PURE__ */ jsx4("rect", { y: "11", width: "10", height: "10", fill: "#00A4EF" }),
575
+ /* @__PURE__ */ jsx4("rect", { x: "11", y: "11", width: "10", height: "10", fill: "#FFB900" })
576
+ ] });
577
+ }
578
+
579
+ // src/components/UserAvatar.tsx
580
+ import { useEffect as useEffect3, useState as useState3 } from "react";
581
+
582
+ // src/hooks/useUserProfile.ts
583
+ import { useState as useState2, useEffect as useEffect2, useCallback as useCallback3 } from "react";
584
+
585
+ // src/hooks/useGraphApi.ts
586
+ import { useCallback as useCallback2 } from "react";
587
+ function useGraphApi() {
588
+ const { acquireToken } = useMsalAuth();
589
+ const request = useCallback2(
590
+ async (endpoint, options = {}) => {
591
+ const {
592
+ scopes = ["User.Read"],
593
+ version = "v1.0",
594
+ debug = false,
595
+ ...fetchOptions
596
+ } = options;
597
+ try {
598
+ const token = await acquireToken(scopes);
599
+ const baseUrl = `https://graph.microsoft.com/${version}`;
600
+ const url = endpoint.startsWith("http") ? endpoint : `${baseUrl}${endpoint.startsWith("/") ? endpoint : `/${endpoint}`}`;
601
+ if (debug) {
602
+ console.log("[GraphAPI] Request:", { url, method: fetchOptions.method || "GET" });
603
+ }
604
+ const response = await fetch(url, {
605
+ ...fetchOptions,
606
+ headers: {
607
+ "Authorization": `Bearer ${token}`,
608
+ "Content-Type": "application/json",
609
+ ...fetchOptions.headers
610
+ }
611
+ });
612
+ if (!response.ok) {
613
+ const errorText = await response.text();
614
+ const errorMessage = `Graph API error (${response.status}): ${errorText}`;
615
+ throw new Error(errorMessage);
616
+ }
617
+ if (response.status === 204 || response.headers.get("content-length") === "0") {
618
+ return null;
619
+ }
620
+ const data = await response.json();
621
+ if (debug) {
622
+ console.log("[GraphAPI] Response:", data);
623
+ }
624
+ return data;
625
+ } catch (error) {
626
+ const sanitizedMessage = sanitizeError(error);
627
+ console.error("[GraphAPI] Request failed:", sanitizedMessage);
628
+ throw new Error(sanitizedMessage);
629
+ }
630
+ },
631
+ [acquireToken]
632
+ );
633
+ const get = useCallback2(
634
+ (endpoint, options = {}) => {
635
+ return request(endpoint, { ...options, method: "GET" });
636
+ },
637
+ [request]
638
+ );
639
+ const post = useCallback2(
640
+ (endpoint, body, options = {}) => {
641
+ return request(endpoint, {
642
+ ...options,
643
+ method: "POST",
644
+ body: body ? JSON.stringify(body) : void 0
645
+ });
646
+ },
647
+ [request]
648
+ );
649
+ const put = useCallback2(
650
+ (endpoint, body, options = {}) => {
651
+ return request(endpoint, {
652
+ ...options,
653
+ method: "PUT",
654
+ body: body ? JSON.stringify(body) : void 0
655
+ });
656
+ },
657
+ [request]
658
+ );
659
+ const patch = useCallback2(
660
+ (endpoint, body, options = {}) => {
661
+ return request(endpoint, {
662
+ ...options,
663
+ method: "PATCH",
664
+ body: body ? JSON.stringify(body) : void 0
665
+ });
666
+ },
667
+ [request]
668
+ );
669
+ const deleteRequest = useCallback2(
670
+ (endpoint, options = {}) => {
671
+ return request(endpoint, { ...options, method: "DELETE" });
672
+ },
673
+ [request]
674
+ );
675
+ return {
676
+ get,
677
+ post,
678
+ put,
679
+ patch,
680
+ delete: deleteRequest,
681
+ request
682
+ };
683
+ }
684
+
685
+ // src/hooks/useUserProfile.ts
686
+ var profileCache = /* @__PURE__ */ new Map();
687
+ var CACHE_DURATION = 5 * 60 * 1e3;
688
+ var MAX_CACHE_SIZE = 100;
689
+ function enforceCacheLimit() {
690
+ if (profileCache.size > MAX_CACHE_SIZE) {
691
+ const entries = Array.from(profileCache.entries());
692
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
693
+ const toRemove = entries.slice(0, profileCache.size - MAX_CACHE_SIZE);
694
+ toRemove.forEach(([key]) => {
695
+ const cached = profileCache.get(key);
696
+ if (cached?.data.photo) {
697
+ URL.revokeObjectURL(cached.data.photo);
698
+ }
699
+ profileCache.delete(key);
700
+ });
701
+ }
702
+ }
703
+ function useUserProfile() {
704
+ const { isAuthenticated, account } = useMsalAuth();
705
+ const graph = useGraphApi();
706
+ const [profile, setProfile] = useState2(null);
707
+ const [loading, setLoading] = useState2(false);
708
+ const [error, setError] = useState2(null);
709
+ const fetchProfile = useCallback3(async () => {
710
+ if (!isAuthenticated || !account) {
711
+ setProfile(null);
712
+ return;
713
+ }
714
+ const cacheKey = account.homeAccountId;
715
+ const cached = profileCache.get(cacheKey);
716
+ if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
717
+ setProfile(cached.data);
718
+ return;
719
+ }
720
+ setLoading(true);
721
+ setError(null);
722
+ try {
723
+ const userData = await graph.get("/me", {
724
+ scopes: ["User.Read"]
725
+ });
726
+ let photoUrl;
727
+ try {
728
+ const photoBlob = await graph.get("/me/photo/$value", {
729
+ scopes: ["User.Read"],
730
+ headers: {
731
+ "Content-Type": "image/jpeg"
732
+ }
733
+ });
734
+ if (photoBlob) {
735
+ photoUrl = URL.createObjectURL(photoBlob);
736
+ }
737
+ } catch (photoError) {
738
+ console.debug("[UserProfile] Photo not available");
739
+ }
740
+ const profileData = {
741
+ id: userData.id,
742
+ displayName: userData.displayName,
743
+ givenName: userData.givenName,
744
+ surname: userData.surname,
745
+ userPrincipalName: userData.userPrincipalName,
746
+ mail: userData.mail,
747
+ jobTitle: userData.jobTitle,
748
+ officeLocation: userData.officeLocation,
749
+ mobilePhone: userData.mobilePhone,
750
+ businessPhones: userData.businessPhones,
751
+ photo: photoUrl
752
+ };
753
+ profileCache.set(cacheKey, {
754
+ data: profileData,
755
+ timestamp: Date.now()
756
+ });
757
+ enforceCacheLimit();
758
+ setProfile(profileData);
759
+ } catch (err) {
760
+ const error2 = err;
761
+ const sanitizedMessage = sanitizeError(error2);
762
+ const sanitizedError = new Error(sanitizedMessage);
763
+ setError(sanitizedError);
764
+ console.error("[UserProfile] Failed to fetch profile:", sanitizedMessage);
765
+ } finally {
766
+ setLoading(false);
767
+ }
768
+ }, [isAuthenticated, account, graph]);
769
+ const clearCache = useCallback3(() => {
770
+ if (account) {
771
+ const cached = profileCache.get(account.homeAccountId);
772
+ if (cached?.data.photo) {
773
+ URL.revokeObjectURL(cached.data.photo);
774
+ }
775
+ profileCache.delete(account.homeAccountId);
776
+ }
777
+ if (profile?.photo) {
778
+ URL.revokeObjectURL(profile.photo);
779
+ }
780
+ setProfile(null);
781
+ }, [account, profile]);
782
+ useEffect2(() => {
783
+ fetchProfile();
784
+ return () => {
785
+ if (profile?.photo) {
786
+ URL.revokeObjectURL(profile.photo);
787
+ }
788
+ };
789
+ }, [fetchProfile]);
790
+ useEffect2(() => {
791
+ return () => {
792
+ if (profile?.photo) {
793
+ URL.revokeObjectURL(profile.photo);
794
+ }
795
+ };
796
+ }, [profile?.photo]);
797
+ return {
798
+ profile,
799
+ loading,
800
+ error,
801
+ refetch: fetchProfile,
802
+ clearCache
803
+ };
804
+ }
805
+
806
+ // src/components/UserAvatar.tsx
807
+ import { jsx as jsx5 } from "react/jsx-runtime";
808
+ function UserAvatar({
809
+ size = 40,
810
+ className = "",
811
+ style,
812
+ showTooltip = true,
813
+ fallbackImage
814
+ }) {
815
+ const { profile, loading } = useUserProfile();
816
+ const [photoUrl, setPhotoUrl] = useState3(null);
817
+ const [photoError, setPhotoError] = useState3(false);
818
+ useEffect3(() => {
819
+ if (profile?.photo) {
820
+ setPhotoUrl(profile.photo);
821
+ }
822
+ }, [profile?.photo]);
823
+ const getInitials = () => {
824
+ if (!profile) return "?";
825
+ const { givenName, surname, displayName: displayName2 } = profile;
826
+ if (givenName && surname) {
827
+ return `${givenName[0]}${surname[0]}`.toUpperCase();
828
+ }
829
+ if (displayName2) {
830
+ const parts = displayName2.split(" ");
831
+ if (parts.length >= 2) {
832
+ return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
833
+ }
834
+ return displayName2.substring(0, 2).toUpperCase();
835
+ }
836
+ return "?";
837
+ };
838
+ const baseStyles = {
839
+ width: `${size}px`,
840
+ height: `${size}px`,
841
+ borderRadius: "50%",
842
+ display: "inline-flex",
843
+ alignItems: "center",
844
+ justifyContent: "center",
845
+ fontSize: `${size * 0.4}px`,
846
+ fontWeight: 600,
847
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
848
+ backgroundColor: "#0078D4",
849
+ color: "#FFFFFF",
850
+ overflow: "hidden",
851
+ userSelect: "none",
852
+ ...style
853
+ };
854
+ const displayName = profile?.displayName || "User";
855
+ if (loading) {
856
+ return /* @__PURE__ */ jsx5(
857
+ "div",
858
+ {
859
+ className,
860
+ style: { ...baseStyles, backgroundColor: "#E1E1E1" },
861
+ "aria-label": "Loading user avatar",
862
+ children: /* @__PURE__ */ jsx5("span", { style: { fontSize: `${size * 0.3}px` }, children: "..." })
863
+ }
864
+ );
865
+ }
866
+ if (photoUrl && !photoError) {
867
+ return /* @__PURE__ */ jsx5(
868
+ "div",
869
+ {
870
+ className,
871
+ style: baseStyles,
872
+ title: showTooltip ? displayName : void 0,
873
+ "aria-label": `${displayName} avatar`,
874
+ children: /* @__PURE__ */ jsx5(
875
+ "img",
876
+ {
877
+ src: photoUrl,
878
+ alt: displayName,
879
+ style: { width: "100%", height: "100%", objectFit: "cover" },
880
+ onError: () => {
881
+ setPhotoError(true);
882
+ if (fallbackImage) {
883
+ setPhotoUrl(fallbackImage);
884
+ }
885
+ }
886
+ }
887
+ )
888
+ }
889
+ );
890
+ }
891
+ return /* @__PURE__ */ jsx5(
892
+ "div",
893
+ {
894
+ className,
895
+ style: baseStyles,
896
+ title: showTooltip ? displayName : void 0,
897
+ "aria-label": `${displayName} avatar`,
898
+ children: getInitials()
899
+ }
900
+ );
901
+ }
902
+
903
+ // src/components/AuthStatus.tsx
904
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
905
+ function AuthStatus({
906
+ className = "",
907
+ style,
908
+ showDetails = false,
909
+ renderLoading,
910
+ renderAuthenticated,
911
+ renderUnauthenticated
912
+ }) {
913
+ const { isAuthenticated, inProgress, account } = useMsalAuth();
914
+ const baseStyles = {
915
+ display: "inline-flex",
916
+ alignItems: "center",
917
+ gap: "8px",
918
+ padding: "8px 12px",
919
+ borderRadius: "4px",
920
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
921
+ fontSize: "14px",
922
+ fontWeight: 500,
923
+ ...style
924
+ };
925
+ if (inProgress) {
926
+ if (renderLoading) {
927
+ return /* @__PURE__ */ jsx6(Fragment2, { children: renderLoading() });
928
+ }
929
+ return /* @__PURE__ */ jsxs3(
930
+ "div",
931
+ {
932
+ className,
933
+ style: { ...baseStyles, backgroundColor: "#FFF4CE", color: "#8A6D3B" },
934
+ role: "status",
935
+ "aria-live": "polite",
936
+ children: [
937
+ /* @__PURE__ */ jsx6(StatusIndicator, { color: "#FFA500" }),
938
+ /* @__PURE__ */ jsx6("span", { children: "Loading..." })
939
+ ]
940
+ }
941
+ );
942
+ }
943
+ if (isAuthenticated) {
944
+ const username = account?.username || account?.name || "User";
945
+ if (renderAuthenticated) {
946
+ return /* @__PURE__ */ jsx6(Fragment2, { children: renderAuthenticated(username) });
947
+ }
948
+ return /* @__PURE__ */ jsxs3(
949
+ "div",
950
+ {
951
+ className,
952
+ style: { ...baseStyles, backgroundColor: "#D4EDDA", color: "#155724" },
953
+ role: "status",
954
+ "aria-live": "polite",
955
+ children: [
956
+ /* @__PURE__ */ jsx6(StatusIndicator, { color: "#28A745" }),
957
+ /* @__PURE__ */ jsx6("span", { children: showDetails ? `Authenticated as ${username}` : "Authenticated" })
958
+ ]
959
+ }
960
+ );
961
+ }
962
+ if (renderUnauthenticated) {
963
+ return /* @__PURE__ */ jsx6(Fragment2, { children: renderUnauthenticated() });
964
+ }
965
+ return /* @__PURE__ */ jsxs3(
966
+ "div",
967
+ {
968
+ className,
969
+ style: { ...baseStyles, backgroundColor: "#F8D7DA", color: "#721C24" },
970
+ role: "status",
971
+ "aria-live": "polite",
972
+ children: [
973
+ /* @__PURE__ */ jsx6(StatusIndicator, { color: "#DC3545" }),
974
+ /* @__PURE__ */ jsx6("span", { children: "Not authenticated" })
975
+ ]
976
+ }
977
+ );
978
+ }
979
+ function StatusIndicator({ color }) {
980
+ return /* @__PURE__ */ jsx6("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx6("circle", { cx: "4", cy: "4", r: "4", fill: color }) });
981
+ }
982
+
983
+ // src/components/AuthGuard.tsx
984
+ import { useEffect as useEffect4 } from "react";
985
+ import { Fragment as Fragment3, jsx as jsx7 } from "react/jsx-runtime";
986
+ function AuthGuard({
987
+ children,
988
+ loadingComponent,
989
+ fallbackComponent,
990
+ useRedirect = true,
991
+ scopes,
992
+ onAuthRequired
993
+ }) {
994
+ const { isAuthenticated, inProgress, loginRedirect, loginPopup } = useMsalAuth();
995
+ useEffect4(() => {
996
+ if (!isAuthenticated && !inProgress) {
997
+ onAuthRequired?.();
998
+ const login = async () => {
999
+ try {
1000
+ if (useRedirect) {
1001
+ await loginRedirect(scopes);
1002
+ } else {
1003
+ await loginPopup(scopes);
1004
+ }
1005
+ } catch (error) {
1006
+ console.error("[AuthGuard] Authentication failed:", error);
1007
+ }
1008
+ };
1009
+ login();
1010
+ }
1011
+ }, [isAuthenticated, inProgress, useRedirect, scopes, loginRedirect, loginPopup, onAuthRequired]);
1012
+ if (inProgress) {
1013
+ return /* @__PURE__ */ jsx7(Fragment3, { children: loadingComponent || /* @__PURE__ */ jsx7("div", { children: "Authenticating..." }) });
1014
+ }
1015
+ if (!isAuthenticated) {
1016
+ return /* @__PURE__ */ jsx7(Fragment3, { children: fallbackComponent || /* @__PURE__ */ jsx7("div", { children: "Redirecting to login..." }) });
1017
+ }
1018
+ return /* @__PURE__ */ jsx7(Fragment3, { children });
1019
+ }
1020
+
1021
+ // src/components/ErrorBoundary.tsx
1022
+ import { Component } from "react";
1023
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1024
+ var ErrorBoundary = class extends Component {
1025
+ constructor(props) {
1026
+ super(props);
1027
+ this.reset = () => {
1028
+ this.setState({
1029
+ hasError: false,
1030
+ error: null
1031
+ });
1032
+ };
1033
+ this.state = {
1034
+ hasError: false,
1035
+ error: null
1036
+ };
1037
+ }
1038
+ static getDerivedStateFromError(error) {
1039
+ return {
1040
+ hasError: true,
1041
+ error
1042
+ };
1043
+ }
1044
+ componentDidCatch(error, errorInfo) {
1045
+ const { onError, debug } = this.props;
1046
+ if (debug) {
1047
+ console.error("[ErrorBoundary] Caught error:", error);
1048
+ console.error("[ErrorBoundary] Error info:", errorInfo);
1049
+ }
1050
+ onError?.(error, errorInfo);
1051
+ }
1052
+ render() {
1053
+ const { hasError, error } = this.state;
1054
+ const { children, fallback } = this.props;
1055
+ if (hasError && error) {
1056
+ if (fallback) {
1057
+ return fallback(error, this.reset);
1058
+ }
1059
+ return /* @__PURE__ */ jsxs4(
1060
+ "div",
1061
+ {
1062
+ style: {
1063
+ padding: "20px",
1064
+ margin: "20px",
1065
+ border: "1px solid #DC3545",
1066
+ borderRadius: "4px",
1067
+ backgroundColor: "#F8D7DA",
1068
+ color: "#721C24",
1069
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif'
1070
+ },
1071
+ children: [
1072
+ /* @__PURE__ */ jsx8("h2", { style: { margin: "0 0 10px 0", fontSize: "18px" }, children: "Authentication Error" }),
1073
+ /* @__PURE__ */ jsx8("p", { style: { margin: "0 0 10px 0" }, children: error.message }),
1074
+ /* @__PURE__ */ jsx8(
1075
+ "button",
1076
+ {
1077
+ onClick: this.reset,
1078
+ style: {
1079
+ padding: "8px 16px",
1080
+ backgroundColor: "#DC3545",
1081
+ color: "#FFFFFF",
1082
+ border: "none",
1083
+ borderRadius: "4px",
1084
+ cursor: "pointer",
1085
+ fontSize: "14px",
1086
+ fontWeight: 600
1087
+ },
1088
+ children: "Try Again"
1089
+ }
1090
+ )
1091
+ ]
1092
+ }
1093
+ );
1094
+ }
1095
+ return children;
1096
+ }
1097
+ };
1098
+
1099
+ // src/hooks/useRoles.ts
1100
+ import { useState as useState4, useEffect as useEffect5, useCallback as useCallback4 } from "react";
1101
+ var rolesCache = /* @__PURE__ */ new Map();
1102
+ var CACHE_DURATION2 = 5 * 60 * 1e3;
1103
+ var MAX_CACHE_SIZE2 = 100;
1104
+ function clearRolesCache(accountId) {
1105
+ if (accountId) {
1106
+ rolesCache.delete(accountId);
1107
+ } else {
1108
+ rolesCache.clear();
1109
+ }
1110
+ }
1111
+ function enforceCacheLimit2() {
1112
+ if (rolesCache.size > MAX_CACHE_SIZE2) {
1113
+ const entries = Array.from(rolesCache.entries());
1114
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
1115
+ const toRemove = entries.slice(0, rolesCache.size - MAX_CACHE_SIZE2);
1116
+ toRemove.forEach(([key]) => rolesCache.delete(key));
1117
+ }
1118
+ }
1119
+ function useRoles() {
1120
+ const { isAuthenticated, account } = useMsalAuth();
1121
+ const graph = useGraphApi();
1122
+ const [roles, setRoles] = useState4([]);
1123
+ const [groups, setGroups] = useState4([]);
1124
+ const [loading, setLoading] = useState4(false);
1125
+ const [error, setError] = useState4(null);
1126
+ const fetchRolesAndGroups = useCallback4(async () => {
1127
+ if (!isAuthenticated || !account) {
1128
+ setRoles([]);
1129
+ setGroups([]);
1130
+ return;
1131
+ }
1132
+ const cacheKey = account.homeAccountId;
1133
+ const cached = rolesCache.get(cacheKey);
1134
+ if (cached && Date.now() - cached.timestamp < CACHE_DURATION2) {
1135
+ setRoles(cached.roles);
1136
+ setGroups(cached.groups);
1137
+ return;
1138
+ }
1139
+ setLoading(true);
1140
+ setError(null);
1141
+ try {
1142
+ const idTokenClaims = account.idTokenClaims;
1143
+ const tokenRoles = idTokenClaims?.roles || [];
1144
+ const groupsResponse = await graph.get("/me/memberOf", {
1145
+ scopes: ["User.Read", "Directory.Read.All"]
1146
+ });
1147
+ const userGroups = groupsResponse.value.map((group) => group.id);
1148
+ rolesCache.set(cacheKey, {
1149
+ roles: tokenRoles,
1150
+ groups: userGroups,
1151
+ timestamp: Date.now()
1152
+ });
1153
+ enforceCacheLimit2();
1154
+ setRoles(tokenRoles);
1155
+ setGroups(userGroups);
1156
+ } catch (err) {
1157
+ const error2 = err;
1158
+ const sanitizedMessage = sanitizeError(error2);
1159
+ const sanitizedError = new Error(sanitizedMessage);
1160
+ setError(sanitizedError);
1161
+ console.error("[Roles] Failed to fetch roles/groups:", sanitizedMessage);
1162
+ const idTokenClaims = account.idTokenClaims;
1163
+ const tokenRoles = idTokenClaims?.roles || [];
1164
+ setRoles(tokenRoles);
1165
+ } finally {
1166
+ setLoading(false);
1167
+ }
1168
+ }, [isAuthenticated, account, graph]);
1169
+ const hasRole = useCallback4(
1170
+ (role) => {
1171
+ return roles.includes(role);
1172
+ },
1173
+ [roles]
1174
+ );
1175
+ const hasGroup = useCallback4(
1176
+ (groupId) => {
1177
+ return groups.includes(groupId);
1178
+ },
1179
+ [groups]
1180
+ );
1181
+ const hasAnyRole = useCallback4(
1182
+ (checkRoles) => {
1183
+ return checkRoles.some((role) => roles.includes(role));
1184
+ },
1185
+ [roles]
1186
+ );
1187
+ const hasAllRoles = useCallback4(
1188
+ (checkRoles) => {
1189
+ return checkRoles.every((role) => roles.includes(role));
1190
+ },
1191
+ [roles]
1192
+ );
1193
+ useEffect5(() => {
1194
+ fetchRolesAndGroups();
1195
+ return () => {
1196
+ if (account) {
1197
+ clearRolesCache(account.homeAccountId);
1198
+ }
1199
+ };
1200
+ }, [fetchRolesAndGroups, account]);
1201
+ return {
1202
+ roles,
1203
+ groups,
1204
+ loading,
1205
+ error,
1206
+ hasRole,
1207
+ hasGroup,
1208
+ hasAnyRole,
1209
+ hasAllRoles,
1210
+ refetch: fetchRolesAndGroups
1211
+ };
1212
+ }
1213
+
1214
+ // src/utils/withAuth.tsx
1215
+ import { jsx as jsx9 } from "react/jsx-runtime";
1216
+ function withAuth(Component2, options = {}) {
1217
+ const { displayName, ...guardProps } = options;
1218
+ const WrappedComponent = (props) => {
1219
+ return /* @__PURE__ */ jsx9(AuthGuard, { ...guardProps, children: /* @__PURE__ */ jsx9(Component2, { ...props }) });
1220
+ };
1221
+ WrappedComponent.displayName = displayName || `withAuth(${Component2.displayName || Component2.name || "Component"})`;
1222
+ return WrappedComponent;
1223
+ }
1224
+
1225
+ // src/utils/tokenRetry.ts
1226
+ async function retryWithBackoff(fn, config = {}) {
1227
+ const {
1228
+ maxRetries = 3,
1229
+ initialDelay = 1e3,
1230
+ maxDelay = 1e4,
1231
+ backoffMultiplier = 2,
1232
+ debug = false
1233
+ } = config;
1234
+ let lastError;
1235
+ let delay = initialDelay;
1236
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1237
+ try {
1238
+ if (debug && attempt > 0) {
1239
+ console.log(`[TokenRetry] Attempt ${attempt + 1}/${maxRetries + 1}`);
1240
+ }
1241
+ return await fn();
1242
+ } catch (error) {
1243
+ lastError = error;
1244
+ if (attempt === maxRetries) {
1245
+ if (debug) {
1246
+ console.error("[TokenRetry] All retry attempts failed");
1247
+ }
1248
+ break;
1249
+ }
1250
+ if (!isRetryableError(error)) {
1251
+ if (debug) {
1252
+ console.log("[TokenRetry] Non-retryable error, aborting");
1253
+ }
1254
+ throw error;
1255
+ }
1256
+ if (debug) {
1257
+ console.warn(`[TokenRetry] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
1258
+ }
1259
+ await sleep(delay);
1260
+ delay = Math.min(delay * backoffMultiplier, maxDelay);
1261
+ }
1262
+ }
1263
+ throw lastError;
1264
+ }
1265
+ function isRetryableError(error) {
1266
+ const message = error.message.toLowerCase();
1267
+ if (message.includes("network") || message.includes("timeout") || message.includes("fetch") || message.includes("connection")) {
1268
+ return true;
1269
+ }
1270
+ if (message.includes("500") || message.includes("502") || message.includes("503")) {
1271
+ return true;
1272
+ }
1273
+ if (message.includes("429") || message.includes("rate limit")) {
1274
+ return true;
1275
+ }
1276
+ if (message.includes("token") && message.includes("expired")) {
1277
+ return true;
1278
+ }
1279
+ return false;
1280
+ }
1281
+ function sleep(ms) {
1282
+ return new Promise((resolve) => setTimeout(resolve, ms));
1283
+ }
1284
+ function createRetryWrapper(fn, config = {}) {
1285
+ return (...args) => {
1286
+ return retryWithBackoff(() => fn(...args), config);
1287
+ };
1288
+ }
1289
+
1290
+ // src/utils/debugLogger.ts
1291
+ var DebugLogger = class {
1292
+ constructor(config = {}) {
1293
+ this.logHistory = [];
1294
+ this.performanceTimings = /* @__PURE__ */ new Map();
1295
+ this.config = {
1296
+ enabled: config.enabled ?? false,
1297
+ prefix: config.prefix ?? "[MSAL-Next]",
1298
+ showTimestamp: config.showTimestamp ?? true,
1299
+ level: config.level ?? "info",
1300
+ enablePerformance: config.enablePerformance ?? false,
1301
+ enableNetworkLogs: config.enableNetworkLogs ?? false,
1302
+ maxHistorySize: config.maxHistorySize ?? 100
1303
+ };
1304
+ }
1305
+ shouldLog(level) {
1306
+ if (!this.config.enabled) return false;
1307
+ const levels = ["error", "warn", "info", "debug"];
1308
+ const currentLevelIndex = levels.indexOf(this.config.level);
1309
+ const messageLevelIndex = levels.indexOf(level);
1310
+ return messageLevelIndex <= currentLevelIndex;
1311
+ }
1312
+ formatMessage(level, message, data) {
1313
+ const timestamp = this.config.showTimestamp ? `[${(/* @__PURE__ */ new Date()).toISOString()}]` : "";
1314
+ const prefix = this.config.prefix;
1315
+ const levelStr = `[${level.toUpperCase()}]`;
1316
+ let formatted = `${timestamp} ${prefix} ${levelStr} ${message}`;
1317
+ if (data !== void 0) {
1318
+ formatted += "\n" + JSON.stringify(data, null, 2);
1319
+ }
1320
+ return formatted;
1321
+ }
1322
+ addToHistory(level, message, data) {
1323
+ if (this.logHistory.length >= this.config.maxHistorySize) {
1324
+ this.logHistory.shift();
1325
+ }
1326
+ this.logHistory.push({
1327
+ timestamp: Date.now(),
1328
+ level,
1329
+ message,
1330
+ data
1331
+ });
1332
+ }
1333
+ error(message, data) {
1334
+ if (this.shouldLog("error")) {
1335
+ console.error(this.formatMessage("error", message, data));
1336
+ this.addToHistory("error", message, data);
1337
+ }
1338
+ }
1339
+ warn(message, data) {
1340
+ if (this.shouldLog("warn")) {
1341
+ console.warn(this.formatMessage("warn", message, data));
1342
+ this.addToHistory("warn", message, data);
1343
+ }
1344
+ }
1345
+ info(message, data) {
1346
+ if (this.shouldLog("info")) {
1347
+ console.info(this.formatMessage("info", message, data));
1348
+ this.addToHistory("info", message, data);
1349
+ }
1350
+ }
1351
+ debug(message, data) {
1352
+ if (this.shouldLog("debug")) {
1353
+ console.debug(this.formatMessage("debug", message, data));
1354
+ this.addToHistory("debug", message, data);
1355
+ }
1356
+ }
1357
+ group(label) {
1358
+ if (this.config.enabled) {
1359
+ console.group(`${this.config.prefix} ${label}`);
1360
+ }
1361
+ }
1362
+ groupEnd() {
1363
+ if (this.config.enabled) {
1364
+ console.groupEnd();
1365
+ }
1366
+ }
1367
+ /**
1368
+ * Start performance timing for an operation
1369
+ */
1370
+ startTiming(operation) {
1371
+ if (this.config.enablePerformance) {
1372
+ this.performanceTimings.set(operation, {
1373
+ operation,
1374
+ startTime: performance.now()
1375
+ });
1376
+ this.debug(`\u23F1\uFE0F Started: ${operation}`);
1377
+ }
1378
+ }
1379
+ /**
1380
+ * End performance timing for an operation
1381
+ */
1382
+ endTiming(operation) {
1383
+ if (this.config.enablePerformance) {
1384
+ const timing = this.performanceTimings.get(operation);
1385
+ if (timing) {
1386
+ timing.endTime = performance.now();
1387
+ timing.duration = timing.endTime - timing.startTime;
1388
+ this.info(`\u23F1\uFE0F Completed: ${operation} (${timing.duration.toFixed(2)}ms)`);
1389
+ return timing.duration;
1390
+ }
1391
+ }
1392
+ return void 0;
1393
+ }
1394
+ /**
1395
+ * Log network request
1396
+ */
1397
+ logRequest(method, url, options) {
1398
+ if (this.config.enableNetworkLogs) {
1399
+ this.debug(`\u{1F310} ${method} ${url}`, options);
1400
+ }
1401
+ }
1402
+ /**
1403
+ * Log network response
1404
+ */
1405
+ logResponse(method, url, status, data) {
1406
+ if (this.config.enableNetworkLogs) {
1407
+ const statusEmoji = status >= 200 && status < 300 ? "\u2705" : "\u274C";
1408
+ this.debug(`${statusEmoji} ${method} ${url} - ${status}`, data);
1409
+ }
1410
+ }
1411
+ /**
1412
+ * Get log history
1413
+ */
1414
+ getHistory() {
1415
+ return [...this.logHistory];
1416
+ }
1417
+ /**
1418
+ * Get performance timings
1419
+ */
1420
+ getPerformanceTimings() {
1421
+ return Array.from(this.performanceTimings.values());
1422
+ }
1423
+ /**
1424
+ * Clear log history
1425
+ */
1426
+ clearHistory() {
1427
+ this.logHistory = [];
1428
+ }
1429
+ /**
1430
+ * Clear performance timings
1431
+ */
1432
+ clearTimings() {
1433
+ this.performanceTimings.clear();
1434
+ }
1435
+ /**
1436
+ * Export logs as JSON
1437
+ */
1438
+ exportLogs() {
1439
+ return JSON.stringify({
1440
+ config: this.config,
1441
+ history: this.logHistory,
1442
+ performanceTimings: Array.from(this.performanceTimings.values()),
1443
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
1444
+ }, null, 2);
1445
+ }
1446
+ /**
1447
+ * Download logs as a file
1448
+ */
1449
+ downloadLogs(filename = "msal-next-debug-logs.json") {
1450
+ if (typeof window === "undefined") return;
1451
+ const logs = this.exportLogs();
1452
+ const blob = new Blob([logs], { type: "application/json" });
1453
+ const url = URL.createObjectURL(blob);
1454
+ const a = document.createElement("a");
1455
+ a.href = url;
1456
+ a.download = filename;
1457
+ a.click();
1458
+ URL.revokeObjectURL(url);
1459
+ }
1460
+ setEnabled(enabled) {
1461
+ this.config.enabled = enabled;
1462
+ }
1463
+ setLevel(level) {
1464
+ if (level) {
1465
+ this.config.level = level;
1466
+ }
1467
+ }
1468
+ };
1469
+ var globalLogger = null;
1470
+ function getDebugLogger(config) {
1471
+ if (!globalLogger) {
1472
+ globalLogger = new DebugLogger(config);
1473
+ } else if (config) {
1474
+ if (config.enabled !== void 0) {
1475
+ globalLogger.setEnabled(config.enabled);
1476
+ }
1477
+ if (config.level) {
1478
+ globalLogger.setLevel(config.level);
1479
+ }
1480
+ }
1481
+ return globalLogger;
1482
+ }
1483
+ function createScopedLogger(scope, config) {
1484
+ return new DebugLogger({
1485
+ ...config,
1486
+ prefix: `[MSAL-Next:${scope}]`
1487
+ });
1488
+ }
1489
+
1490
+ // src/middleware/createAuthMiddleware.ts
1491
+ import { NextResponse } from "next/server";
1492
+ function createAuthMiddleware(config = {}) {
1493
+ const {
1494
+ protectedRoutes = [],
1495
+ publicOnlyRoutes = [],
1496
+ loginPath = "/login",
1497
+ redirectAfterLogin = "/",
1498
+ sessionCookie = "msal.account",
1499
+ isAuthenticated: customAuthCheck,
1500
+ debug = false
1501
+ } = config;
1502
+ return async function authMiddleware(request) {
1503
+ const { pathname } = request.nextUrl;
1504
+ if (debug) {
1505
+ console.log("[AuthMiddleware] Processing:", pathname);
1506
+ }
1507
+ let authenticated = false;
1508
+ if (customAuthCheck) {
1509
+ authenticated = await customAuthCheck(request);
1510
+ } else {
1511
+ const sessionData = request.cookies.get(sessionCookie);
1512
+ authenticated = !!sessionData?.value;
1513
+ }
1514
+ if (debug) {
1515
+ console.log("[AuthMiddleware] Authenticated:", authenticated);
1516
+ }
1517
+ const isProtectedRoute = protectedRoutes.some(
1518
+ (route) => pathname.startsWith(route)
1519
+ );
1520
+ const isPublicOnlyRoute = publicOnlyRoutes.some(
1521
+ (route) => pathname.startsWith(route)
1522
+ );
1523
+ if (isProtectedRoute && !authenticated) {
1524
+ if (debug) {
1525
+ console.log("[AuthMiddleware] Redirecting to login");
1526
+ }
1527
+ const url = request.nextUrl.clone();
1528
+ url.pathname = loginPath;
1529
+ url.searchParams.set("returnUrl", pathname);
1530
+ return NextResponse.redirect(url);
1531
+ }
1532
+ if (isPublicOnlyRoute && authenticated) {
1533
+ if (debug) {
1534
+ console.log("[AuthMiddleware] Redirecting to home");
1535
+ }
1536
+ const returnUrl = request.nextUrl.searchParams.get("returnUrl");
1537
+ const url = request.nextUrl.clone();
1538
+ url.pathname = returnUrl || redirectAfterLogin;
1539
+ url.searchParams.delete("returnUrl");
1540
+ return NextResponse.redirect(url);
1541
+ }
1542
+ const response = NextResponse.next();
1543
+ if (authenticated) {
1544
+ response.headers.set("x-msal-authenticated", "true");
1545
+ try {
1546
+ const sessionData = request.cookies.get(sessionCookie);
1547
+ if (sessionData?.value) {
1548
+ const account = safeJsonParse(sessionData.value, isValidAccountData);
1549
+ if (account?.username) {
1550
+ response.headers.set("x-msal-username", account.username);
1551
+ }
1552
+ }
1553
+ } catch (error) {
1554
+ if (debug) {
1555
+ console.warn("[AuthMiddleware] Failed to parse session data");
1556
+ }
1557
+ }
1558
+ }
1559
+ return response;
1560
+ };
1561
+ }
1562
+
1563
+ // src/client.ts
1564
+ import { useMsal as useMsal2, useIsAuthenticated, useAccount as useAccount2 } from "@azure/msal-react";
1565
+ export {
1566
+ AuthGuard,
1567
+ AuthStatus,
1568
+ ErrorBoundary,
1569
+ MSALProvider,
1570
+ MicrosoftSignInButton,
1571
+ MsalAuthProvider,
1572
+ SignOutButton,
1573
+ UserAvatar,
1574
+ createAuthMiddleware,
1575
+ createMsalConfig,
1576
+ createRetryWrapper,
1577
+ createScopedLogger,
1578
+ getDebugLogger,
1579
+ getMsalInstance,
1580
+ isValidAccountData,
1581
+ isValidRedirectUri,
1582
+ isValidScope,
1583
+ retryWithBackoff,
1584
+ safeJsonParse,
1585
+ sanitizeError,
1586
+ useAccount2 as useAccount,
1587
+ useGraphApi,
1588
+ useIsAuthenticated,
1589
+ useMsal2 as useMsal,
1590
+ useMsalAuth,
1591
+ useRoles,
1592
+ useUserProfile,
1593
+ validateScopes,
1594
+ withAuth
1595
+ };