UNPKG

@gsb-core/core

Version:

GSB core services and classes for platform-independent web applications

638 lines 25.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthService = void 0; const gsb_entity_service_1 = require("../entity/gsb-entity.service"); const gsb_config_1 = require("../../config/gsb-config"); const gsb_cache_service_1 = require("../cache/gsb-cache.service"); // Storage key prefixes const TOKEN_PREFIX = 'gsb_auth_'; const USER_DATA_PREFIX = 'gsb_user_'; const ACTIVE_TENANT_KEY = 'gsb_active_tenant'; // Role constants const ADMIN_ROLE_ID = '59b62a77-b722-44ad-9a2d-478365e8f1a0'; // Cache keys const CACHE_PREFIX = 'auth_'; // Token refresh settings const TOKEN_REFRESH_BUFFER = 3 * 60 * 1000; // 3 minutes before expiry const TOKEN_REFRESH_RETRY_DELAY = 30 * 1000; // 30 seconds between retries const TOKEN_REFRESH_MAX_RETRIES = 3; // Maximum number of retry attempts // Singleton instance let authServiceInstance = null; /** * Authentication service for managing auth tokens with GSB backend * Single-tenant implementation with forward compatibility for multi-tenant */ class AuthService { constructor() { this.token = null; this.refreshTokenTimeout = null; this.refreshTokenRetryCount = 0; this.gsbService = gsb_entity_service_1.GsbEntityService.getInstance(); this.cacheService = gsb_cache_service_1.GsbCacheService.getInstance(); this.tenantCode = (0, gsb_config_1.getGsbTenantCode)(); this.initFromStorage(); } static getInstance() { if (!authServiceInstance) { authServiceInstance = new AuthService(); } return authServiceInstance; } getKeys(tenantCode, keyType) { let prefix; let cachePrefix; switch (keyType) { case 'token': prefix = TOKEN_PREFIX; cachePrefix = `${CACHE_PREFIX}token_`; break; case 'userData': prefix = USER_DATA_PREFIX; cachePrefix = `${CACHE_PREFIX}userData_`; break; case 'expiry': prefix = `${TOKEN_PREFIX}expiry_`; cachePrefix = `${CACHE_PREFIX}expiry_`; break; } return { storageKey: `${prefix}${tenantCode}`, cacheKey: `${cachePrefix}${tenantCode}` }; } getCacheValue(tenantCode, keyType) { const { cacheKey, storageKey } = this.getKeys(tenantCode, keyType); return this.cacheService.getCache(cacheKey, storageKey); } setCacheValue(tenantCode, keyType, value, duration) { const { cacheKey, storageKey } = this.getKeys(tenantCode, keyType); this.cacheService.setCache(cacheKey, value, duration, storageKey); } removeCacheValue(tenantCode, keyType) { const { cacheKey, storageKey } = this.getKeys(tenantCode, keyType); this.cacheService.removeCache(cacheKey, storageKey); } saveToStorage(token, userData, remember) { // Always update memory state this.token = token; this.tenantCode = this.getTenantCode(); if (!this.tenantCode) { return; } if (typeof window !== 'undefined') { try { // Save active tenant this.cacheService.setCache('active_tenant', this.tenantCode, undefined, ACTIVE_TENANT_KEY); // Calculate cache duration based on token expiry let cacheDuration; let tokenExpiryTime = null; // First, try to get expiry from userData if (userData === null || userData === void 0 ? void 0 : userData.expireDate) { tokenExpiryTime = new Date(userData.expireDate).getTime(); } else { // If not in userData, try to decode from JWT token try { const tokenPayload = this.getUserFromToken(token); if (tokenPayload === null || tokenPayload === void 0 ? void 0 : tokenPayload.exp) { // JWT exp is in seconds, convert to milliseconds tokenExpiryTime = tokenPayload.exp * 1000; } } catch (error) { console.warn('Could not decode token expiry:', error); } } if (tokenExpiryTime) { const now = Date.now(); const tokenDuration = Math.max(0, tokenExpiryTime - now); // Use the token's actual expiry time cacheDuration = tokenDuration; } else { // Fallback to hardcoded durations only if no expiry found cacheDuration = remember ? 30 * 24 * 60 * 60 * 1000 : 24 * 60 * 60 * 1000; } // Store token and user data in cache this.setCacheValue(this.tenantCode, 'token', token, cacheDuration); this.setCacheValue(this.tenantCode, 'userData', userData, cacheDuration); // Set cookies for middleware to use if (typeof document !== 'undefined') { let cookieExpiry; if (remember) { if (tokenExpiryTime) { // Use actual token expiry for remember me cookieExpiry = new Date(tokenExpiryTime); } else { // Fallback to cache duration if no token expiry cookieExpiry = new Date(Date.now() + cacheDuration); } } // For session (non-remember), cookieExpiry remains undefined (session cookie) const tokenKeys = this.getKeys(this.tenantCode, 'token'); document.cookie = `${tokenKeys.storageKey}=${token}; path=/; ${cookieExpiry ? `expires=${cookieExpiry.toUTCString()};` : ''} SameSite=Lax`; // Store token expiry for validation if (userData === null || userData === void 0 ? void 0 : userData.expireDate) { const expiryKeys = this.getKeys(this.tenantCode, 'expiry'); document.cookie = `${expiryKeys.storageKey}=${userData.expireDate}; path=/; ${cookieExpiry ? `expires=${cookieExpiry.toUTCString()};` : ''} SameSite=Lax`; // Cache expiry date this.setCacheValue(this.tenantCode, 'expiry', userData.expireDate, cacheDuration); } } // Setup token refresh for session-based auth (non-remember me) if (!remember && (userData === null || userData === void 0 ? void 0 : userData.expireDate)) { this.scheduleTokenRefresh(userData.expireDate); } else { this.clearTokenRefresh(); } } catch (error) { console.error('Error saving auth data to storage:', error); } } } scheduleTokenRefresh(expireDate) { // Clear any existing refresh timeout this.clearTokenRefresh(); this.refreshTokenRetryCount = 0; // Only setup refresh in browser environment if (typeof window !== 'undefined') { try { const expiryTime = new Date(expireDate).getTime(); const now = Date.now(); // Calculate time until refresh (3 minutes before expiry) const timeUntilRefresh = Math.max(0, expiryTime - now - TOKEN_REFRESH_BUFFER); if (timeUntilRefresh > 0) { this.refreshTokenTimeout = setTimeout(() => { this.performTokenRefresh(); }, timeUntilRefresh); } else { // Token is already close to expiring or expired, refresh immediately this.performTokenRefresh(); } } catch (error) { console.error('Error scheduling token refresh:', error); } } } performTokenRefresh() { this.refreshToken() .then(response => { if (response.success) { // Reset retry count on success this.refreshTokenRetryCount = 0; console.log('Token refreshed successfully'); } else { this.handleRefreshFailure(); } }) .catch(() => { this.handleRefreshFailure(); }); } handleRefreshFailure() { this.refreshTokenRetryCount++; if (this.refreshTokenRetryCount <= TOKEN_REFRESH_MAX_RETRIES) { console.log(`Token refresh failed. Retrying (${this.refreshTokenRetryCount}/${TOKEN_REFRESH_MAX_RETRIES}) in ${TOKEN_REFRESH_RETRY_DELAY / 1000}s`); // Schedule retry this.refreshTokenTimeout = setTimeout(() => { this.performTokenRefresh(); }, TOKEN_REFRESH_RETRY_DELAY); } else { console.error('Token refresh failed after maximum retry attempts'); // Reset retry count for next time this.refreshTokenRetryCount = 0; } } clearTokenRefresh() { if (this.refreshTokenTimeout) { clearTimeout(this.refreshTokenTimeout); this.refreshTokenTimeout = null; } } clearStorage(allTenants = false) { // Clear token refresh this.clearTokenRefresh(); if (typeof window !== 'undefined') { try { if (allTenants) { // Get all available tenants const availableTenants = this.getAvailableTenants(); // Clear data for each tenant availableTenants.forEach(tenant => { // Remove from cache this.removeCacheValue(tenant, 'token'); this.removeCacheValue(tenant, 'userData'); this.removeCacheValue(tenant, 'expiry'); // Clear cookies if (typeof document !== 'undefined') { const tokenKeys = this.getKeys(tenant, 'token'); const expiryKeys = this.getKeys(tenant, 'expiry'); document.cookie = `${tokenKeys.storageKey}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`; document.cookie = `${expiryKeys.storageKey}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`; } }); // Remove active tenant this.cacheService.removeCache('active_tenant', ACTIVE_TENANT_KEY); } else { let tenantCode = this.getTenantCode(); if (!tenantCode) { return; } // Clear only current tenant data this.removeCacheValue(tenantCode, 'token'); this.removeCacheValue(tenantCode, 'userData'); this.removeCacheValue(tenantCode, 'expiry'); // Clear cookies if (typeof document !== 'undefined') { const tokenKeys = this.getKeys(tenantCode, 'token'); const expiryKeys = this.getKeys(tenantCode, 'expiry'); document.cookie = `${tokenKeys.storageKey}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`; document.cookie = `${expiryKeys.storageKey}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`; } } } catch (error) { console.error('Error clearing storage:', error); } } } /** * Initialize auth state from storage if available */ initFromStorage() { this.tenantCode = this.getTenantCode(); if (!this.tenantCode) { return; } if (typeof window !== 'undefined') { try { // Get the active tenant from cache or use the current tenant code const activeTenant = this.cacheService.getCache('active_tenant', ACTIVE_TENANT_KEY) || this.tenantCode; // If tenant changed, update internal state if (activeTenant !== this.tenantCode) { this.tenantCode = activeTenant; } // Try to get token from cache const token = this.getCacheValue(this.tenantCode, 'token'); if (token) { this.token = token; } } catch (error) { console.error('Error initializing from storage:', error); } } } /** * Refresh the authentication token * @returns Promise with the refresh response */ async refreshToken() { var _a; try { if (!this.token) { return { success: false, error: 'No token to refresh' }; } const response = await this.gsbService.refreshToken({}, this.token, this.tenantCode); if ((_a = response === null || response === void 0 ? void 0 : response.auth) === null || _a === void 0 ? void 0 : _a.token) { const token = response.auth.token; // Get current user data const userData = this.getCurrentUser() || {}; // Update expiry date if provided if (response.auth.expireDate) { userData.expireDate = response.auth.expireDate; } // Save with the same remember setting (false for auto-refresh) this.saveToStorage(token, userData, false); return { success: true, token, userData }; } else { return { success: false, error: 'Token refresh failed: Invalid response' }; } } catch (error) { console.error('Token refresh error:', error); return { success: false, error: error instanceof Error ? error.message : 'Token refresh failed' }; } } /** * Check if user is authenticated */ isAuthenticated() { const token = this.getToken(); if (!token) { return false; } try { const userData = this.getCurrentUser(); if (!userData || !userData.expireDate) { return false; } const expiryDate = new Date(userData.expireDate); const now = new Date(); return expiryDate > now; } catch (error) { console.error('Error checking token expiry:', error); return false; } } /** * Authenticate a user with GSB */ async login(credentials) { var _a, _b; try { if (credentials.remember === undefined) { credentials.remember = true; } credentials.includeUserInfo = true; if (!((_a = credentials === null || credentials === void 0 ? void 0 : credentials.variation) === null || _a === void 0 ? void 0 : _a.tenantCode)) { if (!(credentials === null || credentials === void 0 ? void 0 : credentials.variation)) { credentials.variation = {}; } credentials.variation.tenantCode = this.tenantCode; } const response = await this.gsbService.getToken(credentials); if ((_b = response === null || response === void 0 ? void 0 : response.auth) === null || _b === void 0 ? void 0 : _b.token) { const token = response.auth.token; const userData = { ...response.auth, roles: response.auth.roles || [], groups: response.auth.groups || [] }; this.saveToStorage(token, userData, credentials.remember); return { success: true, token, userData }; } else { return { success: false, error: 'Authentication failed: Invalid token response' }; } } catch (error) { console.error('Login error:', error); return { success: false, error: error instanceof Error ? error.message : 'Authentication failed' }; } } /** * Log out the current user * @param allTenants Whether to logout from all tenants (default: false) */ logout(allTenants = false) { this.token = null; this.clearTokenRefresh(); this.clearStorage(allTenants); this.cacheService.clearCache(); (0, gsb_config_1.setGsbToken)(undefined); } /** * Set working tenant for multi-tenant support * @param tenantCode The tenant code to switch to * @param token Optional JWT token for the tenant (if not provided, attempts to load from storage) * @returns True if successful switch, false if failed (e.g., no token available) */ setWorkingTenant(tenantCode, token) { // Clear existing refresh for previous tenant this.clearTokenRefresh(); if (tenantCode === this.tenantCode && this.token) { // Already on this tenant with valid token return true; } // If token is provided, use it if (token) { this.tenantCode = tenantCode; this.token = token; // Save active tenant this.cacheService.setCache('active_tenant', tenantCode, undefined, ACTIVE_TENANT_KEY); // Cache the token this.setCacheValue(tenantCode, 'token', token); // Check if we need to setup token refresh for this tenant const userData = this.getCacheValue(tenantCode, 'userData'); if (userData && userData.expireDate && this.token) { // If expiry is less than 30 days, it's a session token (not remember me) const expiry = new Date(userData.expireDate).getTime(); const now = Date.now(); if ((expiry - now) < 30 * 24 * 60 * 60 * 1000) { this.scheduleTokenRefresh(userData.expireDate); } } return true; } // Try to get from cache const cachedToken = this.getCacheValue(tenantCode, 'token'); if (cachedToken) { this.tenantCode = tenantCode; this.token = cachedToken; // Save active tenant this.cacheService.setCache('active_tenant', tenantCode, undefined, ACTIVE_TENANT_KEY); // Check if we need to setup token refresh for this tenant const userData = this.getCacheValue(tenantCode, 'userData'); if (userData && userData.expireDate && this.token) { // If expiry is less than 30 days, it's a session token (not remember me) const expiry = new Date(userData.expireDate).getTime(); const now = Date.now(); if ((expiry - now) < 30 * 24 * 60 * 60 * 1000) { this.scheduleTokenRefresh(userData.expireDate); } } return true; } // No token available for this tenant return false; } /** * Get all available tenant codes from storage * @returns Array of tenant codes that have stored tokens */ getAvailableTenants() { let tenantCode = this.getTenantCode(); const availableTenants = new Set(); if (tenantCode) { availableTenants.add(tenantCode); } try { // Use cache service to find all tenants with tokens if (typeof localStorage !== 'undefined') { const localStorageKeys = Object.keys(localStorage); localStorageKeys.forEach(key => { if (key.startsWith(TOKEN_PREFIX) && !key.includes('expiry_')) { const tenantCode = key.replace(TOKEN_PREFIX, ''); availableTenants.add(tenantCode); } }); } } catch (error) { console.error('Error getting available tenants:', error); } return Array.from(availableTenants); } /** * Get user information from JWT token */ getUserFromToken(token) { try { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent(atob(base64) .split('') .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join('')); return JSON.parse(jsonPayload); } catch (error) { console.error('Error decoding token:', error); return null; } } /** * Check if the current user has a specific role */ hasRole(role) { if (!this.token) { return false; } const userData = this.getCurrentUser(); if (!userData || !userData.roles) { return false; } return userData.roles.includes(role); } /** * Check if the current user has the admin role */ isAdmin() { return this.hasRole(ADMIN_ROLE_ID); } /** * Get the subscription tier from the current user data */ getSubscription() { const userData = this.getCurrentUser(); return userData === null || userData === void 0 ? void 0 : userData.subscription; } /** * Get the current tenant code */ getTenantCode() { if (!this.tenantCode) { this.tenantCode = (0, gsb_config_1.getGsbTenantCode)(); if (this.tenantCode) { this.cacheService.setCache('tenantCode', this.tenantCode, undefined, this.tenantCode); } } return this.tenantCode; } /** * Get the current auth token */ getToken() { let tenantCode = this.getTenantCode(); if (!tenantCode) { return null; } if (this.token) { return this.token; } this.token = (0, gsb_config_1.getEnvToken)() || null; if (this.token) { this.cacheService.setCache('token', this.token, undefined, tenantCode); return this.token; } // If token not loaded yet, try to load from storage if (!this.token && typeof window !== 'undefined') { // Try cache const cachedToken = this.getCacheValue(tenantCode, 'token'); if (cachedToken) { this.token = cachedToken; } } return this.token; } setToken(token, userData, remember = true) { var _a; this.token = token; this.saveToStorage(token, (_a = userData !== null && userData !== void 0 ? userData : this.getCurrentUser()) !== null && _a !== void 0 ? _a : {}, remember); (0, gsb_config_1.setGsbToken)(token); } async sendResetPasswordEmail(email) { var _a, _b; const resp = await this.gsbService.runWfFunction({ function: { name: 'sendResetPasswordEmail' }, instance: { prms: { email } } }, (_a = this.token) !== null && _a !== void 0 ? _a : '', (_b = this.tenantCode) !== null && _b !== void 0 ? _b : ''); if (resp === null || resp === void 0 ? void 0 : resp.response) { return resp.response; } return resp; } async changePassword(oldPassword, newPassword, email, resetToken) { var _a, _b; return await this.gsbService.changePassword({ oldPassword, newPassword, resetToken, email }, (_a = this.token) !== null && _a !== void 0 ? _a : '', (_b = this.tenantCode) !== null && _b !== void 0 ? _b : ''); } /** * Get the current user data from storage */ getCurrentUser() { if (typeof window === 'undefined') { return null; } if (!this.tenantCode) { this.tenantCode = (0, gsb_config_1.getGsbTenantCode)(); } if (!this.tenantCode) { return null; } try { // Get from cache return this.getCacheValue(this.tenantCode, 'userData'); } catch (error) { console.error('Error getting user data from storage:', error); return null; } } } exports.AuthService = AuthService; //# sourceMappingURL=auth.service.js.map