UNPKG

@theoptimalpartner/jwt-auth-validator

Version:

JWT token validation package with offline JWKS validation and Redis-based token revocation support

280 lines 9.7 kB
import NodeCache from 'node-cache'; import { RedisService } from './redis-service.js'; export class UserDataService { redisService; cache; config; stats; constructor(redisConfig, userDataConfig = {}) { this.redisService = new RedisService(redisConfig); this.config = { enableUserDataRetrieval: true, includeApplications: true, includeOrganizations: true, includeRoles: true, includeEffectivePermissions: false, cacheTimeout: 300, ...userDataConfig, }; this.cache = new NodeCache({ stdTTL: this.config.cacheTimeout || 300, checkperiod: Math.floor((this.config.cacheTimeout || 300) / 2), useClones: false }); this.stats = { service: 'UserDataService', connectionStatus: 'not-initialized', initialized: false, cacheHits: 0, cacheMisses: 0, }; } async initialize() { try { await this.redisService.initialize(); this.stats.initialized = true; this.stats.connectionStatus = 'connected'; console.log('✅ UserDataService initialized successfully'); } catch (error) { this.stats.initialized = false; this.stats.connectionStatus = 'error'; this.stats.error = error instanceof Error ? error.message : 'Unknown error'; console.error('❌ UserDataService initialization failed:', error); throw error; } } async getUserPermissions(userId) { if (!this.config.enableUserDataRetrieval) { return null; } const cacheKey = `user_permissions:${userId}`; const cached = this.cache.get(cacheKey); if (cached) { this.stats.cacheHits++; return cached; } this.stats.cacheMisses++; try { const redisKey = `user:permissions:${userId}`; const data = await this.redisService.get(redisKey); if (!data) { return null; } const permissions = JSON.parse(data); this.cache.set(cacheKey, permissions); return permissions; } catch (error) { console.error(`Error fetching user permissions for ${userId}:`, error); return null; } } async getApplication(appId) { if (!this.config.includeApplications) { return null; } const cacheKey = `app:${appId}`; const cached = this.cache.get(cacheKey); if (cached) { this.stats.cacheHits++; return cached; } this.stats.cacheMisses++; try { const redisKey = `app:${appId}`; const data = await this.redisService.get(redisKey); if (!data) { return null; } const application = JSON.parse(data); this.cache.set(cacheKey, application); return application; } catch (error) { console.error(`Error fetching application ${appId}:`, error); return null; } } async getOrganization(appId, organizationId) { if (!this.config.includeOrganizations) { return null; } const cacheKey = `org:${appId}:${organizationId}`; const cached = this.cache.get(cacheKey); if (cached) { this.stats.cacheHits++; return cached; } this.stats.cacheMisses++; try { const redisKey = `org:${appId}:${organizationId}`; const data = await this.redisService.get(redisKey); if (!data) { return null; } const organization = JSON.parse(data); this.cache.set(cacheKey, organization); return organization; } catch (error) { console.error(`Error fetching organization ${appId}:${organizationId}:`, error); return null; } } async getAppRoles(appId, organizationId) { if (!this.config.includeRoles) { return null; } const cacheKey = `app_roles:${appId}:${organizationId}`; const cached = this.cache.get(cacheKey); if (cached) { this.stats.cacheHits++; return cached; } this.stats.cacheMisses++; try { const redisKey = `app:roles:${appId}:${organizationId}`; const data = await this.redisService.get(redisKey); if (!data) { return null; } const roles = JSON.parse(data); this.cache.set(cacheKey, roles); return roles; } catch (error) { console.error(`Error fetching app roles ${appId}:${organizationId}:`, error); return null; } } async getAppSchema(appId) { const cacheKey = `app_schema:${appId}`; const cached = this.cache.get(cacheKey); if (cached) { this.stats.cacheHits++; return cached; } this.stats.cacheMisses++; try { const redisKey = 'app-schemas'; const data = await this.redisService.get(redisKey); if (!data) { return null; } const schemas = JSON.parse(data); const schema = schemas[appId]; if (!schema) { return null; } this.cache.set(cacheKey, schema); return schema; } catch (error) { console.error(`Error fetching app schema for ${appId}:`, error); return null; } } async getEffectivePermissions(userId, appId, organizationId) { if (!this.config.includeEffectivePermissions) { return null; } const cacheKey = `effective_permissions:${userId}:${appId}:${organizationId}`; const cached = this.cache.get(cacheKey); if (cached) { this.stats.cacheHits++; return cached; } this.stats.cacheMisses++; try { const redisKey = `permissions:cache:${userId}:${appId}:${organizationId}`; const data = await this.redisService.get(redisKey); if (!data) { return null; } const effectivePermissions = JSON.parse(data); this.cache.set(cacheKey, effectivePermissions, Math.floor((this.config.cacheTimeout || 300) / 2)); return effectivePermissions; } catch (error) { console.error(`Error fetching effective permissions ${userId}:${appId}:${organizationId}:`, error); return null; } } async getUserOrganizations(userId) { const permissions = await this.getUserPermissions(userId); if (!permissions || !permissions.permissions) { return []; } const userOrganizations = []; for (const [appId, orgs] of Object.entries(permissions.permissions)) { for (const [organizationId, orgData] of Object.entries(orgs)) { if (orgData.status === 'active') { let effectivePermissions; if (this.config.includeEffectivePermissions) { const perms = await this.getEffectivePermissions(userId, appId, organizationId); effectivePermissions = perms || undefined; } const userOrg = { appId, organizationId, roles: orgData.roles, status: orgData.status, }; if (effectivePermissions) { userOrg.effectivePermissions = effectivePermissions; } userOrganizations.push(userOrg); } } } return userOrganizations; } async getUserApplications(userId) { const userOrganizations = await this.getUserOrganizations(userId); const uniqueAppIds = [...new Set(userOrganizations.map(org => org.appId))]; const applications = []; for (const appId of uniqueAppIds) { const app = await this.getApplication(appId); if (app && app.isActive) { applications.push(app); } } return applications; } async getComprehensiveUserData(userId) { const [permissions, organizations, applications] = await Promise.all([ this.getUserPermissions(userId), this.getUserOrganizations(userId), this.getUserApplications(userId), ]); return { permissions, organizations, applications, }; } clearUserCache(userId) { const keys = this.cache.keys(); const userKeys = keys.filter(key => key.includes(userId)); for (const key of userKeys) { this.cache.del(key); } } clearAllCache() { this.cache.flushAll(); } getStats() { return { ...this.stats }; } isInitialized() { return this.stats.initialized && this.redisService.isConnected; } async shutdown() { this.cache.flushAll(); await this.redisService.disconnect(); this.stats.initialized = false; this.stats.connectionStatus = 'disconnected'; } } //# sourceMappingURL=user-data-service.js.map