@theoptimalpartner/jwt-auth-validator
Version:
JWT token validation package with offline JWKS validation and Redis-based token revocation support
280 lines • 9.7 kB
JavaScript
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