@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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getThemeModeFromThemeKey = getThemeModeFromThemeKey;
|
|
7
|
+
exports.getCoreThemes = getCoreThemes;
|
|
8
|
+
exports.getRegisteredThemes = getRegisteredThemes;
|
|
9
|
+
exports.getThemesByMode = getThemesByMode;
|
|
10
|
+
exports.getDefaultThemeKey = getDefaultThemeKey;
|
|
11
|
+
exports.isRegisteredThemeKey = isRegisteredThemeKey;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const DEFAULT_THEME_KEYS = {
|
|
15
|
+
light: "solid-light-purple",
|
|
16
|
+
dark: "solid-dark-purple",
|
|
17
|
+
};
|
|
18
|
+
const THEME_MODE_TOKENS = {
|
|
19
|
+
light: "-light-",
|
|
20
|
+
dark: "-dark-",
|
|
21
|
+
};
|
|
22
|
+
const THEME_MODE_SUFFIXES = {
|
|
23
|
+
light: "-light",
|
|
24
|
+
dark: "-dark",
|
|
25
|
+
};
|
|
26
|
+
function normalizeThemeKey(value) {
|
|
27
|
+
const normalized = value?.trim().toLowerCase();
|
|
28
|
+
return normalized || null;
|
|
29
|
+
}
|
|
30
|
+
function matchesThemeMode(value, mode) {
|
|
31
|
+
return (value.includes(THEME_MODE_TOKENS[mode])
|
|
32
|
+
|| value.endsWith(THEME_MODE_SUFFIXES[mode])
|
|
33
|
+
|| value === mode);
|
|
34
|
+
}
|
|
35
|
+
function titleCase(value) {
|
|
36
|
+
return value
|
|
37
|
+
.split(/[-_\s]+/)
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.map((token) => {
|
|
40
|
+
if (token.toLowerCase() === "solid")
|
|
41
|
+
return "SolidX";
|
|
42
|
+
return token.charAt(0).toUpperCase() + token.slice(1);
|
|
43
|
+
})
|
|
44
|
+
.join(" ");
|
|
45
|
+
}
|
|
46
|
+
function getThemeModeFromThemeKey(value) {
|
|
47
|
+
const normalized = normalizeThemeKey(value);
|
|
48
|
+
if (!normalized)
|
|
49
|
+
return null;
|
|
50
|
+
if (matchesThemeMode(normalized, "light"))
|
|
51
|
+
return "light";
|
|
52
|
+
if (matchesThemeMode(normalized, "dark"))
|
|
53
|
+
return "dark";
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function createThemeDefinition(key, defaultTheme = false) {
|
|
57
|
+
const mode = getThemeModeFromThemeKey(key);
|
|
58
|
+
if (!mode) {
|
|
59
|
+
throw new Error(`Invalid theme key: ${key}`);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
key,
|
|
63
|
+
label: titleCase(key),
|
|
64
|
+
mode,
|
|
65
|
+
default: defaultTheme,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function createDefaultTheme(mode) {
|
|
69
|
+
return createThemeDefinition(DEFAULT_THEME_KEYS[mode], true);
|
|
70
|
+
}
|
|
71
|
+
function groupThemesByMode(themes, mode) {
|
|
72
|
+
return themes.filter((theme) => theme.mode === mode);
|
|
73
|
+
}
|
|
74
|
+
function pickDefaultThemeKey(themes, mode) {
|
|
75
|
+
const themesForMode = groupThemesByMode(themes, mode);
|
|
76
|
+
return (themesForMode.find((theme) => theme.default)?.key
|
|
77
|
+
?? themesForMode[0]?.key
|
|
78
|
+
?? DEFAULT_THEME_KEYS[mode]);
|
|
79
|
+
}
|
|
80
|
+
function resolveThemeDirectory() {
|
|
81
|
+
const themeDirectory = path_1.default.resolve(process.cwd(), "..", "solid-ui", "public", "themes");
|
|
82
|
+
try {
|
|
83
|
+
if (fs_1.default.existsSync(themeDirectory) && fs_1.default.statSync(themeDirectory).isDirectory()) {
|
|
84
|
+
return path_1.default.resolve(themeDirectory);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function isThemeDirectory(themeDirectory, entryName) {
|
|
93
|
+
return fs_1.default.existsSync(path_1.default.join(themeDirectory, entryName, "theme.css"));
|
|
94
|
+
}
|
|
95
|
+
function readThemesFromPath() {
|
|
96
|
+
const themeDirectory = resolveThemeDirectory();
|
|
97
|
+
if (!themeDirectory) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const directoryEntries = fs_1.default.readdirSync(themeDirectory, { withFileTypes: true });
|
|
102
|
+
return directoryEntries
|
|
103
|
+
.filter((entry) => entry.isDirectory())
|
|
104
|
+
.map((entry) => entry.name.trim())
|
|
105
|
+
.filter(Boolean)
|
|
106
|
+
.filter((entryName) => isThemeDirectory(themeDirectory, entryName))
|
|
107
|
+
.flatMap((entryName) => {
|
|
108
|
+
const mode = getThemeModeFromThemeKey(entryName);
|
|
109
|
+
if (!mode)
|
|
110
|
+
return [];
|
|
111
|
+
return [createThemeDefinition(entryName, entryName === DEFAULT_THEME_KEYS[mode])];
|
|
112
|
+
})
|
|
113
|
+
.sort((left, right) => left.label.localeCompare(right.label));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function getThemes() {
|
|
120
|
+
const discoveredThemes = readThemesFromPath();
|
|
121
|
+
if (discoveredThemes.length) {
|
|
122
|
+
return discoveredThemes;
|
|
123
|
+
}
|
|
124
|
+
return [createDefaultTheme("light"), createDefaultTheme("dark")];
|
|
125
|
+
}
|
|
126
|
+
function getCoreThemes() {
|
|
127
|
+
return getThemes();
|
|
128
|
+
}
|
|
129
|
+
function getRegisteredThemes() {
|
|
130
|
+
return getThemes();
|
|
131
|
+
}
|
|
132
|
+
function getThemesByMode(mode) {
|
|
133
|
+
return groupThemesByMode(getRegisteredThemes(), mode);
|
|
134
|
+
}
|
|
135
|
+
function getDefaultThemeKey(mode = "light") {
|
|
136
|
+
return pickDefaultThemeKey(getRegisteredThemes(), mode);
|
|
137
|
+
}
|
|
138
|
+
function isRegisteredThemeKey(value, mode) {
|
|
139
|
+
if (!value)
|
|
140
|
+
return false;
|
|
141
|
+
return getRegisteredThemes().some((theme) => {
|
|
142
|
+
if (theme.key !== value)
|
|
143
|
+
return false;
|
|
144
|
+
return mode ? theme.mode === mode : true;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=theme-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-registry.js","sourceRoot":"","sources":["../../src/theme/theme-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\n/**\n * Backend theme registry.\n *\n * Responsibilities:\n * - discover theme folders from the filesystem\n * - classify each theme as light/dark from its key\n * - expose default and grouped theme helpers for backend settings\n *\n * Shared pure parsing helpers are intentionally mirrored in solid-core-ui,\n * but all filesystem discovery stays here.\n */\nexport type ThemeMode = \"light\" | \"dark\";\n\nexport type AppThemeDefinition = {\n key: string;\n label: string;\n mode: ThemeMode;\n default?: boolean;\n};\n\nconst DEFAULT_THEME_KEYS: Record<ThemeMode, string> = {\n light: \"solid-light-purple\",\n dark: \"solid-dark-purple\",\n};\n\nconst THEME_MODE_TOKENS: Record<ThemeMode, string> = {\n light: \"-light-\",\n dark: \"-dark-\",\n};\n\nconst THEME_MODE_SUFFIXES: Record<ThemeMode, string> = {\n light: \"-light\",\n dark: \"-dark\",\n};\n\nfunction normalizeThemeKey(value?: string | null): string | null {\n const normalized = value?.trim().toLowerCase();\n return normalized || null;\n}\n\nfunction matchesThemeMode(value: string, mode: ThemeMode): boolean {\n return (\n value.includes(THEME_MODE_TOKENS[mode])\n || value.endsWith(THEME_MODE_SUFFIXES[mode])\n || value === mode\n );\n}\n\nfunction titleCase(value: string): string {\n return value\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map((token) => {\n if (token.toLowerCase() === \"solid\") return \"SolidX\";\n return token.charAt(0).toUpperCase() + token.slice(1);\n })\n .join(\" \");\n}\n\nexport function getThemeModeFromThemeKey(value?: string | null): ThemeMode | null {\n const normalized = normalizeThemeKey(value);\n if (!normalized) return null;\n\n if (matchesThemeMode(normalized, \"light\")) return \"light\";\n if (matchesThemeMode(normalized, \"dark\")) return \"dark\";\n\n return null;\n}\n\nfunction createThemeDefinition(key: string, defaultTheme = false): AppThemeDefinition {\n const mode = getThemeModeFromThemeKey(key);\n if (!mode) {\n throw new Error(`Invalid theme key: ${key}`);\n }\n\n return {\n key,\n label: titleCase(key),\n mode,\n default: defaultTheme,\n };\n}\n\nfunction createDefaultTheme(mode: ThemeMode): AppThemeDefinition {\n return createThemeDefinition(DEFAULT_THEME_KEYS[mode], true);\n}\n\nfunction groupThemesByMode(themes: AppThemeDefinition[], mode: ThemeMode): AppThemeDefinition[] {\n return themes.filter((theme) => theme.mode === mode);\n}\n\nfunction pickDefaultThemeKey(themes: AppThemeDefinition[], mode: ThemeMode): string {\n const themesForMode = groupThemesByMode(themes, mode);\n\n return (\n themesForMode.find((theme) => theme.default)?.key\n ?? themesForMode[0]?.key\n ?? DEFAULT_THEME_KEYS[mode]\n );\n}\n\nfunction resolveThemeDirectory(): string | null {\n const themeDirectory = path.resolve(process.cwd(), \"..\", \"solid-ui\", \"public\", \"themes\");\n try {\n if (fs.existsSync(themeDirectory) && fs.statSync(themeDirectory).isDirectory()) {\n return path.resolve(themeDirectory);\n }\n } catch {\n return null;\n }\n\n return null;\n}\n\nfunction isThemeDirectory(themeDirectory: string, entryName: string): boolean {\n return fs.existsSync(path.join(themeDirectory, entryName, \"theme.css\"));\n}\n\nfunction readThemesFromPath(): AppThemeDefinition[] {\n const themeDirectory = resolveThemeDirectory();\n if (!themeDirectory) {\n return [];\n }\n\n try {\n const directoryEntries = fs.readdirSync(themeDirectory, { withFileTypes: true });\n\n return directoryEntries\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name.trim())\n .filter(Boolean)\n .filter((entryName) => isThemeDirectory(themeDirectory, entryName))\n .flatMap((entryName) => {\n const mode = getThemeModeFromThemeKey(entryName);\n if (!mode) return [];\n\n return [createThemeDefinition(entryName, entryName === DEFAULT_THEME_KEYS[mode])];\n })\n .sort((left, right) => left.label.localeCompare(right.label));\n } catch {\n return [];\n }\n}\n\nfunction getThemes(): AppThemeDefinition[] {\n const discoveredThemes = readThemesFromPath();\n\n if (discoveredThemes.length) {\n return discoveredThemes;\n }\n\n return [createDefaultTheme(\"light\"), createDefaultTheme(\"dark\")];\n}\n\nexport function getCoreThemes(): AppThemeDefinition[] {\n return getThemes();\n}\n\nexport function getRegisteredThemes(): AppThemeDefinition[] {\n return getThemes();\n}\n\nexport function getThemesByMode(mode: ThemeMode): AppThemeDefinition[] {\n return groupThemesByMode(getRegisteredThemes(), mode);\n}\n\nexport function getDefaultThemeKey(mode: ThemeMode = \"light\"): string {\n return pickDefaultThemeKey(getRegisteredThemes(), mode);\n}\n\nexport function isRegisteredThemeKey(value?: string | null, mode?: ThemeMode): value is string {\n if (!value) return false;\n\n return getRegisteredThemes().some((theme) => {\n if (theme.key !== value) return false;\n return mode ? theme.mode === mode : true;\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidxai/core",
|
|
3
|
-
"version": "0.1.13
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "This module is a NestJS module containing all the required core providers required by a Solid application",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"bcrypt": "^5.1.1",
|
|
46
46
|
"bson": "^6.10.1",
|
|
47
47
|
"cache-manager": "^5.5.2",
|
|
48
|
-
"cache-manager-redis-
|
|
48
|
+
"cache-manager-redis-yet": "^5.1.5",
|
|
49
49
|
"class-transformer": "^0.5.1",
|
|
50
50
|
"class-validator": "^0.14.1",
|
|
51
51
|
"cron-parser": "^5.5.0",
|
|
@@ -3,6 +3,7 @@ import { SubCommand, CommandRunner, Option } from 'nest-commander';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { ModuleMetadataHelperService } from 'src/helpers/module-metadata-helper.service';
|
|
5
5
|
import { ConsoleReporter } from 'src/testing/reporter/console-reporter';
|
|
6
|
+
import { ProgressReporter } from 'src/testing/reporter/progress-reporter';
|
|
6
7
|
import { WebhookReporter } from 'src/testing/reporter/webhook-reporter';
|
|
7
8
|
import { runFromMetadata } from 'src/testing/runner/run-from-metadata';
|
|
8
9
|
import type { TestingMetadata } from 'src/testing/contracts/testing-metadata.types';
|
|
@@ -27,6 +28,7 @@ interface TestRunCommandOptions {
|
|
|
27
28
|
listSpecs?: boolean;
|
|
28
29
|
printApiLogs?: boolean;
|
|
29
30
|
runName?: string;
|
|
31
|
+
progress?: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
@SubCommand({
|
|
@@ -106,9 +108,12 @@ export class TestRunCommand extends CommandRunner {
|
|
|
106
108
|
|
|
107
109
|
const webhookUrl = process.env.SOLIDCTL_WEBHOOK_URL;
|
|
108
110
|
const runName = options?.runName ?? `run-${Date.now()}`;
|
|
109
|
-
const
|
|
111
|
+
const baseReporter = webhookUrl
|
|
110
112
|
? new WebhookReporter(webhookUrl, runName)
|
|
111
113
|
: new ConsoleReporter();
|
|
114
|
+
const reporter = options?.progress === false
|
|
115
|
+
? baseReporter
|
|
116
|
+
: new ProgressReporter(baseReporter);
|
|
112
117
|
|
|
113
118
|
try {
|
|
114
119
|
await runFromMetadata({
|
|
@@ -138,8 +143,8 @@ export class TestRunCommand extends CommandRunner {
|
|
|
138
143
|
console.error('Run tests command failed');
|
|
139
144
|
process.exitCode = 1;
|
|
140
145
|
} finally {
|
|
141
|
-
if (
|
|
142
|
-
await
|
|
146
|
+
if (baseReporter instanceof WebhookReporter) {
|
|
147
|
+
await baseReporter.flush(Number(process.exitCode ?? 0));
|
|
143
148
|
}
|
|
144
149
|
}
|
|
145
150
|
return;
|
|
@@ -289,6 +294,15 @@ export class TestRunCommand extends CommandRunner {
|
|
|
289
294
|
parseRunName(val: string): string {
|
|
290
295
|
return val;
|
|
291
296
|
}
|
|
297
|
+
|
|
298
|
+
@Option({
|
|
299
|
+
flags: '--no-progress',
|
|
300
|
+
description: 'Disable the interactive test progress footer.',
|
|
301
|
+
required: false,
|
|
302
|
+
})
|
|
303
|
+
parseNoProgress(): boolean {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
292
306
|
}
|
|
293
307
|
|
|
294
308
|
function splitCsv(value?: string): string[] | undefined {
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { CacheModuleAsyncOptions } from '@nestjs/cache-manager';
|
|
2
2
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
3
|
-
|
|
3
|
+
// cache-manager-redis-yet, not cache-manager-redis-store. The latter stops at
|
|
4
|
+
// 3.0.1 and implements the cache-manager v4 store contract: it expects an
|
|
5
|
+
// options object and reads `options.ttl` in seconds. cache-manager v5 calls
|
|
6
|
+
// `store.set(key, value, ttl)` with a bare number of milliseconds, so that
|
|
7
|
+
// store found no `.ttl`, fell back to an unset default, and issued a plain
|
|
8
|
+
// `SET` - silently discarding every TTL passed anywhere in the app and leaving
|
|
9
|
+
// Redis keys that never expire. redis-yet implements the v5 contract and
|
|
10
|
+
// writes with `PX`, so millisecond TTLs land correctly.
|
|
11
|
+
import { redisStore } from 'cache-manager-redis-yet';
|
|
4
12
|
import { isRedisConfigured } from 'src/helpers/environment.helper';
|
|
5
13
|
|
|
6
14
|
export const CacheManagerOptions: CacheModuleAsyncOptions = {
|
|
@@ -127,6 +127,20 @@ export const ERROR_MESSAGES = {
|
|
|
127
127
|
|
|
128
128
|
DEFAULT_REGEX_PATTERN_NOT_MATCHING_ERROR_MSG: 'Invalid regex pattern.',
|
|
129
129
|
|
|
130
|
+
// MPIN errors.
|
|
131
|
+
// MPIN_INVALID is deliberately shared by "wrong MPIN" and "unknown handle",
|
|
132
|
+
// so a failed sign-in never reveals whether the credential handle was real.
|
|
133
|
+
// MPIN_REVOKED is separate and is returned for any attempt against a
|
|
134
|
+
// deactivated credential - it discloses nothing useful, since a dead
|
|
135
|
+
// credential grants no capability, and it lets the client stop retrying.
|
|
136
|
+
MPIN_INVALID: 'Invalid MPIN.',
|
|
137
|
+
MPIN_LOCKED: 'MPIN sign-in is temporarily locked due to repeated failed attempts.',
|
|
138
|
+
MPIN_REVOKED: 'MPIN sign-in has been disabled for this device. Please sign in normally.',
|
|
139
|
+
MPIN_NOT_ENABLED: 'MPIN sign-in is not enabled.',
|
|
140
|
+
MPIN_FORMAT_INVALID: 'MPIN does not meet the required format.',
|
|
141
|
+
MPIN_TOO_PREDICTABLE: 'MPIN is too easy to guess. Avoid repeated or sequential digits.',
|
|
142
|
+
MPIN_CREDENTIAL_NOT_FOUND: 'MPIN device credential not found.',
|
|
143
|
+
|
|
130
144
|
PERMISSION_MESSAGES: {
|
|
131
145
|
insertMany: (model?: string) =>
|
|
132
146
|
`You do not have permission to import ${model ?? 'these'} records.`,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type MediaCategory = 'image' | 'audio' | 'video' | 'file' | 'pdf';
|
|
2
2
|
|
|
3
|
+
export const MEDIA_CATEGORIES = ['image', 'audio', 'video', 'file', 'pdf'] as const;
|
|
4
|
+
|
|
3
5
|
const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'tiff', 'heic', 'heif'];
|
|
4
6
|
const AUDIO_EXTENSIONS = ['mp3', 'wav', 'ogg', 'aac', 'm4a', 'flac', 'webm'];
|
|
5
7
|
const VIDEO_EXTENSIONS = ['mp4', 'mov', 'avi', 'mkv', 'mpeg', 'mpg', '3gp', '3g2', 'webm', 'ogg'];
|
|
@@ -15,6 +17,7 @@ export const DANGEROUS_EXTENSIONS = new Set([
|
|
|
15
17
|
'html', 'htm', 'xhtml', 'svg', 'xml',
|
|
16
18
|
'js', 'mjs', 'php', 'phtml', 'jsp', 'asp', 'aspx',
|
|
17
19
|
'exe', 'sh', 'bat', 'cmd', 'ps1', 'jar', 'dll',
|
|
20
|
+
'msi', 'msix', 'msp', 'com', 'scr', 'vbs', 'vbe', 'wsf', 'wsh', 'hta', 'cpl', 'pif', 'reg',
|
|
18
21
|
]);
|
|
19
22
|
|
|
20
23
|
|
|
@@ -27,17 +30,92 @@ export const DANGEROUS_MIME_TYPES = new Set([
|
|
|
27
30
|
'image/svg+xml', 'text/html', 'application/xhtml+xml',
|
|
28
31
|
]);
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Percent-decodes a filename, repeating a bounded number of times so a double-encoded payload
|
|
35
|
+
* (`%252e` -> `%2e` -> `.`) survives a single pass through a proxy/framework that already
|
|
36
|
+
* decoded once. Malformed escape sequences are left as-is rather than thrown, since this runs
|
|
37
|
+
* on attacker-controlled input and a literal `%` in a filename (`100%done.pdf`) is legal.
|
|
38
|
+
*/
|
|
39
|
+
function decodePercentEncodingSafely(name: string): string {
|
|
40
|
+
let decoded = name;
|
|
41
|
+
for (let i = 0; i < 3; i++) {
|
|
42
|
+
let next: string;
|
|
43
|
+
try {
|
|
44
|
+
next = decodeURIComponent(decoded);
|
|
45
|
+
} catch {
|
|
46
|
+
return decoded;
|
|
47
|
+
}
|
|
48
|
+
if (next === decoded) {
|
|
49
|
+
return decoded;
|
|
50
|
+
}
|
|
51
|
+
decoded = next;
|
|
52
|
+
}
|
|
53
|
+
return decoded;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reduces a client-supplied name to the name that will actually be resolved on disk, so
|
|
58
|
+
* extension checks can't be dodged by decorating it. Each step defends a known bypass:
|
|
59
|
+
*
|
|
60
|
+
* - `evil.exe%2e` -> percent-encoding a trailing dot (or anything else) hides the real
|
|
61
|
+
* extension from naive string matching; decode before inspecting it.
|
|
62
|
+
* - `../../evil.exe` -> only the final path segment is the file name.
|
|
63
|
+
* - `evil.exe\0.png` -> a NUL truncates the name at the syscall/filesystem layer, so
|
|
64
|
+
* anything after it is invisible to the OS but visible to naive
|
|
65
|
+
* validation.
|
|
66
|
+
* - `evil.exe::$DATA` -> NTFS alternate data stream suffix; the file on disk is evil.exe.
|
|
67
|
+
* - `evil.exe.` / `.exe ` -> Windows silently strips trailing dots and spaces when resolving
|
|
68
|
+
* a path, so these land on disk as evil.exe.
|
|
69
|
+
*/
|
|
70
|
+
export function normalizeUploadedFileName(fileName?: string | null): string {
|
|
31
71
|
if (!fileName) {
|
|
32
|
-
return
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let name = decodePercentEncodingSafely(String(fileName));
|
|
76
|
+
name = name.split(/[/\\]/).pop() ?? '';
|
|
77
|
+
|
|
78
|
+
const nulIndex = name.indexOf('\0');
|
|
79
|
+
if (nulIndex >= 0) {
|
|
80
|
+
name = name.slice(0, nulIndex);
|
|
33
81
|
}
|
|
34
82
|
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
83
|
+
const streamIndex = name.indexOf(':');
|
|
84
|
+
if (streamIndex >= 0) {
|
|
85
|
+
name = name.slice(0, streamIndex);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return name.replace(/[\s.]+$/, '');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The effective extension - what the OS/browser will treat the file as. Used for media
|
|
93
|
+
* category resolution and inline-safety decisions. Always normalizes first, so
|
|
94
|
+
* `evil.exe.` resolves to `exe` rather than to nothing.
|
|
95
|
+
*/
|
|
96
|
+
export function getLowercaseFileExtension(fileName?: string | null): string | undefined {
|
|
97
|
+
const name = normalizeUploadedFileName(fileName);
|
|
98
|
+
|
|
99
|
+
const lastDotIndex = name.lastIndexOf('.');
|
|
100
|
+
if (lastDotIndex < 0 || lastDotIndex === name.length - 1) {
|
|
37
101
|
return undefined;
|
|
38
102
|
}
|
|
39
103
|
|
|
40
|
-
return
|
|
104
|
+
return name.slice(lastDotIndex + 1).toLowerCase();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Every dot-delimited segment after the stem, lowercased. The danger check uses this rather
|
|
109
|
+
* than only the effective extension so a dangerous type buried in a double extension
|
|
110
|
+
* (`evil.exe.png`, `shell.php.jpg`) is still caught - several servers and browsers resolve
|
|
111
|
+
* such names by an earlier segment than the last one.
|
|
112
|
+
*
|
|
113
|
+
* The stem is skipped so an innocuous file that merely starts with a keyword (`exe.png`)
|
|
114
|
+
* isn't rejected.
|
|
115
|
+
*/
|
|
116
|
+
export function getLowercaseFileExtensionSegments(fileName?: string | null): string[] {
|
|
117
|
+
const segments = normalizeUploadedFileName(fileName).toLowerCase().split('.');
|
|
118
|
+
return segments.slice(1).filter(segment => segment.length > 0);
|
|
41
119
|
}
|
|
42
120
|
|
|
43
121
|
/** Minimal shape of an uploaded file, so this module stays free of framework imports. */
|
|
@@ -55,12 +133,12 @@ export function getUploadedFileName(file?: UploadedFileLike | null): string {
|
|
|
55
133
|
/**
|
|
56
134
|
* Single predicate for "must never be stored". Shared by every upload entry point
|
|
57
135
|
* (MediaFieldCrudManager, chatter attachments) so the rules can't drift between them.
|
|
58
|
-
*
|
|
59
|
-
* application/octet-stream on an .html file) can't bypass it.
|
|
136
|
+
* Extensions are checked before mimetype so a spoofed Content-Type (e.g.
|
|
137
|
+
* application/octet-stream, or image/png, on an .html file) can't bypass it.
|
|
60
138
|
*/
|
|
61
139
|
export function isDangerousMediaFile(file?: UploadedFileLike | null): boolean {
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
140
|
+
const segments = getLowercaseFileExtensionSegments(getUploadedFileName(file));
|
|
141
|
+
if (segments.some(segment => DANGEROUS_EXTENSIONS.has(segment))) {
|
|
64
142
|
return true;
|
|
65
143
|
}
|
|
66
144
|
|
|
@@ -6,6 +6,11 @@ export const SUCCESS_MESSAGES = {
|
|
|
6
6
|
LOGOUT_SUCCESS: 'Logged out successfully',
|
|
7
7
|
USER_REGISTERED: 'User registered successfully.',
|
|
8
8
|
|
|
9
|
+
// MPIN messages
|
|
10
|
+
MPIN_SETUP_SUCCESS: 'MPIN set up successfully.',
|
|
11
|
+
MPIN_CHANGED_SUCCESS: 'MPIN changed successfully.',
|
|
12
|
+
MPIN_DEVICE_REVOKED: 'MPIN sign-in removed for this device.',
|
|
13
|
+
|
|
9
14
|
// CRUD service messages
|
|
10
15
|
RECORD_RECOVERED: 'Record successfully recovered',
|
|
11
16
|
SELECTED_RECORDS_RECOVERED: 'Selected records successfully recovered',
|
|
@@ -107,8 +107,11 @@ export class AuthenticationController {
|
|
|
107
107
|
@Post('logout')
|
|
108
108
|
@Public()
|
|
109
109
|
@HttpCode(HttpStatus.OK)
|
|
110
|
-
async logout(
|
|
111
|
-
|
|
110
|
+
async logout(
|
|
111
|
+
@Body('refreshToken') refreshToken: string,
|
|
112
|
+
@Body('allDevices') allDevices?: boolean,
|
|
113
|
+
) {
|
|
114
|
+
return this.authService.logout(refreshToken, allDevices === true);
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
@ApiBearerAuth("jwt")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, ParseIntPipe, Post } from '@nestjs/common';
|
|
2
|
+
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
|
3
|
+
import { ActiveUser } from '../decorators/active-user.decorator';
|
|
4
|
+
import { Public } from '../decorators/public.decorator';
|
|
5
|
+
import { ChangeMpinDto } from '../dtos/change-mpin.dto';
|
|
6
|
+
import { MpinLoginDto } from '../dtos/mpin-login.dto';
|
|
7
|
+
import { SetupMpinDto } from '../dtos/setup-mpin.dto';
|
|
8
|
+
import { ActiveUserData } from '../interfaces/active-user-data.interface';
|
|
9
|
+
import { MpinService } from '../services/mpin.service';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* There is deliberately no public "is MPIN set up for this identifier" route.
|
|
13
|
+
* That would reintroduce exactly the user-enumeration oracle the opaque
|
|
14
|
+
* credential handle removes. The client decides whether to show the MPIN
|
|
15
|
+
* screen from the presence of its own stored handle.
|
|
16
|
+
*/
|
|
17
|
+
@Controller('iam/mpin')
|
|
18
|
+
@ApiTags('Solid Core')
|
|
19
|
+
export class MpinAuthenticationController {
|
|
20
|
+
constructor(private readonly mpinService: MpinService) { }
|
|
21
|
+
|
|
22
|
+
@ApiBearerAuth('jwt')
|
|
23
|
+
@Post('setup')
|
|
24
|
+
@HttpCode(HttpStatus.OK)
|
|
25
|
+
setup(
|
|
26
|
+
@Body() setupMpinDto: SetupMpinDto,
|
|
27
|
+
@ActiveUser() activeUser: ActiveUserData,
|
|
28
|
+
) {
|
|
29
|
+
return this.mpinService.setupMpin(activeUser, setupMpinDto);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@ApiBearerAuth('jwt')
|
|
33
|
+
@Post('change')
|
|
34
|
+
@HttpCode(HttpStatus.OK)
|
|
35
|
+
change(
|
|
36
|
+
@Body() changeMpinDto: ChangeMpinDto,
|
|
37
|
+
@ActiveUser() activeUser: ActiveUserData,
|
|
38
|
+
) {
|
|
39
|
+
return this.mpinService.changeMpin(activeUser, changeMpinDto);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Public by necessity - the caller has no session yet. The credential
|
|
43
|
+
// handle is unguessable, so this carries no enumeration surface.
|
|
44
|
+
@Public()
|
|
45
|
+
@Post('login')
|
|
46
|
+
@HttpCode(HttpStatus.OK)
|
|
47
|
+
login(@Body() mpinLoginDto: MpinLoginDto) {
|
|
48
|
+
return this.mpinService.loginWithMpin(mpinLoginDto);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@ApiBearerAuth('jwt')
|
|
52
|
+
@Get('devices')
|
|
53
|
+
listDevices(@ActiveUser() activeUser: ActiveUserData) {
|
|
54
|
+
return this.mpinService.listDevices(activeUser);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// By row id, never by credential handle, so the management UI never has to
|
|
58
|
+
// hold the secret.
|
|
59
|
+
@ApiBearerAuth('jwt')
|
|
60
|
+
@Delete('devices/:id')
|
|
61
|
+
revokeDevice(
|
|
62
|
+
@Param('id', ParseIntPipe) id: number,
|
|
63
|
+
@ActiveUser() activeUser: ActiveUserData,
|
|
64
|
+
) {
|
|
65
|
+
return this.mpinService.revokeDevice(activeUser, id);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
|
|
2
|
+
|
|
3
|
+
export class ChangeMpinDto {
|
|
4
|
+
@IsNotEmpty()
|
|
5
|
+
@IsString()
|
|
6
|
+
@MaxLength(128)
|
|
7
|
+
credentialRef: string;
|
|
8
|
+
|
|
9
|
+
@IsNotEmpty()
|
|
10
|
+
@IsString()
|
|
11
|
+
@MaxLength(32)
|
|
12
|
+
currentMpin: string;
|
|
13
|
+
|
|
14
|
+
@IsNotEmpty()
|
|
15
|
+
@IsString()
|
|
16
|
+
@MaxLength(32)
|
|
17
|
+
newMpin: string;
|
|
18
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ApiProperty } from '@nestjs/swagger';
|
|
2
2
|
import { Transform, Type } from 'class-transformer';
|
|
3
|
+
import { DANGEROUS_EXTENSIONS, MEDIA_CATEGORIES, MediaCategory } from '../constants/media-file-types';
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
IsArray,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
IsEnum,
|
|
8
9
|
IsIn,
|
|
9
10
|
IsInt,
|
|
11
|
+
IsNotIn,
|
|
10
12
|
IsOptional,
|
|
11
13
|
IsString,
|
|
12
14
|
Matches,
|
|
@@ -155,13 +157,7 @@ export enum DecryptWhenType {
|
|
|
155
157
|
afterTransit = 'after-transit',
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
export
|
|
159
|
-
image = 'image',
|
|
160
|
-
audio = 'audio',
|
|
161
|
-
video = 'video',
|
|
162
|
-
// document = 'document',
|
|
163
|
-
file = 'file',
|
|
164
|
-
}
|
|
160
|
+
export type MediaType = MediaCategory;
|
|
165
161
|
|
|
166
162
|
export enum RelationType {
|
|
167
163
|
manyToOne = 'many-to-one',
|
|
@@ -308,7 +304,7 @@ export class CreateFieldMetadataDto {
|
|
|
308
304
|
private: boolean;
|
|
309
305
|
|
|
310
306
|
@ApiProperty({ description: 'Allowed media types for the field. Only for type=mediaSingle,mediaMultiple', })
|
|
311
|
-
@
|
|
307
|
+
@IsIn([...MEDIA_CATEGORIES], { each: true })
|
|
312
308
|
@IsOptional()
|
|
313
309
|
mediaTypes: MediaType[];
|
|
314
310
|
|
|
@@ -317,6 +313,18 @@ export class CreateFieldMetadataDto {
|
|
|
317
313
|
@IsOptional()
|
|
318
314
|
mediaMaxSizeKb: number;
|
|
319
315
|
|
|
316
|
+
@ApiProperty({ description: 'Explicit allowlist of file extensions (e.g. ["pdf","xlsx","png"]) permitted for this field, checked in addition to any mediaTypes category restriction — both must pass if both are set. For a strict allowlist, leave mediaTypes empty and use this alone. Only for type=mediaSingle,mediaMultiple', })
|
|
317
|
+
@Transform(({ value }) =>
|
|
318
|
+
Array.isArray(value)
|
|
319
|
+
? value.map((ext: any) => String(ext).toLowerCase().trim().replace(/^\./, ''))
|
|
320
|
+
: value
|
|
321
|
+
)
|
|
322
|
+
@IsArray()
|
|
323
|
+
@IsString({ each: true })
|
|
324
|
+
@IsNotIn([...DANGEROUS_EXTENSIONS], { each: true })
|
|
325
|
+
@IsOptional()
|
|
326
|
+
mediaAllowedExtensions?: string[];
|
|
327
|
+
|
|
320
328
|
@ApiProperty({ description: 'Configured Media storage provider. Only for type=mediaSingle,mediaMultiple', })
|
|
321
329
|
@IsInt()
|
|
322
330
|
@IsOptional()
|
|
@@ -464,4 +472,4 @@ export class CreateFieldMetadataDto {
|
|
|
464
472
|
@ApiProperty({ description: 'Is Primary Key Field', })
|
|
465
473
|
@IsBoolean()
|
|
466
474
|
isPrimaryKey?: boolean;
|
|
467
|
-
}
|
|
475
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IsNotEmpty, IsString, MaxLength } from 'class-validator';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deliberately carries no identifier - no mobile, email or username.
|
|
5
|
+
*
|
|
6
|
+
* `credentialRef` is a server-issued 256-bit handle that already points at a
|
|
7
|
+
* single credential row, and therefore at a single user. That is what removes
|
|
8
|
+
* the user-enumeration surface a public login route would otherwise have: an
|
|
9
|
+
* attacker cannot probe "does this phone number have MPIN?" because there is
|
|
10
|
+
* nowhere to put a phone number.
|
|
11
|
+
*/
|
|
12
|
+
export class MpinLoginDto {
|
|
13
|
+
// Bounded so a pathological payload never reaches the regex test or the
|
|
14
|
+
// hash comparison. The handle is always 64 hex characters.
|
|
15
|
+
@IsNotEmpty()
|
|
16
|
+
@IsString()
|
|
17
|
+
@MaxLength(128)
|
|
18
|
+
credentialRef: string;
|
|
19
|
+
|
|
20
|
+
@IsNotEmpty()
|
|
21
|
+
@IsString()
|
|
22
|
+
@MaxLength(32)
|
|
23
|
+
mpin: string;
|
|
24
|
+
}
|