@solidxai/core 0.1.15-beta.1 → 0.1.15
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/CHANGELOG.md +1204 -0
- package/dist/helpers/cache.helper.d.ts.map +1 -1
- package/dist/helpers/cache.helper.js +3 -0
- package/dist/helpers/cache.helper.js.map +1 -1
- package/dist/repository/model-metadata.repository.d.ts.map +1 -1
- package/dist/repository/model-metadata.repository.js +3 -1
- package/dist/repository/model-metadata.repository.js.map +1 -1
- package/dist/repository/security-rule.repository.js +1 -1
- package/dist/repository/security-rule.repository.js.map +1 -1
- package/dist/services/permission-metadata.service.d.ts.map +1 -1
- package/dist/services/permission-metadata.service.js +6 -2
- package/dist/services/permission-metadata.service.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers/cache.helper.ts +7 -1
- package/src/repository/model-metadata.repository.ts +6 -1
- package/src/repository/security-rule.repository.ts +1 -1
- package/src/services/permission-metadata.service.ts +9 -3
- package/CLAUDE.md +0 -26
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.helper.d.ts","sourceRoot":"","sources":["../../src/helpers/cache.helper.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"cache.helper.d.ts","sourceRoot":"","sources":["../../src/helpers/cache.helper.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,IAAI,OAAO,CAUxC"}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.shouldUseCache = shouldUseCache;
|
|
4
4
|
function shouldUseCache() {
|
|
5
|
+
if ((process.env.SOLID_CLI_RUNNING ?? '').toLowerCase() === 'true') {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
5
8
|
const env = (process.env.ENV ?? process.env.NODE_ENV ?? '').toLowerCase();
|
|
6
9
|
return env === 'prod' || env === 'production';
|
|
7
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.helper.js","sourceRoot":"","sources":["../../src/helpers/cache.helper.ts"],"names":[],"mappings":";;AAAA,
|
|
1
|
+
{"version":3,"file":"cache.helper.js","sourceRoot":"","sources":["../../src/helpers/cache.helper.ts"],"names":[],"mappings":";;AAAA,wCAUC;AAVD,SAAgB,cAAc;IAI5B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;QACnE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1E,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,YAAY,CAAC;AAChD,CAAC","sourcesContent":["export function shouldUseCache(): boolean {\n // CLI commands such as `solidctl seed` run in a short-lived process and may\n // perform multiple reads around an insert/update. Avoid stale repository\n // cache entries (especially cached negative lookups) during those commands.\n if ((process.env.SOLID_CLI_RUNNING ?? '').toLowerCase() === 'true') {\n return false;\n }\n\n const env = (process.env.ENV ?? process.env.NODE_ENV ?? '').toLowerCase();\n return env === 'prod' || env === 'production';\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-metadata.repository.d.ts","sourceRoot":"","sources":["../../src/repository/model-metadata.repository.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE9D,qBACa,uBAAwB,SAAQ,mBAAmB,CAAC,aAAa,CAAC;IAEvE,QAAQ,CAAC,UAAU,EAAE,UAAU;IACR,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAD3C,UAAU,EAAE,UAAU,EACS,YAAY,EAAE,KAAK;IAOzD,IAAI,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAgB7C,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"model-metadata.repository.d.ts","sourceRoot":"","sources":["../../src/repository/model-metadata.repository.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE9D,qBACa,uBAAwB,SAAQ,mBAAmB,CAAC,aAAa,CAAC;IAEvE,QAAQ,CAAC,UAAU,EAAE,UAAU;IACR,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAD3C,UAAU,EAAE,UAAU,EACS,YAAY,EAAE,KAAK;IAOzD,IAAI,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAgB7C,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAqB1D,OAAO,CAAC,aAAa;CAKxB"}
|
|
@@ -49,7 +49,9 @@ let ModelMetadataRepository = class ModelMetadataRepository extends solid_base_r
|
|
|
49
49
|
return cached;
|
|
50
50
|
}
|
|
51
51
|
const result = await super.findOne(options);
|
|
52
|
-
|
|
52
|
+
if (result !== null) {
|
|
53
|
+
await this.cacheManager.set(cacheKey, result);
|
|
54
|
+
}
|
|
53
55
|
return result;
|
|
54
56
|
}
|
|
55
57
|
buildCacheKey(method, options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-metadata.repository.js","sourceRoot":"","sources":["../../src/repository/model-metadata.repository.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,yDAAsD;AACtD,2CAAoD;AAEpD,mCAAoC;AACpC,6EAAkE;AAClE,0DAA0D;AAC1D,qCAAqC;AACrC,mEAA8D;AAGvD,IAAM,uBAAuB,GAA7B,MAAM,uBAAwB,SAAQ,2CAAkC;IAC3E,YACa,UAAsB,EACS,YAAmB;QAI3D,KAAK,CAAC,qCAAa,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QALpC,eAAU,GAAV,UAAU,CAAY;QACS,iBAAY,GAAZ,YAAY,CAAO;IAK/D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAa;QACpB,IAAI,CAAC,IAAA,6BAAc,GAAE,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAkB,QAAQ,CAAC,CAAC;QACtE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC9C,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAY;QACtB,IAAI,CAAC,IAAA,6BAAc,GAAE,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAuB,QAAQ,CAAC,CAAC;QAC3E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"model-metadata.repository.js","sourceRoot":"","sources":["../../src/repository/model-metadata.repository.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,yDAAsD;AACtD,2CAAoD;AAEpD,mCAAoC;AACpC,6EAAkE;AAClE,0DAA0D;AAC1D,qCAAqC;AACrC,mEAA8D;AAGvD,IAAM,uBAAuB,GAA7B,MAAM,uBAAwB,SAAQ,2CAAkC;IAC3E,YACa,UAAsB,EACS,YAAmB;QAI3D,KAAK,CAAC,qCAAa,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QALpC,eAAU,GAAV,UAAU,CAAY;QACS,iBAAY,GAAZ,YAAY,CAAO;IAK/D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAa;QACpB,IAAI,CAAC,IAAA,6BAAc,GAAE,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAkB,QAAQ,CAAC,CAAC;QACtE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC9C,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAY;QACtB,IAAI,CAAC,IAAA,6BAAc,GAAE,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAuB,QAAQ,CAAC,CAAC;QAC3E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAI5C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAEO,aAAa,CAAC,MAA0B,EAAE,OAAgB;QAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnE,OAAO,qBAAqB,MAAM,IAAI,IAAI,EAAE,CAAC;IACjD,CAAC;CACJ,CAAA;AApDY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,mBAAU,GAAE;IAIJ,WAAA,IAAA,eAAM,EAAC,6BAAa,CAAC,CAAA;qCADD,oBAAU;GAF1B,uBAAuB,CAoDnC","sourcesContent":["import { CACHE_MANAGER } from '@nestjs/cache-manager';\nimport { Inject, Injectable } from '@nestjs/common';\nimport { Cache } from 'cache-manager';\nimport { createHash } from 'crypto';\nimport { ModelMetadata } from '../entities/model-metadata.entity';\nimport { shouldUseCache } from 'src/helpers/cache.helper';\nimport { DataSource } from 'typeorm';\nimport { SolidBaseRepository } from './solid-base.repository';\n\n@Injectable()\nexport class ModelMetadataRepository extends SolidBaseRepository<ModelMetadata> {\n constructor(\n readonly dataSource: DataSource,\n @Inject(CACHE_MANAGER) private readonly cacheManager: Cache,\n // readonly requestContextService: RequestContextService,\n // readonly securityRuleRepository: SecurityRuleRepository,\n ) {\n super(ModelMetadata, dataSource, null, null);\n }\n\n async find(options?: any): Promise<ModelMetadata[]> {\n if (!shouldUseCache()) {\n return super.find(options);\n }\n\n const cacheKey = this.buildCacheKey('find', options);\n const cached = await this.cacheManager.get<ModelMetadata[]>(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const result = await super.find(options);\n await this.cacheManager.set(cacheKey, result);\n return result;\n }\n\n async findOne(options: any): Promise<ModelMetadata | null> {\n if (!shouldUseCache()) {\n return super.findOne(options);\n }\n\n const cacheKey = this.buildCacheKey('findOne', options);\n const cached = await this.cacheManager.get<ModelMetadata | null>(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n const result = await super.findOne(options);\n // Do not cache negative lookups. A record may be inserted immediately\n // after a miss (for example during metadata seeding), and a cached null\n // would make the next lookup incorrectly take the insert path again.\n if (result !== null) {\n await this.cacheManager.set(cacheKey, result);\n }\n return result;\n }\n\n private buildCacheKey(method: 'find' | 'findOne', options: unknown): string {\n const serialized = JSON.stringify(options ?? {});\n const hash = createHash('sha256').update(serialized).digest('hex');\n return `modelMetadataRepo:${method}:${hash}`;\n }\n}\n"]}
|
|
@@ -70,7 +70,7 @@ let SecurityRuleRepository = class SecurityRuleRepository extends solid_base_rep
|
|
|
70
70
|
return qb;
|
|
71
71
|
}
|
|
72
72
|
resolveSecurityRuleConfig(configString, activeUser) {
|
|
73
|
-
return configString.
|
|
73
|
+
return configString.replaceAll('$activeUserId', activeUser.sub.toString());
|
|
74
74
|
}
|
|
75
75
|
hasFilterConditions(filters) {
|
|
76
76
|
if (filters === null || filters === undefined)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-rule.repository.js","sourceRoot":"","sources":["../../src/repository/security-rule.repository.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, InternalServerErrorException } from '@nestjs/common';\nimport { CreateSecurityRuleDto } from 'src/dtos/create-security-rule.dto';\nimport { SecurityRuleConfig } from 'src/dtos/security-rule-config.dto';\nimport { UpdateSecurityRuleDto } from 'src/dtos/update-security-rule.dto';\nimport { CommonEntity } from 'src/entities/common.entity';\nimport { ModelMetadata } from 'src/entities/model-metadata.entity';\nimport { RoleMetadata } from 'src/entities/role-metadata.entity';\nimport { SecurityRule } from 'src/entities/security-rule.entity';\nimport { SolidRegistry } from 'src/helpers/solid-registry';\nimport { ActiveUserData } from 'src/interfaces/active-user-data.interface';\nimport { CrudHelperService } from 'src/services/crud-helper.service';\nimport { Brackets, DataSource, SelectQueryBuilder } from 'typeorm';\nimport { SolidBaseRepository } from './solid-base.repository';\n\n@Injectable()\nexport class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {\n constructor(\n readonly dataSource: DataSource,\n // readonly requestContextService: RequestContextService,\n readonly solidRegistry: SolidRegistry,\n readonly crudHelperService: CrudHelperService,\n ) {\n super(SecurityRule, dataSource, null, null);\n }\n\n async applySecurityRules<T extends CommonEntity>(qb: SelectQueryBuilder<T>, modelSingularName: string, activeUser: ActiveUserData, securityRuleAlias: string = qb.alias): Promise<SelectQueryBuilder<T>> {\n // Fetch the security rules for the model and roles\n const securityRules = this.solidRegistry.getSecurityRules(modelSingularName, activeUser.roles);\n\n // If no security rules, return the original query builder\n if (!securityRules.length) {\n return qb;\n }\n\n const evaluatedRules = [];\n\n for (const rule of securityRules) {\n\n let evaluatedRule = null;\n\n try {\n // First check if the rule has a \"dynamic\" security rule config provider. \n if (rule.securityRuleConfigProvider) {\n // TODO: Evaluation of the securityRuleConfig Provider should happen outside first...\n const securityRuleConfigProviderInstance = this.solidRegistry.getSecurityRuleConfigProviderInstance(rule.securityRuleConfigProvider);\n if (!securityRuleConfigProviderInstance) {\n throw new Error(`Unable to resolve instance for security rule config provider: ${rule.securityRuleConfigProvider}`);\n }\n evaluatedRule = await securityRuleConfigProviderInstance.securityRuleConfig(activeUser, rule);\n }\n else {\n evaluatedRule = JSON.parse(\n this.resolveSecurityRuleConfig(rule.securityRuleConfig, activeUser)\n ) as SecurityRuleConfig;\n }\n\n evaluatedRules.push(evaluatedRule);\n\n } catch (error: any) {\n this.logger.error(`Error parsing security rule: ${rule.securityRuleConfig}`, error);\n this.logger.error(error.stack);\n throw error;\n }\n }\n\n\n // A rule whose filters contribute no conditions is dangerous here: TypeORM renders an empty\n // Brackets as \"1=1\" (always true), so such a rule would be OR'd in as \"match everything\" and\n // silently void the entire security filter. Test emptiness explicitly -- {} and [] are both\n // truthy, so the previous `if (evaluatedRule.filters)` check let them through.\n const applicableRules = evaluatedRules.filter(rule => rule && this.hasFilterConditions(rule.filters));\n\n if (!applicableRules.length) {\n // We only reach here when security rules exist for this model/role (there is an early\n // return above otherwise), so a restriction WAS intended. Fail closed and be loud:\n // denying silently would surface as a mysteriously empty list, and allowing would be a\n // full row-level-security bypass.\n const message = `Security rules for model '${modelSingularName}' produced no filter conditions. `\n + `Check the securityRuleConfig / securityRuleConfigProvider for rules with empty filters. `\n + `Denying access rather than returning unrestricted results.`;\n this.logger.error(message);\n throw new InternalServerErrorException(message);\n }\n\n // Apply each security rule to the query builder. The rules are combined with OR logic at the top level.\n qb.andWhere(new Brackets((outerQb) => {\n for (const evaluatedRule of applicableRules) {\n outerQb.orWhere( // combine each rule-group with OR at the outer level\n new Brackets((innerQb) => {\n // NOTE: do NOT wrap this in try/catch. Field validation inside applyFilters\n // throws on an invalid rule field, and letting it propagate aborts the\n // request with no rows -- i.e. it fails closed. Swallowing it would leave\n // this Brackets empty, which TypeORM renders as \"1=1\", turning the rule into\n // \"match everything\" and voiding row-level security entirely.\n this.crudHelperService.applyFilters(innerQb, evaluatedRule.filters, securityRuleAlias, qb); // AND within a rule\n })\n );\n }\n }));\n\n return qb;\n }\n\n private resolveSecurityRuleConfig(configString: string, activeUser: ActiveUserData) {\n return configString.replace('$activeUserId', activeUser.sub.toString());\n }\n\n /**\n * Does this filter object actually produce at least one WHERE condition?\n *\n * Truthiness is not enough: `{}` and `[]` are truthy but contribute nothing, and an empty\n * Brackets is rendered by TypeORM as \"1=1\" -- which inside the security rules' OR chain means\n * \"match every row\". Recurses through $and/$or so `{ $and: [] }` is treated as empty too.\n */\n private hasFilterConditions(filters: any): boolean {\n if (filters === null || filters === undefined) return false;\n if (Array.isArray(filters)) return filters.some(filter => this.hasFilterConditions(filter));\n if (typeof filters !== 'object') return false;\n\n return Object.keys(filters).some(key => {\n const normalizedKey = key.replace(/^\\[(.*)\\]$/, '$1');\n if (normalizedKey === '$and' || normalizedKey === '$or') {\n return this.hasFilterConditions(filters[key]);\n }\n return true; // any other key is a field condition\n });\n }\n\n async toDto(securityRule: SecurityRule): Promise<UpdateSecurityRuleDto> {\n // load the role and model relations for the security rule\n let populatedSecurityRule: SecurityRule = securityRule;\n // If the security rule does not have the role and model relations loaded, load them\n if (!securityRule.role || !securityRule.modelMetadata) {\n populatedSecurityRule = await this.findOne({\n where: {\n id: securityRule.id,\n },\n relations: {\n role: true,\n modelMetadata: true,\n },\n });\n }\n\n return {\n id: populatedSecurityRule.id,\n name: populatedSecurityRule.name,\n description: populatedSecurityRule.description,\n roleId: populatedSecurityRule.role.id,\n roleUserKey: populatedSecurityRule.role.name,\n modelMetadataId: populatedSecurityRule.modelMetadata.id,\n modelMetadataUserKey: populatedSecurityRule.modelMetadata.singularName,\n securityRuleConfig: populatedSecurityRule.securityRuleConfig,\n securityRuleConfigProvider: populatedSecurityRule.securityRuleConfigProvider,\n };\n }\n\n async upsertWithDto(createDto: CreateSecurityRuleDto) {\n // Populate the role from roleId or roleUserKey\n const roleRepository = this.dataSource.getRepository(RoleMetadata);\n if (createDto.roleId) {\n const role = await roleRepository.findOne({\n where: {\n id: createDto.roleId,\n },\n });\n createDto['role'] = role;\n }\n\n if (createDto.roleUserKey) {\n const role = await roleRepository.findOne({\n where: {\n name: createDto.roleUserKey,\n },\n });\n createDto['role'] = role;\n }\n\n // Populate the model from modelMetadataId or modelMetadataUserKey\n const modelMetadataRepository = this.dataSource.getRepository(ModelMetadata);\n if (createDto.modelMetadataId) {\n const modelMetadata = await modelMetadataRepository.findOne({\n where: {\n id: createDto.modelMetadataId,\n },\n });\n createDto['modelMetadata'] = modelMetadata;\n }\n if (createDto.modelMetadataUserKey) {\n const modelMetadata = await modelMetadataRepository.findOne({\n where: {\n singularName: createDto.modelMetadataUserKey,\n },\n });\n createDto['modelMetadata'] = modelMetadata;\n }\n\n // First check if module already exists using name\n const existingSecurityRule = await this.findOne({\n where: {\n name: createDto.name,\n },\n });\n\n if (existingSecurityRule) {\n const updatedSecurityRule = this.merge(existingSecurityRule, createDto);\n return this.save(updatedSecurityRule);\n }\n else {\n const securityRule = this.create(createDto);\n return this.save(securityRule);\n }\n }\n\n}"]}
|
|
1
|
+
{"version":3,"file":"security-rule.repository.js","sourceRoot":"","sources":["../../src/repository/security-rule.repository.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, InternalServerErrorException } from '@nestjs/common';\nimport { CreateSecurityRuleDto } from 'src/dtos/create-security-rule.dto';\nimport { SecurityRuleConfig } from 'src/dtos/security-rule-config.dto';\nimport { UpdateSecurityRuleDto } from 'src/dtos/update-security-rule.dto';\nimport { CommonEntity } from 'src/entities/common.entity';\nimport { ModelMetadata } from 'src/entities/model-metadata.entity';\nimport { RoleMetadata } from 'src/entities/role-metadata.entity';\nimport { SecurityRule } from 'src/entities/security-rule.entity';\nimport { SolidRegistry } from 'src/helpers/solid-registry';\nimport { ActiveUserData } from 'src/interfaces/active-user-data.interface';\nimport { CrudHelperService } from 'src/services/crud-helper.service';\nimport { Brackets, DataSource, SelectQueryBuilder } from 'typeorm';\nimport { SolidBaseRepository } from './solid-base.repository';\n\n@Injectable()\nexport class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {\n constructor(\n readonly dataSource: DataSource,\n // readonly requestContextService: RequestContextService,\n readonly solidRegistry: SolidRegistry,\n readonly crudHelperService: CrudHelperService,\n ) {\n super(SecurityRule, dataSource, null, null);\n }\n\n async applySecurityRules<T extends CommonEntity>(qb: SelectQueryBuilder<T>, modelSingularName: string, activeUser: ActiveUserData, securityRuleAlias: string = qb.alias): Promise<SelectQueryBuilder<T>> {\n // Fetch the security rules for the model and roles\n const securityRules = this.solidRegistry.getSecurityRules(modelSingularName, activeUser.roles);\n\n // If no security rules, return the original query builder\n if (!securityRules.length) {\n return qb;\n }\n\n const evaluatedRules = [];\n\n for (const rule of securityRules) {\n\n let evaluatedRule = null;\n\n try {\n // First check if the rule has a \"dynamic\" security rule config provider. \n if (rule.securityRuleConfigProvider) {\n // TODO: Evaluation of the securityRuleConfig Provider should happen outside first...\n const securityRuleConfigProviderInstance = this.solidRegistry.getSecurityRuleConfigProviderInstance(rule.securityRuleConfigProvider);\n if (!securityRuleConfigProviderInstance) {\n throw new Error(`Unable to resolve instance for security rule config provider: ${rule.securityRuleConfigProvider}`);\n }\n evaluatedRule = await securityRuleConfigProviderInstance.securityRuleConfig(activeUser, rule);\n }\n else {\n evaluatedRule = JSON.parse(\n this.resolveSecurityRuleConfig(rule.securityRuleConfig, activeUser)\n ) as SecurityRuleConfig;\n }\n\n evaluatedRules.push(evaluatedRule);\n\n } catch (error: any) {\n this.logger.error(`Error parsing security rule: ${rule.securityRuleConfig}`, error);\n this.logger.error(error.stack);\n throw error;\n }\n }\n\n\n // A rule whose filters contribute no conditions is dangerous here: TypeORM renders an empty\n // Brackets as \"1=1\" (always true), so such a rule would be OR'd in as \"match everything\" and\n // silently void the entire security filter. Test emptiness explicitly -- {} and [] are both\n // truthy, so the previous `if (evaluatedRule.filters)` check let them through.\n const applicableRules = evaluatedRules.filter(rule => rule && this.hasFilterConditions(rule.filters));\n\n if (!applicableRules.length) {\n // We only reach here when security rules exist for this model/role (there is an early\n // return above otherwise), so a restriction WAS intended. Fail closed and be loud:\n // denying silently would surface as a mysteriously empty list, and allowing would be a\n // full row-level-security bypass.\n const message = `Security rules for model '${modelSingularName}' produced no filter conditions. `\n + `Check the securityRuleConfig / securityRuleConfigProvider for rules with empty filters. `\n + `Denying access rather than returning unrestricted results.`;\n this.logger.error(message);\n throw new InternalServerErrorException(message);\n }\n\n // Apply each security rule to the query builder. The rules are combined with OR logic at the top level.\n qb.andWhere(new Brackets((outerQb) => {\n for (const evaluatedRule of applicableRules) {\n outerQb.orWhere( // combine each rule-group with OR at the outer level\n new Brackets((innerQb) => {\n // NOTE: do NOT wrap this in try/catch. Field validation inside applyFilters\n // throws on an invalid rule field, and letting it propagate aborts the\n // request with no rows -- i.e. it fails closed. Swallowing it would leave\n // this Brackets empty, which TypeORM renders as \"1=1\", turning the rule into\n // \"match everything\" and voiding row-level security entirely.\n this.crudHelperService.applyFilters(innerQb, evaluatedRule.filters, securityRuleAlias, qb); // AND within a rule\n })\n );\n }\n }));\n\n return qb;\n }\n\n private resolveSecurityRuleConfig(configString: string, activeUser: ActiveUserData) {\n return configString.replaceAll('$activeUserId', activeUser.sub.toString());\n }\n\n /**\n * Does this filter object actually produce at least one WHERE condition?\n *\n * Truthiness is not enough: `{}` and `[]` are truthy but contribute nothing, and an empty\n * Brackets is rendered by TypeORM as \"1=1\" -- which inside the security rules' OR chain means\n * \"match every row\". Recurses through $and/$or so `{ $and: [] }` is treated as empty too.\n */\n private hasFilterConditions(filters: any): boolean {\n if (filters === null || filters === undefined) return false;\n if (Array.isArray(filters)) return filters.some(filter => this.hasFilterConditions(filter));\n if (typeof filters !== 'object') return false;\n\n return Object.keys(filters).some(key => {\n const normalizedKey = key.replace(/^\\[(.*)\\]$/, '$1');\n if (normalizedKey === '$and' || normalizedKey === '$or') {\n return this.hasFilterConditions(filters[key]);\n }\n return true; // any other key is a field condition\n });\n }\n\n async toDto(securityRule: SecurityRule): Promise<UpdateSecurityRuleDto> {\n // load the role and model relations for the security rule\n let populatedSecurityRule: SecurityRule = securityRule;\n // If the security rule does not have the role and model relations loaded, load them\n if (!securityRule.role || !securityRule.modelMetadata) {\n populatedSecurityRule = await this.findOne({\n where: {\n id: securityRule.id,\n },\n relations: {\n role: true,\n modelMetadata: true,\n },\n });\n }\n\n return {\n id: populatedSecurityRule.id,\n name: populatedSecurityRule.name,\n description: populatedSecurityRule.description,\n roleId: populatedSecurityRule.role.id,\n roleUserKey: populatedSecurityRule.role.name,\n modelMetadataId: populatedSecurityRule.modelMetadata.id,\n modelMetadataUserKey: populatedSecurityRule.modelMetadata.singularName,\n securityRuleConfig: populatedSecurityRule.securityRuleConfig,\n securityRuleConfigProvider: populatedSecurityRule.securityRuleConfigProvider,\n };\n }\n\n async upsertWithDto(createDto: CreateSecurityRuleDto) {\n // Populate the role from roleId or roleUserKey\n const roleRepository = this.dataSource.getRepository(RoleMetadata);\n if (createDto.roleId) {\n const role = await roleRepository.findOne({\n where: {\n id: createDto.roleId,\n },\n });\n createDto['role'] = role;\n }\n\n if (createDto.roleUserKey) {\n const role = await roleRepository.findOne({\n where: {\n name: createDto.roleUserKey,\n },\n });\n createDto['role'] = role;\n }\n\n // Populate the model from modelMetadataId or modelMetadataUserKey\n const modelMetadataRepository = this.dataSource.getRepository(ModelMetadata);\n if (createDto.modelMetadataId) {\n const modelMetadata = await modelMetadataRepository.findOne({\n where: {\n id: createDto.modelMetadataId,\n },\n });\n createDto['modelMetadata'] = modelMetadata;\n }\n if (createDto.modelMetadataUserKey) {\n const modelMetadata = await modelMetadataRepository.findOne({\n where: {\n singularName: createDto.modelMetadataUserKey,\n },\n });\n createDto['modelMetadata'] = modelMetadata;\n }\n\n // First check if module already exists using name\n const existingSecurityRule = await this.findOne({\n where: {\n name: createDto.name,\n },\n });\n\n if (existingSecurityRule) {\n const updatedSecurityRule = this.merge(existingSecurityRule, createDto);\n return this.save(updatedSecurityRule);\n }\n else {\n const securityRule = this.create(createDto);\n return this.save(securityRule);\n }\n }\n\n}"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-metadata.service.d.ts","sourceRoot":"","sources":["../../src/services/permission-metadata.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,OAAO,EAAE,aAAa,EAAM,MAAM,SAAS,CAAC;AAG5C,OAAO,EAAE,4BAA4B,EAAE,MAAM,+CAA+C,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAE5E,qBACa,yBAA0B,SAAQ,WAAW,CAAC,kBAAkB,CAAC;IAI1E,QAAQ,CAAC,aAAa,EAAE,aAAa;IAGrC,QAAQ,CAAC,IAAI,EAAE,4BAA4B;IAC3C,QAAQ,CAAC,SAAS,EAAE,SAAS;IACN,OAAO,CAAC,QAAQ,CAAC,YAAY;IARtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8C;gBAG1D,aAAa,EAAE,aAAa,EAG5B,IAAI,EAAE,4BAA4B,EAClC,SAAS,EAAE,SAAS,EACW,YAAY,EAAE,KAAK;IAK7D,OAAO,CAAC,8BAA8B;IAIhC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"permission-metadata.service.d.ts","sourceRoot":"","sources":["../../src/services/permission-metadata.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,OAAO,EAAE,aAAa,EAAM,MAAM,SAAS,CAAC;AAG5C,OAAO,EAAE,4BAA4B,EAAE,MAAM,+CAA+C,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAE5E,qBACa,yBAA0B,SAAQ,WAAW,CAAC,kBAAkB,CAAC;IAI1E,QAAQ,CAAC,aAAa,EAAE,aAAa;IAGrC,QAAQ,CAAC,IAAI,EAAE,4BAA4B;IAC3C,QAAQ,CAAC,SAAS,EAAE,SAAS;IACN,OAAO,CAAC,QAAQ,CAAC,YAAY;IARtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8C;gBAG1D,aAAa,EAAE,aAAa,EAG5B,IAAI,EAAE,4BAA4B,EAClC,SAAS,EAAE,SAAS,EACW,YAAY,EAAE,KAAK;IAK7D,OAAO,CAAC,8BAA8B;IAIhC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAiDjE,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;CA4B9F"}
|
|
@@ -63,7 +63,9 @@ let PermissionMetadataService = PermissionMetadataService_1 = class PermissionMe
|
|
|
63
63
|
if (useCache) {
|
|
64
64
|
for (const role of uncachedRoles) {
|
|
65
65
|
const permsForRole = fromDb.filter(p => p.roles?.some(r => r.name === role));
|
|
66
|
-
|
|
66
|
+
if (permsForRole.length > 0) {
|
|
67
|
+
await this.cacheManager.set(this.buildPermissionsByRoleCacheKey(role), permsForRole);
|
|
68
|
+
}
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
const seen = new Set();
|
|
@@ -88,7 +90,9 @@ let PermissionMetadataService = PermissionMetadataService_1 = class PermissionMe
|
|
|
88
90
|
relations: { roles: true },
|
|
89
91
|
});
|
|
90
92
|
if (useCache) {
|
|
91
|
-
|
|
93
|
+
if (fromDb.length > 0) {
|
|
94
|
+
await this.cacheManager.set(cacheKey, fromDb);
|
|
95
|
+
}
|
|
92
96
|
}
|
|
93
97
|
return fromDb.filter(p => p.name === permission);
|
|
94
98
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-metadata.service.js","sourceRoot":"","sources":["../../src/services/permission-metadata.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAA4D;AAC5D,yDAAsD;AACtD,uCAAyC;AACzC,6CAAsD;AAEtD,iDAAwD;AACxD,0DAA0D;AAC1D,qCAA4C;AAG5C,iGAA6F;AAItF,IAAM,yBAAyB,iCAA/B,MAAM,yBAA0B,SAAQ,0BAA+B;IAE5E,YAEE,aAAqC,EAG5B,IAAkC,EAClC,SAAoB,EACN,YAAoC;QAE3D,KAAK,CAAC,aAAa,EAAE,IAAI,EAAE,oBAAoB,EAAE,YAAY,EAAC,SAAS,CAAC,CAAC;QAPhE,kBAAa,GAAb,aAAa,CAAe;QAG5B,SAAI,GAAJ,IAAI,CAA8B;QAClC,cAAS,GAAT,SAAS,CAAW;QACW,iBAAY,GAAZ,YAAY,CAAO;QAR5C,WAAM,GAAG,IAAI,eAAM,CAAC,2BAAyB,CAAC,IAAI,CAAC,CAAC;IAWrE,CAAC;IAEO,8BAA8B,CAAC,QAAgB;QACrD,OAAO,oBAAoB,QAAQ,EAAE,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,KAAe;QACrC,MAAM,QAAQ,GAAG,IAAA,6BAAc,GAAE,CAAC;QAClC,MAAM,MAAM,GAAyB,EAAE,CAAC;QACxC,MAAM,aAAa,GAAa,EAAE,CAAC;QAEnC,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,CAAC;gBAC3D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAuB,QAAQ,CAAC,CAAC;gBACxE,IAAI,GAAG,EAAE,CAAC;oBAER,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;gBACtB,CAAC;qBAAM,CAAC;oBAEN,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,aAAa,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QAC/B,CAAC;QAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAClC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAA,YAAE,EAAC,aAAa,CAAC,EAAE,EAAE;YAC7C,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;SAC3B,CAAC,CAAC;QAEH,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;gBACjC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"permission-metadata.service.js","sourceRoot":"","sources":["../../src/services/permission-metadata.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAA4D;AAC5D,yDAAsD;AACtD,uCAAyC;AACzC,6CAAsD;AAEtD,iDAAwD;AACxD,0DAA0D;AAC1D,qCAA4C;AAG5C,iGAA6F;AAItF,IAAM,yBAAyB,iCAA/B,MAAM,yBAA0B,SAAQ,0BAA+B;IAE5E,YAEE,aAAqC,EAG5B,IAAkC,EAClC,SAAoB,EACN,YAAoC;QAE3D,KAAK,CAAC,aAAa,EAAE,IAAI,EAAE,oBAAoB,EAAE,YAAY,EAAC,SAAS,CAAC,CAAC;QAPhE,kBAAa,GAAb,aAAa,CAAe;QAG5B,SAAI,GAAJ,IAAI,CAA8B;QAClC,cAAS,GAAT,SAAS,CAAW;QACW,iBAAY,GAAZ,YAAY,CAAO;QAR5C,WAAM,GAAG,IAAI,eAAM,CAAC,2BAAyB,CAAC,IAAI,CAAC,CAAC;IAWrE,CAAC;IAEO,8BAA8B,CAAC,QAAgB;QACrD,OAAO,oBAAoB,QAAQ,EAAE,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,KAAe;QACrC,MAAM,QAAQ,GAAG,IAAA,6BAAc,GAAE,CAAC;QAClC,MAAM,MAAM,GAAyB,EAAE,CAAC;QACxC,MAAM,aAAa,GAAa,EAAE,CAAC;QAEnC,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,CAAC;gBAC3D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAuB,QAAQ,CAAC,CAAC;gBACxE,IAAI,GAAG,EAAE,CAAC;oBAER,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;gBACtB,CAAC;qBAAM,CAAC;oBAEN,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,aAAa,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QAC/B,CAAC;QAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAClC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAA,YAAE,EAAC,aAAa,CAAC,EAAE,EAAE;YAC7C,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;SAC3B,CAAC,CAAC;QAEH,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;gBACjC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC;gBAG7E,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC5B,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC;gBACvF,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,OAAO,CAAC,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;YACvC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAAE,OAAO,KAAK,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACf,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,IAAY,EAAE,UAAkB;QAC3D,MAAM,QAAQ,GAAG,IAAA,6BAAc,GAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,CAAC;QAE3D,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAuB,QAAQ,CAAC,CAAC;YACxE,IAAI,GAAG,EAAE,CAAC;gBAER,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;YAChD,CAAC;QAEH,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAClC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;YAChC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;SAC3B,CAAC,CAAC;QAEH,IAAI,QAAQ,EAAE,CAAC;YAEb,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IACnD,CAAC;CAEF,CAAA;AA/FY,8DAAyB;oCAAzB,yBAAyB;IADrC,IAAA,mBAAU,GAAE;IAIR,WAAA,IAAA,6BAAmB,GAAE,CAAA;IAMrB,WAAA,IAAA,eAAM,EAAC,6BAAa,CAAC,CAAA;qCALE,uBAAa;QAGtB,6DAA4B;QACvB,gBAAS;GARpB,yBAAyB,CA+FrC","sourcesContent":["import { Inject, Injectable, Logger } from '@nestjs/common';\nimport { CACHE_MANAGER } from '@nestjs/cache-manager';\nimport { ModuleRef } from \"@nestjs/core\";\nimport { InjectEntityManager } from '@nestjs/typeorm';\nimport { Cache } from 'cache-manager';\nimport { CRUDService } from 'src/services/crud.service';\nimport { shouldUseCache } from 'src/helpers/cache.helper';\nimport { EntityManager, In } from 'typeorm';\n\n\nimport { PermissionMetadataRepository } from 'src/repository/permission-metadata.repository';\nimport { PermissionMetadata } from '../entities/permission-metadata.entity';\n\n@Injectable()\nexport class PermissionMetadataService extends CRUDService<PermissionMetadata> {\n private readonly logger = new Logger(PermissionMetadataService.name);\n constructor(\n @InjectEntityManager()\n readonly entityManager: EntityManager,\n // @InjectRepository(PermissionMetadata, 'default')\n // readonly repo: Repository<PermissionMetadata>,\n readonly repo: PermissionMetadataRepository,\n readonly moduleRef: ModuleRef,\n @Inject(CACHE_MANAGER) private readonly cacheManager: Cache,\n ) {\n super(entityManager, repo, 'permissionMetadata', 'solid-core',moduleRef);\n }\n\n private buildPermissionsByRoleCacheKey(roleName: string): string {\n return `permissions:role:${roleName}`;\n }\n\n async findAllUsingRoles(roles: string[]): Promise<PermissionMetadata[]> {\n const useCache = shouldUseCache();\n const cached: PermissionMetadata[] = [];\n const uncachedRoles: string[] = [];\n\n if (useCache) {\n for (const role of roles) {\n const cacheKey = this.buildPermissionsByRoleCacheKey(role);\n const hit = await this.cacheManager.get<PermissionMetadata[]>(cacheKey);\n if (hit) {\n // this.logger.debug(`Cache hit for findAllUsingRoles: key=${cacheKey}`);\n cached.push(...hit);\n } else {\n // this.logger.debug(`Cache miss for findAllUsingRoles: key=${cacheKey}`);\n uncachedRoles.push(role);\n }\n }\n } else {\n uncachedRoles.push(...roles);\n }\n\n if (uncachedRoles.length === 0) {\n return cached;\n }\n\n const fromDb = await this.repo.find({\n where: { roles: { name: In(uncachedRoles) } },\n relations: { roles: true },\n });\n\n if (useCache) {\n for (const role of uncachedRoles) {\n const permsForRole = fromDb.filter(p => p.roles?.some(r => r.name === role));\n // Do not cache empty permission sets. They are negative lookups and\n // may become valid after a permission is assigned to the role.\n if (permsForRole.length > 0) {\n await this.cacheManager.set(this.buildPermissionsByRoleCacheKey(role), permsForRole);\n }\n }\n }\n\n const seen = new Set<number>();\n return [...cached, ...fromDb].filter(p => {\n if (seen.has(p.id)) return false;\n seen.add(p.id);\n return true;\n });\n }\n\n async permissionExistsInRole(role: string, permission: string): Promise<PermissionMetadata[]> {\n const useCache = shouldUseCache();\n const cacheKey = this.buildPermissionsByRoleCacheKey(role);\n\n if (useCache) {\n const hit = await this.cacheManager.get<PermissionMetadata[]>(cacheKey);\n if (hit) {\n // this.logger.debug(`Cache hit for permissionExistsInRole: key=${cacheKey}`);\n return hit.filter(p => p.name === permission);\n }\n // this.logger.debug(`Cache miss for permissionExistsInRole: key=${cacheKey}`);\n }\n\n const fromDb = await this.repo.find({\n where: { roles: { name: role } },\n relations: { roles: true },\n });\n\n if (useCache) {\n // Do not cache an empty result; permissions can be added after this miss.\n if (fromDb.length > 0) {\n await this.cacheManager.set(cacheKey, fromDb);\n }\n }\n\n return fromDb.filter(p => p.name === permission);\n }\n\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export function shouldUseCache(): boolean {
|
|
2
|
+
// CLI commands such as `solidctl seed` run in a short-lived process and may
|
|
3
|
+
// perform multiple reads around an insert/update. Avoid stale repository
|
|
4
|
+
// cache entries (especially cached negative lookups) during those commands.
|
|
5
|
+
if ((process.env.SOLID_CLI_RUNNING ?? '').toLowerCase() === 'true') {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
|
|
2
9
|
const env = (process.env.ENV ?? process.env.NODE_ENV ?? '').toLowerCase();
|
|
3
10
|
return env === 'prod' || env === 'production';
|
|
4
11
|
}
|
|
5
|
-
|
|
@@ -46,7 +46,12 @@ export class ModelMetadataRepository extends SolidBaseRepository<ModelMetadata>
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const result = await super.findOne(options);
|
|
49
|
-
|
|
49
|
+
// Do not cache negative lookups. A record may be inserted immediately
|
|
50
|
+
// after a miss (for example during metadata seeding), and a cached null
|
|
51
|
+
// would make the next lookup incorrectly take the insert path again.
|
|
52
|
+
if (result !== null) {
|
|
53
|
+
await this.cacheManager.set(cacheKey, result);
|
|
54
|
+
}
|
|
50
55
|
return result;
|
|
51
56
|
}
|
|
52
57
|
|
|
@@ -102,7 +102,7 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
private resolveSecurityRuleConfig(configString: string, activeUser: ActiveUserData) {
|
|
105
|
-
return configString.
|
|
105
|
+
return configString.replaceAll('$activeUserId', activeUser.sub.toString());
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
@@ -63,7 +63,11 @@ export class PermissionMetadataService extends CRUDService<PermissionMetadata> {
|
|
|
63
63
|
if (useCache) {
|
|
64
64
|
for (const role of uncachedRoles) {
|
|
65
65
|
const permsForRole = fromDb.filter(p => p.roles?.some(r => r.name === role));
|
|
66
|
-
|
|
66
|
+
// Do not cache empty permission sets. They are negative lookups and
|
|
67
|
+
// may become valid after a permission is assigned to the role.
|
|
68
|
+
if (permsForRole.length > 0) {
|
|
69
|
+
await this.cacheManager.set(this.buildPermissionsByRoleCacheKey(role), permsForRole);
|
|
70
|
+
}
|
|
67
71
|
}
|
|
68
72
|
}
|
|
69
73
|
|
|
@@ -94,11 +98,13 @@ export class PermissionMetadataService extends CRUDService<PermissionMetadata> {
|
|
|
94
98
|
});
|
|
95
99
|
|
|
96
100
|
if (useCache) {
|
|
97
|
-
|
|
101
|
+
// Do not cache an empty result; permissions can be added after this miss.
|
|
102
|
+
if (fromDb.length > 0) {
|
|
103
|
+
await this.cacheManager.set(cacheKey, fromDb);
|
|
104
|
+
}
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
return fromDb.filter(p => p.name === permission);
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
}
|
|
104
|
-
|
package/CLAUDE.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# This is the SolidX API project.
|
|
2
|
-
This is included as a dependency in consuming projects.
|
|
3
|
-
|
|
4
|
-
# SolidX UI
|
|
5
|
-
# The corresponding UI project is at the below path:
|
|
6
|
-
/Users/oswald/projects/Solid_Starters/solid-core-ui
|
|
7
|
-
|
|
8
|
-
# SolidX Docs
|
|
9
|
-
# The documentation project is at the below path:
|
|
10
|
-
/Users/oswald/projects/solidxai-docs
|
|
11
|
-
|
|
12
|
-
# SolidX Agent
|
|
13
|
-
# The agent project which has the skills for helping users configure, build and use SolidX is at the below path:
|
|
14
|
-
/Users/oswald/projects/Solid_Starters/agent
|
|
15
|
-
|
|
16
|
-
# SolidX Control Plane Commands
|
|
17
|
-
# The control plane commands for managing SolidX instances are in the below path:
|
|
18
|
-
/Users/oswald/projects/Solid_Starters/solidctl
|
|
19
|
-
|
|
20
|
-
# Testing reference project
|
|
21
|
-
# This is the reference library management project that is used for testing and development of SolidX features. It is located at the below path:
|
|
22
|
-
/Users/oswald/projects/Solid_Starters/solid-library-management
|
|
23
|
-
|
|
24
|
-
# Code Builder project
|
|
25
|
-
# This is the project that contains the code builder which generates code based on user input and templates. It is located at the below path:
|
|
26
|
-
/Users/oswald/projects/Solid_Starters/solid-code-builder
|