@chemmangat/msal-next 4.0.2 → 4.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/dist/index.mjs CHANGED
@@ -1,32 +1,236 @@
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 {createContext,useState,useRef,useEffect,useMemo,useCallback,Component}from'react';import {jsx,Fragment,jsxs}from'react/jsx-runtime';import {useRouter}from'next/navigation';import {NextResponse}from'next/server';function j(t,e){try{let r=JSON.parse(t);return e(r)?r:(console.warn("[Validation] JSON validation failed"),null)}catch(r){return console.error("[Validation] JSON parse error:",r),null}}function Z(t){return typeof t=="object"&&t!==null&&typeof t.homeAccountId=="string"&&t.homeAccountId.length>0&&typeof t.username=="string"&&t.username.length>0&&(t.name===void 0||typeof t.name=="string")}function R(t){return t instanceof Error?t.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 V(t,e){try{let r=new URL(t);return e.some(o=>{let i=new URL(o);return r.origin===i.origin})}catch{return false}}function fe(t){return /^[a-zA-Z0-9._-]+$/.test(t)}function Ne(t){return Array.isArray(t)&&t.every(fe)}function X(t){if(t.msalConfig)return t.msalConfig;let{clientId:e,tenantId:r,authorityType:o="common",redirectUri:i,postLogoutRedirectUri:s,cacheLocation:p="sessionStorage",storeAuthStateInCookie:u=false,navigateToLoginRequestUrl:l=false,enableLogging:a=false,loggerCallback:m,allowedRedirectUris:h}=t;if(!e)throw new Error("@chemmangat/msal-next: clientId is required");let c=()=>{if(o==="tenant"){if(!r)throw new Error('@chemmangat/msal-next: tenantId is required when authorityType is "tenant"');return `https://login.microsoftonline.com/${r}`}return `https://login.microsoftonline.com/${o}`},n=typeof window<"u"?window.location.origin:"http://localhost:3000",f=i||n;if(h&&h.length>0){if(!V(f,h))throw new Error(`@chemmangat/msal-next: redirectUri "${f}" is not in the allowed list`);let d=s||f;if(!V(d,h))throw new Error(`@chemmangat/msal-next: postLogoutRedirectUri "${d}" is not in the allowed list`)}return {auth:{clientId:e,authority:c(),redirectUri:f,postLogoutRedirectUri:s||f,navigateToLoginRequestUrl:l},cache:{cacheLocation:p,storeAuthStateInCookie:u},system:{loggerOptions:{loggerCallback:m||((d,y,C)=>{if(!(C||!a))switch(d){case LogLevel.Error:console.error("[MSAL]",y);break;case LogLevel.Warning:console.warn("[MSAL]",y);break;case LogLevel.Info:console.info("[MSAL]",y);break;case LogLevel.Verbose:console.debug("[MSAL]",y);break}}),logLevel:a?LogLevel.Verbose:LogLevel.Error}}}}var J=null,ge=false;function Y(t){if(process.env.NODE_ENV!=="development")return {valid:true,warnings:[],errors:[]};if(J)return J;let e=[],r=[];if(t.clientId?he(t.clientId)?e.push({field:"clientId",message:"Client ID appears to be a placeholder",fix:`Replace the placeholder with your actual Application (client) ID from Azure Portal.
1
+ "use client";
2
2
 
3
- Current value: ${t.clientId}
4
- Expected format: 12345678-1234-1234-1234-123456789012 (GUID)`}):me(t.clientId)||e.push({field:"clientId",message:"Client ID format is invalid",fix:`Client ID should be a GUID (UUID) format.
3
+ // src/components/MsalAuthProvider.tsx
4
+ import { MsalProvider } from "@azure/msal-react";
5
+ import { PublicClientApplication, EventType } from "@azure/msal-browser";
6
+ import { useEffect as useEffect2, useState, useRef as useRef2 } from "react";
5
7
 
6
- Current value: ${t.clientId}
7
- Expected format: 12345678-1234-1234-1234-123456789012
8
+ // src/utils/createMsalConfig.ts
9
+ import { LogLevel } from "@azure/msal-browser";
10
+
11
+ // src/utils/validation.ts
12
+ function safeJsonParse(jsonString, validator) {
13
+ try {
14
+ const parsed = JSON.parse(jsonString);
15
+ if (validator(parsed)) {
16
+ return parsed;
17
+ }
18
+ console.warn("[Validation] JSON validation failed");
19
+ return null;
20
+ } catch (error) {
21
+ console.error("[Validation] JSON parse error:", error);
22
+ return null;
23
+ }
24
+ }
25
+ function isValidAccountData(data) {
26
+ return typeof data === "object" && data !== null && typeof data.homeAccountId === "string" && data.homeAccountId.length > 0 && typeof data.username === "string" && data.username.length > 0 && (data.name === void 0 || typeof data.name === "string");
27
+ }
28
+ function sanitizeError(error) {
29
+ if (error instanceof Error) {
30
+ const message = error.message;
31
+ const sanitized = 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]");
32
+ return sanitized;
33
+ }
34
+ return "An unexpected error occurred";
35
+ }
36
+ function isValidRedirectUri(uri, allowedOrigins) {
37
+ try {
38
+ const url = new URL(uri);
39
+ return allowedOrigins.some((allowed) => {
40
+ const allowedUrl = new URL(allowed);
41
+ return url.origin === allowedUrl.origin;
42
+ });
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+ function isValidScope(scope) {
48
+ return /^[a-zA-Z0-9._-]+$/.test(scope);
49
+ }
50
+ function validateScopes(scopes) {
51
+ return Array.isArray(scopes) && scopes.every(isValidScope);
52
+ }
53
+
54
+ // src/utils/createMsalConfig.ts
55
+ function createMsalConfig(config) {
56
+ if (config.msalConfig) {
57
+ return config.msalConfig;
58
+ }
59
+ const {
60
+ clientId,
61
+ tenantId,
62
+ authorityType = "common",
63
+ redirectUri,
64
+ postLogoutRedirectUri,
65
+ cacheLocation = "sessionStorage",
66
+ storeAuthStateInCookie = false,
67
+ navigateToLoginRequestUrl = false,
68
+ enableLogging = false,
69
+ loggerCallback,
70
+ allowedRedirectUris
71
+ } = config;
72
+ if (!clientId) {
73
+ throw new Error("@chemmangat/msal-next: clientId is required");
74
+ }
75
+ const getAuthority = () => {
76
+ if (authorityType === "tenant") {
77
+ if (!tenantId) {
78
+ throw new Error('@chemmangat/msal-next: tenantId is required when authorityType is "tenant"');
79
+ }
80
+ return `https://login.microsoftonline.com/${tenantId}`;
81
+ }
82
+ return `https://login.microsoftonline.com/${authorityType}`;
83
+ };
84
+ const defaultRedirectUri = typeof window !== "undefined" ? window.location.origin : "http://localhost:3000";
85
+ const finalRedirectUri = redirectUri || defaultRedirectUri;
86
+ if (allowedRedirectUris && allowedRedirectUris.length > 0) {
87
+ if (!isValidRedirectUri(finalRedirectUri, allowedRedirectUris)) {
88
+ throw new Error(
89
+ `@chemmangat/msal-next: redirectUri "${finalRedirectUri}" is not in the allowed list`
90
+ );
91
+ }
92
+ const finalPostLogoutUri = postLogoutRedirectUri || finalRedirectUri;
93
+ if (!isValidRedirectUri(finalPostLogoutUri, allowedRedirectUris)) {
94
+ throw new Error(
95
+ `@chemmangat/msal-next: postLogoutRedirectUri "${finalPostLogoutUri}" is not in the allowed list`
96
+ );
97
+ }
98
+ }
99
+ const msalConfig = {
100
+ auth: {
101
+ clientId,
102
+ authority: getAuthority(),
103
+ redirectUri: finalRedirectUri,
104
+ postLogoutRedirectUri: postLogoutRedirectUri || finalRedirectUri,
105
+ navigateToLoginRequestUrl
106
+ },
107
+ cache: {
108
+ cacheLocation,
109
+ storeAuthStateInCookie
110
+ },
111
+ system: {
112
+ loggerOptions: {
113
+ loggerCallback: loggerCallback || ((level, message, containsPii) => {
114
+ if (containsPii || !enableLogging) return;
115
+ switch (level) {
116
+ case LogLevel.Error:
117
+ console.error("[MSAL]", message);
118
+ break;
119
+ case LogLevel.Warning:
120
+ console.warn("[MSAL]", message);
121
+ break;
122
+ case LogLevel.Info:
123
+ console.info("[MSAL]", message);
124
+ break;
125
+ case LogLevel.Verbose:
126
+ console.debug("[MSAL]", message);
127
+ break;
128
+ }
129
+ }),
130
+ logLevel: enableLogging ? LogLevel.Verbose : LogLevel.Error
131
+ }
132
+ }
133
+ };
134
+ return msalConfig;
135
+ }
136
+
137
+ // src/utils/configValidator.ts
138
+ var validationCache = null;
139
+ var hasDisplayedResults = false;
140
+ function validateConfig(config) {
141
+ if (process.env.NODE_ENV !== "development") {
142
+ return { valid: true, warnings: [], errors: [] };
143
+ }
144
+ if (validationCache) {
145
+ return validationCache;
146
+ }
147
+ const warnings = [];
148
+ const errors = [];
149
+ if (!config.clientId) {
150
+ errors.push({
151
+ field: "clientId",
152
+ message: "Client ID is missing",
153
+ fix: `Add NEXT_PUBLIC_AZURE_AD_CLIENT_ID to your .env.local file.
8
154
 
9
- Get the correct value from: Azure Portal \u2192 App registrations \u2192 Your app`}):r.push({field:"clientId",message:"Client ID is missing",fix:`Add NEXT_PUBLIC_AZURE_AD_CLIENT_ID to your .env.local file.
155
+ Get it from: Azure Portal \u2192 App registrations \u2192 Your app \u2192 Application (client) ID`
156
+ });
157
+ } else if (isPlaceholderValue(config.clientId)) {
158
+ warnings.push({
159
+ field: "clientId",
160
+ message: "Client ID appears to be a placeholder",
161
+ fix: `Replace the placeholder with your actual Application (client) ID from Azure Portal.
10
162
 
11
- Get it from: Azure Portal \u2192 App registrations \u2192 Your app \u2192 Application (client) ID`}),t.tenantId&&(he(t.tenantId)?e.push({field:"tenantId",message:"Tenant ID appears to be a placeholder",fix:`Replace the placeholder with your actual Directory (tenant) ID from Azure Portal.
163
+ Current value: ${config.clientId}
164
+ Expected format: 12345678-1234-1234-1234-123456789012 (GUID)`
165
+ });
166
+ } else if (!isValidGuid(config.clientId)) {
167
+ warnings.push({
168
+ field: "clientId",
169
+ message: "Client ID format is invalid",
170
+ fix: `Client ID should be a GUID (UUID) format.
12
171
 
13
- Current value: ${t.tenantId}
172
+ Current value: ${config.clientId}
173
+ Expected format: 12345678-1234-1234-1234-123456789012
174
+
175
+ Get the correct value from: Azure Portal \u2192 App registrations \u2192 Your app`
176
+ });
177
+ }
178
+ if (config.tenantId) {
179
+ if (isPlaceholderValue(config.tenantId)) {
180
+ warnings.push({
181
+ field: "tenantId",
182
+ message: "Tenant ID appears to be a placeholder",
183
+ fix: `Replace the placeholder with your actual Directory (tenant) ID from Azure Portal.
184
+
185
+ Current value: ${config.tenantId}
14
186
  Expected format: 87654321-4321-4321-4321-210987654321 (GUID)
15
187
 
16
- Or remove tenantId and use authorityType: 'common' for multi-tenant apps.`}):me(t.tenantId)||e.push({field:"tenantId",message:"Tenant ID format is invalid",fix:`Tenant ID should be a GUID (UUID) format.
188
+ Or remove tenantId and use authorityType: 'common' for multi-tenant apps.`
189
+ });
190
+ } else if (!isValidGuid(config.tenantId)) {
191
+ warnings.push({
192
+ field: "tenantId",
193
+ message: "Tenant ID format is invalid",
194
+ fix: `Tenant ID should be a GUID (UUID) format.
17
195
 
18
- Current value: ${t.tenantId}
196
+ Current value: ${config.tenantId}
19
197
  Expected format: 87654321-4321-4321-4321-210987654321
20
198
 
21
- Get the correct value from: Azure Portal \u2192 Azure Active Directory \u2192 Properties \u2192 Tenant ID`})),t.redirectUri&&(ke(t.redirectUri)&&!t.redirectUri.startsWith("https://")&&r.push({field:"redirectUri",message:"Production redirect URI must use HTTPS",fix:`Change your redirect URI to use HTTPS in production.
199
+ Get the correct value from: Azure Portal \u2192 Azure Active Directory \u2192 Properties \u2192 Tenant ID`
200
+ });
201
+ }
202
+ }
203
+ if (config.redirectUri) {
204
+ if (isProductionUrl(config.redirectUri) && !config.redirectUri.startsWith("https://")) {
205
+ errors.push({
206
+ field: "redirectUri",
207
+ message: "Production redirect URI must use HTTPS",
208
+ fix: `Change your redirect URI to use HTTPS in production.
22
209
 
23
- Current value: ${t.redirectUri}
24
- Should be: ${t.redirectUri.replace("http://","https://")}
210
+ Current value: ${config.redirectUri}
211
+ Should be: ${config.redirectUri.replace("http://", "https://")}
25
212
 
26
- HTTP is only allowed for localhost development.`}),_e(t.redirectUri)||r.push({field:"redirectUri",message:"Redirect URI is not a valid URL",fix:`Provide a valid URL for redirectUri.
213
+ HTTP is only allowed for localhost development.`
214
+ });
215
+ }
216
+ if (!isValidUrl(config.redirectUri)) {
217
+ errors.push({
218
+ field: "redirectUri",
219
+ message: "Redirect URI is not a valid URL",
220
+ fix: `Provide a valid URL for redirectUri.
27
221
 
28
- Current value: ${t.redirectUri}
29
- Expected format: https://yourdomain.com or http://localhost:3000`})),t.scopes&&t.scopes.length>0){let i=t.scopes.filter(s=>!Fe(s));i.length>0&&e.push({field:"scopes",message:"Some scopes have invalid format",fix:`Invalid scopes: ${i.join(", ")}
222
+ Current value: ${config.redirectUri}
223
+ Expected format: https://yourdomain.com or http://localhost:3000`
224
+ });
225
+ }
226
+ }
227
+ if (config.scopes && config.scopes.length > 0) {
228
+ const invalidScopes = config.scopes.filter((scope) => !isValidScope2(scope));
229
+ if (invalidScopes.length > 0) {
230
+ warnings.push({
231
+ field: "scopes",
232
+ message: "Some scopes have invalid format",
233
+ fix: `Invalid scopes: ${invalidScopes.join(", ")}
30
234
 
31
235
  Scopes should be in format: "Resource.Permission" (e.g., "User.Read", "Mail.Read")
32
236
 
@@ -34,26 +238,124 @@ Common scopes:
34
238
  \u2022 User.Read - Read user profile
35
239
  \u2022 Mail.Read - Read user mail
36
240
  \u2022 Calendars.Read - Read user calendars
37
- \u2022 Files.Read - Read user files`});}if(typeof window<"u"){let i=process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID,s=process.env.NEXT_PUBLIC_AZURE_AD_TENANT_ID;i||e.push({field:"environment",message:"NEXT_PUBLIC_AZURE_AD_CLIENT_ID not found in environment",fix:`Add NEXT_PUBLIC_AZURE_AD_CLIENT_ID to your .env.local file:
241
+ \u2022 Files.Read - Read user files`
242
+ });
243
+ }
244
+ }
245
+ if (typeof window !== "undefined") {
246
+ const clientIdFromEnv = process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID;
247
+ const tenantIdFromEnv = process.env.NEXT_PUBLIC_AZURE_AD_TENANT_ID;
248
+ if (!clientIdFromEnv) {
249
+ warnings.push({
250
+ field: "environment",
251
+ message: "NEXT_PUBLIC_AZURE_AD_CLIENT_ID not found in environment",
252
+ fix: `Add NEXT_PUBLIC_AZURE_AD_CLIENT_ID to your .env.local file:
38
253
 
39
254
  NEXT_PUBLIC_AZURE_AD_CLIENT_ID=your-client-id-here
40
255
 
41
- Then restart your development server.`}),!s&&t.authorityType==="tenant"&&e.push({field:"environment",message:'NEXT_PUBLIC_AZURE_AD_TENANT_ID not found but authorityType is "tenant"',fix:`Either:
256
+ Then restart your development server.`
257
+ });
258
+ }
259
+ if (!tenantIdFromEnv && config.authorityType === "tenant") {
260
+ warnings.push({
261
+ field: "environment",
262
+ message: 'NEXT_PUBLIC_AZURE_AD_TENANT_ID not found but authorityType is "tenant"',
263
+ fix: `Either:
42
264
  1. Add NEXT_PUBLIC_AZURE_AD_TENANT_ID to your .env.local file, OR
43
265
  2. Change authorityType to 'common' for multi-tenant support
44
266
 
45
267
  For single-tenant apps:
46
- NEXT_PUBLIC_AZURE_AD_TENANT_ID=your-tenant-id-here`});}let o={valid:r.length===0,warnings:e,errors:r};return J=o,o}function K(t){if(!(ge||process.env.NODE_ENV!=="development")){if(ge=true,t.valid&&t.warnings.length===0){console.log("\u2705 MSAL configuration validated successfully");return}console.group("\u{1F50D} MSAL Configuration Validation"),t.errors.length>0&&(console.group("\u274C Errors (must fix)"),t.errors.forEach(e=>{console.error(`
47
- ${e.field}:`),console.error(` ${e.message}`),console.error(`
268
+ NEXT_PUBLIC_AZURE_AD_TENANT_ID=your-tenant-id-here`
269
+ });
270
+ }
271
+ }
272
+ const result = {
273
+ valid: errors.length === 0,
274
+ warnings,
275
+ errors
276
+ };
277
+ validationCache = result;
278
+ return result;
279
+ }
280
+ function displayValidationResults(result) {
281
+ if (hasDisplayedResults || process.env.NODE_ENV !== "development") {
282
+ return;
283
+ }
284
+ hasDisplayedResults = true;
285
+ if (result.valid && result.warnings.length === 0) {
286
+ console.log("\u2705 MSAL configuration validated successfully");
287
+ return;
288
+ }
289
+ console.group("\u{1F50D} MSAL Configuration Validation");
290
+ if (result.errors.length > 0) {
291
+ console.group("\u274C Errors (must fix)");
292
+ result.errors.forEach((error) => {
293
+ console.error(`
294
+ ${error.field}:`);
295
+ console.error(` ${error.message}`);
296
+ console.error(`
48
297
  Fix:
49
- ${e.fix.split(`
50
- `).join(`
51
- `)}`);}),console.groupEnd()),t.warnings.length>0&&(console.group("\u26A0\uFE0F Warnings (should fix)"),t.warnings.forEach(e=>{console.warn(`
52
- ${e.field}:`),console.warn(` ${e.message}`),console.warn(`
298
+ ${error.fix.split("\n").join("\n ")}`);
299
+ });
300
+ console.groupEnd();
301
+ }
302
+ if (result.warnings.length > 0) {
303
+ console.group("\u26A0\uFE0F Warnings (should fix)");
304
+ result.warnings.forEach((warning) => {
305
+ console.warn(`
306
+ ${warning.field}:`);
307
+ console.warn(` ${warning.message}`);
308
+ console.warn(`
53
309
  Fix:
54
- ${e.fix.split(`
55
- `).join(`
56
- `)}`);}),console.groupEnd()),console.groupEnd();}}function he(t){let e=["your-client-id","your-tenant-id","your-client-id-here","your-tenant-id-here","client-id","tenant-id","replace-me","changeme","placeholder","example","xxx","000"],r=t.toLowerCase();return e.some(o=>r.includes(o))}function me(t){return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)}function ke(t){return !t.includes("localhost")&&!t.includes("127.0.0.1")}function _e(t){try{return new URL(t),!0}catch{return false}}function Fe(t){return /^([a-zA-Z0-9]+\.[a-zA-Z0-9]+|https?:\/\/.+)$/.test(t)}var Q={AADSTS50011:{message:"Redirect URI mismatch",fix:`Your redirect URI doesn't match what's configured in Azure AD.
310
+ ${warning.fix.split("\n").join("\n ")}`);
311
+ });
312
+ console.groupEnd();
313
+ }
314
+ console.groupEnd();
315
+ }
316
+ function isPlaceholderValue(value) {
317
+ const placeholders = [
318
+ "your-client-id",
319
+ "your-tenant-id",
320
+ "your-client-id-here",
321
+ "your-tenant-id-here",
322
+ "client-id",
323
+ "tenant-id",
324
+ "replace-me",
325
+ "changeme",
326
+ "placeholder",
327
+ "example",
328
+ "xxx",
329
+ "000"
330
+ ];
331
+ const lowerValue = value.toLowerCase();
332
+ return placeholders.some((placeholder) => lowerValue.includes(placeholder));
333
+ }
334
+ function isValidGuid(value) {
335
+ const guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
336
+ return guidRegex.test(value);
337
+ }
338
+ function isProductionUrl(url) {
339
+ return !url.includes("localhost") && !url.includes("127.0.0.1");
340
+ }
341
+ function isValidUrl(url) {
342
+ try {
343
+ new URL(url);
344
+ return true;
345
+ } catch {
346
+ return false;
347
+ }
348
+ }
349
+ function isValidScope2(scope) {
350
+ return /^([a-zA-Z0-9]+\.[a-zA-Z0-9]+|https?:\/\/.+)$/.test(scope);
351
+ }
352
+
353
+ // src/errors/MsalError.ts
354
+ var MSAL_ERROR_SOLUTIONS = {
355
+ // Redirect URI mismatch
356
+ "AADSTS50011": {
357
+ message: "Redirect URI mismatch",
358
+ fix: `Your redirect URI doesn't match what's configured in Azure AD.
57
359
 
58
360
  Fix:
59
361
  1. Go to Azure Portal \u2192 Azure Active Directory \u2192 App registrations
@@ -63,7 +365,13 @@ Fix:
63
365
  \u2022 https://yourdomain.com (for production)
64
366
  4. Click "Save"
65
367
 
66
- Current redirect URI: ${typeof window<"u"?window.location.origin:"unknown"}`,docs:"https://learn.microsoft.com/en-us/azure/active-directory/develop/reply-url"},AADSTS65001:{message:"Admin consent required",fix:`Your app requires admin consent for the requested permissions.
368
+ Current redirect URI: ${typeof window !== "undefined" ? window.location.origin : "unknown"}`,
369
+ docs: "https://learn.microsoft.com/en-us/azure/active-directory/develop/reply-url"
370
+ },
371
+ // Consent required
372
+ "AADSTS65001": {
373
+ message: "Admin consent required",
374
+ fix: `Your app requires admin consent for the requested permissions.
67
375
 
68
376
  Fix:
69
377
  1. Go to Azure Portal \u2192 Azure Active Directory \u2192 App registrations
@@ -71,53 +379,1942 @@ Fix:
71
379
  3. Click "Grant admin consent for [Your Organization]"
72
380
  4. Confirm the consent
73
381
 
74
- Alternatively, ask your Azure AD administrator to grant consent.`,docs:"https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-admin-consent"},AADSTS700016:{message:"Invalid client application",fix:`The application ID (client ID) is not found in the directory.
382
+ Alternatively, ask your Azure AD administrator to grant consent.`,
383
+ docs: "https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-admin-consent"
384
+ },
385
+ // Invalid client
386
+ "AADSTS700016": {
387
+ message: "Invalid client application",
388
+ fix: `The application ID (client ID) is not found in the directory.
75
389
 
76
390
  Fix:
77
391
  1. Verify your NEXT_PUBLIC_AZURE_AD_CLIENT_ID in .env.local
78
392
  2. Ensure the app registration exists in Azure Portal
79
393
  3. Check that you're using the correct tenant
80
394
 
81
- Current client ID: Check your environment variables`,docs:"https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes"},AADSTS90002:{message:"Invalid tenant",fix:`The tenant ID is invalid or not found.
395
+ Current client ID: Check your environment variables`,
396
+ docs: "https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes"
397
+ },
398
+ // Invalid tenant
399
+ "AADSTS90002": {
400
+ message: "Invalid tenant",
401
+ fix: `The tenant ID is invalid or not found.
82
402
 
83
403
  Fix:
84
404
  1. Verify your NEXT_PUBLIC_AZURE_AD_TENANT_ID in .env.local
85
405
  2. Ensure you're using the correct tenant ID (GUID format)
86
406
  3. For multi-tenant apps, use authorityType: 'common' instead
87
407
 
88
- Current tenant ID: Check your environment variables`,docs:"https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-national-cloud"},user_cancelled:{message:"User cancelled authentication",fix:"The user closed the authentication window or cancelled the login process. This is normal user behavior.",docs:void 0},no_token_request_cache_error:{message:"No cached token request",fix:`This usually happens when the page is refreshed during authentication.
408
+ Current tenant ID: Check your environment variables`,
409
+ docs: "https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-national-cloud"
410
+ },
411
+ // User cancelled
412
+ "user_cancelled": {
413
+ message: "User cancelled authentication",
414
+ fix: "The user closed the authentication window or cancelled the login process. This is normal user behavior.",
415
+ docs: void 0
416
+ },
417
+ // No token in cache
418
+ "no_token_request_cache_error": {
419
+ message: "No cached token request",
420
+ fix: `This usually happens when the page is refreshed during authentication.
89
421
 
90
422
  This is normal and will be handled automatically. If the issue persists:
91
423
  1. Clear your browser cache and cookies
92
424
  2. Try logging in again
93
- 3. Ensure cookies are enabled in your browser`,docs:void 0},interaction_required:{message:"User interaction required",fix:`The token cannot be acquired silently and requires user interaction.
425
+ 3. Ensure cookies are enabled in your browser`,
426
+ docs: void 0
427
+ },
428
+ // Interaction required
429
+ "interaction_required": {
430
+ message: "User interaction required",
431
+ fix: `The token cannot be acquired silently and requires user interaction.
94
432
 
95
- This is normal behavior. The app will redirect you to sign in.`,docs:void 0},consent_required:{message:"User consent required",fix:`Additional consent is required for the requested permissions.
433
+ This is normal behavior. The app will redirect you to sign in.`,
434
+ docs: void 0
435
+ },
436
+ // Consent required
437
+ "consent_required": {
438
+ message: "User consent required",
439
+ fix: `Additional consent is required for the requested permissions.
96
440
 
97
- This is normal behavior. You'll be prompted to grant consent.`,docs:void 0}},T=class t extends Error{constructor(e){let r=t.parseError(e);super(r.message),this.name="MsalError",this.code=r.code,this.fix=r.fix,this.docs=r.docs,this.originalError=e,Error.captureStackTrace&&Error.captureStackTrace(this,t);}static parseError(e){if(e&&typeof e=="object"){let r=e,o=r.errorCode||r.error||r.code;if(o&&Q[o]){let s=Q[o];return {message:s.message,code:o,fix:s.fix,docs:s.docs}}let i=r.errorMessage||r.message||String(e);for(let[s,p]of Object.entries(Q))if(i.includes(s))return {message:p.message,code:s,fix:p.fix,docs:p.docs};return {message:i||"Authentication error occurred",code:o}}return {message:"An unexpected authentication error occurred"}}toConsoleString(){if(!(process.env.NODE_ENV==="development"))return this.message;let r=`
441
+ This is normal behavior. You'll be prompted to grant consent.`,
442
+ docs: void 0
443
+ }
444
+ };
445
+ var MsalError = class _MsalError extends Error {
446
+ constructor(error) {
447
+ const errorInfo = _MsalError.parseError(error);
448
+ super(errorInfo.message);
449
+ this.name = "MsalError";
450
+ this.code = errorInfo.code;
451
+ this.fix = errorInfo.fix;
452
+ this.docs = errorInfo.docs;
453
+ this.originalError = error;
454
+ if (Error.captureStackTrace) {
455
+ Error.captureStackTrace(this, _MsalError);
456
+ }
457
+ }
458
+ /**
459
+ * Parse error and extract actionable information
460
+ */
461
+ static parseError(error) {
462
+ if (error && typeof error === "object") {
463
+ const err = error;
464
+ const errorCode = err.errorCode || err.error || err.code;
465
+ if (errorCode && MSAL_ERROR_SOLUTIONS[errorCode]) {
466
+ const solution = MSAL_ERROR_SOLUTIONS[errorCode];
467
+ return {
468
+ message: solution.message,
469
+ code: errorCode,
470
+ fix: solution.fix,
471
+ docs: solution.docs
472
+ };
473
+ }
474
+ const errorMessage = err.errorMessage || err.message || String(error);
475
+ for (const [code, solution] of Object.entries(MSAL_ERROR_SOLUTIONS)) {
476
+ if (errorMessage.includes(code)) {
477
+ return {
478
+ message: solution.message,
479
+ code,
480
+ fix: solution.fix,
481
+ docs: solution.docs
482
+ };
483
+ }
484
+ }
485
+ return {
486
+ message: errorMessage || "Authentication error occurred",
487
+ code: errorCode
488
+ };
489
+ }
490
+ return {
491
+ message: "An unexpected authentication error occurred"
492
+ };
493
+ }
494
+ /**
495
+ * Format error for console logging with colors (development only)
496
+ */
497
+ toConsoleString() {
498
+ const isDev = process.env.NODE_ENV === "development";
499
+ if (!isDev) {
500
+ return this.message;
501
+ }
502
+ let output = `
98
503
  \u{1F6A8} MSAL Authentication Error
99
- `;return r+=`
504
+ `;
505
+ output += `
100
506
  Error: ${this.message}
101
- `,this.code&&(r+=`Code: ${this.code}
102
- `),this.fix&&(r+=`
507
+ `;
508
+ if (this.code) {
509
+ output += `Code: ${this.code}
510
+ `;
511
+ }
512
+ if (this.fix) {
513
+ output += `
103
514
  \u{1F4A1} How to fix:
104
515
  ${this.fix}
105
- `),this.docs&&(r+=`
516
+ `;
517
+ }
518
+ if (this.docs) {
519
+ output += `
106
520
  \u{1F4DA} Documentation: ${this.docs}
107
- `),r}isUserCancellation(){return this.code==="user_cancelled"}requiresInteraction(){return this.code==="interaction_required"||this.code==="consent_required"}};function P(t){return t instanceof T?t:new T(t)}function $e(t){let e={errorCode:"missing_env_var",errorMessage:`Missing environment variable: ${t}`,message:`${t} not found`},r=`Environment variable ${t} is not set.
521
+ `;
522
+ }
523
+ return output;
524
+ }
525
+ /**
526
+ * Check if error is a user cancellation (not a real error)
527
+ */
528
+ isUserCancellation() {
529
+ return this.code === "user_cancelled";
530
+ }
531
+ /**
532
+ * Check if error requires user interaction
533
+ */
534
+ requiresInteraction() {
535
+ return this.code === "interaction_required" || this.code === "consent_required";
536
+ }
537
+ };
538
+ function wrapMsalError(error) {
539
+ if (error instanceof MsalError) {
540
+ return error;
541
+ }
542
+ return new MsalError(error);
543
+ }
544
+ function createMissingEnvVarError(varName) {
545
+ const error = {
546
+ errorCode: "missing_env_var",
547
+ errorMessage: `Missing environment variable: ${varName}`,
548
+ message: `${varName} not found`
549
+ };
550
+ const fix = `Environment variable ${varName} is not set.
108
551
 
109
552
  Fix:
110
553
  1. Copy .env.local.example to .env.local (if it exists)
111
554
  2. Add the following to your .env.local file:
112
555
 
113
- ${t}=your-value-here
556
+ ${varName}=your-value-here
114
557
 
115
558
  3. Get the value from Azure Portal:
116
559
  \u2022 Go to Azure Active Directory \u2192 App registrations
117
560
  \u2022 Select your app
118
- \u2022 Copy the ${t.includes("CLIENT_ID")?"Application (client) ID":"Directory (tenant) ID"}
561
+ \u2022 Copy the ${varName.includes("CLIENT_ID") ? "Application (client) ID" : "Directory (tenant) ID"}
119
562
 
120
563
  4. Restart your development server
121
564
 
122
- Note: Environment variables starting with NEXT_PUBLIC_ are exposed to the browser.`;return new T({...e,fix:r,docs:"https://nextjs.org/docs/basic-features/environment-variables"})}var Ae=null;function Be(){return Ae}function ee({children:t,loadingComponent:e,onInitialized:r,...o}){let[i,s]=useState(null),p=useRef(null);return useEffect(()=>{if(typeof window>"u"||p.current)return;(async()=>{try{if(process.env.NODE_ENV==="development"){let c=Y(o);K(c);}let l=X(o),a=new PublicClientApplication(l);await a.initialize();try{let c=await a.handleRedirectPromise();c&&(o.enableLogging&&console.log("[MSAL] Redirect authentication successful"),c.account&&a.setActiveAccount(c.account),window.location.hash&&window.history.replaceState(null,"",window.location.pathname+window.location.search));}catch(c){let n=P(c);n.code==="no_token_request_cache_error"?o.enableLogging&&console.log("[MSAL] No pending redirect found (this is normal)"):n.isUserCancellation()?o.enableLogging&&console.log("[MSAL] User cancelled authentication"):process.env.NODE_ENV==="development"?console.error(n.toConsoleString()):console.error("[MSAL] Redirect handling error:",n.message),window.location.hash&&(window.location.hash.includes("code=")||window.location.hash.includes("error="))&&window.history.replaceState(null,"",window.location.pathname+window.location.search);}let m=a.getAllAccounts();m.length>0&&!a.getActiveAccount()&&a.setActiveAccount(m[0]);let h=o.enableLogging||!1;a.addEventCallback(c=>{if(c.eventType===EventType.LOGIN_SUCCESS){let n=c.payload;n?.account&&a.setActiveAccount(n.account),h&&console.log("[MSAL] Login successful:",n.account?.username);}if(c.eventType===EventType.LOGIN_FAILURE&&console.error("[MSAL] Login failed:",c.error),c.eventType===EventType.LOGOUT_SUCCESS&&(a.setActiveAccount(null),h&&console.log("[MSAL] Logout successful")),c.eventType===EventType.ACQUIRE_TOKEN_SUCCESS){let n=c.payload;n?.account&&!a.getActiveAccount()&&a.setActiveAccount(n.account);}c.eventType===EventType.ACQUIRE_TOKEN_FAILURE&&h&&console.error("[MSAL] Token acquisition failed:",c.error);}),p.current=a,Ae=a,s(a),r&&r(a);}catch(l){throw console.error("[MSAL] Initialization failed:",l),l}})();},[]),typeof window>"u"?jsx(Fragment,{children:e||jsx("div",{children:"Loading authentication..."})}):i?jsx(MsalProvider,{instance:i,children:t}):jsx(Fragment,{children:e||jsx("div",{children:"Loading authentication..."})})}var We=createContext(void 0);function je({children:t,protection:e,...r}){return jsx(We.Provider,{value:e,children:jsx(ee,{...r,children:t})})}var te=new Map;function A(t=["User.Read"]){let{instance:e,accounts:r,inProgress:o}=useMsal(),i=useAccount(r[0]||null),s=useMemo(()=>r.length>0,[r]),p=useCallback(async(c=t)=>{if(o!==InteractionStatus.None){console.warn("[MSAL] Interaction already in progress");return}try{let n={scopes:c,prompt:"select_account"};await e.loginRedirect(n);}catch(n){let f=P(n);if(f.isUserCancellation()){console.log("[MSAL] User cancelled login");return}throw process.env.NODE_ENV==="development"?console.error(f.toConsoleString()):console.error("[MSAL] Login redirect failed:",f.message),f}},[e,t,o]),u=useCallback(async()=>{try{await e.logoutRedirect({account:i||void 0});}catch(c){let n=P(c);throw process.env.NODE_ENV==="development"?console.error(n.toConsoleString()):console.error("[MSAL] Logout redirect failed:",n.message),n}},[e,i]),l=useCallback(async(c=t)=>{if(!i)throw new Error("[MSAL] No active account. Please login first.");try{let n={scopes:c,account:i,forceRefresh:!1};return (await e.acquireTokenSilent(n)).accessToken}catch(n){let f=P(n);throw process.env.NODE_ENV==="development"?console.error(f.toConsoleString()):console.error("[MSAL] Silent token acquisition failed:",f.message),f}},[e,i,t]),a=useCallback(async(c=t)=>{if(!i)throw new Error("[MSAL] No active account. Please login first.");try{let n={scopes:c,account:i};await e.acquireTokenRedirect(n);}catch(n){let f=P(n);throw process.env.NODE_ENV==="development"?console.error(f.toConsoleString()):console.error("[MSAL] Token redirect acquisition failed:",f.message),f}},[e,i,t]),m=useCallback(async(c=t)=>{let n=`${i?.homeAccountId||"anonymous"}-${c.sort().join(",")}`,f=te.get(n);if(f)return f;let g=(async()=>{try{return await l(c)}catch{throw console.warn("[MSAL] Silent token acquisition failed, falling back to redirect"),await a(c),new Error("[MSAL] Redirecting for token acquisition")}finally{te.delete(n);}})();return te.set(n,g),g},[l,a,t,i]),h=useCallback(async()=>{e.setActiveAccount(null),await e.clearCache();},[e]);return {account:i,accounts:r,isAuthenticated:s,inProgress:o!==InteractionStatus.None,loginRedirect:p,logoutRedirect:u,acquireToken:m,acquireTokenSilent:l,acquireTokenRedirect:a,clearSession:h}}function Ke({text:t="Sign in with Microsoft",variant:e="dark",size:r="medium",scopes:o,className:i="",style:s,onSuccess:p,onError:u}){let{loginRedirect:l,inProgress:a}=A(),[m,h]=useState(false),c=async()=>{h(true);try{await l(o),p?.();}catch(y){u?.(y);}finally{setTimeout(()=>h(false),500);}},n={small:{padding:"8px 16px",fontSize:"14px",height:"36px"},medium:{padding:"10px 20px",fontSize:"15px",height:"41px"},large:{padding:"12px 24px",fontSize:"16px",height:"48px"}},f={dark:{backgroundColor:"#2F2F2F",color:"#FFFFFF",border:"1px solid #8C8C8C"},light:{backgroundColor:"#FFFFFF",color:"#5E5E5E",border:"1px solid #8C8C8C"}},g=a||m,d={display:"inline-flex",alignItems:"center",justifyContent:"center",gap:"12px",fontFamily:'"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',fontWeight:600,borderRadius:"2px",cursor:g?"not-allowed":"pointer",transition:"all 0.2s ease",opacity:g?.6:1,...f[e],...n[r],...s};return jsxs("button",{onClick:c,disabled:g,className:i,style:d,"aria-label":t,children:[jsx(Qe,{}),jsx("span",{children:t})]})}function Qe(){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 et({text:t="Sign out",variant:e="dark",size:r="medium",className:o="",style:i,onSuccess:s,onError:p}){let{logoutRedirect:u,inProgress:l}=A(),a=async()=>{try{await u(),s?.();}catch(n){p?.(n);}},m={small:{padding:"8px 16px",fontSize:"14px",height:"36px"},medium:{padding:"10px 20px",fontSize:"15px",height:"41px"},large:{padding:"12px 24px",fontSize:"16px",height:"48px"}},c={display:"inline-flex",alignItems:"center",justifyContent:"center",gap:"12px",fontFamily:'"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',fontWeight:600,borderRadius:"2px",cursor:l?"not-allowed":"pointer",transition:"all 0.2s ease",opacity:l?.6:1,...{dark:{backgroundColor:"#2F2F2F",color:"#FFFFFF",border:"1px solid #8C8C8C"},light:{backgroundColor:"#FFFFFF",color:"#5E5E5E",border:"1px solid #8C8C8C"}}[e],...m[r],...i};return jsxs("button",{onClick:a,disabled:l,className:o,style:c,"aria-label":t,children:[jsx(tt,{}),jsx("span",{children:t})]})}function tt(){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:t}=A(),e=useCallback(async(u,l={})=>{let{scopes:a=["User.Read"],version:m="v1.0",debug:h=false,...c}=l;try{let n=await t(a),f=`https://graph.microsoft.com/${m}`,g=u.startsWith("http")?u:`${f}${u.startsWith("/")?u:`/${u}`}`;h&&console.log("[GraphAPI] Request:",{url:g,method:c.method||"GET"});let d=await fetch(g,{...c,headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json",...c.headers}});if(!d.ok){let C=await d.text(),D=`Graph API error (${d.status}): ${C}`;throw new Error(D)}if(d.status===204||d.headers.get("content-length")==="0")return null;let y=await d.json();return h&&console.log("[GraphAPI] Response:",y),y}catch(n){let f=R(n);throw console.error("[GraphAPI] Request failed:",f),new Error(f)}},[t]),r=useCallback((u,l={})=>e(u,{...l,method:"GET"}),[e]),o=useCallback((u,l,a={})=>e(u,{...a,method:"POST",body:l?JSON.stringify(l):void 0}),[e]),i=useCallback((u,l,a={})=>e(u,{...a,method:"PUT",body:l?JSON.stringify(l):void 0}),[e]),s=useCallback((u,l,a={})=>e(u,{...a,method:"PATCH",body:l?JSON.stringify(l):void 0}),[e]),p=useCallback((u,l={})=>e(u,{...l,method:"DELETE"}),[e]);return {get:r,post:o,put:i,patch:s,delete:p,request:e}}var b=new Map,rt=300*1e3,Ce=100;function ot(){if(b.size>Ce){let t=Array.from(b.entries());t.sort((r,o)=>r[1].timestamp-o[1].timestamp),t.slice(0,b.size-Ce).forEach(([r])=>{let o=b.get(r);o?.data.photo&&URL.revokeObjectURL(o.data.photo),b.delete(r);});}}function oe(){let{isAuthenticated:t,account:e}=A(),r=k(),[o,i]=useState(null),[s,p]=useState(false),[u,l]=useState(null),a=useCallback(async()=>{if(!t||!e){i(null);return}let h=e.homeAccountId,c=b.get(h);if(c&&Date.now()-c.timestamp<rt){i(c.data);return}p(true),l(null);try{let n=await r.get("/me",{scopes:["User.Read"]}),f;try{let d=await r.get("/me/photo/$value",{scopes:["User.Read"],headers:{"Content-Type":"image/jpeg"}});d&&(f=URL.createObjectURL(d));}catch{console.debug("[UserProfile] Photo not available");}let g={id:n.id,displayName:n.displayName,givenName:n.givenName,surname:n.surname,userPrincipalName:n.userPrincipalName,mail:n.mail,jobTitle:n.jobTitle,department:n.department,companyName:n.companyName,officeLocation:n.officeLocation,mobilePhone:n.mobilePhone,businessPhones:n.businessPhones,preferredLanguage:n.preferredLanguage,employeeId:n.employeeId,employeeHireDate:n.employeeHireDate,employeeType:n.employeeType,country:n.country,city:n.city,state:n.state,streetAddress:n.streetAddress,postalCode:n.postalCode,usageLocation:n.usageLocation,manager:n.manager,aboutMe:n.aboutMe,birthday:n.birthday,interests:n.interests,skills:n.skills,schools:n.schools,pastProjects:n.pastProjects,responsibilities:n.responsibilities,mySite:n.mySite,faxNumber:n.faxNumber,accountEnabled:n.accountEnabled,ageGroup:n.ageGroup,userType:n.userType,photo:f,...n};b.set(h,{data:g,timestamp:Date.now()}),ot(),i(g);}catch(n){let g=R(n),d=new Error(g);l(d),console.error("[UserProfile] Failed to fetch profile:",g);}finally{p(false);}},[t,e,r]),m=useCallback(()=>{if(e){let h=b.get(e.homeAccountId);h?.data.photo&&URL.revokeObjectURL(h.data.photo),b.delete(e.homeAccountId);}o?.photo&&URL.revokeObjectURL(o.photo),i(null);},[e,o]);return useEffect(()=>(a(),()=>{o?.photo&&URL.revokeObjectURL(o.photo);}),[a]),useEffect(()=>()=>{o?.photo&&URL.revokeObjectURL(o.photo);},[o?.photo]),{profile:o,loading:s,error:u,refetch:a,clearCache:m}}function it({size:t=40,className:e="",style:r,showTooltip:o=true,fallbackImage:i}){let{profile:s,loading:p}=oe(),[u,l]=useState(null),[a,m]=useState(false);useEffect(()=>{s?.photo&&l(s.photo);},[s?.photo]);let h=()=>{if(!s)return "?";let{givenName:f,surname:g,displayName:d}=s;if(f&&g)return `${f[0]}${g[0]}`.toUpperCase();if(d){let y=d.split(" ");return y.length>=2?`${y[0][0]}${y[y.length-1][0]}`.toUpperCase():d.substring(0,2).toUpperCase()}return "?"},c={width:`${t}px`,height:`${t}px`,borderRadius:"50%",display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:`${t*.4}px`,fontWeight:600,fontFamily:'"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',backgroundColor:"#0078D4",color:"#FFFFFF",overflow:"hidden",userSelect:"none",...r},n=s?.displayName||"User";return p?jsx("div",{className:e,style:{...c,backgroundColor:"#E1E1E1"},"aria-label":"Loading user avatar",children:jsx("span",{style:{fontSize:`${t*.3}px`},children:"..."})}):u&&!a?jsx("div",{className:e,style:c,title:o?n:void 0,"aria-label":`${n} avatar`,children:jsx("img",{src:u,alt:n,style:{width:"100%",height:"100%",objectFit:"cover"},onError:()=>{m(true),i&&l(i);}})}):jsx("div",{className:e,style:c,title:o?n:void 0,"aria-label":`${n} avatar`,children:h()})}function st({className:t="",style:e,showDetails:r=false,renderLoading:o,renderAuthenticated:i,renderUnauthenticated:s}){let{isAuthenticated:p,inProgress:u,account:l}=A(),a={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(u)return o?jsx(Fragment,{children:o()}):jsxs("div",{className:t,style:{...a,backgroundColor:"#FFF4CE",color:"#8A6D3B"},role:"status","aria-live":"polite",children:[jsx(ne,{color:"#FFA500"}),jsx("span",{children:"Loading..."})]});if(p){let m=l?.username||l?.name||"User";return i?jsx(Fragment,{children:i(m)}):jsxs("div",{className:t,style:{...a,backgroundColor:"#D4EDDA",color:"#155724"},role:"status","aria-live":"polite",children:[jsx(ne,{color:"#28A745"}),jsx("span",{children:r?`Authenticated as ${m}`:"Authenticated"})]})}return s?jsx(Fragment,{children:s()}):jsxs("div",{className:t,style:{...a,backgroundColor:"#F8D7DA",color:"#721C24"},role:"status","aria-live":"polite",children:[jsx(ne,{color:"#DC3545"}),jsx("span",{children:"Not authenticated"})]})}function ne({color:t}){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:t})})}function le({children:t,loadingComponent:e,fallbackComponent:r,scopes:o,onAuthRequired:i}){let{isAuthenticated:s,inProgress:p,loginRedirect:u}=A();return useEffect(()=>{!s&&!p&&(i?.(),(async()=>{try{await u(o);}catch(a){console.error("[AuthGuard] Authentication failed:",a);}})());},[s,p,o,u,i]),p?jsx(Fragment,{children:e||jsx("div",{children:"Authenticating..."})}):s?jsx(Fragment,{children:t}):jsx(Fragment,{children:r||jsx("div",{children:"Redirecting to login..."})})}var ue=class extends Component{constructor(r){super(r);this.reset=()=>{this.setState({hasError:false,error:null});};this.state={hasError:false,error:null};}static getDerivedStateFromError(r){return {hasError:true,error:r}}componentDidCatch(r,o){let{onError:i,debug:s}=this.props;s&&(console.error("[ErrorBoundary] Caught error:",r),console.error("[ErrorBoundary] Error info:",o)),i?.(r,o);}render(){let{hasError:r,error:o}=this.state,{children:i,fallback:s}=this.props;return r&&o?s?s(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"})]}):i}};var E=new Map,dt=300*1e3,Te=100;function pt(t){t?E.delete(t):E.clear();}function ft(){if(E.size>Te){let t=Array.from(E.entries());t.sort((r,o)=>r[1].timestamp-o[1].timestamp),t.slice(0,E.size-Te).forEach(([r])=>E.delete(r));}}function gt(){let{isAuthenticated:t,account:e}=A(),r=k(),[o,i]=useState([]),[s,p]=useState([]),[u,l]=useState(false),[a,m]=useState(null),h=useCallback(async()=>{if(!t||!e){i([]),p([]);return}let d=e.homeAccountId,y=E.get(d);if(y&&Date.now()-y.timestamp<dt){i(y.roles),p(y.groups);return}l(true),m(null);try{let D=e.idTokenClaims?.roles||[],G=(await r.get("/me/memberOf",{scopes:["User.Read","Directory.Read.All"]})).value.map(pe=>pe.id);E.set(d,{roles:D,groups:G,timestamp:Date.now()}),ft(),i(D),p(G);}catch(C){let W=R(C),G=new Error(W);m(G),console.error("[Roles] Failed to fetch roles/groups:",W);let Me=e.idTokenClaims?.roles||[];i(Me);}finally{l(false);}},[t,e,r]),c=useCallback(d=>o.includes(d),[o]),n=useCallback(d=>s.includes(d),[s]),f=useCallback(d=>d.some(y=>o.includes(y)),[o]),g=useCallback(d=>d.every(y=>o.includes(y)),[o]);return useEffect(()=>(h(),()=>{e&&pt(e.homeAccountId);}),[h,e]),{roles:o,groups:s,loading:u,error:a,hasRole:c,hasGroup:n,hasAnyRole:f,hasAllRoles:g,refetch:h}}function ht(t,e={}){let{displayName:r,...o}=e,i=s=>jsx(le,{...o,children:jsx(t,{...s})});return i.displayName=r||`withAuth(${t.displayName||t.name||"Component"})`,i}async function Ue(t,e={}){let{maxRetries:r=3,initialDelay:o=1e3,maxDelay:i=1e4,backoffMultiplier:s=2,debug:p=false}=e,u,l=o;for(let a=0;a<=r;a++)try{return p&&a>0&&console.log(`[TokenRetry] Attempt ${a+1}/${r+1}`),await t()}catch(m){if(u=m,a===r){p&&console.error("[TokenRetry] All retry attempts failed");break}if(!mt(m))throw p&&console.log("[TokenRetry] Non-retryable error, aborting"),m;p&&console.warn(`[TokenRetry] Attempt ${a+1} failed, retrying in ${l}ms...`),await yt(l),l=Math.min(l*s,i);}throw u}function mt(t){let e=t.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 yt(t){return new Promise(e=>setTimeout(e,t))}function At(t,e={}){return (...r)=>Ue(()=>t(...r),e)}var B=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 r=["error","warn","info","debug"],o=r.indexOf(this.config.level);return r.indexOf(e)<=o}formatMessage(e,r,o){let i=this.config.showTimestamp?`[${new Date().toISOString()}]`:"",s=this.config.prefix,p=`[${e.toUpperCase()}]`,u=`${i} ${s} ${p} ${r}`;return o!==void 0&&(u+=`
123
- `+JSON.stringify(o,null,2)),u}addToHistory(e,r,o){this.logHistory.length>=this.config.maxHistorySize&&this.logHistory.shift(),this.logHistory.push({timestamp:Date.now(),level:e,message:r,data:o});}error(e,r){this.shouldLog("error")&&(console.error(this.formatMessage("error",e,r)),this.addToHistory("error",e,r));}warn(e,r){this.shouldLog("warn")&&(console.warn(this.formatMessage("warn",e,r)),this.addToHistory("warn",e,r));}info(e,r){this.shouldLog("info")&&(console.info(this.formatMessage("info",e,r)),this.addToHistory("info",e,r));}debug(e,r){this.shouldLog("debug")&&(console.debug(this.formatMessage("debug",e,r)),this.addToHistory("debug",e,r));}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 r=this.performanceTimings.get(e);if(r)return r.endTime=performance.now(),r.duration=r.endTime-r.startTime,this.info(`\u23F1\uFE0F Completed: ${e} (${r.duration.toFixed(2)}ms)`),r.duration}}logRequest(e,r,o){this.config.enableNetworkLogs&&this.debug(`\u{1F310} ${e} ${r}`,o);}logResponse(e,r,o,i){if(this.config.enableNetworkLogs){let s=o>=200&&o<300?"\u2705":"\u274C";this.debug(`${s} ${e} ${r} - ${o}`,i);}}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 r=this.exportLogs(),o=new Blob([r],{type:"application/json"}),i=URL.createObjectURL(o),s=document.createElement("a");s.href=i,s.download=e,s.click(),URL.revokeObjectURL(i);}setEnabled(e){this.config.enabled=e;}setLevel(e){e&&(this.config.level=e);}},O=null;function vt(t){return O?t&&(t.enabled!==void 0&&O.setEnabled(t.enabled),t.level&&O.setLevel(t.level)):O=new B(t),O}function xt(t,e){return new B({...e,prefix:`[MSAL-Next:${t}]`})}function H({children:t,config:e,defaultRedirectTo:r="/login",defaultLoading:o,defaultUnauthorized:i,debug:s=false}){let p=useRouter(),{isAuthenticated:u,account:l,inProgress:a}=A(),[m,h]=useState(true),[c,n]=useState(false);return useEffect(()=>{async function f(){if(s&&console.log("[ProtectedPage] Checking auth...",{isAuthenticated:u,inProgress:a,config:e}),!a){if(!e.required){n(true),h(false);return}if(!u||!l){s&&console.log("[ProtectedPage] Not authenticated, redirecting...");let g=e.redirectTo||r,d=encodeURIComponent(window.location.pathname+window.location.search);p.push(`${g}?returnUrl=${d}`);return}if(e.roles&&e.roles.length>0){let g=l.idTokenClaims?.roles||[];if(!e.roles.some(y=>g.includes(y))){s&&console.log("[ProtectedPage] Missing required role",{required:e.roles,user:g}),n(false),h(false);return}}if(e.validate)try{if(!await e.validate(l)){s&&console.log("[ProtectedPage] Custom validation failed"),n(!1),h(!1);return}}catch(g){console.error("[ProtectedPage] Validation error:",g),n(false),h(false);return}s&&console.log("[ProtectedPage] Authorization successful"),n(true),h(false);}}f();},[u,l,a,e,p,r,s]),m||a?e.loading?jsx(Fragment,{children:e.loading}):o?jsx(Fragment,{children:o}):jsx("div",{className:"flex items-center justify-center min-h-screen",children:jsx("div",{className:"animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"})}):c?jsx(Fragment,{children:t}):e.unauthorized?jsx(Fragment,{children:e.unauthorized}):i?jsx(Fragment,{children:i}):jsx("div",{className:"flex items-center justify-center min-h-screen",children:jsxs("div",{className:"text-center",children:[jsx("h1",{className:"text-2xl font-bold text-gray-900 mb-2",children:"Access Denied"}),jsx("p",{className:"text-gray-600",children:"You don't have permission to access this page."})]})})}function De(t,e,r){let o=i=>jsx(H,{config:e,defaultRedirectTo:r?.defaultRedirectTo,defaultLoading:r?.defaultLoading,defaultUnauthorized:r?.defaultUnauthorized,debug:r?.debug,children:jsx(t,{...i})});return o.displayName=`withPageAuth(${t.displayName||t.name||"Component"})`,o}function Rt(t={}){let{protectedRoutes:e=[],publicOnlyRoutes:r=[],loginPath:o="/login",redirectAfterLogin:i="/",sessionCookie:s="msal.account",isAuthenticated:p,debug:u=false}=t;return async function(a){let{pathname:m}=a.nextUrl;u&&console.log("[AuthMiddleware] Processing:",m);let h=false;p?h=await p(a):h=!!a.cookies.get(s)?.value,u&&console.log("[AuthMiddleware] Authenticated:",h);let c=e.some(g=>m.startsWith(g)),n=r.some(g=>m.startsWith(g));if(c&&!h){u&&console.log("[AuthMiddleware] Redirecting to login");let g=a.nextUrl.clone();return g.pathname=o,g.searchParams.set("returnUrl",m),NextResponse.redirect(g)}if(n&&h){u&&console.log("[AuthMiddleware] Redirecting to home");let g=a.nextUrl.searchParams.get("returnUrl"),d=a.nextUrl.clone();return d.pathname=g||i,d.searchParams.delete("returnUrl"),NextResponse.redirect(d)}let f=NextResponse.next();if(h){f.headers.set("x-msal-authenticated","true");try{let g=a.cookies.get(s);if(g?.value){let d=j(g.value,Z);d?.username&&f.headers.set("x-msal-username",d.username);}}catch{u&&console.warn("[AuthMiddleware] Failed to parse session data");}}return f}}export{le as AuthGuard,st as AuthStatus,ue as ErrorBoundary,je as MSALProvider,Ke as MicrosoftSignInButton,ee as MsalAuthProvider,T as MsalError,H as ProtectedPage,et as SignOutButton,it as UserAvatar,Rt as createAuthMiddleware,$e as createMissingEnvVarError,X as createMsalConfig,At as createRetryWrapper,xt as createScopedLogger,K as displayValidationResults,vt as getDebugLogger,Be as getMsalInstance,Z as isValidAccountData,V as isValidRedirectUri,fe as isValidScope,Ue as retryWithBackoff,j as safeJsonParse,R as sanitizeError,k as useGraphApi,A as useMsalAuth,gt as useRoles,oe as useUserProfile,Y as validateConfig,Ne as validateScopes,ht as withAuth,De as withPageAuth,P as wrapMsalError};
565
+ Note: Environment variables starting with NEXT_PUBLIC_ are exposed to the browser.`;
566
+ return new MsalError({
567
+ ...error,
568
+ fix,
569
+ docs: "https://nextjs.org/docs/basic-features/environment-variables"
570
+ });
571
+ }
572
+
573
+ // src/hooks/useTokenRefresh.ts
574
+ import { useEffect, useRef, useCallback as useCallback2 } from "react";
575
+
576
+ // src/hooks/useMsalAuth.ts
577
+ import { useMsal, useAccount } from "@azure/msal-react";
578
+ import { InteractionStatus } from "@azure/msal-browser";
579
+ import { useCallback, useMemo } from "react";
580
+ var pendingTokenRequests = /* @__PURE__ */ new Map();
581
+ function useMsalAuth(defaultScopes = ["User.Read"]) {
582
+ const { instance, accounts, inProgress } = useMsal();
583
+ const account = useAccount(accounts[0] || null);
584
+ const isAuthenticated = useMemo(() => accounts.length > 0, [accounts]);
585
+ const loginRedirect = useCallback(
586
+ async (scopes = defaultScopes) => {
587
+ if (inProgress !== InteractionStatus.None) {
588
+ console.warn("[MSAL] Interaction already in progress");
589
+ return;
590
+ }
591
+ try {
592
+ const request = {
593
+ scopes,
594
+ prompt: "select_account"
595
+ };
596
+ await instance.loginRedirect(request);
597
+ } catch (error) {
598
+ const msalError = wrapMsalError(error);
599
+ if (msalError.isUserCancellation()) {
600
+ console.log("[MSAL] User cancelled login");
601
+ return;
602
+ }
603
+ if (process.env.NODE_ENV === "development") {
604
+ console.error(msalError.toConsoleString());
605
+ } else {
606
+ console.error("[MSAL] Login redirect failed:", msalError.message);
607
+ }
608
+ throw msalError;
609
+ }
610
+ },
611
+ [instance, defaultScopes, inProgress]
612
+ );
613
+ const logoutRedirect = useCallback(async () => {
614
+ try {
615
+ await instance.logoutRedirect({
616
+ account: account || void 0
617
+ });
618
+ } catch (error) {
619
+ const msalError = wrapMsalError(error);
620
+ if (process.env.NODE_ENV === "development") {
621
+ console.error(msalError.toConsoleString());
622
+ } else {
623
+ console.error("[MSAL] Logout redirect failed:", msalError.message);
624
+ }
625
+ throw msalError;
626
+ }
627
+ }, [instance, account]);
628
+ const acquireTokenSilent = useCallback(
629
+ async (scopes = defaultScopes) => {
630
+ if (!account) {
631
+ throw new Error("[MSAL] No active account. Please login first.");
632
+ }
633
+ try {
634
+ const request = {
635
+ scopes,
636
+ account,
637
+ forceRefresh: false
638
+ };
639
+ const response = await instance.acquireTokenSilent(request);
640
+ return response.accessToken;
641
+ } catch (error) {
642
+ const msalError = wrapMsalError(error);
643
+ if (process.env.NODE_ENV === "development") {
644
+ console.error(msalError.toConsoleString());
645
+ } else {
646
+ console.error("[MSAL] Silent token acquisition failed:", msalError.message);
647
+ }
648
+ throw msalError;
649
+ }
650
+ },
651
+ [instance, account, defaultScopes]
652
+ );
653
+ const acquireTokenRedirect = useCallback(
654
+ async (scopes = defaultScopes) => {
655
+ if (!account) {
656
+ throw new Error("[MSAL] No active account. Please login first.");
657
+ }
658
+ try {
659
+ const request = {
660
+ scopes,
661
+ account
662
+ };
663
+ await instance.acquireTokenRedirect(request);
664
+ } catch (error) {
665
+ const msalError = wrapMsalError(error);
666
+ if (process.env.NODE_ENV === "development") {
667
+ console.error(msalError.toConsoleString());
668
+ } else {
669
+ console.error("[MSAL] Token redirect acquisition failed:", msalError.message);
670
+ }
671
+ throw msalError;
672
+ }
673
+ },
674
+ [instance, account, defaultScopes]
675
+ );
676
+ const acquireToken = useCallback(
677
+ async (scopes = defaultScopes) => {
678
+ const requestKey = `${account?.homeAccountId || "anonymous"}-${scopes.sort().join(",")}`;
679
+ const pendingRequest = pendingTokenRequests.get(requestKey);
680
+ if (pendingRequest) {
681
+ return pendingRequest;
682
+ }
683
+ const tokenRequest = (async () => {
684
+ try {
685
+ return await acquireTokenSilent(scopes);
686
+ } catch (error) {
687
+ console.warn("[MSAL] Silent token acquisition failed, falling back to redirect");
688
+ await acquireTokenRedirect(scopes);
689
+ throw new Error("[MSAL] Redirecting for token acquisition");
690
+ } finally {
691
+ pendingTokenRequests.delete(requestKey);
692
+ }
693
+ })();
694
+ pendingTokenRequests.set(requestKey, tokenRequest);
695
+ return tokenRequest;
696
+ },
697
+ [acquireTokenSilent, acquireTokenRedirect, defaultScopes, account]
698
+ );
699
+ const clearSession = useCallback(async () => {
700
+ instance.setActiveAccount(null);
701
+ await instance.clearCache();
702
+ }, [instance]);
703
+ return {
704
+ account,
705
+ accounts,
706
+ isAuthenticated,
707
+ inProgress: inProgress !== InteractionStatus.None,
708
+ loginRedirect,
709
+ logoutRedirect,
710
+ acquireToken,
711
+ acquireTokenSilent,
712
+ acquireTokenRedirect,
713
+ clearSession
714
+ };
715
+ }
716
+
717
+ // src/hooks/useTokenRefresh.ts
718
+ function useTokenRefresh(options = {}) {
719
+ const {
720
+ enabled = true,
721
+ refreshBeforeExpiry = 300,
722
+ // 5 minutes
723
+ scopes = ["User.Read"],
724
+ onRefresh,
725
+ onError
726
+ } = options;
727
+ const { isAuthenticated, account, acquireTokenSilent } = useMsalAuth();
728
+ const intervalRef = useRef(null);
729
+ const lastRefreshRef = useRef(null);
730
+ const expiresInRef = useRef(null);
731
+ const refresh = useCallback2(async () => {
732
+ if (!isAuthenticated || !account) {
733
+ return;
734
+ }
735
+ try {
736
+ await acquireTokenSilent(scopes);
737
+ lastRefreshRef.current = /* @__PURE__ */ new Date();
738
+ const expiresIn = 3600;
739
+ expiresInRef.current = expiresIn;
740
+ onRefresh?.(expiresIn);
741
+ } catch (error) {
742
+ console.error("[TokenRefresh] Failed to refresh token:", error);
743
+ onError?.(error);
744
+ }
745
+ }, [isAuthenticated, account, acquireTokenSilent, scopes, onRefresh, onError]);
746
+ useEffect(() => {
747
+ if (!enabled || !isAuthenticated) {
748
+ return;
749
+ }
750
+ refresh();
751
+ intervalRef.current = setInterval(() => {
752
+ if (!expiresInRef.current) {
753
+ return;
754
+ }
755
+ const timeSinceRefresh = lastRefreshRef.current ? (Date.now() - lastRefreshRef.current.getTime()) / 1e3 : 0;
756
+ const remainingTime = expiresInRef.current - timeSinceRefresh;
757
+ expiresInRef.current = Math.max(0, remainingTime);
758
+ if (remainingTime <= refreshBeforeExpiry && remainingTime > 0) {
759
+ refresh();
760
+ }
761
+ }, 6e4);
762
+ return () => {
763
+ if (intervalRef.current) {
764
+ clearInterval(intervalRef.current);
765
+ }
766
+ };
767
+ }, [enabled, isAuthenticated, refreshBeforeExpiry, refresh]);
768
+ const isExpiringSoon = expiresInRef.current !== null && expiresInRef.current <= refreshBeforeExpiry;
769
+ return {
770
+ expiresIn: expiresInRef.current,
771
+ isExpiringSoon,
772
+ refresh,
773
+ lastRefresh: lastRefreshRef.current
774
+ };
775
+ }
776
+
777
+ // src/components/TokenRefreshManager.tsx
778
+ function TokenRefreshManager({
779
+ enabled,
780
+ refreshBeforeExpiry = 300,
781
+ scopes = ["User.Read"],
782
+ enableLogging = false
783
+ }) {
784
+ useTokenRefresh({
785
+ enabled,
786
+ refreshBeforeExpiry,
787
+ scopes,
788
+ onRefresh: (expiresIn) => {
789
+ if (enableLogging) {
790
+ console.log(`[TokenRefresh] Token refreshed successfully. Expires in ${expiresIn} seconds.`);
791
+ }
792
+ },
793
+ onError: (error) => {
794
+ if (enableLogging) {
795
+ console.error("[TokenRefresh] Failed to refresh token:", error);
796
+ }
797
+ }
798
+ });
799
+ return null;
800
+ }
801
+
802
+ // src/components/MsalAuthProvider.tsx
803
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
804
+ var globalMsalInstance = null;
805
+ function getMsalInstance() {
806
+ return globalMsalInstance;
807
+ }
808
+ function MsalAuthProvider({
809
+ children,
810
+ loadingComponent,
811
+ onInitialized,
812
+ autoRefreshToken = false,
813
+ refreshBeforeExpiry = 300,
814
+ ...config
815
+ }) {
816
+ const [msalInstance, setMsalInstance] = useState(null);
817
+ const instanceRef = useRef2(null);
818
+ const { scopes = ["User.Read"], enableLogging = false } = config;
819
+ useEffect2(() => {
820
+ if (typeof window === "undefined") {
821
+ return;
822
+ }
823
+ if (instanceRef.current) {
824
+ return;
825
+ }
826
+ const initializeMsal = async () => {
827
+ try {
828
+ if (process.env.NODE_ENV === "development") {
829
+ const validationResult = validateConfig(config);
830
+ displayValidationResults(validationResult);
831
+ }
832
+ const msalConfig = createMsalConfig(config);
833
+ const instance = new PublicClientApplication(msalConfig);
834
+ await instance.initialize();
835
+ try {
836
+ const response = await instance.handleRedirectPromise();
837
+ if (response) {
838
+ if (config.enableLogging) {
839
+ console.log("[MSAL] Redirect authentication successful");
840
+ }
841
+ if (response.account) {
842
+ instance.setActiveAccount(response.account);
843
+ }
844
+ if (window.location.hash) {
845
+ window.history.replaceState(null, "", window.location.pathname + window.location.search);
846
+ }
847
+ }
848
+ } catch (redirectError) {
849
+ const msalError = wrapMsalError(redirectError);
850
+ if (msalError.code === "no_token_request_cache_error") {
851
+ if (config.enableLogging) {
852
+ console.log("[MSAL] No pending redirect found (this is normal)");
853
+ }
854
+ } else if (msalError.isUserCancellation()) {
855
+ if (config.enableLogging) {
856
+ console.log("[MSAL] User cancelled authentication");
857
+ }
858
+ } else {
859
+ if (process.env.NODE_ENV === "development") {
860
+ console.error(msalError.toConsoleString());
861
+ } else {
862
+ console.error("[MSAL] Redirect handling error:", msalError.message);
863
+ }
864
+ }
865
+ if (window.location.hash && (window.location.hash.includes("code=") || window.location.hash.includes("error="))) {
866
+ window.history.replaceState(null, "", window.location.pathname + window.location.search);
867
+ }
868
+ }
869
+ const accounts = instance.getAllAccounts();
870
+ if (accounts.length > 0 && !instance.getActiveAccount()) {
871
+ instance.setActiveAccount(accounts[0]);
872
+ }
873
+ const loggingEnabled = config.enableLogging || false;
874
+ instance.addEventCallback((event) => {
875
+ if (event.eventType === EventType.LOGIN_SUCCESS) {
876
+ const payload = event.payload;
877
+ if (payload?.account) {
878
+ instance.setActiveAccount(payload.account);
879
+ }
880
+ if (loggingEnabled) {
881
+ console.log("[MSAL] Login successful:", payload.account?.username);
882
+ }
883
+ }
884
+ if (event.eventType === EventType.LOGIN_FAILURE) {
885
+ console.error("[MSAL] Login failed:", event.error);
886
+ }
887
+ if (event.eventType === EventType.LOGOUT_SUCCESS) {
888
+ instance.setActiveAccount(null);
889
+ if (loggingEnabled) {
890
+ console.log("[MSAL] Logout successful");
891
+ }
892
+ }
893
+ if (event.eventType === EventType.ACQUIRE_TOKEN_SUCCESS) {
894
+ const payload = event.payload;
895
+ if (payload?.account && !instance.getActiveAccount()) {
896
+ instance.setActiveAccount(payload.account);
897
+ }
898
+ }
899
+ if (event.eventType === EventType.ACQUIRE_TOKEN_FAILURE) {
900
+ if (loggingEnabled) {
901
+ console.error("[MSAL] Token acquisition failed:", event.error);
902
+ }
903
+ }
904
+ });
905
+ instanceRef.current = instance;
906
+ globalMsalInstance = instance;
907
+ setMsalInstance(instance);
908
+ if (onInitialized) {
909
+ onInitialized(instance);
910
+ }
911
+ } catch (error) {
912
+ console.error("[MSAL] Initialization failed:", error);
913
+ throw error;
914
+ }
915
+ };
916
+ initializeMsal();
917
+ }, []);
918
+ if (typeof window === "undefined") {
919
+ return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx("div", { children: "Loading authentication..." }) });
920
+ }
921
+ if (!msalInstance) {
922
+ return /* @__PURE__ */ jsx(Fragment, { children: loadingComponent || /* @__PURE__ */ jsx("div", { children: "Loading authentication..." }) });
923
+ }
924
+ return /* @__PURE__ */ jsxs(MsalProvider, { instance: msalInstance, children: [
925
+ autoRefreshToken && /* @__PURE__ */ jsx(
926
+ TokenRefreshManager,
927
+ {
928
+ enabled: autoRefreshToken,
929
+ refreshBeforeExpiry,
930
+ scopes,
931
+ enableLogging
932
+ }
933
+ ),
934
+ children
935
+ ] });
936
+ }
937
+
938
+ // src/components/MSALProvider.tsx
939
+ import { createContext, useContext } from "react";
940
+ import { jsx as jsx2 } from "react/jsx-runtime";
941
+ var ProtectionConfigContext = createContext(void 0);
942
+ function MSALProvider({ children, protection, ...props }) {
943
+ return /* @__PURE__ */ jsx2(ProtectionConfigContext.Provider, { value: protection, children: /* @__PURE__ */ jsx2(MsalAuthProvider, { ...props, children }) });
944
+ }
945
+
946
+ // src/components/MicrosoftSignInButton.tsx
947
+ import { useState as useState2, useEffect as useEffect3, useRef as useRef3 } from "react";
948
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
949
+ function MicrosoftSignInButton({
950
+ text = "Sign in with Microsoft",
951
+ variant = "dark",
952
+ size = "medium",
953
+ scopes,
954
+ className = "",
955
+ style,
956
+ onSuccess,
957
+ onError
958
+ }) {
959
+ const { loginRedirect, inProgress, isAuthenticated } = useMsalAuth();
960
+ const [isLoading, setIsLoading] = useState2(false);
961
+ const timeoutRef = useRef3(null);
962
+ useEffect3(() => {
963
+ return () => {
964
+ if (timeoutRef.current) {
965
+ clearTimeout(timeoutRef.current);
966
+ }
967
+ };
968
+ }, []);
969
+ useEffect3(() => {
970
+ if (isAuthenticated && isLoading) {
971
+ setIsLoading(false);
972
+ }
973
+ }, [isAuthenticated, isLoading]);
974
+ const handleClick = async () => {
975
+ if (inProgress || isLoading || isAuthenticated) {
976
+ return;
977
+ }
978
+ setIsLoading(true);
979
+ try {
980
+ await loginRedirect(scopes);
981
+ onSuccess?.();
982
+ } catch (error) {
983
+ onError?.(error);
984
+ setIsLoading(false);
985
+ }
986
+ timeoutRef.current = setTimeout(() => {
987
+ setIsLoading(false);
988
+ }, 3e3);
989
+ };
990
+ const sizeStyles = {
991
+ small: {
992
+ padding: "8px 16px",
993
+ fontSize: "14px",
994
+ height: "36px"
995
+ },
996
+ medium: {
997
+ padding: "10px 20px",
998
+ fontSize: "15px",
999
+ height: "41px"
1000
+ },
1001
+ large: {
1002
+ padding: "12px 24px",
1003
+ fontSize: "16px",
1004
+ height: "48px"
1005
+ }
1006
+ };
1007
+ const variantStyles = {
1008
+ dark: {
1009
+ backgroundColor: "#2F2F2F",
1010
+ color: "#FFFFFF",
1011
+ border: "1px solid #8C8C8C"
1012
+ },
1013
+ light: {
1014
+ backgroundColor: "#FFFFFF",
1015
+ color: "#5E5E5E",
1016
+ border: "1px solid #8C8C8C"
1017
+ }
1018
+ };
1019
+ const isDisabled = inProgress || isLoading;
1020
+ const baseStyles = {
1021
+ display: "inline-flex",
1022
+ alignItems: "center",
1023
+ justifyContent: "center",
1024
+ gap: "12px",
1025
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
1026
+ fontWeight: 600,
1027
+ borderRadius: "2px",
1028
+ cursor: isDisabled ? "not-allowed" : "pointer",
1029
+ transition: "all 0.2s ease",
1030
+ opacity: isDisabled ? 0.6 : 1,
1031
+ ...variantStyles[variant],
1032
+ ...sizeStyles[size],
1033
+ ...style
1034
+ };
1035
+ return /* @__PURE__ */ jsxs2(
1036
+ "button",
1037
+ {
1038
+ onClick: handleClick,
1039
+ disabled: isDisabled,
1040
+ className,
1041
+ style: baseStyles,
1042
+ "aria-label": text,
1043
+ children: [
1044
+ /* @__PURE__ */ jsx3(MicrosoftLogo, {}),
1045
+ /* @__PURE__ */ jsx3("span", { children: text })
1046
+ ]
1047
+ }
1048
+ );
1049
+ }
1050
+ function MicrosoftLogo() {
1051
+ return /* @__PURE__ */ jsxs2("svg", { width: "21", height: "21", viewBox: "0 0 21 21", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1052
+ /* @__PURE__ */ jsx3("rect", { width: "10", height: "10", fill: "#F25022" }),
1053
+ /* @__PURE__ */ jsx3("rect", { x: "11", width: "10", height: "10", fill: "#7FBA00" }),
1054
+ /* @__PURE__ */ jsx3("rect", { y: "11", width: "10", height: "10", fill: "#00A4EF" }),
1055
+ /* @__PURE__ */ jsx3("rect", { x: "11", y: "11", width: "10", height: "10", fill: "#FFB900" })
1056
+ ] });
1057
+ }
1058
+
1059
+ // src/components/SignOutButton.tsx
1060
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1061
+ function SignOutButton({
1062
+ text = "Sign out",
1063
+ variant = "dark",
1064
+ size = "medium",
1065
+ className = "",
1066
+ style,
1067
+ onSuccess,
1068
+ onError
1069
+ }) {
1070
+ const { logoutRedirect, inProgress } = useMsalAuth();
1071
+ const handleClick = async () => {
1072
+ try {
1073
+ await logoutRedirect();
1074
+ onSuccess?.();
1075
+ } catch (error) {
1076
+ onError?.(error);
1077
+ }
1078
+ };
1079
+ const sizeStyles = {
1080
+ small: {
1081
+ padding: "8px 16px",
1082
+ fontSize: "14px",
1083
+ height: "36px"
1084
+ },
1085
+ medium: {
1086
+ padding: "10px 20px",
1087
+ fontSize: "15px",
1088
+ height: "41px"
1089
+ },
1090
+ large: {
1091
+ padding: "12px 24px",
1092
+ fontSize: "16px",
1093
+ height: "48px"
1094
+ }
1095
+ };
1096
+ const variantStyles = {
1097
+ dark: {
1098
+ backgroundColor: "#2F2F2F",
1099
+ color: "#FFFFFF",
1100
+ border: "1px solid #8C8C8C"
1101
+ },
1102
+ light: {
1103
+ backgroundColor: "#FFFFFF",
1104
+ color: "#5E5E5E",
1105
+ border: "1px solid #8C8C8C"
1106
+ }
1107
+ };
1108
+ const baseStyles = {
1109
+ display: "inline-flex",
1110
+ alignItems: "center",
1111
+ justifyContent: "center",
1112
+ gap: "12px",
1113
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
1114
+ fontWeight: 600,
1115
+ borderRadius: "2px",
1116
+ cursor: inProgress ? "not-allowed" : "pointer",
1117
+ transition: "all 0.2s ease",
1118
+ opacity: inProgress ? 0.6 : 1,
1119
+ ...variantStyles[variant],
1120
+ ...sizeStyles[size],
1121
+ ...style
1122
+ };
1123
+ return /* @__PURE__ */ jsxs3(
1124
+ "button",
1125
+ {
1126
+ onClick: handleClick,
1127
+ disabled: inProgress,
1128
+ className,
1129
+ style: baseStyles,
1130
+ "aria-label": text,
1131
+ children: [
1132
+ /* @__PURE__ */ jsx4(MicrosoftLogo2, {}),
1133
+ /* @__PURE__ */ jsx4("span", { children: text })
1134
+ ]
1135
+ }
1136
+ );
1137
+ }
1138
+ function MicrosoftLogo2() {
1139
+ return /* @__PURE__ */ jsxs3("svg", { width: "21", height: "21", viewBox: "0 0 21 21", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1140
+ /* @__PURE__ */ jsx4("rect", { width: "10", height: "10", fill: "#F25022" }),
1141
+ /* @__PURE__ */ jsx4("rect", { x: "11", width: "10", height: "10", fill: "#7FBA00" }),
1142
+ /* @__PURE__ */ jsx4("rect", { y: "11", width: "10", height: "10", fill: "#00A4EF" }),
1143
+ /* @__PURE__ */ jsx4("rect", { x: "11", y: "11", width: "10", height: "10", fill: "#FFB900" })
1144
+ ] });
1145
+ }
1146
+
1147
+ // src/components/UserAvatar.tsx
1148
+ import { useEffect as useEffect5, useState as useState4 } from "react";
1149
+
1150
+ // src/hooks/useUserProfile.ts
1151
+ import { useState as useState3, useEffect as useEffect4, useCallback as useCallback4 } from "react";
1152
+
1153
+ // src/hooks/useGraphApi.ts
1154
+ import { useCallback as useCallback3 } from "react";
1155
+ function useGraphApi() {
1156
+ const { acquireToken } = useMsalAuth();
1157
+ const request = useCallback3(
1158
+ async (endpoint, options = {}) => {
1159
+ const {
1160
+ scopes = ["User.Read"],
1161
+ version = "v1.0",
1162
+ debug = false,
1163
+ ...fetchOptions
1164
+ } = options;
1165
+ try {
1166
+ const token = await acquireToken(scopes);
1167
+ const baseUrl = `https://graph.microsoft.com/${version}`;
1168
+ const url = endpoint.startsWith("http") ? endpoint : `${baseUrl}${endpoint.startsWith("/") ? endpoint : `/${endpoint}`}`;
1169
+ if (debug) {
1170
+ console.log("[GraphAPI] Request:", { url, method: fetchOptions.method || "GET" });
1171
+ }
1172
+ const response = await fetch(url, {
1173
+ ...fetchOptions,
1174
+ headers: {
1175
+ "Authorization": `Bearer ${token}`,
1176
+ "Content-Type": "application/json",
1177
+ ...fetchOptions.headers
1178
+ }
1179
+ });
1180
+ if (!response.ok) {
1181
+ const errorText = await response.text();
1182
+ const errorMessage = `Graph API error (${response.status}): ${errorText}`;
1183
+ throw new Error(errorMessage);
1184
+ }
1185
+ if (response.status === 204 || response.headers.get("content-length") === "0") {
1186
+ return null;
1187
+ }
1188
+ const data = await response.json();
1189
+ if (debug) {
1190
+ console.log("[GraphAPI] Response:", data);
1191
+ }
1192
+ return data;
1193
+ } catch (error) {
1194
+ const sanitizedMessage = sanitizeError(error);
1195
+ console.error("[GraphAPI] Request failed:", sanitizedMessage);
1196
+ throw new Error(sanitizedMessage);
1197
+ }
1198
+ },
1199
+ [acquireToken]
1200
+ );
1201
+ const get = useCallback3(
1202
+ (endpoint, options = {}) => {
1203
+ return request(endpoint, { ...options, method: "GET" });
1204
+ },
1205
+ [request]
1206
+ );
1207
+ const post = useCallback3(
1208
+ (endpoint, body, options = {}) => {
1209
+ return request(endpoint, {
1210
+ ...options,
1211
+ method: "POST",
1212
+ body: body ? JSON.stringify(body) : void 0
1213
+ });
1214
+ },
1215
+ [request]
1216
+ );
1217
+ const put = useCallback3(
1218
+ (endpoint, body, options = {}) => {
1219
+ return request(endpoint, {
1220
+ ...options,
1221
+ method: "PUT",
1222
+ body: body ? JSON.stringify(body) : void 0
1223
+ });
1224
+ },
1225
+ [request]
1226
+ );
1227
+ const patch = useCallback3(
1228
+ (endpoint, body, options = {}) => {
1229
+ return request(endpoint, {
1230
+ ...options,
1231
+ method: "PATCH",
1232
+ body: body ? JSON.stringify(body) : void 0
1233
+ });
1234
+ },
1235
+ [request]
1236
+ );
1237
+ const deleteRequest = useCallback3(
1238
+ (endpoint, options = {}) => {
1239
+ return request(endpoint, { ...options, method: "DELETE" });
1240
+ },
1241
+ [request]
1242
+ );
1243
+ return {
1244
+ get,
1245
+ post,
1246
+ put,
1247
+ patch,
1248
+ delete: deleteRequest,
1249
+ request
1250
+ };
1251
+ }
1252
+
1253
+ // src/hooks/useUserProfile.ts
1254
+ var profileCache = /* @__PURE__ */ new Map();
1255
+ var CACHE_DURATION = 5 * 60 * 1e3;
1256
+ var MAX_CACHE_SIZE = 100;
1257
+ function enforceCacheLimit() {
1258
+ if (profileCache.size > MAX_CACHE_SIZE) {
1259
+ const entries = Array.from(profileCache.entries());
1260
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
1261
+ const toRemove = entries.slice(0, profileCache.size - MAX_CACHE_SIZE);
1262
+ toRemove.forEach(([key]) => {
1263
+ const cached = profileCache.get(key);
1264
+ if (cached?.data.photo) {
1265
+ URL.revokeObjectURL(cached.data.photo);
1266
+ }
1267
+ profileCache.delete(key);
1268
+ });
1269
+ }
1270
+ }
1271
+ function useUserProfile() {
1272
+ const { isAuthenticated, account } = useMsalAuth();
1273
+ const graph = useGraphApi();
1274
+ const [profile, setProfile] = useState3(null);
1275
+ const [loading, setLoading] = useState3(false);
1276
+ const [error, setError] = useState3(null);
1277
+ const fetchProfile = useCallback4(async () => {
1278
+ if (!isAuthenticated || !account) {
1279
+ setProfile(null);
1280
+ return;
1281
+ }
1282
+ const cacheKey = account.homeAccountId;
1283
+ const cached = profileCache.get(cacheKey);
1284
+ if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
1285
+ setProfile(cached.data);
1286
+ return;
1287
+ }
1288
+ setLoading(true);
1289
+ setError(null);
1290
+ try {
1291
+ const userData = await graph.get("/me", {
1292
+ scopes: ["User.Read"]
1293
+ });
1294
+ let photoUrl;
1295
+ try {
1296
+ const photoBlob = await graph.get("/me/photo/$value", {
1297
+ scopes: ["User.Read"],
1298
+ headers: {
1299
+ "Content-Type": "image/jpeg"
1300
+ }
1301
+ });
1302
+ if (photoBlob) {
1303
+ photoUrl = URL.createObjectURL(photoBlob);
1304
+ }
1305
+ } catch (photoError) {
1306
+ console.debug("[UserProfile] Photo not available");
1307
+ }
1308
+ const profileData = {
1309
+ id: userData.id,
1310
+ displayName: userData.displayName,
1311
+ givenName: userData.givenName,
1312
+ surname: userData.surname,
1313
+ userPrincipalName: userData.userPrincipalName,
1314
+ mail: userData.mail,
1315
+ jobTitle: userData.jobTitle,
1316
+ department: userData.department,
1317
+ companyName: userData.companyName,
1318
+ officeLocation: userData.officeLocation,
1319
+ mobilePhone: userData.mobilePhone,
1320
+ businessPhones: userData.businessPhones,
1321
+ preferredLanguage: userData.preferredLanguage,
1322
+ employeeId: userData.employeeId,
1323
+ employeeHireDate: userData.employeeHireDate,
1324
+ employeeType: userData.employeeType,
1325
+ country: userData.country,
1326
+ city: userData.city,
1327
+ state: userData.state,
1328
+ streetAddress: userData.streetAddress,
1329
+ postalCode: userData.postalCode,
1330
+ usageLocation: userData.usageLocation,
1331
+ manager: userData.manager,
1332
+ aboutMe: userData.aboutMe,
1333
+ birthday: userData.birthday,
1334
+ interests: userData.interests,
1335
+ skills: userData.skills,
1336
+ schools: userData.schools,
1337
+ pastProjects: userData.pastProjects,
1338
+ responsibilities: userData.responsibilities,
1339
+ mySite: userData.mySite,
1340
+ faxNumber: userData.faxNumber,
1341
+ accountEnabled: userData.accountEnabled,
1342
+ ageGroup: userData.ageGroup,
1343
+ userType: userData.userType,
1344
+ photo: photoUrl,
1345
+ ...userData
1346
+ // Include any additional fields from the API
1347
+ };
1348
+ profileCache.set(cacheKey, {
1349
+ data: profileData,
1350
+ timestamp: Date.now()
1351
+ });
1352
+ enforceCacheLimit();
1353
+ setProfile(profileData);
1354
+ } catch (err) {
1355
+ const error2 = err;
1356
+ const sanitizedMessage = sanitizeError(error2);
1357
+ const sanitizedError = new Error(sanitizedMessage);
1358
+ setError(sanitizedError);
1359
+ console.error("[UserProfile] Failed to fetch profile:", sanitizedMessage);
1360
+ } finally {
1361
+ setLoading(false);
1362
+ }
1363
+ }, [isAuthenticated, account, graph]);
1364
+ const clearCache = useCallback4(() => {
1365
+ if (account) {
1366
+ const cached = profileCache.get(account.homeAccountId);
1367
+ if (cached?.data.photo) {
1368
+ URL.revokeObjectURL(cached.data.photo);
1369
+ }
1370
+ profileCache.delete(account.homeAccountId);
1371
+ }
1372
+ if (profile?.photo) {
1373
+ URL.revokeObjectURL(profile.photo);
1374
+ }
1375
+ setProfile(null);
1376
+ }, [account, profile]);
1377
+ useEffect4(() => {
1378
+ fetchProfile();
1379
+ return () => {
1380
+ if (profile?.photo) {
1381
+ URL.revokeObjectURL(profile.photo);
1382
+ }
1383
+ };
1384
+ }, [fetchProfile]);
1385
+ useEffect4(() => {
1386
+ return () => {
1387
+ if (profile?.photo) {
1388
+ URL.revokeObjectURL(profile.photo);
1389
+ }
1390
+ };
1391
+ }, [profile?.photo]);
1392
+ return {
1393
+ profile,
1394
+ loading,
1395
+ error,
1396
+ refetch: fetchProfile,
1397
+ clearCache
1398
+ };
1399
+ }
1400
+
1401
+ // src/components/UserAvatar.tsx
1402
+ import { jsx as jsx5 } from "react/jsx-runtime";
1403
+ function UserAvatar({
1404
+ size = 40,
1405
+ className = "",
1406
+ style,
1407
+ showTooltip = true,
1408
+ fallbackImage
1409
+ }) {
1410
+ const { profile, loading } = useUserProfile();
1411
+ const [photoUrl, setPhotoUrl] = useState4(null);
1412
+ const [photoError, setPhotoError] = useState4(false);
1413
+ useEffect5(() => {
1414
+ if (profile?.photo) {
1415
+ setPhotoUrl(profile.photo);
1416
+ }
1417
+ }, [profile?.photo]);
1418
+ const getInitials = () => {
1419
+ if (!profile) return "?";
1420
+ const { givenName, surname, displayName: displayName2 } = profile;
1421
+ if (givenName && surname) {
1422
+ return `${givenName[0]}${surname[0]}`.toUpperCase();
1423
+ }
1424
+ if (displayName2) {
1425
+ const parts = displayName2.split(" ");
1426
+ if (parts.length >= 2) {
1427
+ return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
1428
+ }
1429
+ return displayName2.substring(0, 2).toUpperCase();
1430
+ }
1431
+ return "?";
1432
+ };
1433
+ const baseStyles = {
1434
+ width: `${size}px`,
1435
+ height: `${size}px`,
1436
+ borderRadius: "50%",
1437
+ display: "inline-flex",
1438
+ alignItems: "center",
1439
+ justifyContent: "center",
1440
+ fontSize: `${size * 0.4}px`,
1441
+ fontWeight: 600,
1442
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
1443
+ backgroundColor: "#0078D4",
1444
+ color: "#FFFFFF",
1445
+ overflow: "hidden",
1446
+ userSelect: "none",
1447
+ ...style
1448
+ };
1449
+ const displayName = profile?.displayName || "User";
1450
+ if (loading) {
1451
+ return /* @__PURE__ */ jsx5(
1452
+ "div",
1453
+ {
1454
+ className,
1455
+ style: { ...baseStyles, backgroundColor: "#E1E1E1" },
1456
+ "aria-label": "Loading user avatar",
1457
+ children: /* @__PURE__ */ jsx5("span", { style: { fontSize: `${size * 0.3}px` }, children: "..." })
1458
+ }
1459
+ );
1460
+ }
1461
+ if (photoUrl && !photoError) {
1462
+ return /* @__PURE__ */ jsx5(
1463
+ "div",
1464
+ {
1465
+ className,
1466
+ style: baseStyles,
1467
+ title: showTooltip ? displayName : void 0,
1468
+ "aria-label": `${displayName} avatar`,
1469
+ children: /* @__PURE__ */ jsx5(
1470
+ "img",
1471
+ {
1472
+ src: photoUrl,
1473
+ alt: displayName,
1474
+ style: { width: "100%", height: "100%", objectFit: "cover" },
1475
+ onError: () => {
1476
+ setPhotoError(true);
1477
+ if (fallbackImage) {
1478
+ setPhotoUrl(fallbackImage);
1479
+ }
1480
+ }
1481
+ }
1482
+ )
1483
+ }
1484
+ );
1485
+ }
1486
+ return /* @__PURE__ */ jsx5(
1487
+ "div",
1488
+ {
1489
+ className,
1490
+ style: baseStyles,
1491
+ title: showTooltip ? displayName : void 0,
1492
+ "aria-label": `${displayName} avatar`,
1493
+ children: getInitials()
1494
+ }
1495
+ );
1496
+ }
1497
+
1498
+ // src/components/AuthStatus.tsx
1499
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1500
+ function AuthStatus({
1501
+ className = "",
1502
+ style,
1503
+ showDetails = false,
1504
+ renderLoading,
1505
+ renderAuthenticated,
1506
+ renderUnauthenticated
1507
+ }) {
1508
+ const { isAuthenticated, inProgress, account } = useMsalAuth();
1509
+ const baseStyles = {
1510
+ display: "inline-flex",
1511
+ alignItems: "center",
1512
+ gap: "8px",
1513
+ padding: "8px 12px",
1514
+ borderRadius: "4px",
1515
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
1516
+ fontSize: "14px",
1517
+ fontWeight: 500,
1518
+ ...style
1519
+ };
1520
+ if (inProgress) {
1521
+ if (renderLoading) {
1522
+ return /* @__PURE__ */ jsx6(Fragment2, { children: renderLoading() });
1523
+ }
1524
+ return /* @__PURE__ */ jsxs4(
1525
+ "div",
1526
+ {
1527
+ className,
1528
+ style: { ...baseStyles, backgroundColor: "#FFF4CE", color: "#8A6D3B" },
1529
+ role: "status",
1530
+ "aria-live": "polite",
1531
+ children: [
1532
+ /* @__PURE__ */ jsx6(StatusIndicator, { color: "#FFA500" }),
1533
+ /* @__PURE__ */ jsx6("span", { children: "Loading..." })
1534
+ ]
1535
+ }
1536
+ );
1537
+ }
1538
+ if (isAuthenticated) {
1539
+ const username = account?.username || account?.name || "User";
1540
+ if (renderAuthenticated) {
1541
+ return /* @__PURE__ */ jsx6(Fragment2, { children: renderAuthenticated(username) });
1542
+ }
1543
+ return /* @__PURE__ */ jsxs4(
1544
+ "div",
1545
+ {
1546
+ className,
1547
+ style: { ...baseStyles, backgroundColor: "#D4EDDA", color: "#155724" },
1548
+ role: "status",
1549
+ "aria-live": "polite",
1550
+ children: [
1551
+ /* @__PURE__ */ jsx6(StatusIndicator, { color: "#28A745" }),
1552
+ /* @__PURE__ */ jsx6("span", { children: showDetails ? `Authenticated as ${username}` : "Authenticated" })
1553
+ ]
1554
+ }
1555
+ );
1556
+ }
1557
+ if (renderUnauthenticated) {
1558
+ return /* @__PURE__ */ jsx6(Fragment2, { children: renderUnauthenticated() });
1559
+ }
1560
+ return /* @__PURE__ */ jsxs4(
1561
+ "div",
1562
+ {
1563
+ className,
1564
+ style: { ...baseStyles, backgroundColor: "#F8D7DA", color: "#721C24" },
1565
+ role: "status",
1566
+ "aria-live": "polite",
1567
+ children: [
1568
+ /* @__PURE__ */ jsx6(StatusIndicator, { color: "#DC3545" }),
1569
+ /* @__PURE__ */ jsx6("span", { children: "Not authenticated" })
1570
+ ]
1571
+ }
1572
+ );
1573
+ }
1574
+ function StatusIndicator({ color }) {
1575
+ 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 }) });
1576
+ }
1577
+
1578
+ // src/components/AuthGuard.tsx
1579
+ import { useEffect as useEffect6 } from "react";
1580
+ import { Fragment as Fragment3, jsx as jsx7 } from "react/jsx-runtime";
1581
+ function AuthGuard({
1582
+ children,
1583
+ loadingComponent,
1584
+ fallbackComponent,
1585
+ scopes,
1586
+ onAuthRequired
1587
+ }) {
1588
+ const { isAuthenticated, inProgress, loginRedirect } = useMsalAuth();
1589
+ useEffect6(() => {
1590
+ if (!isAuthenticated && !inProgress) {
1591
+ onAuthRequired?.();
1592
+ const login = async () => {
1593
+ try {
1594
+ await loginRedirect(scopes);
1595
+ } catch (error) {
1596
+ console.error("[AuthGuard] Authentication failed:", error);
1597
+ }
1598
+ };
1599
+ login();
1600
+ }
1601
+ }, [isAuthenticated, inProgress, scopes, loginRedirect, onAuthRequired]);
1602
+ if (inProgress) {
1603
+ return /* @__PURE__ */ jsx7(Fragment3, { children: loadingComponent || /* @__PURE__ */ jsx7("div", { children: "Authenticating..." }) });
1604
+ }
1605
+ if (!isAuthenticated) {
1606
+ return /* @__PURE__ */ jsx7(Fragment3, { children: fallbackComponent || /* @__PURE__ */ jsx7("div", { children: "Redirecting to login..." }) });
1607
+ }
1608
+ return /* @__PURE__ */ jsx7(Fragment3, { children });
1609
+ }
1610
+
1611
+ // src/components/ErrorBoundary.tsx
1612
+ import { Component } from "react";
1613
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1614
+ var ErrorBoundary = class extends Component {
1615
+ constructor(props) {
1616
+ super(props);
1617
+ this.reset = () => {
1618
+ this.setState({
1619
+ hasError: false,
1620
+ error: null
1621
+ });
1622
+ };
1623
+ this.state = {
1624
+ hasError: false,
1625
+ error: null
1626
+ };
1627
+ }
1628
+ static getDerivedStateFromError(error) {
1629
+ return {
1630
+ hasError: true,
1631
+ error
1632
+ };
1633
+ }
1634
+ componentDidCatch(error, errorInfo) {
1635
+ const { onError, debug } = this.props;
1636
+ if (debug) {
1637
+ console.error("[ErrorBoundary] Caught error:", error);
1638
+ console.error("[ErrorBoundary] Error info:", errorInfo);
1639
+ }
1640
+ onError?.(error, errorInfo);
1641
+ }
1642
+ render() {
1643
+ const { hasError, error } = this.state;
1644
+ const { children, fallback } = this.props;
1645
+ if (hasError && error) {
1646
+ if (fallback) {
1647
+ return fallback(error, this.reset);
1648
+ }
1649
+ return /* @__PURE__ */ jsxs5(
1650
+ "div",
1651
+ {
1652
+ style: {
1653
+ padding: "20px",
1654
+ margin: "20px",
1655
+ border: "1px solid #DC3545",
1656
+ borderRadius: "4px",
1657
+ backgroundColor: "#F8D7DA",
1658
+ color: "#721C24",
1659
+ fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif'
1660
+ },
1661
+ children: [
1662
+ /* @__PURE__ */ jsx8("h2", { style: { margin: "0 0 10px 0", fontSize: "18px" }, children: "Authentication Error" }),
1663
+ /* @__PURE__ */ jsx8("p", { style: { margin: "0 0 10px 0" }, children: error.message }),
1664
+ /* @__PURE__ */ jsx8(
1665
+ "button",
1666
+ {
1667
+ onClick: this.reset,
1668
+ style: {
1669
+ padding: "8px 16px",
1670
+ backgroundColor: "#DC3545",
1671
+ color: "#FFFFFF",
1672
+ border: "none",
1673
+ borderRadius: "4px",
1674
+ cursor: "pointer",
1675
+ fontSize: "14px",
1676
+ fontWeight: 600
1677
+ },
1678
+ children: "Try Again"
1679
+ }
1680
+ )
1681
+ ]
1682
+ }
1683
+ );
1684
+ }
1685
+ return children;
1686
+ }
1687
+ };
1688
+
1689
+ // src/hooks/useRoles.ts
1690
+ import { useState as useState5, useEffect as useEffect7, useCallback as useCallback5 } from "react";
1691
+ var rolesCache = /* @__PURE__ */ new Map();
1692
+ var CACHE_DURATION2 = 5 * 60 * 1e3;
1693
+ var MAX_CACHE_SIZE2 = 100;
1694
+ function clearRolesCache(accountId) {
1695
+ if (accountId) {
1696
+ rolesCache.delete(accountId);
1697
+ } else {
1698
+ rolesCache.clear();
1699
+ }
1700
+ }
1701
+ function enforceCacheLimit2() {
1702
+ if (rolesCache.size > MAX_CACHE_SIZE2) {
1703
+ const entries = Array.from(rolesCache.entries());
1704
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
1705
+ const toRemove = entries.slice(0, rolesCache.size - MAX_CACHE_SIZE2);
1706
+ toRemove.forEach(([key]) => rolesCache.delete(key));
1707
+ }
1708
+ }
1709
+ function useRoles() {
1710
+ const { isAuthenticated, account } = useMsalAuth();
1711
+ const graph = useGraphApi();
1712
+ const [roles, setRoles] = useState5([]);
1713
+ const [groups, setGroups] = useState5([]);
1714
+ const [loading, setLoading] = useState5(false);
1715
+ const [error, setError] = useState5(null);
1716
+ const fetchRolesAndGroups = useCallback5(async () => {
1717
+ if (!isAuthenticated || !account) {
1718
+ setRoles([]);
1719
+ setGroups([]);
1720
+ return;
1721
+ }
1722
+ const cacheKey = account.homeAccountId;
1723
+ const cached = rolesCache.get(cacheKey);
1724
+ if (cached && Date.now() - cached.timestamp < CACHE_DURATION2) {
1725
+ setRoles(cached.roles);
1726
+ setGroups(cached.groups);
1727
+ return;
1728
+ }
1729
+ setLoading(true);
1730
+ setError(null);
1731
+ try {
1732
+ const idTokenClaims = account.idTokenClaims;
1733
+ const tokenRoles = idTokenClaims?.roles || [];
1734
+ const groupsResponse = await graph.get("/me/memberOf", {
1735
+ scopes: ["User.Read", "Directory.Read.All"]
1736
+ });
1737
+ const userGroups = groupsResponse.value.map((group) => group.id);
1738
+ rolesCache.set(cacheKey, {
1739
+ roles: tokenRoles,
1740
+ groups: userGroups,
1741
+ timestamp: Date.now()
1742
+ });
1743
+ enforceCacheLimit2();
1744
+ setRoles(tokenRoles);
1745
+ setGroups(userGroups);
1746
+ } catch (err) {
1747
+ const error2 = err;
1748
+ const sanitizedMessage = sanitizeError(error2);
1749
+ const sanitizedError = new Error(sanitizedMessage);
1750
+ setError(sanitizedError);
1751
+ console.error("[Roles] Failed to fetch roles/groups:", sanitizedMessage);
1752
+ const idTokenClaims = account.idTokenClaims;
1753
+ const tokenRoles = idTokenClaims?.roles || [];
1754
+ setRoles(tokenRoles);
1755
+ } finally {
1756
+ setLoading(false);
1757
+ }
1758
+ }, [isAuthenticated, account, graph]);
1759
+ const hasRole = useCallback5(
1760
+ (role) => {
1761
+ return roles.includes(role);
1762
+ },
1763
+ [roles]
1764
+ );
1765
+ const hasGroup = useCallback5(
1766
+ (groupId) => {
1767
+ return groups.includes(groupId);
1768
+ },
1769
+ [groups]
1770
+ );
1771
+ const hasAnyRole = useCallback5(
1772
+ (checkRoles) => {
1773
+ return checkRoles.some((role) => roles.includes(role));
1774
+ },
1775
+ [roles]
1776
+ );
1777
+ const hasAllRoles = useCallback5(
1778
+ (checkRoles) => {
1779
+ return checkRoles.every((role) => roles.includes(role));
1780
+ },
1781
+ [roles]
1782
+ );
1783
+ useEffect7(() => {
1784
+ fetchRolesAndGroups();
1785
+ return () => {
1786
+ if (account) {
1787
+ clearRolesCache(account.homeAccountId);
1788
+ }
1789
+ };
1790
+ }, [fetchRolesAndGroups, account]);
1791
+ return {
1792
+ roles,
1793
+ groups,
1794
+ loading,
1795
+ error,
1796
+ hasRole,
1797
+ hasGroup,
1798
+ hasAnyRole,
1799
+ hasAllRoles,
1800
+ refetch: fetchRolesAndGroups
1801
+ };
1802
+ }
1803
+
1804
+ // src/utils/withAuth.tsx
1805
+ import { jsx as jsx9 } from "react/jsx-runtime";
1806
+ function withAuth(Component2, options = {}) {
1807
+ const { displayName, ...guardProps } = options;
1808
+ const WrappedComponent = (props) => {
1809
+ return /* @__PURE__ */ jsx9(AuthGuard, { ...guardProps, children: /* @__PURE__ */ jsx9(Component2, { ...props }) });
1810
+ };
1811
+ WrappedComponent.displayName = displayName || `withAuth(${Component2.displayName || Component2.name || "Component"})`;
1812
+ return WrappedComponent;
1813
+ }
1814
+
1815
+ // src/utils/tokenRetry.ts
1816
+ async function retryWithBackoff(fn, config = {}) {
1817
+ const {
1818
+ maxRetries = 3,
1819
+ initialDelay = 1e3,
1820
+ maxDelay = 1e4,
1821
+ backoffMultiplier = 2,
1822
+ debug = false
1823
+ } = config;
1824
+ let lastError;
1825
+ let delay = initialDelay;
1826
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1827
+ try {
1828
+ if (debug && attempt > 0) {
1829
+ console.log(`[TokenRetry] Attempt ${attempt + 1}/${maxRetries + 1}`);
1830
+ }
1831
+ return await fn();
1832
+ } catch (error) {
1833
+ lastError = error;
1834
+ if (attempt === maxRetries) {
1835
+ if (debug) {
1836
+ console.error("[TokenRetry] All retry attempts failed");
1837
+ }
1838
+ break;
1839
+ }
1840
+ if (!isRetryableError(error)) {
1841
+ if (debug) {
1842
+ console.log("[TokenRetry] Non-retryable error, aborting");
1843
+ }
1844
+ throw error;
1845
+ }
1846
+ if (debug) {
1847
+ console.warn(`[TokenRetry] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
1848
+ }
1849
+ await sleep(delay);
1850
+ delay = Math.min(delay * backoffMultiplier, maxDelay);
1851
+ }
1852
+ }
1853
+ throw lastError;
1854
+ }
1855
+ function isRetryableError(error) {
1856
+ const message = error.message.toLowerCase();
1857
+ if (message.includes("network") || message.includes("timeout") || message.includes("fetch") || message.includes("connection")) {
1858
+ return true;
1859
+ }
1860
+ if (message.includes("500") || message.includes("502") || message.includes("503")) {
1861
+ return true;
1862
+ }
1863
+ if (message.includes("429") || message.includes("rate limit")) {
1864
+ return true;
1865
+ }
1866
+ if (message.includes("token") && message.includes("expired")) {
1867
+ return true;
1868
+ }
1869
+ return false;
1870
+ }
1871
+ function sleep(ms) {
1872
+ return new Promise((resolve) => setTimeout(resolve, ms));
1873
+ }
1874
+ function createRetryWrapper(fn, config = {}) {
1875
+ return (...args) => {
1876
+ return retryWithBackoff(() => fn(...args), config);
1877
+ };
1878
+ }
1879
+
1880
+ // src/utils/debugLogger.ts
1881
+ var DebugLogger = class {
1882
+ constructor(config = {}) {
1883
+ this.logHistory = [];
1884
+ this.performanceTimings = /* @__PURE__ */ new Map();
1885
+ this.config = {
1886
+ enabled: config.enabled ?? false,
1887
+ prefix: config.prefix ?? "[MSAL-Next]",
1888
+ showTimestamp: config.showTimestamp ?? true,
1889
+ level: config.level ?? "info",
1890
+ enablePerformance: config.enablePerformance ?? false,
1891
+ enableNetworkLogs: config.enableNetworkLogs ?? false,
1892
+ maxHistorySize: config.maxHistorySize ?? 100
1893
+ };
1894
+ }
1895
+ shouldLog(level) {
1896
+ if (!this.config.enabled) return false;
1897
+ const levels = ["error", "warn", "info", "debug"];
1898
+ const currentLevelIndex = levels.indexOf(this.config.level);
1899
+ const messageLevelIndex = levels.indexOf(level);
1900
+ return messageLevelIndex <= currentLevelIndex;
1901
+ }
1902
+ formatMessage(level, message, data) {
1903
+ const timestamp = this.config.showTimestamp ? `[${(/* @__PURE__ */ new Date()).toISOString()}]` : "";
1904
+ const prefix = this.config.prefix;
1905
+ const levelStr = `[${level.toUpperCase()}]`;
1906
+ let formatted = `${timestamp} ${prefix} ${levelStr} ${message}`;
1907
+ if (data !== void 0) {
1908
+ formatted += "\n" + JSON.stringify(data, null, 2);
1909
+ }
1910
+ return formatted;
1911
+ }
1912
+ addToHistory(level, message, data) {
1913
+ if (this.logHistory.length >= this.config.maxHistorySize) {
1914
+ this.logHistory.shift();
1915
+ }
1916
+ this.logHistory.push({
1917
+ timestamp: Date.now(),
1918
+ level,
1919
+ message,
1920
+ data
1921
+ });
1922
+ }
1923
+ error(message, data) {
1924
+ if (this.shouldLog("error")) {
1925
+ console.error(this.formatMessage("error", message, data));
1926
+ this.addToHistory("error", message, data);
1927
+ }
1928
+ }
1929
+ warn(message, data) {
1930
+ if (this.shouldLog("warn")) {
1931
+ console.warn(this.formatMessage("warn", message, data));
1932
+ this.addToHistory("warn", message, data);
1933
+ }
1934
+ }
1935
+ info(message, data) {
1936
+ if (this.shouldLog("info")) {
1937
+ console.info(this.formatMessage("info", message, data));
1938
+ this.addToHistory("info", message, data);
1939
+ }
1940
+ }
1941
+ debug(message, data) {
1942
+ if (this.shouldLog("debug")) {
1943
+ console.debug(this.formatMessage("debug", message, data));
1944
+ this.addToHistory("debug", message, data);
1945
+ }
1946
+ }
1947
+ group(label) {
1948
+ if (this.config.enabled) {
1949
+ console.group(`${this.config.prefix} ${label}`);
1950
+ }
1951
+ }
1952
+ groupEnd() {
1953
+ if (this.config.enabled) {
1954
+ console.groupEnd();
1955
+ }
1956
+ }
1957
+ /**
1958
+ * Start performance timing for an operation
1959
+ */
1960
+ startTiming(operation) {
1961
+ if (this.config.enablePerformance) {
1962
+ this.performanceTimings.set(operation, {
1963
+ operation,
1964
+ startTime: performance.now()
1965
+ });
1966
+ this.debug(`\u23F1\uFE0F Started: ${operation}`);
1967
+ }
1968
+ }
1969
+ /**
1970
+ * End performance timing for an operation
1971
+ */
1972
+ endTiming(operation) {
1973
+ if (this.config.enablePerformance) {
1974
+ const timing = this.performanceTimings.get(operation);
1975
+ if (timing) {
1976
+ timing.endTime = performance.now();
1977
+ timing.duration = timing.endTime - timing.startTime;
1978
+ this.info(`\u23F1\uFE0F Completed: ${operation} (${timing.duration.toFixed(2)}ms)`);
1979
+ return timing.duration;
1980
+ }
1981
+ }
1982
+ return void 0;
1983
+ }
1984
+ /**
1985
+ * Log network request
1986
+ */
1987
+ logRequest(method, url, options) {
1988
+ if (this.config.enableNetworkLogs) {
1989
+ this.debug(`\u{1F310} ${method} ${url}`, options);
1990
+ }
1991
+ }
1992
+ /**
1993
+ * Log network response
1994
+ */
1995
+ logResponse(method, url, status, data) {
1996
+ if (this.config.enableNetworkLogs) {
1997
+ const statusEmoji = status >= 200 && status < 300 ? "\u2705" : "\u274C";
1998
+ this.debug(`${statusEmoji} ${method} ${url} - ${status}`, data);
1999
+ }
2000
+ }
2001
+ /**
2002
+ * Get log history
2003
+ */
2004
+ getHistory() {
2005
+ return [...this.logHistory];
2006
+ }
2007
+ /**
2008
+ * Get performance timings
2009
+ */
2010
+ getPerformanceTimings() {
2011
+ return Array.from(this.performanceTimings.values());
2012
+ }
2013
+ /**
2014
+ * Clear log history
2015
+ */
2016
+ clearHistory() {
2017
+ this.logHistory = [];
2018
+ }
2019
+ /**
2020
+ * Clear performance timings
2021
+ */
2022
+ clearTimings() {
2023
+ this.performanceTimings.clear();
2024
+ }
2025
+ /**
2026
+ * Export logs as JSON
2027
+ */
2028
+ exportLogs() {
2029
+ return JSON.stringify({
2030
+ config: this.config,
2031
+ history: this.logHistory,
2032
+ performanceTimings: Array.from(this.performanceTimings.values()),
2033
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString()
2034
+ }, null, 2);
2035
+ }
2036
+ /**
2037
+ * Download logs as a file
2038
+ */
2039
+ downloadLogs(filename = "msal-next-debug-logs.json") {
2040
+ if (typeof window === "undefined") return;
2041
+ const logs = this.exportLogs();
2042
+ const blob = new Blob([logs], { type: "application/json" });
2043
+ const url = URL.createObjectURL(blob);
2044
+ const a = document.createElement("a");
2045
+ a.href = url;
2046
+ a.download = filename;
2047
+ a.click();
2048
+ URL.revokeObjectURL(url);
2049
+ }
2050
+ setEnabled(enabled) {
2051
+ this.config.enabled = enabled;
2052
+ }
2053
+ setLevel(level) {
2054
+ if (level) {
2055
+ this.config.level = level;
2056
+ }
2057
+ }
2058
+ };
2059
+ var globalLogger = null;
2060
+ function getDebugLogger(config) {
2061
+ if (!globalLogger) {
2062
+ globalLogger = new DebugLogger(config);
2063
+ } else if (config) {
2064
+ if (config.enabled !== void 0) {
2065
+ globalLogger.setEnabled(config.enabled);
2066
+ }
2067
+ if (config.level) {
2068
+ globalLogger.setLevel(config.level);
2069
+ }
2070
+ }
2071
+ return globalLogger;
2072
+ }
2073
+ function createScopedLogger(scope, config) {
2074
+ return new DebugLogger({
2075
+ ...config,
2076
+ prefix: `[MSAL-Next:${scope}]`
2077
+ });
2078
+ }
2079
+
2080
+ // src/protection/ProtectedPage.tsx
2081
+ import { useEffect as useEffect8, useState as useState6 } from "react";
2082
+ import { useRouter } from "next/navigation";
2083
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
2084
+ function ProtectedPage({
2085
+ children,
2086
+ config,
2087
+ defaultRedirectTo = "/login",
2088
+ defaultLoading,
2089
+ defaultUnauthorized,
2090
+ debug = false
2091
+ }) {
2092
+ const router = useRouter();
2093
+ const { isAuthenticated, account, inProgress } = useMsalAuth();
2094
+ const [isValidating, setIsValidating] = useState6(true);
2095
+ const [isAuthorized, setIsAuthorized] = useState6(false);
2096
+ useEffect8(() => {
2097
+ async function checkAuth() {
2098
+ if (debug) {
2099
+ console.log("[ProtectedPage] Checking auth...", {
2100
+ isAuthenticated,
2101
+ inProgress,
2102
+ config
2103
+ });
2104
+ }
2105
+ if (inProgress) {
2106
+ return;
2107
+ }
2108
+ if (!config.required) {
2109
+ setIsAuthorized(true);
2110
+ setIsValidating(false);
2111
+ return;
2112
+ }
2113
+ if (!isAuthenticated || !account) {
2114
+ if (debug) {
2115
+ console.log("[ProtectedPage] Not authenticated, redirecting...");
2116
+ }
2117
+ const redirectPath = config.redirectTo || defaultRedirectTo;
2118
+ const returnUrl = encodeURIComponent(window.location.pathname + window.location.search);
2119
+ router.push(`${redirectPath}?returnUrl=${returnUrl}`);
2120
+ return;
2121
+ }
2122
+ if (config.roles && config.roles.length > 0) {
2123
+ const userRoles = account.idTokenClaims?.roles || [];
2124
+ const hasRequiredRole = config.roles.some((role) => userRoles.includes(role));
2125
+ if (!hasRequiredRole) {
2126
+ if (debug) {
2127
+ console.log("[ProtectedPage] Missing required role", {
2128
+ required: config.roles,
2129
+ user: userRoles
2130
+ });
2131
+ }
2132
+ setIsAuthorized(false);
2133
+ setIsValidating(false);
2134
+ return;
2135
+ }
2136
+ }
2137
+ if (config.validate) {
2138
+ try {
2139
+ const isValid = await config.validate(account);
2140
+ if (!isValid) {
2141
+ if (debug) {
2142
+ console.log("[ProtectedPage] Custom validation failed");
2143
+ }
2144
+ setIsAuthorized(false);
2145
+ setIsValidating(false);
2146
+ return;
2147
+ }
2148
+ } catch (error) {
2149
+ console.error("[ProtectedPage] Validation error:", error);
2150
+ setIsAuthorized(false);
2151
+ setIsValidating(false);
2152
+ return;
2153
+ }
2154
+ }
2155
+ if (debug) {
2156
+ console.log("[ProtectedPage] Authorization successful");
2157
+ }
2158
+ setIsAuthorized(true);
2159
+ setIsValidating(false);
2160
+ }
2161
+ checkAuth();
2162
+ }, [isAuthenticated, account, inProgress, config, router, defaultRedirectTo, debug]);
2163
+ if (isValidating || inProgress) {
2164
+ if (config.loading) {
2165
+ return /* @__PURE__ */ jsx10(Fragment4, { children: config.loading });
2166
+ }
2167
+ if (defaultLoading) {
2168
+ return /* @__PURE__ */ jsx10(Fragment4, { children: defaultLoading });
2169
+ }
2170
+ return /* @__PURE__ */ jsx10("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsx10("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600" }) });
2171
+ }
2172
+ if (!isAuthorized) {
2173
+ if (config.unauthorized) {
2174
+ return /* @__PURE__ */ jsx10(Fragment4, { children: config.unauthorized });
2175
+ }
2176
+ if (defaultUnauthorized) {
2177
+ return /* @__PURE__ */ jsx10(Fragment4, { children: defaultUnauthorized });
2178
+ }
2179
+ return /* @__PURE__ */ jsx10("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs6("div", { className: "text-center", children: [
2180
+ /* @__PURE__ */ jsx10("h1", { className: "text-2xl font-bold text-gray-900 mb-2", children: "Access Denied" }),
2181
+ /* @__PURE__ */ jsx10("p", { className: "text-gray-600", children: "You don't have permission to access this page." })
2182
+ ] }) });
2183
+ }
2184
+ return /* @__PURE__ */ jsx10(Fragment4, { children });
2185
+ }
2186
+
2187
+ // src/protection/withPageAuth.tsx
2188
+ import { jsx as jsx11 } from "react/jsx-runtime";
2189
+ function withPageAuth(Component2, authConfig, globalConfig) {
2190
+ const WrappedComponent = (props) => {
2191
+ return /* @__PURE__ */ jsx11(
2192
+ ProtectedPage,
2193
+ {
2194
+ config: authConfig,
2195
+ defaultRedirectTo: globalConfig?.defaultRedirectTo,
2196
+ defaultLoading: globalConfig?.defaultLoading,
2197
+ defaultUnauthorized: globalConfig?.defaultUnauthorized,
2198
+ debug: globalConfig?.debug,
2199
+ children: /* @__PURE__ */ jsx11(Component2, { ...props })
2200
+ }
2201
+ );
2202
+ };
2203
+ WrappedComponent.displayName = `withPageAuth(${Component2.displayName || Component2.name || "Component"})`;
2204
+ return WrappedComponent;
2205
+ }
2206
+
2207
+ // src/middleware/createAuthMiddleware.ts
2208
+ import { NextResponse } from "next/server";
2209
+ function createAuthMiddleware(config = {}) {
2210
+ const {
2211
+ protectedRoutes = [],
2212
+ publicOnlyRoutes = [],
2213
+ loginPath = "/login",
2214
+ redirectAfterLogin = "/",
2215
+ sessionCookie = "msal.account",
2216
+ isAuthenticated: customAuthCheck,
2217
+ debug = false
2218
+ } = config;
2219
+ return async function authMiddleware(request) {
2220
+ const { pathname } = request.nextUrl;
2221
+ if (debug) {
2222
+ console.log("[AuthMiddleware] Processing:", pathname);
2223
+ }
2224
+ let authenticated = false;
2225
+ if (customAuthCheck) {
2226
+ authenticated = await customAuthCheck(request);
2227
+ } else {
2228
+ const sessionData = request.cookies.get(sessionCookie);
2229
+ authenticated = !!sessionData?.value;
2230
+ }
2231
+ if (debug) {
2232
+ console.log("[AuthMiddleware] Authenticated:", authenticated);
2233
+ }
2234
+ const isProtectedRoute = protectedRoutes.some(
2235
+ (route) => pathname.startsWith(route)
2236
+ );
2237
+ const isPublicOnlyRoute = publicOnlyRoutes.some(
2238
+ (route) => pathname.startsWith(route)
2239
+ );
2240
+ if (isProtectedRoute && !authenticated) {
2241
+ if (debug) {
2242
+ console.log("[AuthMiddleware] Redirecting to login");
2243
+ }
2244
+ const url = request.nextUrl.clone();
2245
+ url.pathname = loginPath;
2246
+ url.searchParams.set("returnUrl", pathname);
2247
+ return NextResponse.redirect(url);
2248
+ }
2249
+ if (isPublicOnlyRoute && authenticated) {
2250
+ if (debug) {
2251
+ console.log("[AuthMiddleware] Redirecting to home");
2252
+ }
2253
+ const returnUrl = request.nextUrl.searchParams.get("returnUrl");
2254
+ const url = request.nextUrl.clone();
2255
+ url.pathname = returnUrl || redirectAfterLogin;
2256
+ url.searchParams.delete("returnUrl");
2257
+ return NextResponse.redirect(url);
2258
+ }
2259
+ const response = NextResponse.next();
2260
+ if (authenticated) {
2261
+ response.headers.set("x-msal-authenticated", "true");
2262
+ try {
2263
+ const sessionData = request.cookies.get(sessionCookie);
2264
+ if (sessionData?.value) {
2265
+ const account = safeJsonParse(sessionData.value, isValidAccountData);
2266
+ if (account?.username) {
2267
+ response.headers.set("x-msal-username", account.username);
2268
+ }
2269
+ }
2270
+ } catch (error) {
2271
+ if (debug) {
2272
+ console.warn("[AuthMiddleware] Failed to parse session data");
2273
+ }
2274
+ }
2275
+ }
2276
+ return response;
2277
+ };
2278
+ }
2279
+
2280
+ // src/client.ts
2281
+ import { useMsal as useMsal2, useIsAuthenticated, useAccount as useAccount2 } from "@azure/msal-react";
2282
+ export {
2283
+ AuthGuard,
2284
+ AuthStatus,
2285
+ ErrorBoundary,
2286
+ MSALProvider,
2287
+ MicrosoftSignInButton,
2288
+ MsalAuthProvider,
2289
+ MsalError,
2290
+ ProtectedPage,
2291
+ SignOutButton,
2292
+ UserAvatar,
2293
+ createAuthMiddleware,
2294
+ createMissingEnvVarError,
2295
+ createMsalConfig,
2296
+ createRetryWrapper,
2297
+ createScopedLogger,
2298
+ displayValidationResults,
2299
+ getDebugLogger,
2300
+ getMsalInstance,
2301
+ isValidAccountData,
2302
+ isValidRedirectUri,
2303
+ isValidScope,
2304
+ retryWithBackoff,
2305
+ safeJsonParse,
2306
+ sanitizeError,
2307
+ useAccount2 as useAccount,
2308
+ useGraphApi,
2309
+ useIsAuthenticated,
2310
+ useMsal2 as useMsal,
2311
+ useMsalAuth,
2312
+ useRoles,
2313
+ useTokenRefresh,
2314
+ useUserProfile,
2315
+ validateConfig,
2316
+ validateScopes,
2317
+ withAuth,
2318
+ withPageAuth,
2319
+ wrapMsalError
2320
+ };