@rvoh/dream 2.17.2 → 2.18.0
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/dist/cjs/src/dream/Query.js +15 -2
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +77 -2
- package/dist/cjs/src/dream-app/index.js +13 -16
- package/dist/cjs/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +7 -2
- package/dist/cjs/src/errors/MaxRecursiveStringCaseDepthExceeded.js +14 -0
- package/dist/cjs/src/errors/dream-app/DreamAppInitInvalidEncryptionKey.js +27 -0
- package/dist/cjs/src/helpers/stringCasing.js +16 -4
- package/dist/esm/src/dream/Query.js +15 -2
- package/dist/esm/src/dream/QueryDriver/Kysely.js +77 -2
- package/dist/esm/src/dream-app/index.js +13 -16
- package/dist/esm/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +7 -2
- package/dist/esm/src/errors/MaxRecursiveStringCaseDepthExceeded.js +14 -0
- package/dist/esm/src/errors/dream-app/DreamAppInitInvalidEncryptionKey.js +27 -0
- package/dist/esm/src/helpers/stringCasing.js +16 -4
- package/dist/types/src/dream/Query.d.ts +9 -0
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +37 -0
- package/dist/types/src/dream-app/index.d.ts +4 -2
- package/dist/types/src/errors/MaxRecursiveStringCaseDepthExceeded.d.ts +5 -0
- package/dist/types/src/errors/dream-app/DreamAppInitInvalidEncryptionKey.d.ts +8 -0
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +11 -11
- package/docs/classes/db.KyselyQueryDriver.html +34 -34
- package/docs/classes/db.PostgresQueryDriver.html +35 -35
- package/docs/classes/db.QueryDriverBase.html +33 -33
- package/docs/classes/errors.CheckConstraintViolation.html +3 -3
- package/docs/classes/errors.ColumnOverflow.html +3 -3
- package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
- package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
- package/docs/classes/errors.DecryptionError.html +2 -2
- package/docs/classes/errors.DecryptionParseError.html +2 -2
- package/docs/classes/errors.DecryptionRotationError.html +3 -3
- package/docs/classes/errors.GlobalNameNotSet.html +3 -3
- package/docs/classes/errors.InvalidCalendarDate.html +2 -2
- package/docs/classes/errors.InvalidClockTime.html +2 -2
- package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
- package/docs/classes/errors.InvalidDateTime.html +2 -2
- package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
- package/docs/classes/errors.NonLoadedAssociation.html +3 -3
- package/docs/classes/errors.NotNullViolation.html +3 -3
- package/docs/classes/errors.RecordNotFound.html +3 -3
- package/docs/classes/errors.ValidationError.html +3 -3
- package/docs/classes/index.CalendarDate.html +33 -33
- package/docs/classes/index.ClockTime.html +32 -32
- package/docs/classes/index.ClockTimeTz.html +35 -35
- package/docs/classes/index.DateTime.html +86 -86
- package/docs/classes/index.Decorators.html +19 -19
- package/docs/classes/index.Dream.html +118 -118
- package/docs/classes/index.DreamApp.html +11 -10
- package/docs/classes/index.DreamTransaction.html +2 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +57 -57
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +7 -7
- package/docs/classes/system.DreamImporter.html +2 -2
- package/docs/classes/system.DreamLogos.html +2 -2
- package/docs/classes/system.DreamSerializerBuilder.html +11 -11
- package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
- package/docs/classes/system.PathHelpers.html +3 -3
- package/docs/classes/utils.Encrypt.html +3 -3
- package/docs/classes/utils.Range.html +2 -2
- package/docs/functions/db.closeAllDbConnections.html +1 -1
- package/docs/functions/db.dreamDbConnections.html +1 -1
- package/docs/functions/db.untypedDb.html +1 -1
- package/docs/functions/db.validateColumn.html +1 -1
- package/docs/functions/db.validateTable.html +1 -1
- package/docs/functions/errors.pgErrorType.html +1 -1
- package/docs/functions/index.DreamSerializer.html +1 -1
- package/docs/functions/index.ObjectSerializer.html +1 -1
- package/docs/functions/index.ReplicaSafe.html +1 -1
- package/docs/functions/index.STI.html +1 -1
- package/docs/functions/index.SoftDelete.html +1 -1
- package/docs/functions/utils.camelize.html +1 -1
- package/docs/functions/utils.capitalize.html +1 -1
- package/docs/functions/utils.cloneDeepSafe.html +1 -1
- package/docs/functions/utils.compact.html +1 -1
- package/docs/functions/utils.groupBy.html +1 -1
- package/docs/functions/utils.hyphenize.html +1 -1
- package/docs/functions/utils.intersection.html +1 -1
- package/docs/functions/utils.isEmpty.html +1 -1
- package/docs/functions/utils.normalizeUnicode.html +1 -1
- package/docs/functions/utils.pascalize.html +1 -1
- package/docs/functions/utils.percent.html +1 -1
- package/docs/functions/utils.range.html +1 -1
- package/docs/functions/utils.round.html +1 -1
- package/docs/functions/utils.sanitizeString.html +1 -1
- package/docs/functions/utils.snakeify.html +1 -1
- package/docs/functions/utils.sort.html +1 -1
- package/docs/functions/utils.sortBy.html +1 -1
- package/docs/functions/utils.sortObjectByKey.html +1 -1
- package/docs/functions/utils.sortObjectByValue.html +1 -1
- package/docs/functions/utils.uncapitalize.html +1 -1
- package/docs/functions/utils.uniq.html +1 -1
- package/docs/hierarchy.html +1 -1
- package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
- package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/types.BelongsToStatement.html +2 -2
- package/docs/interfaces/types.DecoratorContext.html +2 -2
- package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
- package/docs/interfaces/types.DreamAppOpts.html +2 -2
- package/docs/interfaces/types.DreamDbConfig.html +5 -5
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
- package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
- package/docs/types/openapi.OpenapiAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiFormats.html +1 -1
- package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
- package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
- package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
- package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaString.html +1 -1
- package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiTypeField.html +1 -1
- package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/types.CalendarDateDurationUnit.html +1 -1
- package/docs/types/types.CalendarDateObject.html +1 -1
- package/docs/types/types.Camelized.html +1 -1
- package/docs/types/types.ClockTimeObject.html +1 -1
- package/docs/types/types.DbConnectionType.html +1 -1
- package/docs/types/types.DbTypes.html +1 -1
- package/docs/types/types.DreamAssociationMetadata.html +1 -1
- package/docs/types/types.DreamAttributes.html +1 -1
- package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/types.DreamClassColumn.html +1 -1
- package/docs/types/types.DreamColumn.html +1 -1
- package/docs/types/types.DreamColumnNames.html +1 -1
- package/docs/types/types.DreamLogLevel.html +1 -1
- package/docs/types/types.DreamLogger.html +2 -2
- package/docs/types/types.DreamModelSerializerType.html +1 -1
- package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/types.DreamParamSafeAttributes.html +1 -1
- package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
- package/docs/types/types.DreamSerializable.html +1 -1
- package/docs/types/types.DreamSerializableArray.html +1 -1
- package/docs/types/types.DreamSerializerKey.html +1 -1
- package/docs/types/types.DreamSerializers.html +1 -1
- package/docs/types/types.DreamVirtualColumns.html +1 -1
- package/docs/types/types.DurationUnit.html +1 -1
- package/docs/types/types.EncryptAlgorithm.html +1 -1
- package/docs/types/types.HasManyStatement.html +1 -1
- package/docs/types/types.HasOneStatement.html +1 -1
- package/docs/types/types.Hyphenized.html +1 -1
- package/docs/types/types.Pascalized.html +1 -1
- package/docs/types/types.PrimaryKeyType.html +1 -1
- package/docs/types/types.RoundingPrecision.html +1 -1
- package/docs/types/types.SerializerCasing.html +1 -1
- package/docs/types/types.SimpleObjectSerializerType.html +1 -1
- package/docs/types/types.Snakeified.html +1 -1
- package/docs/types/types.StrictInterface.html +1 -1
- package/docs/types/types.UpdateableAssociationProperties.html +1 -1
- package/docs/types/types.UpdateableProperties.html +1 -1
- package/docs/types/types.ValidationType.html +1 -1
- package/docs/types/types.ViewModel.html +2 -2
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WeekdayName.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/system.primaryKeyTypes.html +1 -1
- package/package.json +1 -1
- /package/dist/types/src/types/{types/associations → associations}/belongsTo.ts +0 -0
- /package/dist/types/src/types/{types/associations → associations}/hasMany.ts +0 -0
- /package/dist/types/src/types/{types/associations → associations}/hasOne.ts +0 -0
- /package/dist/types/src/types/{types/associations → associations}/shared.ts +0 -0
- /package/dist/types/src/types/{types/calendardate.ts → calendardate.ts} +0 -0
- /package/dist/types/src/types/{types/clocktime.ts → clocktime.ts} +0 -0
- /package/dist/types/src/types/{types/datetime.ts → datetime.ts} +0 -0
- /package/dist/types/src/types/{types/db.ts → db.ts} +0 -0
- /package/dist/types/src/types/{types/dream.ts → dream.ts} +0 -0
- /package/dist/types/src/types/{types/lifecycle.ts → lifecycle.ts} +0 -0
- /package/dist/types/src/types/{types/logger.ts → logger.ts} +0 -0
- /package/dist/types/src/types/{types/moduleDeclarations → moduleDeclarations}/luxon.d.ts +0 -0
- /package/dist/types/src/types/{types/openapi.ts → openapi.ts} +0 -0
- /package/dist/types/src/types/{types/query.ts → query.ts} +0 -0
- /package/dist/types/src/types/{types/recursiveSerialization.ts → recursiveSerialization.ts} +0 -0
- /package/dist/types/src/types/{types/serializer.ts → serializer.ts} +0 -0
- /package/dist/types/src/types/{types/utils.ts → utils.ts} +0 -0
- /package/dist/types/src/types/{types/validation.ts → validation.ts} +0 -0
- /package/dist/types/src/types/{types/variadic.ts → variadic.ts} +0 -0
|
@@ -1193,6 +1193,19 @@ export default class Query {
|
|
|
1193
1193
|
get connectionName() {
|
|
1194
1194
|
return this.dreamInstance.connectionName || 'default';
|
|
1195
1195
|
}
|
|
1196
|
+
/**
|
|
1197
|
+
* @internal
|
|
1198
|
+
*
|
|
1199
|
+
* Resolves the effective page size for a pagination request: applies the
|
|
1200
|
+
* configured default when no page size is requested, and clamps the result to
|
|
1201
|
+
* the configured maximum (`paginationMaxPageSize`, default 200) so that a
|
|
1202
|
+
* forwarded, attacker-controlled `pageSize` cannot force a full-table load.
|
|
1203
|
+
*/
|
|
1204
|
+
clampedPaginationPageSize(requestedPageSize) {
|
|
1205
|
+
const dreamApp = DreamApp.getOrFail();
|
|
1206
|
+
const pageSize = Math.max(0, requestedPageSize ?? 0) || dreamApp.paginationPageSize;
|
|
1207
|
+
return Math.min(pageSize, dreamApp.paginationMaxPageSize);
|
|
1208
|
+
}
|
|
1196
1209
|
/**
|
|
1197
1210
|
* Paginates the results of your query, accepting a pageSize and page argument,
|
|
1198
1211
|
* which it uses to segment your query into pages, leveraging limit and offset
|
|
@@ -1231,7 +1244,7 @@ export default class Query {
|
|
|
1231
1244
|
throw new CannotPaginateWithLeftJoinPreload();
|
|
1232
1245
|
const page = computedPaginatePage(options.page);
|
|
1233
1246
|
const recordCount = await this.count();
|
|
1234
|
-
const pageSize =
|
|
1247
|
+
const pageSize = this.clampedPaginationPageSize(options.pageSize);
|
|
1235
1248
|
const pageCount = Math.ceil(recordCount / pageSize);
|
|
1236
1249
|
const query = this.orderStatements.length
|
|
1237
1250
|
? this
|
|
@@ -1323,7 +1336,7 @@ export default class Query {
|
|
|
1323
1336
|
*/
|
|
1324
1337
|
async cursorPaginate(opts) {
|
|
1325
1338
|
const options = opts;
|
|
1326
|
-
const pageSize =
|
|
1339
|
+
const pageSize = this.clampedPaginationPageSize(options.pageSize);
|
|
1327
1340
|
if (options === null)
|
|
1328
1341
|
return { cursor: null, results: [] };
|
|
1329
1342
|
if (this.limitStatement)
|
|
@@ -14,6 +14,7 @@ import DreamCLI from '../../cli/index.js';
|
|
|
14
14
|
import { CHECK_VIOLATION, COLUMN_OVERFLOW, INVALID_INPUT_SYNTAX, NOT_NULL_VIOLATION, pgErrorType, } from '../../db/errors.js';
|
|
15
15
|
import syncDbTypesFiles from '../../db/helpers/syncDbTypesFiles.js';
|
|
16
16
|
import { default as _db } from '../../db/index.js';
|
|
17
|
+
import validateColumn from '../../db/validators/validateColumn.js';
|
|
17
18
|
import associationToGetterSetterProp from '../../decorators/field/association/associationToGetterSetterProp.js';
|
|
18
19
|
import PackageManager from '../../dream-app/helpers/PackageManager.js';
|
|
19
20
|
import DreamApp from '../../dream-app/index.js';
|
|
@@ -551,6 +552,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
551
552
|
// that can exceed 63 bytes. Short aliases like "pluck0" avoid this entirely.
|
|
552
553
|
const shortAliases = [];
|
|
553
554
|
fields.forEach((field, index) => {
|
|
555
|
+
this.validatePlainColumn(this.dreamClass, field);
|
|
554
556
|
const shortAlias = `pluck${index}`;
|
|
555
557
|
shortAliases.push(shortAlias);
|
|
556
558
|
// namespace the selection so that when plucking the same column name from
|
|
@@ -719,6 +721,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
719
721
|
});
|
|
720
722
|
if (this.query['orderStatements'].length && !bypassOrder) {
|
|
721
723
|
this.query['orderStatements'].forEach(orderStatement => {
|
|
724
|
+
this.validatePlainColumn(this.dreamClass, orderStatement.column);
|
|
722
725
|
kyselyQuery = kyselyQuery.orderBy(this.namespaceColumn(orderStatement.column), this.orderByDirection(orderStatement.direction));
|
|
723
726
|
});
|
|
724
727
|
}
|
|
@@ -1034,6 +1037,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1034
1037
|
if (query['whereStatements'].length ||
|
|
1035
1038
|
query['whereNotStatements'].length ||
|
|
1036
1039
|
query['whereAnyStatements'].length) {
|
|
1040
|
+
this.validateBaseWhereStatementColumns(query);
|
|
1037
1041
|
kyselyQuery = kyselyQuery.where((eb) => eb.and([
|
|
1038
1042
|
...this.aliasWhereStatements(query['whereStatements'], query['baseSqlAlias']).map(whereStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereStatement, {
|
|
1039
1043
|
disallowSimilarityOperator: false,
|
|
@@ -1368,6 +1372,66 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1368
1372
|
namespaceColumn(column, alias = this.query['baseSqlAlias']) {
|
|
1369
1373
|
return namespaceColumn(column, alias);
|
|
1370
1374
|
}
|
|
1375
|
+
/**
|
|
1376
|
+
* @internal
|
|
1377
|
+
*
|
|
1378
|
+
* Defense-in-depth (DREAM-SQLI-01): when a runtime column identifier is a
|
|
1379
|
+
* plain, un-namespaced column name, validate it against the owning model's
|
|
1380
|
+
* compiled schema so an unknown identifier throws a Dream `InvalidColumnName`
|
|
1381
|
+
* error before it reaches the database (rather than surfacing as a raw
|
|
1382
|
+
* Postgres error or a silent blind-ordering side-channel).
|
|
1383
|
+
*
|
|
1384
|
+
* Only bare identifiers are validated. Namespaced / table-aliased /
|
|
1385
|
+
* association-namespaced identifiers (anything containing a `.`) are left on
|
|
1386
|
+
* their existing path, because the segment before the `.` may be an
|
|
1387
|
+
* association alias or table alias rather than a schema table name, and
|
|
1388
|
+
* validating those safely is not possible here without risking rejection of
|
|
1389
|
+
* currently-valid input.
|
|
1390
|
+
*/
|
|
1391
|
+
validatePlainColumn(dreamClass, column) {
|
|
1392
|
+
if (typeof column !== 'string')
|
|
1393
|
+
return;
|
|
1394
|
+
if (column.includes('.'))
|
|
1395
|
+
return;
|
|
1396
|
+
validateColumn(dreamClass.prototype.schema, dreamClass.table, column);
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* @internal
|
|
1400
|
+
*
|
|
1401
|
+
* Defense-in-depth (DREAM-SQLI-01) for the base model's dynamic `where` keys.
|
|
1402
|
+
* These are the un-namespaced keys the developer passed to `where` /
|
|
1403
|
+
* `whereNot` / `whereAny` on the root query (validated here, before
|
|
1404
|
+
* `aliasWhereStatement` namespaces them with the base alias). Only plain
|
|
1405
|
+
* column keys are validated. Deliberately skips:
|
|
1406
|
+
* - namespaced keys (contain a `.`) — the developer explicitly namespaced
|
|
1407
|
+
* them (e.g. `{ 'users.email': ... }` or a joined table), and the prefix
|
|
1408
|
+
* may be an association/table alias rather than a schema table name;
|
|
1409
|
+
* - association-name keys (e.g. `where({ user: userInstance })`), which are
|
|
1410
|
+
* resolved to foreign keys, not columns;
|
|
1411
|
+
* - `DreamConst.passthrough` / `DreamConst.required` sentinel values, whose
|
|
1412
|
+
* keys are resolved elsewhere.
|
|
1413
|
+
*
|
|
1414
|
+
* Join-and / association where statements (which target other tables and are
|
|
1415
|
+
* pre-namespaced) are left on their existing path.
|
|
1416
|
+
*/
|
|
1417
|
+
validateBaseWhereStatementColumns(query) {
|
|
1418
|
+
const dreamClass = query['dreamClass'];
|
|
1419
|
+
const whereStatements = [
|
|
1420
|
+
...query['whereStatements'],
|
|
1421
|
+
...query['whereNotStatements'],
|
|
1422
|
+
...query['whereAnyStatements'].flat(),
|
|
1423
|
+
];
|
|
1424
|
+
whereStatements.forEach(whereStatement => Object.keys(whereStatement).forEach(attr => {
|
|
1425
|
+
if (attr.includes('.'))
|
|
1426
|
+
return;
|
|
1427
|
+
const val = whereStatement[attr];
|
|
1428
|
+
if (val === DreamConst.passthrough || val === DreamConst.required)
|
|
1429
|
+
return;
|
|
1430
|
+
if (dreamClass.associationNames.includes(attr))
|
|
1431
|
+
return;
|
|
1432
|
+
validateColumn(dreamClass.prototype.schema, dreamClass.table, attr);
|
|
1433
|
+
}));
|
|
1434
|
+
}
|
|
1371
1435
|
/**
|
|
1372
1436
|
* @internal
|
|
1373
1437
|
*
|
|
@@ -1924,8 +1988,19 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1924
1988
|
scopesQuery = tempQuery;
|
|
1925
1989
|
}
|
|
1926
1990
|
}
|
|
1927
|
-
if (scopesQuery['whereStatements'].length
|
|
1928
|
-
|
|
1991
|
+
if (scopesQuery['whereStatements'].length ||
|
|
1992
|
+
scopesQuery['whereNotStatements'].length ||
|
|
1993
|
+
scopesQuery['whereAnyStatements'].length) {
|
|
1994
|
+
// Mirror the base-table clause logic in QueryDriverBase#buildCommon so that default
|
|
1995
|
+
// scopes expressed with whereNot(...)/whereAny(...) — not just where(...) — cross into
|
|
1996
|
+
// association loads (preload/join/leftJoinPreload/innerJoin). Without the whereNot/whereAny
|
|
1997
|
+
// branches below, such default scopes are silently dropped on association loads, leaking
|
|
1998
|
+
// rows the app intended to hide.
|
|
1999
|
+
join = join.on((eb) => eb.and([
|
|
2000
|
+
...scopesQuery['whereStatements'].map(whereStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereStatement, tableNameOrAlias), { disallowSimilarityOperator: false })),
|
|
2001
|
+
...scopesQuery['whereNotStatements'].map(whereNotStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereNotStatement, tableNameOrAlias), { negate: true })),
|
|
2002
|
+
...scopesQuery['whereAnyStatements'].map(whereAnyStatements => eb.or(whereAnyStatements.map(whereAnyStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereAnyStatement, tableNameOrAlias))))),
|
|
2003
|
+
]));
|
|
1929
2004
|
}
|
|
1930
2005
|
return join;
|
|
1931
2006
|
}
|
|
@@ -4,6 +4,7 @@ import validateTable from '../db/validators/validateTable.js';
|
|
|
4
4
|
import Query from '../dream/Query.js';
|
|
5
5
|
import PostgresQueryDriver from '../dream/QueryDriver/Postgres.js';
|
|
6
6
|
import Encrypt from '../encrypt/index.js';
|
|
7
|
+
import DreamAppInitInvalidEncryptionKey from '../errors/dream-app/DreamAppInitInvalidEncryptionKey.js';
|
|
7
8
|
import DreamAppInitMissingCallToLoadModels from '../errors/dream-app/DreamAppInitMissingCallToLoadModels.js';
|
|
8
9
|
import DreamAppInitMissingMissingProjectRoot from '../errors/dream-app/DreamAppInitMissingMissingProjectRoot.js';
|
|
9
10
|
import DreamAppInitMissingPackageManager from '../errors/dream-app/DreamAppInitMissingPackageManager.js';
|
|
@@ -119,7 +120,9 @@ export default class DreamApp {
|
|
|
119
120
|
if (!this.loadedModels)
|
|
120
121
|
throw new DreamAppInitMissingCallToLoadModels();
|
|
121
122
|
if (this.encryption?.columns?.current)
|
|
122
|
-
DreamApp.checkKey('columns', this.encryption.columns.current.key, this.encryption.columns.current.algorithm);
|
|
123
|
+
DreamApp.checkKey('columns', 'current', this.encryption.columns.current.key, this.encryption.columns.current.algorithm);
|
|
124
|
+
if (this.encryption?.columns?.legacy)
|
|
125
|
+
DreamApp.checkKey('columns', 'legacy', this.encryption.columns.legacy.key, this.encryption.columns.legacy.algorithm);
|
|
123
126
|
if (!DreamAppAllowedPackageManagersEnumValues.includes(this.packageManager) &&
|
|
124
127
|
!EnvInternal.boolean('DREAM_BYPASS_PACKAGE_MANAGER_CHECK'))
|
|
125
128
|
throw new DreamAppInitMissingPackageManager();
|
|
@@ -148,22 +151,9 @@ export default class DreamApp {
|
|
|
148
151
|
await dbDriverClass.setDatabaseTypeParsers(connectionName);
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
|
-
static checkKey(encryptionIdentifier, key, algorithm) {
|
|
154
|
+
static checkKey(encryptionIdentifier, keyType, key, algorithm) {
|
|
152
155
|
if (!Encrypt.validateKey(key, algorithm))
|
|
153
|
-
|
|
154
|
-
console.warn(`
|
|
155
|
-
Your current key value for ${encryptionIdentifier} encryption is invalid.
|
|
156
|
-
Try setting it to something valid, like:
|
|
157
|
-
${Encrypt.generateKey(algorithm)}
|
|
158
|
-
|
|
159
|
-
This was done by calling:
|
|
160
|
-
Encrypt.generateKey('${algorithm}'
|
|
161
|
-
|
|
162
|
-
A new key can also be generated from the CLI:
|
|
163
|
-
% pnpm psy g:encryption-key
|
|
164
|
-
OR
|
|
165
|
-
% pnpm psy g:encryption-key --algorithm=aes-256-gcm
|
|
166
|
-
`);
|
|
156
|
+
throw new DreamAppInitInvalidEncryptionKey(encryptionIdentifier, keyType, algorithm);
|
|
167
157
|
}
|
|
168
158
|
/**
|
|
169
159
|
* Returns the cached dream application if it has been set.
|
|
@@ -218,6 +208,10 @@ A new key can also be generated from the CLI:
|
|
|
218
208
|
get paginationPageSize() {
|
|
219
209
|
return this._paginationPageSize;
|
|
220
210
|
}
|
|
211
|
+
_paginationMaxPageSize = 200;
|
|
212
|
+
get paginationMaxPageSize() {
|
|
213
|
+
return this._paginationMaxPageSize;
|
|
214
|
+
}
|
|
221
215
|
_primaryKeyType = 'bigint';
|
|
222
216
|
get primaryKeyType() {
|
|
223
217
|
return this._primaryKeyType;
|
|
@@ -495,6 +489,9 @@ A new key can also be generated from the CLI:
|
|
|
495
489
|
case 'paginationPageSize':
|
|
496
490
|
this._paginationPageSize = options;
|
|
497
491
|
break;
|
|
492
|
+
case 'paginationMaxPageSize':
|
|
493
|
+
this._paginationMaxPageSize = options;
|
|
494
|
+
break;
|
|
498
495
|
default: {
|
|
499
496
|
// protection so that if a new ApplyOpt is ever added, this will throw a type error at build time
|
|
500
497
|
const _never = applyOption;
|
|
@@ -23,8 +23,13 @@ export default function decryptAESGCM(algorithm, encrypted, key) {
|
|
|
23
23
|
try {
|
|
24
24
|
return JSON.parse(plaintext);
|
|
25
25
|
}
|
|
26
|
-
catch
|
|
27
|
-
|
|
26
|
+
catch {
|
|
27
|
+
// Intentionally drop the underlying SyntaxError as `cause`: its message embeds a
|
|
28
|
+
// snippet of the just-decrypted plaintext (e.g. `Unexpected token 'x', "secret..." is
|
|
29
|
+
// not valid JSON`). Carrying it would leak decrypted plaintext into any logger that
|
|
30
|
+
// serializes `error.cause`. DecryptionParseError's own message is generic and
|
|
31
|
+
// plaintext-free, which is all callers need to diagnose an encrypted-format mismatch.
|
|
32
|
+
throw new DecryptionParseError();
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
function unpackPayloadOrFail(payload) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default class MaxRecursiveStringCaseDepthExceeded extends Error {
|
|
2
|
+
maxDepth;
|
|
3
|
+
constructor(maxDepth) {
|
|
4
|
+
super();
|
|
5
|
+
this.maxDepth = maxDepth;
|
|
6
|
+
}
|
|
7
|
+
get message() {
|
|
8
|
+
return `
|
|
9
|
+
Maximum recursion depth (${this.maxDepth}) exceeded while string-casing an object.
|
|
10
|
+
The input is nested more deeply than Dream will process, which usually means it is
|
|
11
|
+
malformed or maliciously deeply-nested rather than legitimate application data.
|
|
12
|
+
`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Encrypt from '../../encrypt/index.js';
|
|
2
|
+
export default class DreamAppInitInvalidEncryptionKey extends Error {
|
|
3
|
+
encryptionIdentifier;
|
|
4
|
+
keyType;
|
|
5
|
+
algorithm;
|
|
6
|
+
constructor(encryptionIdentifier, keyType, algorithm) {
|
|
7
|
+
super();
|
|
8
|
+
this.encryptionIdentifier = encryptionIdentifier;
|
|
9
|
+
this.keyType = keyType;
|
|
10
|
+
this.algorithm = algorithm;
|
|
11
|
+
}
|
|
12
|
+
get message() {
|
|
13
|
+
return `
|
|
14
|
+
Your ${this.keyType} key value for ${this.encryptionIdentifier} encryption is invalid.
|
|
15
|
+
Try setting it to something valid, like:
|
|
16
|
+
${Encrypt.generateKey(this.algorithm)}
|
|
17
|
+
|
|
18
|
+
This was done by calling:
|
|
19
|
+
Encrypt.generateKey('${this.algorithm}')
|
|
20
|
+
|
|
21
|
+
A new key can also be generated from the CLI:
|
|
22
|
+
% pnpm psy g:encryption-key
|
|
23
|
+
OR
|
|
24
|
+
% pnpm psy g:encryption-key --algorithm=aes-256-gcm
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
import Dream from '../Dream.js';
|
|
2
|
+
import MaxRecursiveStringCaseDepthExceeded from '../errors/MaxRecursiveStringCaseDepthExceeded.js';
|
|
2
3
|
import BaseClockTime from '../utils/datetime/BaseClockTime.js';
|
|
3
4
|
import CalendarDate from '../utils/datetime/CalendarDate.js';
|
|
4
5
|
import { DateTime } from '../utils/datetime/DateTime.js';
|
|
5
6
|
import isObject from './isObject.js';
|
|
7
|
+
// Maximum object-nesting depth `recursiveStringCase` will traverse. Each nesting
|
|
8
|
+
// level recurses once, so without a cap a deeply-nested input (e.g. a malicious
|
|
9
|
+
// ~480KB JSON body under the request size limit) exhausts the call stack and
|
|
10
|
+
// surfaces as an uncontrolled `RangeError` (500). The cap sits far below the
|
|
11
|
+
// point at which this recursion overflows the stack (measured ~2,000+ levels)
|
|
12
|
+
// while remaining far above any legitimate nesting depth, so real application
|
|
13
|
+
// data — request params, serializer output, config objects — is never affected.
|
|
14
|
+
const MAX_RECURSIVE_STRING_CASE_DEPTH = 500;
|
|
6
15
|
export default function stringCase(target, stringCaser) {
|
|
7
16
|
if (typeof target === 'string')
|
|
8
17
|
return stringCaser(target);
|
|
9
|
-
return recursiveStringCase(target, stringCaser);
|
|
18
|
+
return recursiveStringCase(target, stringCaser, 0);
|
|
10
19
|
}
|
|
11
|
-
function recursiveStringCase(target, stringCaser) {
|
|
20
|
+
function recursiveStringCase(target, stringCaser, depth) {
|
|
12
21
|
if (target === null)
|
|
13
22
|
return null;
|
|
14
23
|
if (target === undefined)
|
|
15
24
|
return undefined;
|
|
16
25
|
if (typeof target === 'string')
|
|
17
26
|
return target;
|
|
27
|
+
if (depth >= MAX_RECURSIVE_STRING_CASE_DEPTH) {
|
|
28
|
+
throw new MaxRecursiveStringCaseDepthExceeded(MAX_RECURSIVE_STRING_CASE_DEPTH);
|
|
29
|
+
}
|
|
18
30
|
if (Array.isArray(target))
|
|
19
|
-
return target.map(s => recursiveStringCase(s, stringCaser));
|
|
31
|
+
return target.map(s => recursiveStringCase(s, stringCaser, depth + 1));
|
|
20
32
|
if (isObject(target)) {
|
|
21
33
|
if (target instanceof DateTime ||
|
|
22
34
|
target instanceof CalendarDate ||
|
|
@@ -25,7 +37,7 @@ function recursiveStringCase(target, stringCaser) {
|
|
|
25
37
|
return target;
|
|
26
38
|
}
|
|
27
39
|
return Object.keys(target).reduce((stringCasedObject, targetKey) => {
|
|
28
|
-
stringCasedObject[stringCaser(targetKey)] = recursiveStringCase(target[targetKey], stringCaser);
|
|
40
|
+
stringCasedObject[stringCaser(targetKey)] = recursiveStringCase(target[targetKey], stringCaser, depth + 1);
|
|
29
41
|
return stringCasedObject;
|
|
30
42
|
}, {});
|
|
31
43
|
}
|
|
@@ -1193,6 +1193,19 @@ export default class Query {
|
|
|
1193
1193
|
get connectionName() {
|
|
1194
1194
|
return this.dreamInstance.connectionName || 'default';
|
|
1195
1195
|
}
|
|
1196
|
+
/**
|
|
1197
|
+
* @internal
|
|
1198
|
+
*
|
|
1199
|
+
* Resolves the effective page size for a pagination request: applies the
|
|
1200
|
+
* configured default when no page size is requested, and clamps the result to
|
|
1201
|
+
* the configured maximum (`paginationMaxPageSize`, default 200) so that a
|
|
1202
|
+
* forwarded, attacker-controlled `pageSize` cannot force a full-table load.
|
|
1203
|
+
*/
|
|
1204
|
+
clampedPaginationPageSize(requestedPageSize) {
|
|
1205
|
+
const dreamApp = DreamApp.getOrFail();
|
|
1206
|
+
const pageSize = Math.max(0, requestedPageSize ?? 0) || dreamApp.paginationPageSize;
|
|
1207
|
+
return Math.min(pageSize, dreamApp.paginationMaxPageSize);
|
|
1208
|
+
}
|
|
1196
1209
|
/**
|
|
1197
1210
|
* Paginates the results of your query, accepting a pageSize and page argument,
|
|
1198
1211
|
* which it uses to segment your query into pages, leveraging limit and offset
|
|
@@ -1231,7 +1244,7 @@ export default class Query {
|
|
|
1231
1244
|
throw new CannotPaginateWithLeftJoinPreload();
|
|
1232
1245
|
const page = computedPaginatePage(options.page);
|
|
1233
1246
|
const recordCount = await this.count();
|
|
1234
|
-
const pageSize =
|
|
1247
|
+
const pageSize = this.clampedPaginationPageSize(options.pageSize);
|
|
1235
1248
|
const pageCount = Math.ceil(recordCount / pageSize);
|
|
1236
1249
|
const query = this.orderStatements.length
|
|
1237
1250
|
? this
|
|
@@ -1323,7 +1336,7 @@ export default class Query {
|
|
|
1323
1336
|
*/
|
|
1324
1337
|
async cursorPaginate(opts) {
|
|
1325
1338
|
const options = opts;
|
|
1326
|
-
const pageSize =
|
|
1339
|
+
const pageSize = this.clampedPaginationPageSize(options.pageSize);
|
|
1327
1340
|
if (options === null)
|
|
1328
1341
|
return { cursor: null, results: [] };
|
|
1329
1342
|
if (this.limitStatement)
|
|
@@ -14,6 +14,7 @@ import DreamCLI from '../../cli/index.js';
|
|
|
14
14
|
import { CHECK_VIOLATION, COLUMN_OVERFLOW, INVALID_INPUT_SYNTAX, NOT_NULL_VIOLATION, pgErrorType, } from '../../db/errors.js';
|
|
15
15
|
import syncDbTypesFiles from '../../db/helpers/syncDbTypesFiles.js';
|
|
16
16
|
import { default as _db } from '../../db/index.js';
|
|
17
|
+
import validateColumn from '../../db/validators/validateColumn.js';
|
|
17
18
|
import associationToGetterSetterProp from '../../decorators/field/association/associationToGetterSetterProp.js';
|
|
18
19
|
import PackageManager from '../../dream-app/helpers/PackageManager.js';
|
|
19
20
|
import DreamApp from '../../dream-app/index.js';
|
|
@@ -551,6 +552,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
551
552
|
// that can exceed 63 bytes. Short aliases like "pluck0" avoid this entirely.
|
|
552
553
|
const shortAliases = [];
|
|
553
554
|
fields.forEach((field, index) => {
|
|
555
|
+
this.validatePlainColumn(this.dreamClass, field);
|
|
554
556
|
const shortAlias = `pluck${index}`;
|
|
555
557
|
shortAliases.push(shortAlias);
|
|
556
558
|
// namespace the selection so that when plucking the same column name from
|
|
@@ -719,6 +721,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
719
721
|
});
|
|
720
722
|
if (this.query['orderStatements'].length && !bypassOrder) {
|
|
721
723
|
this.query['orderStatements'].forEach(orderStatement => {
|
|
724
|
+
this.validatePlainColumn(this.dreamClass, orderStatement.column);
|
|
722
725
|
kyselyQuery = kyselyQuery.orderBy(this.namespaceColumn(orderStatement.column), this.orderByDirection(orderStatement.direction));
|
|
723
726
|
});
|
|
724
727
|
}
|
|
@@ -1034,6 +1037,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1034
1037
|
if (query['whereStatements'].length ||
|
|
1035
1038
|
query['whereNotStatements'].length ||
|
|
1036
1039
|
query['whereAnyStatements'].length) {
|
|
1040
|
+
this.validateBaseWhereStatementColumns(query);
|
|
1037
1041
|
kyselyQuery = kyselyQuery.where((eb) => eb.and([
|
|
1038
1042
|
...this.aliasWhereStatements(query['whereStatements'], query['baseSqlAlias']).map(whereStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, whereStatement, {
|
|
1039
1043
|
disallowSimilarityOperator: false,
|
|
@@ -1368,6 +1372,66 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1368
1372
|
namespaceColumn(column, alias = this.query['baseSqlAlias']) {
|
|
1369
1373
|
return namespaceColumn(column, alias);
|
|
1370
1374
|
}
|
|
1375
|
+
/**
|
|
1376
|
+
* @internal
|
|
1377
|
+
*
|
|
1378
|
+
* Defense-in-depth (DREAM-SQLI-01): when a runtime column identifier is a
|
|
1379
|
+
* plain, un-namespaced column name, validate it against the owning model's
|
|
1380
|
+
* compiled schema so an unknown identifier throws a Dream `InvalidColumnName`
|
|
1381
|
+
* error before it reaches the database (rather than surfacing as a raw
|
|
1382
|
+
* Postgres error or a silent blind-ordering side-channel).
|
|
1383
|
+
*
|
|
1384
|
+
* Only bare identifiers are validated. Namespaced / table-aliased /
|
|
1385
|
+
* association-namespaced identifiers (anything containing a `.`) are left on
|
|
1386
|
+
* their existing path, because the segment before the `.` may be an
|
|
1387
|
+
* association alias or table alias rather than a schema table name, and
|
|
1388
|
+
* validating those safely is not possible here without risking rejection of
|
|
1389
|
+
* currently-valid input.
|
|
1390
|
+
*/
|
|
1391
|
+
validatePlainColumn(dreamClass, column) {
|
|
1392
|
+
if (typeof column !== 'string')
|
|
1393
|
+
return;
|
|
1394
|
+
if (column.includes('.'))
|
|
1395
|
+
return;
|
|
1396
|
+
validateColumn(dreamClass.prototype.schema, dreamClass.table, column);
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* @internal
|
|
1400
|
+
*
|
|
1401
|
+
* Defense-in-depth (DREAM-SQLI-01) for the base model's dynamic `where` keys.
|
|
1402
|
+
* These are the un-namespaced keys the developer passed to `where` /
|
|
1403
|
+
* `whereNot` / `whereAny` on the root query (validated here, before
|
|
1404
|
+
* `aliasWhereStatement` namespaces them with the base alias). Only plain
|
|
1405
|
+
* column keys are validated. Deliberately skips:
|
|
1406
|
+
* - namespaced keys (contain a `.`) — the developer explicitly namespaced
|
|
1407
|
+
* them (e.g. `{ 'users.email': ... }` or a joined table), and the prefix
|
|
1408
|
+
* may be an association/table alias rather than a schema table name;
|
|
1409
|
+
* - association-name keys (e.g. `where({ user: userInstance })`), which are
|
|
1410
|
+
* resolved to foreign keys, not columns;
|
|
1411
|
+
* - `DreamConst.passthrough` / `DreamConst.required` sentinel values, whose
|
|
1412
|
+
* keys are resolved elsewhere.
|
|
1413
|
+
*
|
|
1414
|
+
* Join-and / association where statements (which target other tables and are
|
|
1415
|
+
* pre-namespaced) are left on their existing path.
|
|
1416
|
+
*/
|
|
1417
|
+
validateBaseWhereStatementColumns(query) {
|
|
1418
|
+
const dreamClass = query['dreamClass'];
|
|
1419
|
+
const whereStatements = [
|
|
1420
|
+
...query['whereStatements'],
|
|
1421
|
+
...query['whereNotStatements'],
|
|
1422
|
+
...query['whereAnyStatements'].flat(),
|
|
1423
|
+
];
|
|
1424
|
+
whereStatements.forEach(whereStatement => Object.keys(whereStatement).forEach(attr => {
|
|
1425
|
+
if (attr.includes('.'))
|
|
1426
|
+
return;
|
|
1427
|
+
const val = whereStatement[attr];
|
|
1428
|
+
if (val === DreamConst.passthrough || val === DreamConst.required)
|
|
1429
|
+
return;
|
|
1430
|
+
if (dreamClass.associationNames.includes(attr))
|
|
1431
|
+
return;
|
|
1432
|
+
validateColumn(dreamClass.prototype.schema, dreamClass.table, attr);
|
|
1433
|
+
}));
|
|
1434
|
+
}
|
|
1371
1435
|
/**
|
|
1372
1436
|
* @internal
|
|
1373
1437
|
*
|
|
@@ -1924,8 +1988,19 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1924
1988
|
scopesQuery = tempQuery;
|
|
1925
1989
|
}
|
|
1926
1990
|
}
|
|
1927
|
-
if (scopesQuery['whereStatements'].length
|
|
1928
|
-
|
|
1991
|
+
if (scopesQuery['whereStatements'].length ||
|
|
1992
|
+
scopesQuery['whereNotStatements'].length ||
|
|
1993
|
+
scopesQuery['whereAnyStatements'].length) {
|
|
1994
|
+
// Mirror the base-table clause logic in QueryDriverBase#buildCommon so that default
|
|
1995
|
+
// scopes expressed with whereNot(...)/whereAny(...) — not just where(...) — cross into
|
|
1996
|
+
// association loads (preload/join/leftJoinPreload/innerJoin). Without the whereNot/whereAny
|
|
1997
|
+
// branches below, such default scopes are silently dropped on association loads, leaking
|
|
1998
|
+
// rows the app intended to hide.
|
|
1999
|
+
join = join.on((eb) => eb.and([
|
|
2000
|
+
...scopesQuery['whereStatements'].map(whereStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereStatement, tableNameOrAlias), { disallowSimilarityOperator: false })),
|
|
2001
|
+
...scopesQuery['whereNotStatements'].map(whereNotStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereNotStatement, tableNameOrAlias), { negate: true })),
|
|
2002
|
+
...scopesQuery['whereAnyStatements'].map(whereAnyStatements => eb.or(whereAnyStatements.map(whereAnyStatement => this.whereStatementToExpressionWrapper(dreamClass, eb, this.aliasWhereStatement(whereAnyStatement, tableNameOrAlias))))),
|
|
2003
|
+
]));
|
|
1929
2004
|
}
|
|
1930
2005
|
return join;
|
|
1931
2006
|
}
|
|
@@ -4,6 +4,7 @@ import validateTable from '../db/validators/validateTable.js';
|
|
|
4
4
|
import Query from '../dream/Query.js';
|
|
5
5
|
import PostgresQueryDriver from '../dream/QueryDriver/Postgres.js';
|
|
6
6
|
import Encrypt from '../encrypt/index.js';
|
|
7
|
+
import DreamAppInitInvalidEncryptionKey from '../errors/dream-app/DreamAppInitInvalidEncryptionKey.js';
|
|
7
8
|
import DreamAppInitMissingCallToLoadModels from '../errors/dream-app/DreamAppInitMissingCallToLoadModels.js';
|
|
8
9
|
import DreamAppInitMissingMissingProjectRoot from '../errors/dream-app/DreamAppInitMissingMissingProjectRoot.js';
|
|
9
10
|
import DreamAppInitMissingPackageManager from '../errors/dream-app/DreamAppInitMissingPackageManager.js';
|
|
@@ -119,7 +120,9 @@ export default class DreamApp {
|
|
|
119
120
|
if (!this.loadedModels)
|
|
120
121
|
throw new DreamAppInitMissingCallToLoadModels();
|
|
121
122
|
if (this.encryption?.columns?.current)
|
|
122
|
-
DreamApp.checkKey('columns', this.encryption.columns.current.key, this.encryption.columns.current.algorithm);
|
|
123
|
+
DreamApp.checkKey('columns', 'current', this.encryption.columns.current.key, this.encryption.columns.current.algorithm);
|
|
124
|
+
if (this.encryption?.columns?.legacy)
|
|
125
|
+
DreamApp.checkKey('columns', 'legacy', this.encryption.columns.legacy.key, this.encryption.columns.legacy.algorithm);
|
|
123
126
|
if (!DreamAppAllowedPackageManagersEnumValues.includes(this.packageManager) &&
|
|
124
127
|
!EnvInternal.boolean('DREAM_BYPASS_PACKAGE_MANAGER_CHECK'))
|
|
125
128
|
throw new DreamAppInitMissingPackageManager();
|
|
@@ -148,22 +151,9 @@ export default class DreamApp {
|
|
|
148
151
|
await dbDriverClass.setDatabaseTypeParsers(connectionName);
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
|
-
static checkKey(encryptionIdentifier, key, algorithm) {
|
|
154
|
+
static checkKey(encryptionIdentifier, keyType, key, algorithm) {
|
|
152
155
|
if (!Encrypt.validateKey(key, algorithm))
|
|
153
|
-
|
|
154
|
-
console.warn(`
|
|
155
|
-
Your current key value for ${encryptionIdentifier} encryption is invalid.
|
|
156
|
-
Try setting it to something valid, like:
|
|
157
|
-
${Encrypt.generateKey(algorithm)}
|
|
158
|
-
|
|
159
|
-
This was done by calling:
|
|
160
|
-
Encrypt.generateKey('${algorithm}'
|
|
161
|
-
|
|
162
|
-
A new key can also be generated from the CLI:
|
|
163
|
-
% pnpm psy g:encryption-key
|
|
164
|
-
OR
|
|
165
|
-
% pnpm psy g:encryption-key --algorithm=aes-256-gcm
|
|
166
|
-
`);
|
|
156
|
+
throw new DreamAppInitInvalidEncryptionKey(encryptionIdentifier, keyType, algorithm);
|
|
167
157
|
}
|
|
168
158
|
/**
|
|
169
159
|
* Returns the cached dream application if it has been set.
|
|
@@ -218,6 +208,10 @@ A new key can also be generated from the CLI:
|
|
|
218
208
|
get paginationPageSize() {
|
|
219
209
|
return this._paginationPageSize;
|
|
220
210
|
}
|
|
211
|
+
_paginationMaxPageSize = 200;
|
|
212
|
+
get paginationMaxPageSize() {
|
|
213
|
+
return this._paginationMaxPageSize;
|
|
214
|
+
}
|
|
221
215
|
_primaryKeyType = 'bigint';
|
|
222
216
|
get primaryKeyType() {
|
|
223
217
|
return this._primaryKeyType;
|
|
@@ -495,6 +489,9 @@ A new key can also be generated from the CLI:
|
|
|
495
489
|
case 'paginationPageSize':
|
|
496
490
|
this._paginationPageSize = options;
|
|
497
491
|
break;
|
|
492
|
+
case 'paginationMaxPageSize':
|
|
493
|
+
this._paginationMaxPageSize = options;
|
|
494
|
+
break;
|
|
498
495
|
default: {
|
|
499
496
|
// protection so that if a new ApplyOpt is ever added, this will throw a type error at build time
|
|
500
497
|
const _never = applyOption;
|
|
@@ -23,8 +23,13 @@ export default function decryptAESGCM(algorithm, encrypted, key) {
|
|
|
23
23
|
try {
|
|
24
24
|
return JSON.parse(plaintext);
|
|
25
25
|
}
|
|
26
|
-
catch
|
|
27
|
-
|
|
26
|
+
catch {
|
|
27
|
+
// Intentionally drop the underlying SyntaxError as `cause`: its message embeds a
|
|
28
|
+
// snippet of the just-decrypted plaintext (e.g. `Unexpected token 'x', "secret..." is
|
|
29
|
+
// not valid JSON`). Carrying it would leak decrypted plaintext into any logger that
|
|
30
|
+
// serializes `error.cause`. DecryptionParseError's own message is generic and
|
|
31
|
+
// plaintext-free, which is all callers need to diagnose an encrypted-format mismatch.
|
|
32
|
+
throw new DecryptionParseError();
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
function unpackPayloadOrFail(payload) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default class MaxRecursiveStringCaseDepthExceeded extends Error {
|
|
2
|
+
maxDepth;
|
|
3
|
+
constructor(maxDepth) {
|
|
4
|
+
super();
|
|
5
|
+
this.maxDepth = maxDepth;
|
|
6
|
+
}
|
|
7
|
+
get message() {
|
|
8
|
+
return `
|
|
9
|
+
Maximum recursion depth (${this.maxDepth}) exceeded while string-casing an object.
|
|
10
|
+
The input is nested more deeply than Dream will process, which usually means it is
|
|
11
|
+
malformed or maliciously deeply-nested rather than legitimate application data.
|
|
12
|
+
`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Encrypt from '../../encrypt/index.js';
|
|
2
|
+
export default class DreamAppInitInvalidEncryptionKey extends Error {
|
|
3
|
+
encryptionIdentifier;
|
|
4
|
+
keyType;
|
|
5
|
+
algorithm;
|
|
6
|
+
constructor(encryptionIdentifier, keyType, algorithm) {
|
|
7
|
+
super();
|
|
8
|
+
this.encryptionIdentifier = encryptionIdentifier;
|
|
9
|
+
this.keyType = keyType;
|
|
10
|
+
this.algorithm = algorithm;
|
|
11
|
+
}
|
|
12
|
+
get message() {
|
|
13
|
+
return `
|
|
14
|
+
Your ${this.keyType} key value for ${this.encryptionIdentifier} encryption is invalid.
|
|
15
|
+
Try setting it to something valid, like:
|
|
16
|
+
${Encrypt.generateKey(this.algorithm)}
|
|
17
|
+
|
|
18
|
+
This was done by calling:
|
|
19
|
+
Encrypt.generateKey('${this.algorithm}')
|
|
20
|
+
|
|
21
|
+
A new key can also be generated from the CLI:
|
|
22
|
+
% pnpm psy g:encryption-key
|
|
23
|
+
OR
|
|
24
|
+
% pnpm psy g:encryption-key --algorithm=aes-256-gcm
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
}
|