@chemmangat/msal-next 1.2.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,8 +1,27 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { Configuration, LogLevel, IPublicClientApplication, PublicClientApplication, AccountInfo } from '@azure/msal-browser';
3
- import { ReactNode, CSSProperties } from 'react';
3
+ export { AccountInfo } from '@azure/msal-browser';
4
+ import { ReactNode, CSSProperties, Component, ErrorInfo, ComponentType } from 'react';
5
+ import { NextRequest, NextResponse } from 'next/server';
4
6
  export { useAccount, useIsAuthenticated, useMsal } from '@azure/msal-react';
5
7
 
8
+ /**
9
+ * Custom token claims interface for TypeScript generics
10
+ * Extend this interface to add your custom claims
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * interface MyCustomClaims extends CustomTokenClaims {
15
+ * roles: string[];
16
+ * department: string;
17
+ * }
18
+ *
19
+ * const claims = account.idTokenClaims as MyCustomClaims;
20
+ * ```
21
+ */
22
+ interface CustomTokenClaims {
23
+ [key: string]: any;
24
+ }
6
25
  interface MsalAuthConfig {
7
26
  /**
8
27
  * Azure AD Application (client) ID
@@ -60,6 +79,12 @@ interface MsalAuthConfig {
60
79
  * Custom logger callback
61
80
  */
62
81
  loggerCallback?: (level: LogLevel, message: string, containsPii: boolean) => void;
82
+ /**
83
+ * Allowed redirect URIs for validation (optional but recommended)
84
+ * Helps prevent open redirect vulnerabilities
85
+ * @example ['https://myapp.com', 'http://localhost:3000']
86
+ */
87
+ allowedRedirectUris?: string[];
63
88
  /**
64
89
  * Loading component to show while MSAL initializes
65
90
  */
@@ -124,6 +149,207 @@ interface MicrosoftSignInButtonProps {
124
149
  }
125
150
  declare function MicrosoftSignInButton({ text, variant, size, useRedirect, scopes, className, style, onSuccess, onError, }: MicrosoftSignInButtonProps): react_jsx_runtime.JSX.Element;
126
151
 
152
+ interface SignOutButtonProps {
153
+ /**
154
+ * Button text
155
+ * @default 'Sign out'
156
+ */
157
+ text?: string;
158
+ /**
159
+ * Button variant
160
+ * @default 'dark'
161
+ */
162
+ variant?: 'dark' | 'light';
163
+ /**
164
+ * Button size
165
+ * @default 'medium'
166
+ */
167
+ size?: 'small' | 'medium' | 'large';
168
+ /**
169
+ * Use redirect flow instead of popup
170
+ * @default false
171
+ */
172
+ useRedirect?: boolean;
173
+ /**
174
+ * Custom className
175
+ */
176
+ className?: string;
177
+ /**
178
+ * Custom styles
179
+ */
180
+ style?: CSSProperties;
181
+ /**
182
+ * Callback on successful logout
183
+ */
184
+ onSuccess?: () => void;
185
+ /**
186
+ * Callback on error
187
+ */
188
+ onError?: (error: Error) => void;
189
+ }
190
+ /**
191
+ * SignOutButton component with Microsoft branding
192
+ *
193
+ * @example
194
+ * ```tsx
195
+ * <SignOutButton variant="light" />
196
+ * ```
197
+ */
198
+ declare function SignOutButton({ text, variant, size, useRedirect, className, style, onSuccess, onError, }: SignOutButtonProps): react_jsx_runtime.JSX.Element;
199
+
200
+ interface UserAvatarProps {
201
+ /**
202
+ * Avatar size in pixels
203
+ * @default 40
204
+ */
205
+ size?: number;
206
+ /**
207
+ * Custom className
208
+ */
209
+ className?: string;
210
+ /**
211
+ * Custom styles
212
+ */
213
+ style?: CSSProperties;
214
+ /**
215
+ * Show user name tooltip on hover
216
+ * @default true
217
+ */
218
+ showTooltip?: boolean;
219
+ /**
220
+ * Fallback image URL if MS Graph photo fails
221
+ */
222
+ fallbackImage?: string;
223
+ }
224
+ /**
225
+ * UserAvatar component that displays user photo from MS Graph with fallback initials
226
+ *
227
+ * @example
228
+ * ```tsx
229
+ * <UserAvatar size={48} />
230
+ * ```
231
+ */
232
+ declare function UserAvatar({ size, className, style, showTooltip, fallbackImage, }: UserAvatarProps): react_jsx_runtime.JSX.Element;
233
+
234
+ interface AuthStatusProps {
235
+ /**
236
+ * Custom className
237
+ */
238
+ className?: string;
239
+ /**
240
+ * Custom styles
241
+ */
242
+ style?: CSSProperties;
243
+ /**
244
+ * Show detailed status (includes username)
245
+ * @default false
246
+ */
247
+ showDetails?: boolean;
248
+ /**
249
+ * Custom render function for loading state
250
+ */
251
+ renderLoading?: () => ReactNode;
252
+ /**
253
+ * Custom render function for authenticated state
254
+ */
255
+ renderAuthenticated?: (username: string) => ReactNode;
256
+ /**
257
+ * Custom render function for unauthenticated state
258
+ */
259
+ renderUnauthenticated?: () => ReactNode;
260
+ }
261
+ /**
262
+ * AuthStatus component that shows current authentication state
263
+ *
264
+ * @example
265
+ * ```tsx
266
+ * <AuthStatus showDetails />
267
+ * ```
268
+ */
269
+ declare function AuthStatus({ className, style, showDetails, renderLoading, renderAuthenticated, renderUnauthenticated, }: AuthStatusProps): react_jsx_runtime.JSX.Element;
270
+
271
+ interface AuthGuardProps {
272
+ /**
273
+ * Content to render when authenticated
274
+ */
275
+ children: ReactNode;
276
+ /**
277
+ * Component to show while checking authentication
278
+ */
279
+ loadingComponent?: ReactNode;
280
+ /**
281
+ * Component to show when not authenticated (before redirect)
282
+ */
283
+ fallbackComponent?: ReactNode;
284
+ /**
285
+ * Use redirect flow instead of popup
286
+ * @default true
287
+ */
288
+ useRedirect?: boolean;
289
+ /**
290
+ * Scopes to request during authentication
291
+ */
292
+ scopes?: string[];
293
+ /**
294
+ * Callback when authentication is required
295
+ */
296
+ onAuthRequired?: () => void;
297
+ }
298
+ /**
299
+ * AuthGuard component that protects content and auto-redirects to login
300
+ *
301
+ * @example
302
+ * ```tsx
303
+ * <AuthGuard>
304
+ * <ProtectedContent />
305
+ * </AuthGuard>
306
+ * ```
307
+ */
308
+ declare function AuthGuard({ children, loadingComponent, fallbackComponent, useRedirect, scopes, onAuthRequired, }: AuthGuardProps): react_jsx_runtime.JSX.Element;
309
+
310
+ interface ErrorBoundaryProps {
311
+ /**
312
+ * Content to render when no error
313
+ */
314
+ children: ReactNode;
315
+ /**
316
+ * Custom error fallback component
317
+ */
318
+ fallback?: (error: Error, reset: () => void) => ReactNode;
319
+ /**
320
+ * Callback when error occurs
321
+ */
322
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
323
+ /**
324
+ * Enable debug logging
325
+ * @default false
326
+ */
327
+ debug?: boolean;
328
+ }
329
+ interface ErrorBoundaryState {
330
+ hasError: boolean;
331
+ error: Error | null;
332
+ }
333
+ /**
334
+ * Error boundary for catching authentication errors
335
+ *
336
+ * @example
337
+ * ```tsx
338
+ * <ErrorBoundary>
339
+ * <MsalAuthProvider clientId="...">
340
+ * <App />
341
+ * </MsalAuthProvider>
342
+ * </ErrorBoundary>
343
+ * ```
344
+ */
345
+ declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
346
+ constructor(props: ErrorBoundaryProps);
347
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState;
348
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
349
+ reset: () => void;
350
+ render(): ReactNode;
351
+ }
352
+
127
353
  interface UseMsalAuthReturn {
128
354
  /**
129
355
  * Current authenticated account
@@ -180,4 +406,408 @@ interface UseMsalAuthReturn {
180
406
  }
181
407
  declare function useMsalAuth(defaultScopes?: string[]): UseMsalAuthReturn;
182
408
 
183
- export { MicrosoftSignInButton, type MicrosoftSignInButtonProps, type MsalAuthConfig, MsalAuthProvider, type MsalAuthProviderProps, type UseMsalAuthReturn, getMsalInstance, useMsalAuth };
409
+ interface GraphApiOptions extends RequestInit {
410
+ /**
411
+ * Scopes required for the API call
412
+ * @default ['User.Read']
413
+ */
414
+ scopes?: string[];
415
+ /**
416
+ * API version
417
+ * @default 'v1.0'
418
+ */
419
+ version?: 'v1.0' | 'beta';
420
+ /**
421
+ * Enable debug logging
422
+ * @default false
423
+ */
424
+ debug?: boolean;
425
+ }
426
+ interface UseGraphApiReturn {
427
+ /**
428
+ * Make a GET request to MS Graph API
429
+ */
430
+ get: <T = any>(endpoint: string, options?: GraphApiOptions) => Promise<T>;
431
+ /**
432
+ * Make a POST request to MS Graph API
433
+ */
434
+ post: <T = any>(endpoint: string, body?: any, options?: GraphApiOptions) => Promise<T>;
435
+ /**
436
+ * Make a PUT request to MS Graph API
437
+ */
438
+ put: <T = any>(endpoint: string, body?: any, options?: GraphApiOptions) => Promise<T>;
439
+ /**
440
+ * Make a PATCH request to MS Graph API
441
+ */
442
+ patch: <T = any>(endpoint: string, body?: any, options?: GraphApiOptions) => Promise<T>;
443
+ /**
444
+ * Make a DELETE request to MS Graph API
445
+ */
446
+ delete: <T = any>(endpoint: string, options?: GraphApiOptions) => Promise<T>;
447
+ /**
448
+ * Make a custom request to MS Graph API
449
+ */
450
+ request: <T = any>(endpoint: string, options?: GraphApiOptions) => Promise<T>;
451
+ }
452
+ /**
453
+ * Hook for making authenticated requests to MS Graph API
454
+ *
455
+ * @example
456
+ * ```tsx
457
+ * const graph = useGraphApi();
458
+ * const user = await graph.get('/me');
459
+ * ```
460
+ */
461
+ declare function useGraphApi(): UseGraphApiReturn;
462
+
463
+ interface UserProfile {
464
+ id: string;
465
+ displayName: string;
466
+ givenName: string;
467
+ surname: string;
468
+ userPrincipalName: string;
469
+ mail: string;
470
+ jobTitle?: string;
471
+ officeLocation?: string;
472
+ mobilePhone?: string;
473
+ businessPhones?: string[];
474
+ photo?: string;
475
+ }
476
+ interface UseUserProfileReturn {
477
+ /**
478
+ * User profile data
479
+ */
480
+ profile: UserProfile | null;
481
+ /**
482
+ * Whether profile is loading
483
+ */
484
+ loading: boolean;
485
+ /**
486
+ * Error if profile fetch failed
487
+ */
488
+ error: Error | null;
489
+ /**
490
+ * Refetch user profile
491
+ */
492
+ refetch: () => Promise<void>;
493
+ /**
494
+ * Clear cached profile
495
+ */
496
+ clearCache: () => void;
497
+ }
498
+ /**
499
+ * Hook for fetching and caching user profile from MS Graph
500
+ *
501
+ * @example
502
+ * ```tsx
503
+ * const { profile, loading } = useUserProfile();
504
+ * ```
505
+ */
506
+ declare function useUserProfile(): UseUserProfileReturn;
507
+
508
+ interface UseRolesReturn {
509
+ /**
510
+ * User's Azure AD roles
511
+ */
512
+ roles: string[];
513
+ /**
514
+ * User's Azure AD groups
515
+ */
516
+ groups: string[];
517
+ /**
518
+ * Whether roles/groups are loading
519
+ */
520
+ loading: boolean;
521
+ /**
522
+ * Error if fetch failed
523
+ */
524
+ error: Error | null;
525
+ /**
526
+ * Check if user has a specific role
527
+ */
528
+ hasRole: (role: string) => boolean;
529
+ /**
530
+ * Check if user is in a specific group
531
+ */
532
+ hasGroup: (groupId: string) => boolean;
533
+ /**
534
+ * Check if user has any of the specified roles
535
+ */
536
+ hasAnyRole: (roles: string[]) => boolean;
537
+ /**
538
+ * Check if user has all of the specified roles
539
+ */
540
+ hasAllRoles: (roles: string[]) => boolean;
541
+ /**
542
+ * Refetch roles and groups
543
+ */
544
+ refetch: () => Promise<void>;
545
+ }
546
+ /**
547
+ * Hook for fetching user's Azure AD roles and groups
548
+ *
549
+ * @example
550
+ * ```tsx
551
+ * const { roles, hasRole } = useRoles();
552
+ * if (hasRole('Admin')) {
553
+ * // Show admin content
554
+ * }
555
+ * ```
556
+ */
557
+ declare function useRoles(): UseRolesReturn;
558
+
559
+ declare function createMsalConfig(config: MsalAuthConfig): Configuration;
560
+
561
+ interface WithAuthOptions extends Omit<AuthGuardProps, 'children'> {
562
+ /**
563
+ * Display name for the wrapped component (for debugging)
564
+ */
565
+ displayName?: string;
566
+ }
567
+ /**
568
+ * Higher-order component for protecting pages/components
569
+ *
570
+ * @example
571
+ * ```tsx
572
+ * const ProtectedPage = withAuth(MyPage);
573
+ *
574
+ * // With options
575
+ * const ProtectedPage = withAuth(MyPage, {
576
+ * useRedirect: true,
577
+ * scopes: ['User.Read', 'Mail.Read']
578
+ * });
579
+ * ```
580
+ */
581
+ declare function withAuth<P extends object>(Component: ComponentType<P>, options?: WithAuthOptions): ComponentType<P>;
582
+
583
+ /**
584
+ * Retry configuration for token acquisition
585
+ */
586
+ interface RetryConfig {
587
+ /**
588
+ * Maximum number of retry attempts
589
+ * @default 3
590
+ */
591
+ maxRetries?: number;
592
+ /**
593
+ * Initial delay in milliseconds
594
+ * @default 1000
595
+ */
596
+ initialDelay?: number;
597
+ /**
598
+ * Maximum delay in milliseconds
599
+ * @default 10000
600
+ */
601
+ maxDelay?: number;
602
+ /**
603
+ * Backoff multiplier
604
+ * @default 2
605
+ */
606
+ backoffMultiplier?: number;
607
+ /**
608
+ * Enable debug logging
609
+ * @default false
610
+ */
611
+ debug?: boolean;
612
+ }
613
+ /**
614
+ * Exponential backoff retry utility for token acquisition
615
+ *
616
+ * @example
617
+ * ```tsx
618
+ * const token = await retryWithBackoff(
619
+ * () => acquireTokenSilent(scopes),
620
+ * { maxRetries: 3, debug: true }
621
+ * );
622
+ * ```
623
+ */
624
+ declare function retryWithBackoff<T>(fn: () => Promise<T>, config?: RetryConfig): Promise<T>;
625
+ /**
626
+ * Create a retry wrapper for a function
627
+ *
628
+ * @example
629
+ * ```tsx
630
+ * const acquireTokenWithRetry = createRetryWrapper(acquireToken, {
631
+ * maxRetries: 3,
632
+ * debug: true
633
+ * });
634
+ *
635
+ * const token = await acquireTokenWithRetry(scopes);
636
+ * ```
637
+ */
638
+ declare function createRetryWrapper<TArgs extends any[], TReturn>(fn: (...args: TArgs) => Promise<TReturn>, config?: RetryConfig): (...args: TArgs) => Promise<TReturn>;
639
+
640
+ /**
641
+ * Debug logger configuration
642
+ */
643
+ interface DebugLoggerConfig {
644
+ /**
645
+ * Enable debug mode
646
+ * @default false
647
+ */
648
+ enabled?: boolean;
649
+ /**
650
+ * Prefix for log messages
651
+ * @default '[MSAL-Next]'
652
+ */
653
+ prefix?: string;
654
+ /**
655
+ * Show timestamps
656
+ * @default true
657
+ */
658
+ showTimestamp?: boolean;
659
+ /**
660
+ * Log level
661
+ * @default 'info'
662
+ */
663
+ level?: 'error' | 'warn' | 'info' | 'debug';
664
+ }
665
+ declare class DebugLogger {
666
+ private config;
667
+ constructor(config?: DebugLoggerConfig);
668
+ private shouldLog;
669
+ private formatMessage;
670
+ error(message: string, data?: any): void;
671
+ warn(message: string, data?: any): void;
672
+ info(message: string, data?: any): void;
673
+ debug(message: string, data?: any): void;
674
+ group(label: string): void;
675
+ groupEnd(): void;
676
+ setEnabled(enabled: boolean): void;
677
+ setLevel(level: DebugLoggerConfig['level']): void;
678
+ }
679
+ /**
680
+ * Get or create the global debug logger
681
+ *
682
+ * @example
683
+ * ```tsx
684
+ * const logger = getDebugLogger({ enabled: true, level: 'debug' });
685
+ * logger.info('User logged in', { username: 'user@example.com' });
686
+ * ```
687
+ */
688
+ declare function getDebugLogger(config?: DebugLoggerConfig): DebugLogger;
689
+ /**
690
+ * Create a scoped logger with a custom prefix
691
+ *
692
+ * @example
693
+ * ```tsx
694
+ * const logger = createScopedLogger('GraphAPI', { enabled: true });
695
+ * logger.info('Fetching user profile');
696
+ * ```
697
+ */
698
+ declare function createScopedLogger(scope: string, config?: DebugLoggerConfig): DebugLogger;
699
+
700
+ /**
701
+ * Security utilities for input validation and sanitization
702
+ */
703
+ /**
704
+ * Validate account data structure from cookie
705
+ */
706
+ interface ValidatedAccountData {
707
+ homeAccountId: string;
708
+ username: string;
709
+ name?: string;
710
+ }
711
+ /**
712
+ * Safely parse and validate JSON from untrusted sources
713
+ */
714
+ declare function safeJsonParse<T>(jsonString: string, validator: (data: any) => data is T): T | null;
715
+ /**
716
+ * Validate account data structure
717
+ */
718
+ declare function isValidAccountData(data: any): data is ValidatedAccountData;
719
+ /**
720
+ * Sanitize error messages to prevent information disclosure
721
+ */
722
+ declare function sanitizeError(error: unknown): string;
723
+ /**
724
+ * Validate redirect URI to prevent open redirect vulnerabilities
725
+ */
726
+ declare function isValidRedirectUri(uri: string, allowedOrigins: string[]): boolean;
727
+ /**
728
+ * Validate scope strings to prevent injection
729
+ */
730
+ declare function isValidScope(scope: string): boolean;
731
+ /**
732
+ * Validate array of scopes
733
+ */
734
+ declare function validateScopes(scopes: string[]): boolean;
735
+
736
+ interface AuthMiddlewareConfig {
737
+ /**
738
+ * Routes that require authentication
739
+ * @example ['/dashboard', '/profile', '/api/protected']
740
+ */
741
+ protectedRoutes?: string[];
742
+ /**
743
+ * Routes that should be accessible only when NOT authenticated
744
+ * @example ['/login', '/signup']
745
+ */
746
+ publicOnlyRoutes?: string[];
747
+ /**
748
+ * Login page path
749
+ * @default '/login'
750
+ */
751
+ loginPath?: string;
752
+ /**
753
+ * Redirect path after login
754
+ * @default '/'
755
+ */
756
+ redirectAfterLogin?: string;
757
+ /**
758
+ * Cookie name for session
759
+ * @default 'msal.account'
760
+ */
761
+ sessionCookie?: string;
762
+ /**
763
+ * Custom authentication check function
764
+ */
765
+ isAuthenticated?: (request: NextRequest) => boolean | Promise<boolean>;
766
+ /**
767
+ * Enable debug logging
768
+ * @default false
769
+ */
770
+ debug?: boolean;
771
+ }
772
+ /**
773
+ * Creates authentication middleware for Next.js App Router
774
+ *
775
+ * @example
776
+ * ```tsx
777
+ * // middleware.ts
778
+ * import { createAuthMiddleware } from '@chemmangat/msal-next';
779
+ *
780
+ * export const middleware = createAuthMiddleware({
781
+ * protectedRoutes: ['/dashboard', '/profile'],
782
+ * publicOnlyRoutes: ['/login'],
783
+ * loginPath: '/login',
784
+ * });
785
+ *
786
+ * export const config = {
787
+ * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
788
+ * };
789
+ * ```
790
+ */
791
+ declare function createAuthMiddleware(config?: AuthMiddlewareConfig): (request: NextRequest) => Promise<NextResponse<unknown>>;
792
+
793
+ interface ServerSession {
794
+ /**
795
+ * Whether user is authenticated
796
+ */
797
+ isAuthenticated: boolean;
798
+ /**
799
+ * User's account ID from MSAL cache
800
+ */
801
+ accountId?: string;
802
+ /**
803
+ * User's username/email
804
+ */
805
+ username?: string;
806
+ /**
807
+ * Access token (if available in cookie)
808
+ * @deprecated Storing tokens in cookies is not recommended for security reasons
809
+ */
810
+ accessToken?: string;
811
+ }
812
+
813
+ export { AuthGuard, type AuthGuardProps, type AuthMiddlewareConfig, AuthStatus, type AuthStatusProps, type CustomTokenClaims, type DebugLoggerConfig, ErrorBoundary, type ErrorBoundaryProps, type GraphApiOptions, MicrosoftSignInButton, type MicrosoftSignInButtonProps, type MsalAuthConfig, MsalAuthProvider, type MsalAuthProviderProps, type RetryConfig, type ServerSession, SignOutButton, type SignOutButtonProps, type UseGraphApiReturn, type UseMsalAuthReturn, type UseRolesReturn, type UseUserProfileReturn, UserAvatar, type UserAvatarProps, type UserProfile, type ValidatedAccountData, type WithAuthOptions, createAuthMiddleware, createMsalConfig, createRetryWrapper, createScopedLogger, getDebugLogger, getMsalInstance, isValidAccountData, isValidRedirectUri, isValidScope, retryWithBackoff, safeJsonParse, sanitizeError, useGraphApi, useMsalAuth, useRoles, useUserProfile, validateScopes, withAuth };