UNPKG

@resk/core

Version:

An innovative TypeScript framework that empowers developers to build applications with a fully decorator-based architecture for efficient resource management. By combining the power of decorators with a resource-oriented design, DecorRes enhances code cla

1,137 lines (1,136 loc) 89.9 kB
import { IObservable } from "../observable"; import { IResourceActionName, IResourceName } from "../resources/types"; import { IDict } from "../types/dictionary"; import "./types"; import { IAuthEvent, IAuthPerm, IAuthPerms, IAuthSessionStorage, IAuthUser } from "./types"; export * from "./types"; /*** * A class that provides methods for managing session data. * */ declare class Session { /** * Retrieves a specific value from the session data based on the provided session name and key. * * This function first checks if the provided key is a non-null string. If it is not, it returns undefined. * It then retrieves the session data using `getData` and returns the value associated with the specified key. * * @param sessionName - An optional string that represents the name of the session. * @param key - A string representing the key of the value to retrieve from the session data. * * @returns The value associated with the specified key in the session data, or undefined if the key is invalid. * * @example * // Example of retrieving a specific value from session data * const value = get('mySession', 'userPreference'); // Returns: 'darkMode' * * @example * // Example of trying to retrieve a value with an invalid key * const value = get('mySession', null); // Returns: undefined */ static get(sessionName?: string, key?: string): any; /** * Sets a value or an object in the session data for a specific session name. * * This function retrieves the current session data using `getData`. If a valid key is provided, it sets the * corresponding value in the session data. If an object is provided as the key, it replaces the entire session data. * Finally, it saves the updated session data back to the session storage. * * @param sessionName - An optional string that represents the name of the session. * @param key - A string representing the key to set a value for, or an object containing multiple key-value pairs. * @param value - The value to set for the specified key. This parameter is ignored if an object is provided as the key. * * @returns The updated session data as an object. * * @example * // Example of setting a single value in session data * const updatedData = set('mySession', 'userPreference', 'darkMode'); // Returns: { userPreference: 'darkMode' } * * @example * // Example of replacing the entire session data with an object * const updatedData = set('mySession', { userPreference: 'lightMode', language: ' English' }); // Returns: { userPreference: 'lightMode', language: 'English' } */ static set(sessionName?: string, key?: string | IDict, value?: any): IDict; /** * Generates a unique session key based on the authenticated user's ID and an optional session name. * * The session key is constructed in the format: `auth-{userId}-{sessionName}`. If the user is not signed in, * the user ID will be an empty string. This key is used to store and retrieve session data. * * @param sessionName - An optional string that represents the name of the session. If not provided, * an empty string will be used in the key. * * @returns A string representing the unique session key. * * @example * // Example of generating a session key for a user with ID '12345' * const sessionKey = getKey('mySession'); // Returns: 'auth-12345-mySession' * * @example * // Example of generating a session key when no user is signed in * const sessionKey = getKey(); // Returns: 'auth--' */ static getKey(sessionName?: string): string; /** * Retrieves session data associated with a specific session name. * * This function checks if the provided session name is a non-null string. If it is not, an empty object is returned. * Otherwise, it constructs a key using `getKey` and retrieves the corresponding data from the session storage. * * @param sessionName - An optional string that represents the name of the session. If not provided or invalid, * an empty object will be returned. * * @returns An object containing the session data associated with the specified session name. * If the session name is invalid, an empty object is returned. * * @example * // Example of retrieving session data for a specific session name * const sessionData = getData('mySession'); // Returns: { } * * @example * // Example of retrieving session data with an invalid session name * const sessionData = getData(null); // Returns: {} */ static getData(sessionName?: string): IDict; /** * Retrieves the authentication token from the session storage. * * This function checks the currently signed-in user and returns their token. * If the user is not signed in or if there is no token available, it will return * `undefined` or `null`. * * @returns {string | undefined | null} The authentication token of the signed user, * or `undefined` if the user is not signed in, or `null` if there is no token. * * @example * const token = getToken(); * if (token) { * console.log("User token:", token); * } else { * console.log("No user is signed in or token is not available."); * } */ static getToken(): string | undefined | null; /** * Sets the authentication token in the session storage for the currently signed-in user. * * This function updates the signed user's information by adding or updating the token * in the session storage. If the token is `null`, it will remove the token from the user's * session data. * * @param {string | null} token - The token to be set for the signed user. * If `null`, the token will be removed from the user's session data. * * @returns {void} This function does not return a value. * * @example * setToken("my-secret-token"); * // To remove the token * setToken(null); */ static setToken(token: string | null): void; /** * Retrieves a session storage object that provides methods for managing session data. * * This function creates an object that allows you to interact with session storage * using a specified session name. It provides methods to get, set, and retrieve data * associated with the session, as well as to retrieve the session key. * * @param sessionName - An optional string that represents the name of the session. * If provided, it will be used as a prefix for the keys stored * in session storage. If not provided, the session will be * treated as anonymous. * * @returns An object implementing the `IAuthSessionStorage` interface, which includes * methods for session management: * - `get(key?: string): any`: Retrieves the value associated with the specified key. * - `set(key?: string | IDict, value?: any): void`: Stores a value under the specified key. * - `getData(): IDict`: Returns all data stored in the session as a dictionary. * - `getKey(): string`: Returns the session key used for storage. * * @example * // Create a session storage object with a specific session name * const session = getSessionStorage('userSession'); * * // Set a value in the session storage * session.set('token', 'abc123'); * * // Retrieve the value from session storage * const token = session.get('token'); // 'abc123' * * // Get all data stored in the session * const allData = session.getData(); // { token: 'abc123' } * * // Get the session key * const sessionKey = session.getKey(); // 'userSession' * * @remarks * This function is particularly useful for managing user authentication sessions * in web applications. By using session storage, data persists across page reloads * but is cleared when the tab or browser is closed. * * Ensure that the keys used for storing data are unique to avoid collisions with * other session data. Consider using a structured naming convention for keys. */ static getStorage(sessionName?: string): IAuthSessionStorage; } export declare class Auth { /** * Authentication event handler. * Initializes an observable event handler for authentication Auth.events. * * This constant `events` is assigned an instance of `IObservable<IAuthEvent>`, which is used to manage * authentication-related events in the application. The initialization checks if the global * `Global.eventsResourcesObservableHandler` exists and is an object. If it does, it assigns it to * `events`; otherwise, it defaults to an empty object cast as `IObservable<IAuthEvent>`. * * This pattern allows for flexible handling of events, ensuring that the application can respond * to authentication actions such as sign-in, sign-out, and sign-up. * * @type {IObservable<IAuthEvent>} * * @example * import {Auth} from '@resk/core'; * Auth.events.on('SIGN_IN', (user) => { * console.log(`User signed in: ${user.username}`); * }); * * function userSignIn(user) { * Auth.events.trigger('SIGN_IN', user); * } */ static events: IObservable<IAuthEvent>; private static localUserRef; /** * Checks if the user is a master admin. * * The `isMasterAdmin` function determines whether the provided user * has master admin privileges. If no user is provided, it will * attempt to retrieve the signed user from the session. * * ### Parameters * * - `user` (IAuthUser , optional): The user object to check. If not * provided, the function will attempt to retrieve the signed user * from the session. * * ### Returns * * - `boolean`: Returns `true` if the user is a master admin, or `false` otherwise. * * ### Example Usage * * ```typescript * const user: IAuthUser = { id: "admin123" }; * Auth.isMasterAdmin = (user)=>{ * return checkSomeCondition(user); * }; // false (assuming the user is not a master admin) * ``` * @see {@link IAuthUser} for the `IAuthUser` type. */ static isMasterAdmin?: (user?: IAuthUser) => boolean; private static _isMasterAdmin; /** * Retrieves the currently authenticated user from secure session storage. * * This method implements a secure user session retrieval system that handles encrypted * user data storage. It first checks for a cached user reference in memory for performance * optimization, then attempts to decrypt and parse the user data from session storage * if no cached version exists. The method includes comprehensive error handling for * decryption failures and data corruption scenarios. * * ### Security Features: * - **Encrypted Storage**: User data is encrypted using AES encryption before storage * - **Memory Caching**: Cached in `localUserRef` for improved performance and reduced decryption overhead * - **Safe Parsing**: Uses `JsonHelper.parse` for secure JSON deserialization * - **Error Recovery**: Gracefully handles corrupted or invalid session data * * ### Performance Optimization: * The method implements a two-tier retrieval strategy: * 1. **Memory Cache**: Returns immediately if user is already loaded in memory * 2. **Session Storage**: Decrypts and parses data from persistent storage only when needed * * @returns The authenticated user object containing user information, permissions, and roles, * or `null` if no user is currently signed in, session data is corrupted, or * decryption fails. The returned object conforms to the `IAuthUser` interface. * * @example * ```typescript * // Basic usage - check if user is signed in * const currentUser = Auth.getSignedUser(); * if (currentUser) { * console.log(`Welcome back, ${currentUser.username}!`); * console.log(`User ID: ${currentUser.id}`); * } else { * console.log("No user is currently signed in"); * } * ``` * * @example * ```typescript * // Using with permission checking * const user = Auth.getSignedUser(); * if (user) { * const canEditDocuments = Auth.checkUserPermission(user, "documents", "update"); * if (canEditDocuments) { * showEditButton(); * } * } * ``` * * @example * ```typescript * // Handling authentication state in React component * function UserProfile() { * const [user, setUser] = useState<IAuthUser | null>(null); * * useEffect(() => { * const currentUser = Auth.getSignedUser(); * setUser(currentUser); * * if (!currentUser) { * // Redirect to login page * router.push('/login'); * } * }, []); * * return user ? <div>Hello, {user.username}</div> : <div>Loading...</div>; * } * ``` * * @example * ```typescript * // Using with authentication guards * class AuthGuard { * static requireAuth(): boolean { * const user = Auth.getSignedUser(); * if (!user) { * throw new Error("Authentication required"); * } * return true; * } * * static requireRole(roleName: string): boolean { * const user = Auth.getSignedUser(); * return user?.roles?.some(role => role.name === roleName) ?? false; * } * } * ``` * * @example * ```typescript * // API request with user token * async function makeAuthenticatedRequest(url: string) { * const user = Auth.getSignedUser(); * if (!user?.token) { * throw new Error("No valid authentication token"); * } * * return fetch(url, { * headers: { * 'Authorization': `Bearer ${user.token}`, * 'Content-Type': 'application/json' * } * }); * } * ``` * * @throws {CryptoError} May throw during decryption if session data is corrupted * @throws {SyntaxError} May throw during JSON parsing if decrypted data is malformed * * @see {@link IAuthUser} - Complete user object interface definition * @see {@link setSignedUser} - Method to store user in session * @see {@link signIn} - High-level user authentication method * @see {@link signOut} - Method to clear user session * @see {@link Session.getToken} - Utility to get user's authentication token * @see {@link checkUserPermission} - Check specific user permissions * * @since 1.0.0 * @public * * @remarks * **Security Considerations:** * - Session data is automatically encrypted using AES encryption * - The encryption key `SESSION_ENCRYPT_KEY` should be kept secure * - User data includes sensitive information like tokens and permissions * - Always validate user data before using in security-critical operations * * **Performance Notes:** * - First call after page load requires decryption (slower) * - Subsequent calls return cached data (faster) * - Cache is automatically cleared when user signs out * - Consider the performance impact in render loops * * **Error Handling:** * - Returns `null` instead of throwing errors for better UX * - Logs errors to console for debugging purposes * - Gracefully handles session storage corruption * - Automatically recovers from temporary decryption failures */ static getSignedUser(): IAuthUser | null; /** * Securely stores an authenticated user in encrypted session storage with event broadcasting. * * This method is the core user session management function that handles secure storage of user * authentication data. It encrypts user information using AES encryption before persisting to * session storage, maintains local memory cache for performance, and optionally broadcasts * authentication events to notify other parts of the application about user state changes. * * ### Security Architecture: * - **AES Encryption**: User data is encrypted before storage to protect sensitive information * - **Session Timestamping**: Automatically adds `authSessionCreatedAt` timestamp for session tracking * - **Error Isolation**: Encryption failures don't crash the application, user reference is safely cleared * - **Memory Management**: Updates local cache reference for immediate access * * ### Event System Integration: * The method integrates with the authentication event system to broadcast state changes: * - **SIGN_IN Event**: Triggered when a valid user is stored with successful encryption * - **SIGN_OUT Event**: Triggered when user is cleared (null) or encryption fails * - **Event Payload**: Includes the user object for event handlers to process * * @param u - The user object to store in session, or `null` to clear the current session. * When providing a user object, it should contain all necessary authentication * information including permissions, roles, and tokens. The object will be * automatically timestamped with `authSessionCreatedAt`. * * @param triggerEvent - Optional flag controlling whether to broadcast authentication events. * When `true` (default), triggers either 'SIGN_IN' or 'SIGN_OUT' events * based on the operation result. Set to `false` to perform silent * session updates without notifying event listeners. * * @returns A promise that resolves to the result of the session storage operation. * The promise completes after the encrypted data has been written to storage. * Returns the storage operation result, typically indicating success or failure. * * @example * ```typescript * // Standard user sign-in with event broadcasting * const user: IAuthUser = { * id: "user123", * username: "john_doe", * email: "john@example.com", * token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", * perms: { documents: ["read", "write"] }, * roles: [{ name: "editor", perms: { images: ["upload"] } }] * }; * * await Auth.setSignedUser(user, true); * console.log("User signed in with events triggered"); * ``` * * @example * ```typescript * // Silent session update without triggering events * const updatedUser = { ...currentUser, lastActivity: new Date() }; * await Auth.setSignedUser(updatedUser, false); * console.log("User session updated silently"); * ``` * * @example * ```typescript * // Clear user session (sign out) * await Auth.setSignedUser(null, true); * console.log("User signed out, SIGN_OUT event triggered"); * ``` * * @example * ```typescript * // Using with event listeners * Auth.events.on('SIGN_IN', (user) => { * console.log(`Welcome ${user.username}!`); * initializeUserDashboard(user); * trackUserLogin(user.id); * }); * * Auth.events.on('SIGN_OUT', () => { * console.log('User signed out'); * clearUserDashboard(); * redirectToLogin(); * }); * * // Now when we set a user, events will fire automatically * await Auth.setSignedUser(authenticatedUser); * ``` * * @example * ```typescript * // Error handling with session management * try { * await Auth.setSignedUser(userFromAPI); * showSuccessMessage("Login successful"); * } catch (error) { * console.error("Failed to store user session:", error); * showErrorMessage("Login failed, please try again"); * } * ``` * * @example * ```typescript * // Updating user permissions after role change * const currentUser = Auth.getSignedUser(); * if (currentUser) { * const updatedUser = { * ...currentUser, * roles: [...currentUser.roles, newRole], * perms: { ...currentUser.perms, ...newPermissions } * }; * * await Auth.setSignedUser(updatedUser, false); // Silent update * console.log("User permissions updated"); * } * ``` * * @throws {CryptoError} May throw if encryption fails due to invalid encryption key * @throws {StorageError} May throw if session storage is unavailable or quota exceeded * * @see {@link IAuthUser} - Complete user object interface with all required fields * @see {@link getSignedUser} - Retrieve the currently stored user from session * @see {@link signIn} - High-level user authentication wrapper * @see {@link signOut} - High-level user sign-out wrapper * @see {@link Auth.events} - Authentication event system for state change notifications * @see {@link IAuthEvent} - Available authentication event types and payloads * * @since 1.0.0 * @public * @async * * @remarks * **Security Best Practices:** * - User objects should be validated before storage * - Sensitive data like tokens are automatically encrypted * - Session timestamps help with security auditing * - Always handle encryption errors gracefully * * **Performance Considerations:** * - Encryption/decryption adds computational overhead * - Local cache is updated synchronously for immediate access * - Event broadcasting may trigger multiple listeners * - Consider batching multiple user updates to reduce I/O * * **Event System:** * - Events are asynchronous and don't block the storage operation * - Multiple listeners can subscribe to the same event * - Event payload includes the full user object for flexibility * - Use `triggerEvent: false` for internal operations to avoid recursion */ static setSignedUser(u: IAuthUser | null, triggerEvent?: boolean): Promise<IAuthUser | null>; /** * Authenticates a user and establishes a secure session with comprehensive validation. * * This high-level authentication method provides a complete user sign-in workflow with * input validation, secure session establishment, and event broadcasting. It serves as * the primary entry point for user authentication in applications, handling all the * complexity of secure session management while providing a simple, promise-based API. * * ### Authentication Workflow: * 1. **Input Validation**: Validates that the provided user object is properly structured * 2. **Session Creation**: Calls `setSignedUser` to securely store user data * 3. **Event Broadcasting**: Optionally triggers authentication events for app-wide notifications * 4. **Return Confirmation**: Returns the authenticated user object on successful completion * * ### Security Features: * - **Object Validation**: Ensures user object is valid before processing * - **Encrypted Storage**: Leverages `setSignedUser` for secure data persistence * - **Error Handling**: Provides meaningful error messages for failed authentication * - **Session Timestamping**: Automatically tracks when authentication session was created * * @param user - The authenticated user object containing all necessary user information. * Must be a valid object conforming to the `IAuthUser` interface, including * properties like id, username, email, permissions, roles, and authentication token. * The object should come from a successful authentication process (login API, OAuth, etc.). * * @param triggerEvent - Optional flag controlling whether to broadcast authentication events. * When `true` (default), triggers a 'SIGN_IN' event that other parts of * the application can listen to for initialization, analytics, or UI updates. * Set to `false` for silent authentication without event notifications. * * @returns A promise that resolves to the authenticated user object when sign-in is successful. * The returned user object is the same as the input but may include additional * properties added during the authentication process (like session timestamps). * * @throws {Error} Throws an error with internationalized message if the user object is invalid, * null, undefined, or not a proper object structure. The error message is * retrieved from the i18n system using key "auth.invalidSignInUser". * * @example * ```typescript * // Basic user sign-in after successful API authentication * try { * const response = await fetch('/api/auth/login', { * method: 'POST', * body: JSON.stringify({ username, password }), * headers: { 'Content-Type': 'application/json' } * }); * * const userData = await response.json(); * const authenticatedUser = await Auth.signIn(userData); * * console.log(`Welcome ${authenticatedUser.username}!`); * router.push('/dashboard'); * } catch (error) { * console.error('Sign-in failed:', error.message); * showErrorMessage('Invalid credentials'); * } * ``` * * @example * ```typescript * // OAuth authentication workflow * async function handleOAuthCallback(authCode: string) { * try { * // Exchange auth code for user data * const tokenResponse = await exchangeCodeForToken(authCode); * const userProfile = await fetchUserProfile(tokenResponse.access_token); * * const user: IAuthUser = { * id: userProfile.id, * username: userProfile.login, * email: userProfile.email, * token: tokenResponse.access_token, * perms: await fetchUserPermissions(userProfile.id), * roles: await fetchUserRoles(userProfile.id), * provider: 'oauth' * }; * * await Auth.signIn(user); * console.log('OAuth sign-in successful'); * } catch (error) { * console.error('OAuth sign-in failed:', error); * } * } * ``` * * @example * ```typescript * // Silent authentication without triggering events * async function silentAuth(sessionToken: string) { * try { * const userData = await validateSessionToken(sessionToken); * const user = await Auth.signIn(userData, false); // No events * * console.log('Silent authentication successful'); * return user; * } catch (error) { * console.log('Silent auth failed, user needs to login'); * return null; * } * } * ``` * * @example * ```typescript * // Complete authentication flow with event handling * // Set up event listeners first * Auth.events.on('SIGN_IN', (user) => { * // Initialize user-specific features * initializeUserPreferences(user.id); * loadUserDashboard(user.perms); * trackAnalyticsEvent('user_signin', { userId: user.id }); * * // Update UI state * updateNavigationForUser(user.roles); * showWelcomeMessage(user.username); * }); * * // Perform authentication * async function loginUser(credentials: LoginCredentials) { * try { * const authResult = await authenticateWithAPI(credentials); * const user = await Auth.signIn(authResult.user); // Events will fire * * return { success: true, user }; * } catch (error) { * return { success: false, error: error.message }; * } * } * ``` * * @example * ```typescript * // Multi-step authentication with role-based redirection * async function signInWithRoleRedirect(userData: any) { * try { * const user = await Auth.signIn(userData); * * // Redirect based on user role * if (user.roles?.some(role => role.name === 'admin')) { * router.push('/admin/dashboard'); * } else if (user.roles?.some(role => role.name === 'moderator')) { * router.push('/moderator/panel'); * } else { * router.push('/user/dashboard'); * } * * return user; * } catch (error) { * console.error('Role-based sign-in failed:', error); * throw error; * } * } * ``` * * @see {@link IAuthUser} - Complete user object interface specification * @see {@link setSignedUser} - Lower-level method for secure user storage * @see {@link getSignedUser} - Retrieve currently authenticated user * @see {@link signOut} - Sign out and clear user session * @see {@link Auth.events} - Authentication event system for state notifications * @see {@link isAllowed} - Check user permissions for access control * * @since 1.0.0 * @public * @async * * @remarks * **Best Practices:** * - Always validate user data before calling this method * - Use try-catch blocks to handle authentication failures gracefully * - Consider implementing token refresh logic for long-lived sessions * - Use event listeners to initialize user-specific application features * * **Error Handling:** * - The method throws immediately on invalid input for fast failure * - Use internationalized error messages for better user experience * - Consider logging authentication attempts for security monitoring * - Implement retry logic for transient authentication failures * * **Integration Notes:** * - Works seamlessly with any authentication provider (JWT, OAuth, custom) * - Integrates with the permission system for access control * - Compatible with SSR/SPA applications through secure session storage * - Supports both traditional and modern authentication workflows */ static signIn(user: IAuthUser, triggerEvent?: boolean): Promise<IAuthUser>; /** * Signs out the currently authenticated user and securely clears their session. * * This method provides a high-level, convenient interface for user sign-out operations. * It handles the complete user session termination workflow by clearing the encrypted * user data from session storage, removing the cached user reference from memory, and * optionally broadcasting sign-out events to notify other parts of the application * about the authentication state change. * * ### Sign-Out Workflow: * 1. **Session Clearing**: Calls `setSignedUser(null)` to remove user data from encrypted storage * 2. **Memory Cleanup**: Clears the local user reference cache for immediate effect * 3. **Event Broadcasting**: Optionally triggers 'SIGN_OUT' events for application-wide notifications * 4. **Security Cleanup**: Ensures no sensitive user data remains in browser storage * * ### Security Features: * - **Complete Data Removal**: Eliminates all traces of user session from storage * - **Memory Safety**: Clears in-memory user references to prevent data leakage * - **Event Coordination**: Allows other components to perform cleanup operations * - **Immediate Effect**: Session termination is effective immediately after method completion * * ### Application Integration: * The method integrates seamlessly with the authentication event system, allowing * other parts of the application to react to sign-out events by clearing user-specific * data, redirecting to login pages, or performing cleanup operations. * * @param triggerEvent - Optional flag controlling whether to broadcast authentication events. * When `true` (default), triggers a 'SIGN_OUT' event that other parts * of the application can listen to for cleanup operations, analytics, * or UI state updates. Set to `false` to perform silent sign-out * without notifying event listeners (useful for internal operations). * * @returns A promise that resolves when the sign-out operation is complete and all * user session data has been successfully removed from storage. The promise * resolves to `void` as no return value is needed after successful sign-out. * * @example * ```typescript * // Standard user sign-out with event broadcasting * async function handleUserSignOut() { * try { * await Auth.signOut(); * console.log('User signed out successfully'); * * // Redirect to login page * window.location.href = '/login'; * } catch (error) { * console.error('Sign-out failed:', error); * showErrorMessage('Failed to sign out'); * } * } * ``` * * @example * ```typescript * // Silent sign-out without triggering events * async function silentSignOut() { * await Auth.signOut(false); // No events triggered * console.log('Silent sign-out completed'); * * // Manually handle post-signout operations * clearUserSpecificData(); * redirectToPublicArea(); * } * ``` * * @example * ```typescript * // Sign-out with comprehensive event handling * // Set up event listener first * Auth.events.on('SIGN_OUT', () => { * console.log('User signed out - cleaning up...'); * * // Clear user-specific application state * clearUserPreferences(); * clearUserCache(); * resetApplicationState(); * * // Update UI * hideUserMenus(); * showGuestContent(); * * // Analytics and logging * trackAnalyticsEvent('user_signout'); * logSecurityEvent('session_terminated'); * }); * * // Perform sign-out - events will fire automatically * await Auth.signOut(); * ``` * * @example * ```typescript * // Session timeout handling * class SessionManager { * private timeoutId: NodeJS.Timeout | null = null; * * startSessionTimer(durationMs: number) { * this.clearSessionTimer(); * * this.timeoutId = setTimeout(async () => { * console.log('Session expired - signing out user'); * await Auth.signOut(); // Will trigger events * * showNotification('Session expired. Please sign in again.'); * }, durationMs); * } * * clearSessionTimer() { * if (this.timeoutId) { * clearTimeout(this.timeoutId); * this.timeoutId = null; * } * } * } * ``` * * @example * ```typescript * // Multi-tab sign-out coordination * // Listen for storage events to handle sign-out in other tabs * window.addEventListener('storage', (event) => { * if (event.key === USER_SESSION_KEY && event.newValue === null) { * console.log('User signed out in another tab'); * * // Perform silent sign-out in this tab without triggering events * Auth.signOut(false); * * // Update UI to reflect signed-out state * updateUIForSignedOutUser(); * } * }); * ``` * * @example * ```typescript * // Complete authentication flow with error handling * class AuthenticationService { * async performSignOut(): Promise<boolean> { * try { * // Check if user is actually signed in * const currentUser = Auth.getSignedUser(); * if (!currentUser) { * console.log('No user to sign out'); * return true; * } * * // Perform API sign-out call if needed * await this.notifyServerSignOut(currentUser.token); * * // Sign out locally * await Auth.signOut(); * * console.log('Complete sign-out successful'); * return true; * } catch (error) { * console.error('Sign-out process failed:', error); * * // Force local sign-out even if server call failed * await Auth.signOut(false); * return false; * } * } * * private async notifyServerSignOut(token: string): Promise<void> { * await fetch('/api/auth/logout', { * method: 'POST', * headers: { * 'Authorization': `Bearer ${token}`, * 'Content-Type': 'application/json' * } * }); * } * } * ``` * * @throws {StorageError} May throw if session storage is unavailable during cleanup * @throws {CryptoError} May throw if there are issues clearing encrypted session data * * @see {@link setSignedUser} - Lower-level method used internally for session management * @see {@link getSignedUser} - Check if a user is currently signed in before sign-out * @see {@link signIn} - Corresponding method for user authentication * @see {@link Auth.events} - Event system for handling sign-out notifications * @see {@link IAuthEvent} - Authentication event types including 'SIGN_OUT' * @see {@link USER_SESSION_KEY} - Storage key used for session data * * @since 1.0.0 * @public * @async * * @remarks * **Security Considerations:** * - Always sign out users when suspicious activity is detected * - Consider notifying the server about client-side sign-outs for security auditing * - Be aware that local sign-out doesn't invalidate server-side sessions automatically * - Use HTTPS to prevent session hijacking during the sign-out process * * **Best Practices:** * - Always handle sign-out errors gracefully to avoid leaving users in inconsistent states * - Use event listeners to coordinate sign-out across multiple application components * - Consider implementing automatic sign-out for security-sensitive applications * - Provide clear feedback to users about successful sign-out operations * * **Performance Notes:** * - Sign-out is typically fast as it only involves storage cleanup * - Event broadcasting may trigger multiple listeners, consider the performance impact * - Silent sign-out (`triggerEvent: false`) is faster as it skips event processing * - Consider batching multiple sign-out operations if needed programmatically * * **Multi-Tab Considerations:** * - Sign-out in one tab affects session storage visible to all tabs * - Other tabs should listen for storage events to stay synchronized * - Consider implementing cross-tab communication for better user experience * - Be careful with silent sign-outs in multi-tab scenarios to avoid confusion */ static signOut(triggerEvent?: boolean): Promise<IAuthUser | null>; private static isResourceActionTupleArray; private static isResourceActionTupleObject; /** * Determines whether a user has permission to access a resource or perform an action. * * This comprehensive authorization method evaluates various types of permission configurations * to determine if the specified user (or currently signed-in user) is allowed to perform * the requested operation. It supports multiple permission formats including boolean flags, * function-based permissions, resource-action tuples, and complex permission arrays. * * The method follows a priority-based evaluation system: * 1. Boolean permissions are returned directly * 2. Master admin users are always granted access * 3. Null/undefined permissions default to `true` (open access) * 4. Function permissions are evaluated with the user context * 5. Resource-action permissions are checked against user's role permissions * 6. Array permissions are evaluated with OR logic (any match grants access) * * @template ResourceName - The resource name type, extending IResourceName * * @param perm - The permission configuration to evaluate. Can be: * - `boolean`: Direct permission flag (true = allowed, false = denied) * - `function`: Custom permission evaluator receiving user context * - `IResourceActionTupleObject`: Object with resourceName and action properties * - `IResourceActionTupleArray`: Array tuple [resourceName, action] * - `Array<IAuthPerm>`: Multiple permission configurations (OR logic) * - `null|undefined`: Defaults to allowing access * * @param user - Optional user object to check permissions against. * If not provided, uses the currently signed-in user from session. * The user object should contain permissions and role information. * * @returns `true` if the user is authorized to perform the action, `false` otherwise * * @example * ```typescript * // Boolean permission - direct access control * const canAccess = Auth.isAllowed(true); // Returns: true * const cannotAccess = Auth.isAllowed(false); // Returns: false * * // Function-based permission - custom logic * const customPerm = (user: IAuthUser) => user.age >= 18; * const canAccessAdultContent = Auth.isAllowed(customPerm); // Returns: true if user is 18+ * ``` * * @example * ```typescript * // Resource-action tuple object - structured permissions * const documentEditPerm = { resourceName: "documents", action: "update" }; * const canEditDocs = Auth.isAllowed(documentEditPerm); * // Returns: true if user has "update" permission on "documents" resource * * // Resource-action tuple array - compact format * const userDeletePerm: [string, string] = ["users", "delete"]; * const canDeleteUsers = Auth.isAllowed(userDeletePerm); * // Returns: true if user has "delete" permission on "users" resource * ``` * * @example * ```typescript * // Array of permissions - OR logic (any match grants access) * const multiplePerms = [ * { resourceName: "documents", action: "read" }, * { resourceName: "documents", action: "update" }, * ["admin", "all"] * ]; * const canAccessDocs = Auth.isAllowed(multiplePerms); * // Returns: true if user has any of the specified permissions * ``` * * @example * ```typescript * // Checking permissions for a specific user * const specificUser: IAuthUser = { * id: "user123", * perms: { documents: ["read", "update"] }, * roles: [{ name: "editor", perms: { images: ["upload"] } }] * }; * * const canEdit = Auth.isAllowed( * { resourceName: "documents", action: "update" }, * specificUser * ); // Returns: true * ``` * * @example * ```typescript * // Master admin bypass - always returns true * Auth.isMasterAdmin = (user) => user.id === "admin"; * const adminUser = { id: "admin" }; * const canDoAnything = Auth.isAllowed( * { resourceName: "secret", action: "delete" }, * adminUser * ); // Returns: true (master admin bypass) * ``` * * @see {@link IAuthPerm} - Permission configuration type definitions * @see {@link IAuthUser} - User object structure with permissions and roles * @see {@link IResourceName} - Valid resource name types * @see {@link IResourceActionName} - Valid action name types * @see {@link checkUserPermission} - Low-level permission checking * @see {@link isMasterAdmin} - Master admin detection function * * @since 1.0.0 * @public */ static isAllowed<ResourceName extends IResourceName = IResourceName>(perm: IAuthPerm<ResourceName>, user?: IAuthUser): boolean; /** * Validates whether a specific user has permission to perform an action on a resource. * * This core authorization method performs comprehensive permission checking by evaluating * both direct user permissions and role-based permissions. It serves as the foundation * for access control throughout the application, providing a reliable and secure way to * determine if a user is authorized to perform specific operations on protected resources. * * ### Permission Evaluation Strategy: * The method implements a hierarchical permission checking system: * 1. **Input Validation**: Ensures the user object is valid and properly structured * 2. **Direct Permissions**: Checks permissions directly assigned to the user * 3. **Role-Based Permissions**: Iterates through user roles to check role-specific permissions * 4. **First Match Wins**: Returns `true` as soon as any valid permission is found * 5. **Secure Default**: Returns `false` if no matching permissions are discovered * * ### Security Architecture: * - **Fail-Safe Design**: Defaults to denying access when permissions are unclear * - **Comprehensive Validation**: Validates user object structure and permission data * - **Role Inheritance**: Supports complex permission models through role-based access * - **Performance Optimized**: Uses early return to minimize computation time * * ### Permission Hierarchy: * The method checks permissions in the following order: * 1. User's direct permissions (`user.perms`) * 2. Permissions inherited from user roles (`user.roles[].perms`) * * @param user - The user object containing permission and role information. * Must be a valid `IAuthUser` object with properly structured * permissions and roles. The object should include `perms` for * direct permissions and optionally `roles` array for role-based permissions. * * @param resource - The resource name to check permissions against. * Should be a valid resource identifier from the `IResourceName` type. * Examples include "documents", "users", "admin", "reports", etc. * The resource name is case-sensitive and should match exactly. * * @param action - The specific action to check permission for on the given resource. * Defaults to "read" if not specified. Common actions include: * "read", "create", "update", "delete", "all", or custom actions * specific to your application's permission model. * * @returns `true` if the user has permission to perform the specified action * on the resource, either through direct permissions or role inheritance. * Returns `false` if the user lacks permission, has invalid data, * or if any validation checks fail. * * @example * ```typescript * // Basic permission checking - user with direct permissions * const user: IAuthUser = { * id: "user123", * username: "john_doe", * perms: { * documents: ["read", "create", "update"], * reports: ["read"] * } * }; * * const canReadDocs = Auth.checkUserPermission(user, "documents", "read"); * console.log(canReadDocs); // true * * const canDeleteDocs = Auth.checkUserPermission(user, "documents", "delete"); * console.log(canDeleteDocs); // false * ``` * * @example * ```typescript * // Role-based permission checking * const userWithRoles: IAuthUser = { * id: "user456", * username: "jane_smith", * perms: { * profile: ["read", "update"] * }, * roles: [ * { * name: "editor", * perms: { * documents: ["read", "create", "update"], * images: ["upload", "edit"] * } * }, * { * name: "moderator", * perms: { * comments: ["read", "update", "delete"], * users: ["read", "suspend"] * } * } * ] * }; * * // Check direct permission * const canUpdateProfile = Auth.checkUserPermission(userWithRoles, "profile", "update"); * console.log(canUpdateProfile); // true (from direct perms) * * // Check role-inherited permission * const canEditDocs = Auth.checkUserPermission(userWithRoles, "documents", "update"); * console.log(canEditDocs); // true (from editor role) * * // Check another role permission * const canDeleteComments = Auth.checkUserPermission(userWithRoles, "comments", "delete"); * console.log(canDeleteComments); // true (from moderator role) * ``` * * @example * ```typescript * // Default action parameter (read) * const user: IAuthUser = { * id: "reader",