@solidxai/core 0.1.13-beta.9 → 0.1.13
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 +7465 -0
- package/README.md +19 -0
- package/dist/commands/run-tests.command.d.ts +2 -0
- package/dist/commands/run-tests.command.d.ts.map +1 -1
- package/dist/commands/run-tests.command.js +20 -3
- package/dist/commands/run-tests.command.js.map +1 -1
- package/dist/config/cache.options.d.ts.map +1 -1
- package/dist/config/cache.options.js +2 -2
- package/dist/config/cache.options.js.map +1 -1
- package/dist/constants/error-messages.d.ts +7 -0
- package/dist/constants/error-messages.d.ts.map +1 -1
- package/dist/constants/error-messages.js +7 -0
- package/dist/constants/error-messages.js.map +1 -1
- package/dist/constants/media-file-types.d.ts +3 -0
- package/dist/constants/media-file-types.d.ts.map +1 -1
- package/dist/constants/media-file-types.js +47 -8
- package/dist/constants/media-file-types.js.map +1 -1
- package/dist/constants/success-messages.d.ts +3 -0
- package/dist/constants/success-messages.d.ts.map +1 -1
- package/dist/constants/success-messages.js +3 -0
- package/dist/constants/success-messages.js.map +1 -1
- package/dist/controllers/authentication.controller.d.ts +1 -1
- package/dist/controllers/authentication.controller.d.ts.map +1 -1
- package/dist/controllers/authentication.controller.js +4 -3
- package/dist/controllers/authentication.controller.js.map +1 -1
- package/dist/controllers/field-metadata.controller.d.ts +2 -2
- package/dist/controllers/mpin-authentication.controller.d.ts +44 -0
- package/dist/controllers/mpin-authentication.controller.d.ts.map +1 -0
- package/dist/controllers/mpin-authentication.controller.js +102 -0
- package/dist/controllers/mpin-authentication.controller.js.map +1 -0
- package/dist/dtos/change-mpin.dto.d.ts +6 -0
- package/dist/dtos/change-mpin.dto.d.ts.map +1 -0
- package/dist/dtos/change-mpin.dto.js +39 -0
- package/dist/dtos/change-mpin.dto.js.map +1 -0
- package/dist/dtos/create-field-metadata.dto.d.ts +3 -6
- package/dist/dtos/create-field-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-field-metadata.dto.js +15 -10
- package/dist/dtos/create-field-metadata.dto.js.map +1 -1
- package/dist/dtos/create-model-sequence.dto.d.ts +1 -1
- package/dist/dtos/create-model-sequence.dto.d.ts.map +1 -1
- package/dist/dtos/create-model-sequence.dto.js +1 -1
- package/dist/dtos/create-model-sequence.dto.js.map +1 -1
- package/dist/dtos/mpin-login.dto.d.ts +5 -0
- package/dist/dtos/mpin-login.dto.d.ts.map +1 -0
- package/dist/dtos/mpin-login.dto.js +33 -0
- package/dist/dtos/mpin-login.dto.js.map +1 -0
- package/dist/dtos/setup-mpin.dto.d.ts +7 -0
- package/dist/dtos/setup-mpin.dto.d.ts.map +1 -0
- package/dist/dtos/setup-mpin.dto.js +47 -0
- package/dist/dtos/setup-mpin.dto.js.map +1 -0
- package/dist/entities/dashboard-user-layout.entity.js +2 -2
- package/dist/entities/dashboard-user-layout.entity.js.map +1 -1
- package/dist/entities/field-metadata.entity.d.ts +1 -0
- package/dist/entities/field-metadata.entity.d.ts.map +1 -1
- package/dist/entities/field-metadata.entity.js +5 -1
- package/dist/entities/field-metadata.entity.js.map +1 -1
- package/dist/entities/user-device-credential.entity.d.ts +18 -0
- package/dist/entities/user-device-credential.entity.d.ts.map +1 -0
- package/dist/entities/user-device-credential.entity.js +91 -0
- package/dist/entities/user-device-credential.entity.js.map +1 -0
- package/dist/entities/user.entity.js +2 -2
- package/dist/entities/user.entity.js.map +1 -1
- package/dist/entities/view-metadata.entity.js.map +1 -1
- package/dist/entities/workflow-execution-artifact.entity.d.ts.map +1 -1
- package/dist/entities/workflow-execution-artifact.entity.js +4 -3
- package/dist/entities/workflow-execution-artifact.entity.js.map +1 -1
- package/dist/entities/workflow-execution-log.entity.d.ts.map +1 -1
- package/dist/entities/workflow-execution-log.entity.js +4 -3
- package/dist/entities/workflow-execution-log.entity.js.map +1 -1
- package/dist/entities/workflow-execution.entity.d.ts.map +1 -1
- package/dist/entities/workflow-execution.entity.js +3 -2
- package/dist/entities/workflow-execution.entity.js.map +1 -1
- package/dist/entities/workflow-secret.entity.d.ts.map +1 -1
- package/dist/entities/workflow-secret.entity.js +3 -2
- package/dist/entities/workflow-secret.entity.js.map +1 -1
- package/dist/entities/workflow-step-execution.entity.d.ts.map +1 -1
- package/dist/entities/workflow-step-execution.entity.js +3 -2
- package/dist/entities/workflow-step-execution.entity.js.map +1 -1
- package/dist/entities/workflow-trigger-execution.entity.d.ts.map +1 -1
- package/dist/entities/workflow-trigger-execution.entity.js +6 -5
- package/dist/entities/workflow-trigger-execution.entity.js.map +1 -1
- package/dist/filters/http-exception.filter.d.ts.map +1 -1
- package/dist/filters/http-exception.filter.js +5 -1
- package/dist/filters/http-exception.filter.js.map +1 -1
- package/dist/helpers/bootstrap.helper.d.ts +5 -2
- package/dist/helpers/bootstrap.helper.d.ts.map +1 -1
- package/dist/helpers/bootstrap.helper.js +7 -3
- package/dist/helpers/bootstrap.helper.js.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts +6 -0
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +54 -27
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
- package/dist/helpers/file-download.helper.d.ts.map +1 -1
- package/dist/helpers/file-download.helper.js +6 -1
- package/dist/helpers/file-download.helper.js.map +1 -1
- package/dist/helpers/security.helper.d.ts +5 -1
- package/dist/helpers/security.helper.d.ts.map +1 -1
- package/dist/helpers/security.helper.js +15 -4
- package/dist/helpers/security.helper.js.map +1 -1
- package/dist/helpers/solid-core-error-codes-provider.service.d.ts.map +1 -1
- package/dist/helpers/solid-core-error-codes-provider.service.js +45 -0
- package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -1
- package/dist/helpers/solid-microservice-adapter.service.d.ts.map +1 -1
- package/dist/helpers/solid-microservice-adapter.service.js +1 -1
- package/dist/helpers/solid-microservice-adapter.service.js.map +1 -1
- package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
- package/dist/helpers/typeorm-db-helper.js +9 -0
- package/dist/helpers/typeorm-db-helper.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/active-user-data.interface.d.ts +1 -0
- package/dist/interfaces/active-user-data.interface.d.ts.map +1 -1
- package/dist/interfaces/active-user-data.interface.js.map +1 -1
- package/dist/repository/user-device-credential.repository.d.ts +12 -0
- package/dist/repository/user-device-credential.repository.d.ts.map +1 -0
- package/dist/repository/user-device-credential.repository.js +34 -0
- package/dist/repository/user-device-credential.repository.js.map +1 -0
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +7 -3
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +168 -2
- package/dist/services/api-key.service.d.ts.map +1 -1
- package/dist/services/api-key.service.js +1 -1
- package/dist/services/api-key.service.js.map +1 -1
- package/dist/services/authentication.service.d.ts +5 -4
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +50 -19
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts +5 -1
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +48 -13
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/crud-helper.service.d.ts +1 -2
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +2 -16
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +2 -1
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/draft-publish-helper.service.d.ts +1 -0
- package/dist/services/draft-publish-helper.service.d.ts.map +1 -1
- package/dist/services/draft-publish-helper.service.js +38 -4
- package/dist/services/draft-publish-helper.service.js.map +1 -1
- package/dist/services/field-metadata.service.d.ts +2 -2
- package/dist/services/field-metadata.service.d.ts.map +1 -1
- package/dist/services/field-metadata.service.js +4 -1
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/media-storage.utils.d.ts +1 -0
- package/dist/services/media-storage.utils.d.ts.map +1 -1
- package/dist/services/media-storage.utils.js +12 -1
- package/dist/services/media-storage.utils.js.map +1 -1
- package/dist/services/media.service.d.ts.map +1 -1
- package/dist/services/media.service.js +1 -0
- package/dist/services/media.service.js.map +1 -1
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +9 -1
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/services/mpin.service.d.ts +67 -0
- package/dist/services/mpin.service.d.ts.map +1 -0
- package/dist/services/mpin.service.js +282 -0
- package/dist/services/mpin.service.js.map +1 -0
- package/dist/services/refresh-token-ids-storage.service.d.ts +25 -6
- package/dist/services/refresh-token-ids-storage.service.d.ts.map +1 -1
- package/dist/services/refresh-token-ids-storage.service.js +80 -42
- package/dist/services/refresh-token-ids-storage.service.js.map +1 -1
- package/dist/services/setting.service.d.ts.map +1 -1
- package/dist/services/setting.service.js +6 -1
- package/dist/services/setting.service.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts +202 -6
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +114 -3
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/solid-core.module.d.ts +1 -3
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +14 -39
- package/dist/solid-core.module.js.map +1 -1
- package/dist/testing/reporter/console-reporter.d.ts +3 -0
- package/dist/testing/reporter/console-reporter.d.ts.map +1 -1
- package/dist/testing/reporter/console-reporter.js +2 -0
- package/dist/testing/reporter/console-reporter.js.map +1 -1
- package/dist/testing/reporter/progress-reporter.d.ts +64 -0
- package/dist/testing/reporter/progress-reporter.d.ts.map +1 -0
- package/dist/testing/reporter/progress-reporter.js +224 -0
- package/dist/testing/reporter/progress-reporter.js.map +1 -0
- package/dist/testing/reporter/reporter.types.d.ts +3 -0
- package/dist/testing/reporter/reporter.types.d.ts.map +1 -1
- package/dist/testing/reporter/reporter.types.js.map +1 -1
- package/dist/testing/runner/run-from-metadata.d.ts.map +1 -1
- package/dist/testing/runner/run-from-metadata.js +1 -0
- package/dist/testing/runner/run-from-metadata.js.map +1 -1
- package/dist/theme/theme-registry.d.ts +14 -0
- package/dist/theme/theme-registry.d.ts.map +1 -0
- package/dist/theme/theme-registry.js +147 -0
- package/dist/theme/theme-registry.js.map +1 -0
- package/package.json +2 -2
- package/src/commands/run-tests.command.ts +17 -3
- package/src/config/cache.options.ts +9 -1
- package/src/constants/error-messages.ts +14 -0
- package/src/constants/media-file-types.ts +87 -9
- package/src/constants/success-messages.ts +5 -0
- package/src/controllers/authentication.controller.ts +5 -2
- package/src/controllers/mpin-authentication.controller.ts +67 -0
- package/src/dtos/change-mpin.dto.ts +18 -0
- package/src/dtos/create-field-metadata.dto.ts +17 -9
- package/src/dtos/create-model-sequence.dto.ts +1 -1
- package/src/dtos/mpin-login.dto.ts +24 -0
- package/src/dtos/setup-mpin.dto.ts +51 -0
- package/src/entities/dashboard-user-layout.entity.ts +2 -2
- package/src/entities/field-metadata.entity.ts +3 -0
- package/src/entities/user-device-credential.entity.ts +130 -0
- package/src/entities/user.entity.ts +2 -2
- package/src/entities/view-metadata.entity.ts +2 -2
- package/src/entities/workflow-execution-artifact.entity.ts +4 -3
- package/src/entities/workflow-execution-log.entity.ts +4 -3
- package/src/entities/workflow-execution.entity.ts +3 -2
- package/src/entities/workflow-secret.entity.ts +3 -2
- package/src/entities/workflow-step-execution.entity.ts +3 -2
- package/src/entities/workflow-trigger-execution.entity.ts +6 -5
- package/src/filters/http-exception.filter.ts +11 -1
- package/src/helpers/bootstrap.helper.ts +43 -6
- package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +73 -34
- package/src/helpers/file-download.helper.ts +15 -1
- package/src/helpers/security.helper.ts +35 -8
- package/src/helpers/solid-core-error-codes-provider.service.ts +53 -0
- package/src/helpers/solid-microservice-adapter.service.ts +6 -1
- package/src/helpers/typeorm-db-helper.ts +12 -1
- package/src/index.ts +6 -0
- package/src/interfaces/active-user-data.interface.ts +9 -0
- package/src/repository/user-device-credential.repository.ts +17 -0
- package/src/seeders/module-metadata-seeder.service.ts +11 -3
- package/src/seeders/seed-data/solid-core-metadata.json +168 -2
- package/src/services/api-key.service.ts +13 -1
- package/src/services/authentication.service.ts +127 -36
- package/src/services/chatter-message.service.ts +78 -15
- package/src/services/crud-helper.service.ts +19 -16
- package/src/services/crud.service.ts +3 -1
- package/src/services/draft-publish-helper.service.ts +56 -4
- package/src/services/field-metadata.service.ts +5 -2
- package/src/services/media-storage.utils.ts +26 -1
- package/src/services/media.service.ts +1 -0
- package/src/services/module-metadata.service.ts +10 -1
- package/src/services/mpin.service.ts +447 -0
- package/src/services/refresh-token-ids-storage.service.ts +229 -90
- package/src/services/setting.service.ts +8 -1
- package/src/services/settings/default-settings-provider.service.ts +119 -3
- package/src/solid-core.module.ts +26 -12
- package/src/testing/README.md +2 -1
- package/src/testing/reporter/console-reporter.ts +4 -0
- package/src/testing/reporter/progress-reporter.ts +251 -0
- package/src/testing/reporter/reporter.types.ts +3 -0
- package/src/testing/runner/run-from-metadata.ts +1 -0
- package/src/theme/theme-registry.ts +181 -0
- package/CLAUDE.md +0 -26
|
@@ -12,6 +12,7 @@ export interface MediaFieldOptions {
|
|
|
12
12
|
fieldName: string | undefined | null;
|
|
13
13
|
mediaMaxSizeKb: number | undefined | null;
|
|
14
14
|
mediaTypes: string[];
|
|
15
|
+
mediaAllowedExtensions?: string[] | null;
|
|
15
16
|
isUpdate: boolean | undefined | null;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -86,24 +87,28 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
86
87
|
return null;
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
// Extension is authoritative: a declared mimetype is fully attacker-controlled and
|
|
91
|
+
// proved insufficient on its own (a .msi uploaded as image/png previously resolved to
|
|
92
|
+
// 'image', since the mimetype match short-circuited before the extension was ever
|
|
93
|
+
// checked). The extension must independently resolve to a known-safe category before
|
|
94
|
+
// anything is accepted - not merely "not on the denylist".
|
|
89
95
|
const ext = getLowercaseFileExtension(getUploadedFileName(file));
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
92
|
-
return
|
|
96
|
+
const extType = ext ? EXT_TO_MEDIA_TYPE[ext] : undefined;
|
|
97
|
+
if (!extType) {
|
|
98
|
+
return null;
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return EXT_TO_MEDIA_TYPE[ext];
|
|
101
|
+
// The mimetype may only confirm or veto what the extension already established, never
|
|
102
|
+
// grant a category on its own. A mimetype that doesn't resolve to anything (e.g.
|
|
103
|
+
// application/octet-stream) has no opinion and is treated as agreeing.
|
|
104
|
+
const mt = (file.mimetype || '').toLowerCase().trim();
|
|
105
|
+
const mimeType = MIME_TO_MEDIA_TYPE[mt]
|
|
106
|
+
?? (mt.startsWith('image/') ? 'image' : mt.startsWith('audio/') ? 'audio' : mt.startsWith('video/') ? 'video' : undefined);
|
|
107
|
+
if (mimeType && mimeType !== extType) {
|
|
108
|
+
return null;
|
|
104
109
|
}
|
|
105
110
|
|
|
106
|
-
return
|
|
111
|
+
return extType;
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
validate(dto: any, files: Array<Express.Multer.File>): ValidationError[] {
|
|
@@ -112,18 +117,17 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
private applyValidations(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
const errors = this.validateDangerousFiles(fieldFiles);
|
|
120
|
+
// A dangerous upload is never acceptable, regardless of admin config, so this is the
|
|
121
|
+
// only check that sits outside validateCommon() and can't be skipped either way below.
|
|
122
|
+
const dangerousFileErrors = this.validateDangerousFiles(fieldFiles);
|
|
119
123
|
|
|
120
124
|
switch (this.options.type) {
|
|
121
125
|
case SolidMediaType.mediaSingle:
|
|
122
|
-
return [...
|
|
126
|
+
return [...dangerousFileErrors, ...this.validateMediaSingle(fieldFiles)];
|
|
123
127
|
case SolidMediaType.mediaMultiple:
|
|
124
|
-
return [...
|
|
128
|
+
return [...dangerousFileErrors, ...this.validateMediaMultiple(fieldFiles)];
|
|
125
129
|
default:
|
|
126
|
-
return
|
|
130
|
+
return dangerousFileErrors;
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
|
|
@@ -137,7 +141,18 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
137
141
|
}));
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
// Every optional, admin-configured, per-field check - no-ops when its setting is unset -
|
|
145
|
+
// shared between mediaSingle and mediaMultiple so neither can drift out of sync with it.
|
|
146
|
+
private validateCommon(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
|
|
147
|
+
return [
|
|
148
|
+
...this.validateRequired(fieldFiles),
|
|
149
|
+
...this.validateSize(fieldFiles),
|
|
150
|
+
...this.validateTypes(fieldFiles),
|
|
151
|
+
...this.validateAllowedExtensions(fieldFiles),
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private validateRequired(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
|
|
141
156
|
const errors: ValidationError[] = [];
|
|
142
157
|
if (!this.options.isUpdate && this.options.required && fieldFiles.length === 0) {
|
|
143
158
|
errors.push({
|
|
@@ -145,13 +160,11 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
145
160
|
error: `${this.options.fieldName} is required`
|
|
146
161
|
});
|
|
147
162
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
// validate size
|
|
163
|
+
return errors;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private validateSize(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
|
|
167
|
+
const errors: ValidationError[] = [];
|
|
155
168
|
if (this.options.mediaMaxSizeKb) {
|
|
156
169
|
for (let i = 0; i < fieldFiles.length; i++) {
|
|
157
170
|
const fieldFile = fieldFiles[i];
|
|
@@ -164,7 +177,11 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
164
177
|
}
|
|
165
178
|
}
|
|
166
179
|
}
|
|
167
|
-
|
|
180
|
+
return errors;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private validateTypes(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
|
|
184
|
+
const errors: ValidationError[] = [];
|
|
168
185
|
if (this.options.mediaTypes && this.options.mediaTypes.length > 0) {
|
|
169
186
|
const allowedFileTypes = this.options.mediaTypes as MediaType[];
|
|
170
187
|
|
|
@@ -182,21 +199,43 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
182
199
|
}
|
|
183
200
|
}
|
|
184
201
|
}
|
|
185
|
-
|
|
186
202
|
return errors;
|
|
187
203
|
}
|
|
188
204
|
|
|
189
|
-
private
|
|
190
|
-
const
|
|
191
|
-
if (!
|
|
205
|
+
private validateAllowedExtensions(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
|
|
206
|
+
const allowed = this.options.mediaAllowedExtensions;
|
|
207
|
+
if (!allowed || allowed.length === 0) {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const normalizedAllowed = new Set(allowed.map(ext => ext.toLowerCase().replace(/^\./, '')));
|
|
212
|
+
return fieldFiles
|
|
213
|
+
.filter(fieldFile => {
|
|
214
|
+
const ext = getLowercaseFileExtension(getUploadedFileName(fieldFile));
|
|
215
|
+
return !ext || !normalizedAllowed.has(ext);
|
|
216
|
+
})
|
|
217
|
+
.map(fieldFile => ({
|
|
218
|
+
field: this.options.fieldName,
|
|
219
|
+
error: `${this.options.fieldName} file type not allowed. ` +
|
|
220
|
+
`Allowed extensions: ${[...normalizedAllowed].join(', ')}.`
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private validateMediaSingle(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
|
|
225
|
+
const errors = this.validateCommon(fieldFiles);
|
|
226
|
+
if (fieldFiles.length > 1) {
|
|
192
227
|
errors.push({
|
|
193
228
|
field: this.options.fieldName,
|
|
194
|
-
error: `${this.options.fieldName}
|
|
229
|
+
error: `${this.options.fieldName} must be a single file`
|
|
195
230
|
});
|
|
196
231
|
}
|
|
197
232
|
return errors;
|
|
198
233
|
}
|
|
199
234
|
|
|
235
|
+
private validateMediaMultiple(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
|
|
236
|
+
return this.validateCommon(fieldFiles);
|
|
237
|
+
}
|
|
238
|
+
|
|
200
239
|
transformForCreate(dto: any): any {
|
|
201
240
|
return dto;
|
|
202
241
|
}
|
|
@@ -9,6 +9,20 @@ export interface FileDownloadHeaderOptions {
|
|
|
9
9
|
crossOriginResourcePolicy?: boolean;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Builds an RFC 6266 Content-Disposition value. The file name is attacker-controlled (it comes
|
|
14
|
+
* from the uploaded originalFileName), so it can't be interpolated raw: a quote would close the
|
|
15
|
+
* quoted-string early and let the rest of the name forge further header parameters. The ASCII
|
|
16
|
+
* fallback is stripped to a safe subset and the real name is carried in the RFC 5987
|
|
17
|
+
* `filename*` parameter, which is also what preserves non-ASCII names correctly.
|
|
18
|
+
*/
|
|
19
|
+
function buildContentDisposition(disposition: "inline" | "attachment", fileName: string): string {
|
|
20
|
+
const name = (fileName || "file").replace(/[\r\n]/g, "");
|
|
21
|
+
const asciiFallback = name.replace(/[^\x20-\x7E]/g, "_").replace(/["\\]/g, "_") || "file";
|
|
22
|
+
|
|
23
|
+
return `${disposition}; filename="${asciiFallback}"; filename*=UTF-8''${encodeURIComponent(name)}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
/**
|
|
13
27
|
* Single place that stamps the response headers for every streamed file download, so the
|
|
14
28
|
* security headers can't be applied to one endpoint and forgotten on the next.
|
|
@@ -16,7 +30,7 @@ export interface FileDownloadHeaderOptions {
|
|
|
16
30
|
export function setFileDownloadHeaders(res: Response, options: FileDownloadHeaderOptions): void {
|
|
17
31
|
const disposition = options.disposition ?? "attachment";
|
|
18
32
|
|
|
19
|
-
res.setHeader("Content-Disposition",
|
|
33
|
+
res.setHeader("Content-Disposition", buildContentDisposition(disposition, options.fileName));
|
|
20
34
|
res.setHeader("Content-Type", options.mimeType);
|
|
21
35
|
res.setHeader("Access-Control-Expose-Headers", "Content-Disposition, Content-Type");
|
|
22
36
|
|
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
import { HelmetOptions } from "helmet";
|
|
2
2
|
import { Environment } from "src/decorators/disallow-in-production.decorator";
|
|
3
3
|
|
|
4
|
+
export interface SolidSecurityOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Origins that may embed this application in an iframe.
|
|
7
|
+
* Defaults to the local HTTP/HTTPS loopback origins on any port.
|
|
8
|
+
*/
|
|
9
|
+
frameAncestors?: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_FRAME_ANCESTORS = [
|
|
13
|
+
"'self'",
|
|
14
|
+
"http://localhost:*",
|
|
15
|
+
"https://localhost:*",
|
|
16
|
+
"http://127.0.0.1:*",
|
|
17
|
+
"https://127.0.0.1:*",
|
|
18
|
+
"http://[::1]:*",
|
|
19
|
+
"https://[::1]:*",
|
|
20
|
+
];
|
|
21
|
+
|
|
4
22
|
/**
|
|
5
23
|
* Default security headers for SolidX apps.
|
|
6
24
|
* - HSTS only in prod over HTTPS
|
|
7
|
-
* - CSP with frame-ancestors
|
|
8
|
-
* - X-Frame-Options
|
|
25
|
+
* - CSP with configurable frame-ancestors
|
|
26
|
+
* - X-Frame-Options disabled when cross-origin frame ancestors are allowed
|
|
9
27
|
* - No X-XSS-Protection (deprecated)
|
|
10
28
|
*/
|
|
11
|
-
export function buildDefaultSecurityHeaderOptions(
|
|
29
|
+
export function buildDefaultSecurityHeaderOptions(
|
|
30
|
+
options: SolidSecurityOptions = {},
|
|
31
|
+
): Readonly<HelmetOptions> {
|
|
12
32
|
const isProd = process.env.ENV === Environment.Production;
|
|
33
|
+
const frameAncestors = options.frameAncestors ?? DEFAULT_FRAME_ANCESTORS;
|
|
34
|
+
const allowsCrossOriginFrames = frameAncestors.some(
|
|
35
|
+
(source) => source !== "'self'" && source !== "'none'",
|
|
36
|
+
);
|
|
13
37
|
|
|
14
38
|
return {
|
|
15
39
|
// Modern CSP. Add more directives as your app needs (script-src, connect-src, etc.)
|
|
@@ -23,7 +47,7 @@ export function buildDefaultSecurityHeaderOptions(): Readonly<HelmetOptions> {
|
|
|
23
47
|
// "form-action": ["'self'"],
|
|
24
48
|
|
|
25
49
|
// clickjacking defense (modern)
|
|
26
|
-
"frame-ancestors":
|
|
50
|
+
"frame-ancestors": frameAncestors,
|
|
27
51
|
"style-src": ["'self'"],
|
|
28
52
|
// add/adjust as needed for your app:
|
|
29
53
|
// "script-src": ["'self'"], // add hashes/nonces/CSPRO if needed
|
|
@@ -34,9 +58,12 @@ export function buildDefaultSecurityHeaderOptions(): Readonly<HelmetOptions> {
|
|
|
34
58
|
},
|
|
35
59
|
},
|
|
36
60
|
|
|
37
|
-
|
|
38
|
-
//
|
|
39
|
-
|
|
61
|
+
// Legacy clickjacking defense for configurations that do not allow
|
|
62
|
+
// cross-origin parents (kept for older UAs).
|
|
63
|
+
// X-Frame-Options cannot express an allowlist. It must be omitted when
|
|
64
|
+
// CSP allows a parent on another origin, otherwise older browsers may
|
|
65
|
+
// still block the iframe despite the CSP configuration.
|
|
66
|
+
frameguard: allowsCrossOriginFrames ? false : { action: "deny" },
|
|
40
67
|
|
|
41
68
|
// Referrer/cross-origin policies
|
|
42
69
|
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
|
|
@@ -115,4 +142,4 @@ export function setDefaultCacheControl() {
|
|
|
115
142
|
// res.setHeader("Permissions-Policy", buildPermissionsPolicyHeader());
|
|
116
143
|
// next();
|
|
117
144
|
// });
|
|
118
|
-
// app.use(setDefaultCacheControl());
|
|
145
|
+
// app.use(setDefaultCacheControl());
|
|
@@ -32,6 +32,59 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
|
|
|
32
32
|
httpStatus: 401,
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
|
+
// MPIN rules sit above the generic session rules so that an MPIN
|
|
36
|
+
// failure is never reported as a session problem. The three codes
|
|
37
|
+
// drive three different client behaviours: retry, wait, and
|
|
38
|
+
// discard-the-handle.
|
|
39
|
+
{
|
|
40
|
+
code: 'solidx-mpin-locked',
|
|
41
|
+
priority: 120,
|
|
42
|
+
match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_LOCKED.toLowerCase()),
|
|
43
|
+
meta: {
|
|
44
|
+
message: ERROR_MESSAGES.MPIN_LOCKED,
|
|
45
|
+
httpStatus: 401,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
code: 'solidx-mpin-revoked',
|
|
50
|
+
priority: 120,
|
|
51
|
+
match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_REVOKED.toLowerCase()),
|
|
52
|
+
meta: {
|
|
53
|
+
message: ERROR_MESSAGES.MPIN_REVOKED,
|
|
54
|
+
httpStatus: 401,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
code: 'solidx-mpin-invalid',
|
|
59
|
+
priority: 120,
|
|
60
|
+
match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_INVALID.toLowerCase()),
|
|
61
|
+
meta: {
|
|
62
|
+
message: ERROR_MESSAGES.MPIN_INVALID,
|
|
63
|
+
httpStatus: 401,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
// Setup-time rejections. Distinct codes so the client can say
|
|
67
|
+
// "choose a less predictable PIN" rather than parsing a message.
|
|
68
|
+
// Safe to distinguish: both occur only on bearer-authenticated
|
|
69
|
+
// routes, so neither discloses anything.
|
|
70
|
+
{
|
|
71
|
+
code: 'solidx-mpin-too-predictable',
|
|
72
|
+
priority: 120,
|
|
73
|
+
match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_TOO_PREDICTABLE.toLowerCase()),
|
|
74
|
+
meta: {
|
|
75
|
+
message: ERROR_MESSAGES.MPIN_TOO_PREDICTABLE,
|
|
76
|
+
httpStatus: 400,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
code: 'solidx-mpin-format-invalid',
|
|
81
|
+
priority: 120,
|
|
82
|
+
match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_FORMAT_INVALID.toLowerCase()),
|
|
83
|
+
meta: {
|
|
84
|
+
message: ERROR_MESSAGES.MPIN_FORMAT_INVALID,
|
|
85
|
+
httpStatus: 400,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
35
88
|
{
|
|
36
89
|
code: 'solidx-mcp-server-unavailable',
|
|
37
90
|
priority: 100,
|
|
@@ -79,7 +79,12 @@ export class SolidMicroserviceAdapter {
|
|
|
79
79
|
const responseData = response.data as SolidxIamAuthResponse;
|
|
80
80
|
const ttlSeconds = this.getJwtTtlSeconds(responseData?.data?.accessToken);
|
|
81
81
|
if (ttlSeconds && ttlSeconds > 0) {
|
|
82
|
-
|
|
82
|
+
// cache-manager v5 expects milliseconds. This previously passed seconds,
|
|
83
|
+
// which the old Redis store discarded outright - so the entry simply
|
|
84
|
+
// never expired. Now that TTLs are honoured, passing seconds unconverted
|
|
85
|
+
// would expire the cached token ~1000x too early and re-authenticate
|
|
86
|
+
// against IAM on nearly every call.
|
|
87
|
+
await this.cacheManager.set(cacheKey, responseData, ttlSeconds * 1000);
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
return responseData;
|
|
@@ -40,6 +40,14 @@ const DECIMAL_MAP: Record<DatasourceType, ColumnOptions> = {
|
|
|
40
40
|
[DatasourceType.oracle]: { type: "float" },
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
const DATE_TIME_MAP: Record<DatasourceType, ColumnOptions> = {
|
|
44
|
+
[DatasourceType.postgres]: { type: "timestamp" },
|
|
45
|
+
[DatasourceType.mssql]: { type: "datetime2" },
|
|
46
|
+
[DatasourceType.mysql]: { type: "datetime" },
|
|
47
|
+
[DatasourceType.mariadb]: { type: "datetime" },
|
|
48
|
+
[DatasourceType.oracle]: { type: "timestamp" },
|
|
49
|
+
};
|
|
50
|
+
|
|
43
51
|
const solidCoreDbType: DatasourceType =
|
|
44
52
|
Object.values(DatasourceType).includes(process.env.SOLID_CORE_DB_TYPE as DatasourceType)
|
|
45
53
|
? (process.env.SOLID_CORE_DB_TYPE as DatasourceType)
|
|
@@ -72,7 +80,10 @@ export function getColumnType(solidType: string): ColumnOptions {
|
|
|
72
80
|
case "decimal":
|
|
73
81
|
return DECIMAL_MAP[solidCoreDbType];
|
|
74
82
|
|
|
83
|
+
case "datetime":
|
|
84
|
+
return DATE_TIME_MAP[solidCoreDbType];
|
|
85
|
+
|
|
75
86
|
default:
|
|
76
87
|
return {};
|
|
77
88
|
}
|
|
78
|
-
}
|
|
89
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -160,6 +160,12 @@ export * from './entities/role-metadata.entity'
|
|
|
160
160
|
export * from './entities/sms-template.entity'
|
|
161
161
|
export * from './entities/user.entity'
|
|
162
162
|
export * from './entities/user-api-key.entity'
|
|
163
|
+
export * from './entities/user-device-credential.entity'
|
|
164
|
+
export * from './repository/user-device-credential.repository'
|
|
165
|
+
export * from './services/mpin.service'
|
|
166
|
+
export * from './dtos/setup-mpin.dto'
|
|
167
|
+
export * from './dtos/change-mpin.dto'
|
|
168
|
+
export * from './dtos/mpin-login.dto'
|
|
163
169
|
export * from './entities/view-metadata.entity'
|
|
164
170
|
export * from './entities/setting.entity'
|
|
165
171
|
export * from './entities/saved-filters.entity'
|
|
@@ -22,6 +22,15 @@ export interface ActiveUserData {
|
|
|
22
22
|
*/
|
|
23
23
|
sessionId?: string;
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Identifies which per-device refresh-token bucket this session belongs to.
|
|
27
|
+
* Present only while concurrent logins are permitted; mutually exclusive with
|
|
28
|
+
* sessionId, which exists only when they are not. Carried on the access token
|
|
29
|
+
* so bearer-authenticated endpoints that need this session's refresh token
|
|
30
|
+
* can find it.
|
|
31
|
+
*/
|
|
32
|
+
deviceKey?: string;
|
|
33
|
+
|
|
25
34
|
/**
|
|
26
35
|
* The subject's (user) roles.
|
|
27
36
|
* These are part of the JWT token, we simply decode them.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { UserDeviceCredential } from 'src/entities/user-device-credential.entity';
|
|
3
|
+
import { RequestContextService } from 'src/services/request-context.service';
|
|
4
|
+
import { DataSource } from 'typeorm';
|
|
5
|
+
import { SecurityRuleRepository } from './security-rule.repository';
|
|
6
|
+
import { SolidBaseRepository } from './solid-base.repository';
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class UserDeviceCredentialRepository extends SolidBaseRepository<UserDeviceCredential> {
|
|
10
|
+
constructor(
|
|
11
|
+
readonly dataSource: DataSource,
|
|
12
|
+
readonly requestContextService: RequestContextService,
|
|
13
|
+
readonly securityRuleRepository: SecurityRuleRepository,
|
|
14
|
+
) {
|
|
15
|
+
super(UserDeviceCredential, dataSource, requestContextService, securityRuleRepository);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1049,17 +1049,25 @@ export class ModuleMetadataSeederService {
|
|
|
1049
1049
|
|
|
1050
1050
|
private async seedModelSequences(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
|
|
1051
1051
|
const modelSequences = this.getSeedArray<CreateModelSequenceDto>(overallMetadata?.modelSequences);
|
|
1052
|
-
|
|
1052
|
+
|
|
1053
|
+
// Getting rid of currentValue even if it is present in the seed data.
|
|
1054
|
+
// This is because we don't want to overwrite the currentValue in the database with the seed data.
|
|
1055
|
+
// The currentValue should be managed by the application and not by the seed data.
|
|
1056
|
+
const cleanModelSequences = modelSequences.map((sequence) => {
|
|
1057
|
+
const { currentValue, ...cleanModelSequence }: CreateModelSequenceDto = sequence;
|
|
1058
|
+
return cleanModelSequence;
|
|
1059
|
+
});
|
|
1060
|
+
const pruned = this.enablePruning ? await this.timeOperation('prune-model-sequences', () => this.pruneModelSequences(cleanModelSequences, overallMetadata?.moduleMetadata?.name), {
|
|
1053
1061
|
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
1054
1062
|
component: 'model-sequences',
|
|
1055
1063
|
serviceCall: 'pruneModelSequences',
|
|
1056
1064
|
}) : 0;
|
|
1057
|
-
await this.timeOperation('handle-model-sequences', () => this.handleSeedModelSequences(
|
|
1065
|
+
await this.timeOperation('handle-model-sequences', () => this.handleSeedModelSequences(cleanModelSequences), {
|
|
1058
1066
|
moduleName: overallMetadata?.moduleMetadata?.name,
|
|
1059
1067
|
component: 'model-sequences',
|
|
1060
1068
|
serviceCall: 'handleSeedModelSequences',
|
|
1061
1069
|
});
|
|
1062
|
-
return { pruned, upserted:
|
|
1070
|
+
return { pruned, upserted: cleanModelSequences.length };
|
|
1063
1071
|
}
|
|
1064
1072
|
|
|
1065
1073
|
// OK
|
|
@@ -2360,6 +2360,164 @@
|
|
|
2360
2360
|
}
|
|
2361
2361
|
]
|
|
2362
2362
|
},
|
|
2363
|
+
{
|
|
2364
|
+
"singularName": "userDeviceCredential",
|
|
2365
|
+
"tableName": "ss_user_device_credential",
|
|
2366
|
+
"pluralName": "userDeviceCredentials",
|
|
2367
|
+
"displayName": "User Device Credential",
|
|
2368
|
+
"description": "Per-device MPIN credentials",
|
|
2369
|
+
"dataSource": "default",
|
|
2370
|
+
"dataSourceType": "postgres",
|
|
2371
|
+
"userKeyFieldUserKey": "credentialKey",
|
|
2372
|
+
"isSystem": true,
|
|
2373
|
+
"fields": [
|
|
2374
|
+
{
|
|
2375
|
+
"name": "credentialKey",
|
|
2376
|
+
"displayName": "Credential Key",
|
|
2377
|
+
"description": "Natural key: <username>-<deviceId>. Populated by MpinService, not by the computed-field pipeline.",
|
|
2378
|
+
"type": "shortText",
|
|
2379
|
+
"length": 512,
|
|
2380
|
+
"required": true,
|
|
2381
|
+
"unique": true,
|
|
2382
|
+
"index": true,
|
|
2383
|
+
"private": false,
|
|
2384
|
+
"encrypt": false,
|
|
2385
|
+
"isUserKey": true,
|
|
2386
|
+
"isSystem": true
|
|
2387
|
+
},
|
|
2388
|
+
{
|
|
2389
|
+
"name": "hashedCredentialRef",
|
|
2390
|
+
"displayName": "Hashed Credential Ref",
|
|
2391
|
+
"type": "shortText",
|
|
2392
|
+
"length": 512,
|
|
2393
|
+
"required": true,
|
|
2394
|
+
"unique": true,
|
|
2395
|
+
"index": true,
|
|
2396
|
+
"private": true,
|
|
2397
|
+
"encrypt": false,
|
|
2398
|
+
"isSystem": true
|
|
2399
|
+
},
|
|
2400
|
+
{
|
|
2401
|
+
"name": "hashedMpin",
|
|
2402
|
+
"displayName": "Hashed MPIN",
|
|
2403
|
+
"type": "shortText",
|
|
2404
|
+
"length": 512,
|
|
2405
|
+
"required": true,
|
|
2406
|
+
"unique": false,
|
|
2407
|
+
"index": false,
|
|
2408
|
+
"private": true,
|
|
2409
|
+
"encrypt": false,
|
|
2410
|
+
"isSystem": true
|
|
2411
|
+
},
|
|
2412
|
+
{
|
|
2413
|
+
"name": "mpinScheme",
|
|
2414
|
+
"displayName": "MPIN Scheme",
|
|
2415
|
+
"type": "selectionStatic",
|
|
2416
|
+
"required": true,
|
|
2417
|
+
"private": false,
|
|
2418
|
+
"encrypt": false,
|
|
2419
|
+
"isSystem": true,
|
|
2420
|
+
"selectionStaticValues": [
|
|
2421
|
+
"bcrypt:bcrypt"
|
|
2422
|
+
]
|
|
2423
|
+
},
|
|
2424
|
+
{
|
|
2425
|
+
"name": "mpinSchemeVersion",
|
|
2426
|
+
"displayName": "MPIN Scheme Version",
|
|
2427
|
+
"type": "int",
|
|
2428
|
+
"required": true,
|
|
2429
|
+
"private": false,
|
|
2430
|
+
"encrypt": false,
|
|
2431
|
+
"defaultValue": "1",
|
|
2432
|
+
"isSystem": true
|
|
2433
|
+
},
|
|
2434
|
+
{
|
|
2435
|
+
"name": "deviceId",
|
|
2436
|
+
"displayName": "Device Id",
|
|
2437
|
+
"type": "shortText",
|
|
2438
|
+
"length": 512,
|
|
2439
|
+
"required": true,
|
|
2440
|
+
"unique": false,
|
|
2441
|
+
"index": true,
|
|
2442
|
+
"private": false,
|
|
2443
|
+
"encrypt": false,
|
|
2444
|
+
"isSystem": true
|
|
2445
|
+
},
|
|
2446
|
+
{
|
|
2447
|
+
"name": "deviceName",
|
|
2448
|
+
"displayName": "Device Name",
|
|
2449
|
+
"type": "shortText",
|
|
2450
|
+
"length": 512,
|
|
2451
|
+
"required": false,
|
|
2452
|
+
"unique": false,
|
|
2453
|
+
"index": false,
|
|
2454
|
+
"private": false,
|
|
2455
|
+
"encrypt": false,
|
|
2456
|
+
"isSystem": true
|
|
2457
|
+
},
|
|
2458
|
+
{
|
|
2459
|
+
"name": "platform",
|
|
2460
|
+
"displayName": "Platform",
|
|
2461
|
+
"type": "shortText",
|
|
2462
|
+
"length": 512,
|
|
2463
|
+
"required": false,
|
|
2464
|
+
"unique": false,
|
|
2465
|
+
"index": false,
|
|
2466
|
+
"private": false,
|
|
2467
|
+
"encrypt": false,
|
|
2468
|
+
"isSystem": true
|
|
2469
|
+
},
|
|
2470
|
+
{
|
|
2471
|
+
"name": "isActive",
|
|
2472
|
+
"displayName": "Is Active",
|
|
2473
|
+
"type": "boolean",
|
|
2474
|
+
"required": true,
|
|
2475
|
+
"unique": false,
|
|
2476
|
+
"index": false,
|
|
2477
|
+
"private": false,
|
|
2478
|
+
"encrypt": false,
|
|
2479
|
+
"defaultValue": "true",
|
|
2480
|
+
"isSystem": true
|
|
2481
|
+
},
|
|
2482
|
+
{
|
|
2483
|
+
"name": "failedAttempts",
|
|
2484
|
+
"displayName": "Failed Attempts",
|
|
2485
|
+
"type": "int",
|
|
2486
|
+
"required": true,
|
|
2487
|
+
"private": false,
|
|
2488
|
+
"encrypt": false,
|
|
2489
|
+
"defaultValue": "0",
|
|
2490
|
+
"isSystem": true
|
|
2491
|
+
},
|
|
2492
|
+
{
|
|
2493
|
+
"name": "lockedUntil",
|
|
2494
|
+
"displayName": "Locked Until",
|
|
2495
|
+
"type": "datetime",
|
|
2496
|
+
"required": false,
|
|
2497
|
+
"private": false,
|
|
2498
|
+
"encrypt": false,
|
|
2499
|
+
"isSystem": true
|
|
2500
|
+
},
|
|
2501
|
+
{
|
|
2502
|
+
"name": "lastUsedAt",
|
|
2503
|
+
"displayName": "Last Used At",
|
|
2504
|
+
"type": "datetime",
|
|
2505
|
+
"required": false,
|
|
2506
|
+
"private": false,
|
|
2507
|
+
"encrypt": false,
|
|
2508
|
+
"isSystem": true
|
|
2509
|
+
},
|
|
2510
|
+
{
|
|
2511
|
+
"name": "user",
|
|
2512
|
+
"displayName": "User",
|
|
2513
|
+
"type": "relation",
|
|
2514
|
+
"required": true,
|
|
2515
|
+
"private": false,
|
|
2516
|
+
"encrypt": false,
|
|
2517
|
+
"isSystem": true
|
|
2518
|
+
}
|
|
2519
|
+
]
|
|
2520
|
+
},
|
|
2363
2521
|
{
|
|
2364
2522
|
"singularName": "userActivityHistory",
|
|
2365
2523
|
"tableName": "ss_user_activity_history",
|
|
@@ -11642,7 +11800,8 @@
|
|
|
11642
11800
|
"type": "field",
|
|
11643
11801
|
"attrs": {
|
|
11644
11802
|
"name": "fileSize",
|
|
11645
|
-
"sortable": true
|
|
11803
|
+
"sortable": true,
|
|
11804
|
+
"viewWidget": "MediaFileSizeListWidget"
|
|
11646
11805
|
}
|
|
11647
11806
|
}
|
|
11648
11807
|
]
|
|
@@ -11807,6 +11966,13 @@
|
|
|
11807
11966
|
"attrs": {
|
|
11808
11967
|
"name": "fieldMetadata"
|
|
11809
11968
|
}
|
|
11969
|
+
},
|
|
11970
|
+
{
|
|
11971
|
+
"type": "field",
|
|
11972
|
+
"attrs": {
|
|
11973
|
+
"name": "fileSize",
|
|
11974
|
+
"viewWidget": "MediaFileSizeFormViewWidget"
|
|
11975
|
+
}
|
|
11810
11976
|
}
|
|
11811
11977
|
]
|
|
11812
11978
|
}
|
|
@@ -12583,7 +12749,7 @@
|
|
|
12583
12749
|
50
|
|
12584
12750
|
],
|
|
12585
12751
|
"enableGlobalSearch": true,
|
|
12586
|
-
"create":
|
|
12752
|
+
"create": false,
|
|
12587
12753
|
"edit": true,
|
|
12588
12754
|
"delete": true,
|
|
12589
12755
|
"import": true,
|