@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
|
@@ -1,133 +1,272 @@
|
|
|
1
1
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
|
2
2
|
import { Inject, Injectable, forwardRef } from '@nestjs/common';
|
|
3
3
|
import { Cache } from 'cache-manager';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
import type { SolidCoreSetting } from 'src/services/settings/default-settings-provider.service';
|
|
4
6
|
import { AuthenticationService } from './authentication.service';
|
|
7
|
+
import { SettingService } from './setting.service';
|
|
5
8
|
|
|
6
9
|
// TODO: Ideally this should be in a separate file - putting this here for brevity
|
|
7
10
|
export class InvalidatedRefreshTokenError extends Error { }
|
|
8
11
|
|
|
12
|
+
// How long a rotated-out refresh token stays acceptable. This window is the
|
|
13
|
+
// point of keeping a previous token at all: concurrent requests from one user
|
|
14
|
+
// must not break each other. core-ui's single-flight guard is module-level and
|
|
15
|
+
// therefore per tab, so two tabs crossing the refresh threshold together will
|
|
16
|
+
// both present the same token - the second one is served from here.
|
|
17
|
+
const PREVIOUS_TOKEN_GRACE_MS = 60 * 1000;
|
|
18
|
+
|
|
19
|
+
// The cache entry must outlive the JWT it holds. If it expired first, refresh
|
|
20
|
+
// would fail with ACCESS_DENIED (InvalidatedRefreshTokenError) rather than the
|
|
21
|
+
// SESSION_EXPIRED that the token's own `exp` produces - the same event
|
|
22
|
+
// reported two different ways.
|
|
23
|
+
const STATE_TTL_BUFFER_SECONDS = 60;
|
|
24
|
+
|
|
9
25
|
type RefreshTokenState = {
|
|
10
26
|
currentRefreshToken: string;
|
|
11
27
|
previousRefreshToken: string;
|
|
28
|
+
// Absolute epoch-ms deadline after which previousRefreshToken is refused.
|
|
29
|
+
// Stored rather than scheduled: an in-process timer is lost when the pod
|
|
30
|
+
// restarts, which used to leave the rotated-out token valid until the next
|
|
31
|
+
// rotation instead of for one minute. Optional because entries written
|
|
32
|
+
// before this field existed will not carry it.
|
|
33
|
+
previousValidUntil?: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The bucket-identifying claims carried on a refresh token. Absent on tokens
|
|
38
|
+
* issued before per-device sessions existed, and on tokens issued while
|
|
39
|
+
* preventConcurrentLogins is on - which is exactly how the read path tells the
|
|
40
|
+
* two schemes apart without consulting any setting.
|
|
41
|
+
*/
|
|
42
|
+
export type RefreshTokenClaims = {
|
|
43
|
+
deviceKey?: string;
|
|
44
|
+
epoch?: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type RotatedRefreshToken = {
|
|
48
|
+
refreshToken: string;
|
|
49
|
+
// The bucket the rotated token now lives in. Differs from the incoming
|
|
50
|
+
// deviceKey only when a pre-migration token was just moved into one.
|
|
51
|
+
deviceKey?: string;
|
|
12
52
|
};
|
|
13
53
|
|
|
14
54
|
@Injectable()
|
|
15
|
-
// export class RefreshTokenIdsStorageService implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
16
55
|
export class RefreshTokenIdsStorageService {
|
|
17
|
-
// private redisClient: Redis;
|
|
18
|
-
// onApplicationBootstrap() {
|
|
19
|
-
// // TODO: Ideally, we should move this to the dedicated "RedisModule" instead of initiating the connection here.
|
|
20
|
-
// this.redisClient = new Redis({
|
|
21
|
-
// // TODO: According to best practices, we should use the environment variables here instead.
|
|
22
|
-
// host: 'localhost',
|
|
23
|
-
// port: 6379,
|
|
24
|
-
// });
|
|
25
|
-
// }
|
|
26
|
-
// onApplicationShutdown(signal?: string) {
|
|
27
|
-
// return this.redisClient.quit();
|
|
28
|
-
// }
|
|
29
|
-
|
|
30
56
|
constructor(
|
|
31
57
|
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
|
32
58
|
@Inject(forwardRef(() => AuthenticationService))
|
|
33
|
-
private readonly authenticationService: AuthenticationService
|
|
59
|
+
private readonly authenticationService: AuthenticationService,
|
|
60
|
+
private readonly settingService: SettingService,
|
|
34
61
|
) { }
|
|
35
62
|
|
|
36
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The inverse of the `preventConcurrentLogins` setting, and the only place
|
|
65
|
+
* it is interpreted.
|
|
66
|
+
*
|
|
67
|
+
* That setting already promises, when off, that sessions may coexist;
|
|
68
|
+
* splitting the refresh-token keyspace per device is what finally delivers
|
|
69
|
+
* it. When it is on, its stated purpose is a single live session, so
|
|
70
|
+
* per-device keys would be meaningless and the original single slot is kept.
|
|
71
|
+
*
|
|
72
|
+
* This governs the WRITE path only. Reads always branch on the token's own
|
|
73
|
+
* claim, so toggling the setting never invalidates a live session.
|
|
74
|
+
*/
|
|
75
|
+
areConcurrentLoginsAllowed(): boolean {
|
|
76
|
+
return !this.settingService.getConfigValue<SolidCoreSetting>("preventConcurrentLogins");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async insert(
|
|
80
|
+
userId: number,
|
|
81
|
+
refreshToken: string,
|
|
82
|
+
previousRefreshToken?: string,
|
|
83
|
+
deviceKey?: string,
|
|
84
|
+
): Promise<void> {
|
|
37
85
|
const refreshTokenState: RefreshTokenState = {
|
|
38
86
|
currentRefreshToken: refreshToken,
|
|
39
87
|
previousRefreshToken: previousRefreshToken ?? "",
|
|
88
|
+
...(previousRefreshToken
|
|
89
|
+
? { previousValidUntil: Date.now() + PREVIOUS_TOKEN_GRACE_MS }
|
|
90
|
+
: {}),
|
|
40
91
|
};
|
|
41
|
-
await this.cacheManager.set(
|
|
92
|
+
await this.cacheManager.set(
|
|
93
|
+
this.getKey(userId, deviceKey),
|
|
94
|
+
refreshTokenState,
|
|
95
|
+
this.getStateTtlMs(),
|
|
96
|
+
);
|
|
42
97
|
}
|
|
43
98
|
|
|
44
|
-
async
|
|
45
|
-
|
|
46
|
-
// Then you will compare against the currentRefreshToken.
|
|
47
|
-
const storedId = await this.cacheManager.get(this.getKey(userId));
|
|
48
|
-
if (storedId !== refreshToken) {
|
|
49
|
-
throw new InvalidatedRefreshTokenError();
|
|
50
|
-
}
|
|
51
|
-
return storedId === refreshToken;
|
|
99
|
+
async invalidate(userId: number, deviceKey?: string): Promise<void> {
|
|
100
|
+
await this.cacheManager.del(this.getKey(userId, deviceKey));
|
|
52
101
|
}
|
|
53
102
|
|
|
54
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Bulk invalidation - "log out everywhere", and the right hook for password
|
|
105
|
+
* change or forced deactivation.
|
|
106
|
+
*
|
|
107
|
+
* Per-device buckets cannot be enumerated through cache-manager, so this
|
|
108
|
+
* bumps a per-user epoch that every per-device token carries as a claim.
|
|
109
|
+
* One write invalidates every outstanding token without a scan and without
|
|
110
|
+
* the read-modify-write race an index of device keys would have.
|
|
111
|
+
*/
|
|
112
|
+
async invalidateAll(userId: number): Promise<void> {
|
|
113
|
+
const currentEpoch = (await this.getEpoch(userId)) ?? 0;
|
|
114
|
+
await this.cacheManager.set(
|
|
115
|
+
this.getEpochKey(userId),
|
|
116
|
+
currentEpoch + 1,
|
|
117
|
+
// Outlives every bucket that could hold a pre-bump token: those were
|
|
118
|
+
// written earlier, so they expire no later than this key does.
|
|
119
|
+
this.getStateTtlMs(),
|
|
120
|
+
);
|
|
121
|
+
// Single-slot mode and pre-migration entries live under the bare key,
|
|
122
|
+
// which carries no epoch claim - delete it directly.
|
|
55
123
|
await this.cacheManager.del(this.getKey(userId));
|
|
56
124
|
}
|
|
57
125
|
|
|
58
|
-
async
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
//
|
|
70
|
-
// valid
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
async getEpoch(userId: number): Promise<number | undefined> {
|
|
127
|
+
return (await this.cacheManager.get<number>(this.getEpochKey(userId))) ?? undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async validateAndRotate(
|
|
131
|
+
user: any,
|
|
132
|
+
refreshToken: string,
|
|
133
|
+
claims: RefreshTokenClaims = {},
|
|
134
|
+
): Promise<RotatedRefreshToken> {
|
|
135
|
+
const { deviceKey, epoch } = claims;
|
|
136
|
+
|
|
137
|
+
// The read key comes from the token's own claim, never from a setting.
|
|
138
|
+
// A token issued under either scheme therefore stays valid when
|
|
139
|
+
// preventConcurrentLogins is toggled - in both directions - and a
|
|
140
|
+
// pre-migration token still finds the bare key it was written to.
|
|
141
|
+
const refreshTokenState = await this.cacheManager.get(
|
|
142
|
+
this.getKey(user.id, deviceKey),
|
|
143
|
+
) as RefreshTokenState | undefined;
|
|
144
|
+
|
|
145
|
+
if (!this.isRefreshTokenState(refreshTokenState)) {
|
|
146
|
+
throw new InvalidatedRefreshTokenError();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (deviceKey && !(await this.isEpochCurrent(user.id, epoch))) {
|
|
150
|
+
throw new InvalidatedRefreshTokenError();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Scenario 1: the live token. Rotate it. generateRefreshToken calls
|
|
154
|
+
// insert(), which writes the new state and stamps the grace deadline
|
|
155
|
+
// onto the token being rotated out.
|
|
156
|
+
if (refreshTokenState.currentRefreshToken === refreshToken) {
|
|
157
|
+
const nextDeviceKey = this.resolveNextDeviceKey(deviceKey);
|
|
158
|
+
const rotated = await this.authenticationService.generateRefreshToken(
|
|
159
|
+
user,
|
|
160
|
+
refreshToken,
|
|
161
|
+
nextDeviceKey,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (nextDeviceKey !== deviceKey) {
|
|
165
|
+
// The session changed buckets - either migrating out of the
|
|
166
|
+
// pre-deploy single slot, or collapsing back into it because
|
|
167
|
+
// preventConcurrentLogins was switched on. The new state is
|
|
168
|
+
// already written; drop the key it came from so nothing is
|
|
169
|
+
// left behind.
|
|
170
|
+
await this.cacheManager.del(this.getKey(user.id, deviceKey));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { refreshToken: rotated, deviceKey: nextDeviceKey };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Scenario 2: the token just rotated out. This is the concurrent-request
|
|
177
|
+
// case the previous slot exists for - a second in-flight request that
|
|
178
|
+
// was issued the old token before the first one rotated it. Hand back
|
|
179
|
+
// the live token so it succeeds, provided the grace window is still open.
|
|
180
|
+
if (refreshTokenState.previousRefreshToken && refreshTokenState.previousRefreshToken === refreshToken) {
|
|
181
|
+
if (!this.isWithinGraceWindow(refreshTokenState)) {
|
|
182
|
+
throw new InvalidatedRefreshTokenError();
|
|
114
183
|
}
|
|
184
|
+
return { refreshToken: refreshTokenState.currentRefreshToken, deviceKey };
|
|
115
185
|
}
|
|
116
186
|
|
|
187
|
+
throw new InvalidatedRefreshTokenError();
|
|
188
|
+
}
|
|
117
189
|
|
|
118
|
-
|
|
119
|
-
|
|
190
|
+
getCurrentRefreshTokenState(
|
|
191
|
+
userId: number,
|
|
192
|
+
deviceKey?: string,
|
|
193
|
+
): Promise<RefreshTokenState | undefined> {
|
|
194
|
+
return this.cacheManager.get(this.getKey(userId, deviceKey));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private getKey(userId: number, deviceKey?: string): string {
|
|
198
|
+
return deviceKey ? `user-${userId}-${deviceKey}` : `user-${userId}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Deliberately not `user-${userId}-epoch`, which a device key of the
|
|
202
|
+
// literal string "epoch" would collide with.
|
|
203
|
+
private getEpochKey(userId: number): string {
|
|
204
|
+
return `user-epoch-${userId}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Which bucket a rotated token should land in. The setting is consulted
|
|
209
|
+
* first so that turning preventConcurrentLogins ON collapses existing
|
|
210
|
+
* per-device sessions back into the single slot on their next refresh -
|
|
211
|
+
* where normal last-login-wins applies, which is what that setting means.
|
|
212
|
+
* Leaving them in their own buckets would keep concurrent sessions alive
|
|
213
|
+
* while the setting claimed to forbid them.
|
|
214
|
+
*/
|
|
215
|
+
private resolveNextDeviceKey(deviceKey?: string): string | undefined {
|
|
216
|
+
if (!this.areConcurrentLoginsAllowed()) {
|
|
217
|
+
return undefined;
|
|
120
218
|
}
|
|
219
|
+
return deviceKey ?? randomUUID();
|
|
220
|
+
}
|
|
121
221
|
|
|
122
|
-
|
|
123
|
-
|
|
222
|
+
private async isEpochCurrent(userId: number, epoch?: number): Promise<boolean> {
|
|
223
|
+
const storedEpoch = await this.getEpoch(userId);
|
|
224
|
+
// Nothing has ever been bulk-invalidated for this user - or the cache
|
|
225
|
+
// was flushed, in which case every bucket is gone and the token is
|
|
226
|
+
// already dead by the read above. Either way, accept.
|
|
227
|
+
if (storedEpoch === undefined) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
return epoch === storedEpoch;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// cache-manager v5 expects milliseconds; refreshTokenTtl is configured in
|
|
234
|
+
// seconds. Without an explicit TTL these entries never expire - neither
|
|
235
|
+
// cache path supplies a default - and the keyspace grows without bound.
|
|
236
|
+
private getStateTtlMs(): number {
|
|
237
|
+
const refreshTokenTtlSeconds = Number(
|
|
238
|
+
this.settingService.getConfigValue<SolidCoreSetting>("refreshTokenTtl"),
|
|
239
|
+
);
|
|
240
|
+
return (refreshTokenTtlSeconds + STATE_TTL_BUFFER_SECONDS) * 1000;
|
|
124
241
|
}
|
|
125
242
|
|
|
126
|
-
private
|
|
127
|
-
return
|
|
243
|
+
private isRefreshTokenState(state: unknown): state is RefreshTokenState {
|
|
244
|
+
return (
|
|
245
|
+
!!state &&
|
|
246
|
+
typeof state === 'object' &&
|
|
247
|
+
'currentRefreshToken' in state &&
|
|
248
|
+
'previousRefreshToken' in state
|
|
249
|
+
);
|
|
128
250
|
}
|
|
129
251
|
|
|
130
|
-
|
|
131
|
-
|
|
252
|
+
private isWithinGraceWindow(state: RefreshTokenState): boolean {
|
|
253
|
+
// TRANSITIONAL - safe to delete one refreshTokenTtl after this ships.
|
|
254
|
+
//
|
|
255
|
+
// Entries written before previousValidUntil existed carry no deadline
|
|
256
|
+
// to test. They are accepted so that the deploy rejects nobody: a tab
|
|
257
|
+
// that legitimately rotated moments earlier keeps working. The cost is
|
|
258
|
+
// that for such an entry the previous token stays acceptable until its
|
|
259
|
+
// own JWT `exp` rather than for 60s - which is exactly how the old code
|
|
260
|
+
// already behaved whenever its in-process timer was lost to a restart,
|
|
261
|
+
// so this is an unfixed pre-existing case, not a new one.
|
|
262
|
+
//
|
|
263
|
+
// It self-heals: the user's next rotation writes a new-format state
|
|
264
|
+
// with a deadline. Once every pre-deploy entry has rotated or expired
|
|
265
|
+
// (one refreshTokenTtl), this branch is unreachable - drop it, and the
|
|
266
|
+
// undefined case becomes a rejection.
|
|
267
|
+
if (state.previousValidUntil === undefined) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
return Date.now() < state.previousValidUntil;
|
|
132
271
|
}
|
|
133
272
|
}
|
|
@@ -16,6 +16,8 @@ import { ModuleMetadataRepository } from 'src/repository/module-metadata.reposit
|
|
|
16
16
|
import type { SolidCoreSetting } from './settings/default-settings-provider.service';
|
|
17
17
|
import { Logger } from '@nestjs/common';
|
|
18
18
|
import { EncryptionService } from './encryption.service';
|
|
19
|
+
import { sanitizeStoredMediaFileName } from './media-storage.utils';
|
|
20
|
+
import { isDangerousMediaFile } from 'src/constants/media-file-types';
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
@Injectable()
|
|
@@ -357,8 +359,13 @@ export class SettingService {
|
|
|
357
359
|
// Handle uploaded files
|
|
358
360
|
if (uploadedFiles?.length) {
|
|
359
361
|
for (const file of uploadedFiles) {
|
|
362
|
+
if (isDangerousMediaFile(file)) {
|
|
363
|
+
throw new BadRequestException('Dangerous file types are not allowed for settings.');
|
|
364
|
+
}
|
|
365
|
+
|
|
360
366
|
const settingKey = file.fieldname;
|
|
361
|
-
|
|
367
|
+
// originalname is attacker-controlled and feeds straight into a storage path.
|
|
368
|
+
const relativeFileName = `${file.filename}-${sanitizeStoredMediaFileName(file.originalname)}`;
|
|
362
369
|
|
|
363
370
|
// Read file from local disk (where Multer stores uploads) and write to storage
|
|
364
371
|
// The path builder constructs the provider-appropriate storage path
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SettingDefinition,
|
|
7
7
|
SettingLevel,
|
|
8
8
|
} from "src/interfaces";
|
|
9
|
+
import { getDefaultThemeKey, getThemesByMode } from "src/theme/theme-registry";
|
|
9
10
|
|
|
10
11
|
export const DEFAULT_MEDIA_UPLOAD_DIR = "media-uploads";
|
|
11
12
|
export const DEFAULT_MEDIA_FILE_STORAGE_DIR = "media-files-storage";
|
|
@@ -224,14 +225,44 @@ const getSolidCoreSettings = (isProd: boolean) =>
|
|
|
224
225
|
},
|
|
225
226
|
{
|
|
226
227
|
moduleName: "solid-core",
|
|
227
|
-
key: "
|
|
228
|
+
key: "showThemeToggle",
|
|
228
229
|
value: true,
|
|
229
230
|
level: SettingLevel.SystemAdminEditable,
|
|
230
|
-
label: "
|
|
231
|
+
label: "Show Theme Toggle",
|
|
231
232
|
group: "app-settings",
|
|
232
233
|
sortOrder: 120,
|
|
233
234
|
controlType: "boolean",
|
|
234
|
-
helpText: "
|
|
235
|
+
helpText: "Shows the theme toggle in the frontend so users can switch between light and dark themes.",
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
moduleName: "solid-core",
|
|
239
|
+
key: "lightTheme",
|
|
240
|
+
value: getDefaultThemeKey("light"),
|
|
241
|
+
level: SettingLevel.SystemAdminEditable,
|
|
242
|
+
label: "Light Theme",
|
|
243
|
+
group: "app-settings",
|
|
244
|
+
sortOrder: 125,
|
|
245
|
+
controlType: "selectionStatic",
|
|
246
|
+
options: getThemesByMode("light").map((theme) => ({
|
|
247
|
+
label: theme.label,
|
|
248
|
+
value: theme.key,
|
|
249
|
+
})),
|
|
250
|
+
helpText: "Selects the theme used whenever the application is in light mode.",
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
moduleName: "solid-core",
|
|
254
|
+
key: "darkTheme",
|
|
255
|
+
value: getDefaultThemeKey("dark"),
|
|
256
|
+
level: SettingLevel.SystemAdminEditable,
|
|
257
|
+
label: "Dark Theme",
|
|
258
|
+
group: "app-settings",
|
|
259
|
+
sortOrder: 126,
|
|
260
|
+
controlType: "selectionStatic",
|
|
261
|
+
options: getThemesByMode("dark").map((theme) => ({
|
|
262
|
+
label: theme.label,
|
|
263
|
+
value: theme.key,
|
|
264
|
+
})),
|
|
265
|
+
helpText: "Selects the theme used whenever the application is in dark mode.",
|
|
235
266
|
},
|
|
236
267
|
{
|
|
237
268
|
moduleName: "solid-core",
|
|
@@ -501,6 +532,20 @@ const getSolidCoreSettings = (isProd: boolean) =>
|
|
|
501
532
|
],
|
|
502
533
|
helpText: "Default storage implementation used for file handling, such as local disk or S3.",
|
|
503
534
|
},
|
|
535
|
+
{
|
|
536
|
+
moduleName: "solid-core",
|
|
537
|
+
key: "maxMediaFileSizeMb",
|
|
538
|
+
value: (() => {
|
|
539
|
+
const parsedMb = Number(process.env.AB_MEDIA_MAX_FILE_SIZE_MB);
|
|
540
|
+
return Number.isFinite(parsedMb) && parsedMb > 0 ? parsedMb : 100;
|
|
541
|
+
})(),
|
|
542
|
+
level: SettingLevel.SystemAdminReadonly,
|
|
543
|
+
label: "Max Media File Size (MB)",
|
|
544
|
+
group: "storage-settings",
|
|
545
|
+
sortOrder: 45,
|
|
546
|
+
controlType: "numeric",
|
|
547
|
+
helpText: "Global upload size limit enforced by Multer under every per-field mediaMaxSizeKb restriction. Read-only here: it's applied once at process start, so changing AB_MEDIA_MAX_FILE_SIZE_MB requires an app restart to take effect - editing this in the admin UI would not do that.",
|
|
548
|
+
},
|
|
504
549
|
|
|
505
550
|
// aws-s3-settings-provider.service.ts
|
|
506
551
|
{
|
|
@@ -1218,6 +1263,77 @@ const getSolidCoreSettings = (isProd: boolean) =>
|
|
|
1218
1263
|
sortOrder: 200,
|
|
1219
1264
|
controlType: "boolean",
|
|
1220
1265
|
},
|
|
1266
|
+
{
|
|
1267
|
+
moduleName: "solid-core",
|
|
1268
|
+
key: "mpinEnabled",
|
|
1269
|
+
value: false,
|
|
1270
|
+
level: SettingLevel.SystemAdminEditable,
|
|
1271
|
+
label: "Enable MPIN Sign-in",
|
|
1272
|
+
group: "authentication-settings",
|
|
1273
|
+
sortOrder: 210,
|
|
1274
|
+
controlType: "boolean",
|
|
1275
|
+
helpText:
|
|
1276
|
+
"Allows users to set a short device PIN for faster sign-in. Every MPIN route returns 404 while this is off.",
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
moduleName: "solid-core",
|
|
1280
|
+
key: "mpinRegex",
|
|
1281
|
+
value: process.env.IAM_MPIN_REGEX ?? "^\\d{4,6}$",
|
|
1282
|
+
level: SettingLevel.SystemAdminEditable,
|
|
1283
|
+
label: "MPIN Format",
|
|
1284
|
+
group: "authentication-settings",
|
|
1285
|
+
sortOrder: 211,
|
|
1286
|
+
controlType: "shortText",
|
|
1287
|
+
helpText:
|
|
1288
|
+
"Regular expression the MPIN must match. Sole authority on MPIN length - the default allows 4 to 6 digits. Predictable PINs (repeated or sequential digits) are rejected separately.",
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
moduleName: "solid-core",
|
|
1292
|
+
key: "mpinMaxFailedAttempts",
|
|
1293
|
+
value: parseInt(process.env.IAM_MPIN_MAX_FAILED_ATTEMPTS ?? "5", 10),
|
|
1294
|
+
level: SettingLevel.SystemAdminEditable,
|
|
1295
|
+
label: "MPIN Attempts Before Lockout",
|
|
1296
|
+
group: "authentication-settings",
|
|
1297
|
+
sortOrder: 212,
|
|
1298
|
+
controlType: "numeric",
|
|
1299
|
+
helpText:
|
|
1300
|
+
"Cumulative failed attempts that trigger a temporary lockout. The counter resets only on a successful MPIN sign-in, not when the lockout expires.",
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
moduleName: "solid-core",
|
|
1304
|
+
key: "mpinLockoutDuration",
|
|
1305
|
+
value: parseInt(process.env.IAM_MPIN_LOCKOUT_DURATION ?? "900", 10),
|
|
1306
|
+
level: SettingLevel.SystemAdminEditable,
|
|
1307
|
+
label: "MPIN Lockout Duration (seconds)",
|
|
1308
|
+
group: "authentication-settings",
|
|
1309
|
+
sortOrder: 213,
|
|
1310
|
+
controlType: "numeric",
|
|
1311
|
+
helpText: "How long the temporary lockout lasts.",
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
moduleName: "solid-core",
|
|
1315
|
+
key: "mpinMaxTotalFailedAttempts",
|
|
1316
|
+
value: parseInt(process.env.IAM_MPIN_MAX_TOTAL_FAILED_ATTEMPTS ?? "10", 10),
|
|
1317
|
+
level: SettingLevel.SystemAdminEditable,
|
|
1318
|
+
label: "MPIN Attempts Before Permanent Deactivation",
|
|
1319
|
+
group: "authentication-settings",
|
|
1320
|
+
sortOrder: 214,
|
|
1321
|
+
controlType: "numeric",
|
|
1322
|
+
helpText:
|
|
1323
|
+
"Cumulative failed attempts that permanently disable MPIN on that device. Must exceed the lockout threshold. The account itself is never locked - the user signs in normally and sets MPIN up again.",
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
moduleName: "solid-core",
|
|
1327
|
+
key: "mpinMaxDevicesPerUser",
|
|
1328
|
+
value: parseInt(process.env.IAM_MPIN_MAX_DEVICES_PER_USER ?? "5", 10),
|
|
1329
|
+
level: SettingLevel.SystemAdminEditable,
|
|
1330
|
+
label: "Max MPIN Devices Per User",
|
|
1331
|
+
group: "authentication-settings",
|
|
1332
|
+
sortOrder: 215,
|
|
1333
|
+
controlType: "numeric",
|
|
1334
|
+
helpText:
|
|
1335
|
+
"At the cap the least recently used credential is evicted rather than the setup rejected, so repeated app reinstalls cannot lock a user out of MPIN setup.",
|
|
1336
|
+
},
|
|
1221
1337
|
// queues-settings-provider.service.ts
|
|
1222
1338
|
{
|
|
1223
1339
|
moduleName: "solid-core",
|
package/src/solid-core.module.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import "multer";
|
|
2
|
-
import { Global,
|
|
3
|
-
import * as express from "express";
|
|
2
|
+
import { Global, Module } from "@nestjs/common";
|
|
4
3
|
import { ConfigModule, ConfigService } from "@nestjs/config";
|
|
5
4
|
import {
|
|
6
5
|
APP_FILTER,
|
|
@@ -98,6 +97,7 @@ import { GoogleAuthenticationController } from "./controllers/google-authenticat
|
|
|
98
97
|
import { MenuItemMetadataController } from "./controllers/menu-item-metadata.controller";
|
|
99
98
|
import { MqMessageQueueController } from "./controllers/mq-message-queue.controller";
|
|
100
99
|
import { MqMessageController } from "./controllers/mq-message.controller";
|
|
100
|
+
import { MpinAuthenticationController } from "./controllers/mpin-authentication.controller";
|
|
101
101
|
import { OTPAuthenticationController } from "./controllers/otp-authentication.controller";
|
|
102
102
|
import { ServiceController } from "./controllers/service.controller";
|
|
103
103
|
import { SmsTemplateController } from "./controllers/sms-template.controller";
|
|
@@ -167,6 +167,7 @@ import { TwilioSmsQueueSubscriberRedis } from "./jobs/redis/twilio-sms-subscribe
|
|
|
167
167
|
import { UserRegistrationListener } from "./listeners/user-registration.listener";
|
|
168
168
|
import { GoogleOauthStrategy } from "./passport-strategies/google-oauth.strategy";
|
|
169
169
|
import { ApiKeyService } from "./services/api-key.service";
|
|
170
|
+
import { MpinService } from "./services/mpin.service";
|
|
170
171
|
import { ActiveSessionStorageService } from "./services/active-session-storage.service";
|
|
171
172
|
import { AuthenticationService } from "./services/authentication.service";
|
|
172
173
|
import { MetadataValidationService } from "./services/metadata-validation.service";
|
|
@@ -247,6 +248,7 @@ import { Setting } from './entities/setting.entity';
|
|
|
247
248
|
import { UserActivityHistory } from './entities/user-activity-history.entity';
|
|
248
249
|
import { UserViewMetadata } from './entities/user-view-metadata.entity';
|
|
249
250
|
import { UserApiKey } from './entities/user-api-key.entity';
|
|
251
|
+
import { UserDeviceCredential } from './entities/user-device-credential.entity';
|
|
250
252
|
import { User } from './entities/user.entity';
|
|
251
253
|
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
|
252
254
|
import { ModelMetadataHelperService } from './helpers/model-metadata-helper.service';
|
|
@@ -319,6 +321,7 @@ import { SmsTemplateRepository } from './repository/sms-template.repository';
|
|
|
319
321
|
import { UserActivityHistoryRepository } from './repository/user-activity-history.repository';
|
|
320
322
|
import { UserViewMetadataRepository } from './repository/user-view-metadata.repository';
|
|
321
323
|
import { UserApiKeyRepository } from './repository/user-api-key.repository';
|
|
324
|
+
import { UserDeviceCredentialRepository } from './repository/user-device-credential.repository';
|
|
322
325
|
import { UserRepository } from './repository/user.repository';
|
|
323
326
|
import { ViewMetadataRepository } from './repository/view-metadata.repository';
|
|
324
327
|
import { PermissionMetadataSeederService } from './seeders/permission-metadata-seeder.service';
|
|
@@ -501,6 +504,7 @@ import { SwitchNode } from './services/workflow/nodes/switch.node';
|
|
|
501
504
|
SmsTemplate,
|
|
502
505
|
User,
|
|
503
506
|
UserApiKey,
|
|
507
|
+
UserDeviceCredential,
|
|
504
508
|
UserActivityHistory,
|
|
505
509
|
UserViewMetadata,
|
|
506
510
|
ViewMetadata,
|
|
@@ -553,6 +557,19 @@ import { SwitchNode } from './services/workflow/nodes/switch.node';
|
|
|
553
557
|
imports: [ConfigModule],
|
|
554
558
|
useFactory: async (configService: ConfigService) => ({
|
|
555
559
|
dest: process.env.AB_MEDIA_UPLOAD_DIR ?? "media-uploads",
|
|
560
|
+
// A global floor under whatever per-field mediaMaxSizeKb restrictions apply, not a
|
|
561
|
+
// replacement for them - some upload paths (e.g. chatter attachments) had no size cap
|
|
562
|
+
// enforced anywhere before this. Must stay >= the largest legitimate per-field limit
|
|
563
|
+
// already configured (chatter's messageAttachments is 102400 KB / 100MB), since Multer
|
|
564
|
+
// enforces this before any of our own field-level checks ever run. Mirrored (read-only,
|
|
565
|
+
// for visibility only - see comment there for why) as the "maxMediaFileSizeMb" setting
|
|
566
|
+
// in default-settings-provider.service.ts; keep both parsing expressions in sync.
|
|
567
|
+
limits: {
|
|
568
|
+
fileSize: (() => {
|
|
569
|
+
const parsedMb = Number(process.env.AB_MEDIA_MAX_FILE_SIZE_MB);
|
|
570
|
+
return (Number.isFinite(parsedMb) && parsedMb > 0 ? parsedMb : 100) * 1024 * 1024;
|
|
571
|
+
})(),
|
|
572
|
+
},
|
|
556
573
|
}),
|
|
557
574
|
inject: [ConfigService],
|
|
558
575
|
}),
|
|
@@ -596,6 +613,7 @@ import { SwitchNode } from './services/workflow/nodes/switch.node';
|
|
|
596
613
|
GupshupWebhookController,
|
|
597
614
|
MetaCloudWhatsappWebhookController,
|
|
598
615
|
OTPAuthenticationController,
|
|
616
|
+
MpinAuthenticationController,
|
|
599
617
|
PermissionMetadataController,
|
|
600
618
|
RoleMetadataController,
|
|
601
619
|
SavedFiltersController,
|
|
@@ -761,6 +779,7 @@ import { SwitchNode } from './services/workflow/nodes/switch.node';
|
|
|
761
779
|
ApiKeyGuard,
|
|
762
780
|
MediaSignedUrlGuard,
|
|
763
781
|
ApiKeyService,
|
|
782
|
+
MpinService,
|
|
764
783
|
ActiveSessionStorageService,
|
|
765
784
|
AuthenticationService,
|
|
766
785
|
GoogleAuthenticationController,
|
|
@@ -830,6 +849,7 @@ import { SwitchNode } from './services/workflow/nodes/switch.node';
|
|
|
830
849
|
PermissionMetadataSeederService,
|
|
831
850
|
UserService,
|
|
832
851
|
UserApiKeyRepository,
|
|
852
|
+
UserDeviceCredentialRepository,
|
|
833
853
|
UserRepository,
|
|
834
854
|
SettingService,
|
|
835
855
|
ConcatComputedFieldProvider,
|
|
@@ -1050,13 +1070,7 @@ import { SwitchNode } from './services/workflow/nodes/switch.node';
|
|
|
1050
1070
|
WorkflowExpressionService,
|
|
1051
1071
|
],
|
|
1052
1072
|
})
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
express.json({ limit: "10mb" }),
|
|
1058
|
-
express.urlencoded({ limit: "10mb", extended: true }),
|
|
1059
|
-
)
|
|
1060
|
-
.forRoutes("*");
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1073
|
+
// Body-size limits live in bootstrapSolidApp (see SolidBootstrapOptions.bodyLimit).
|
|
1074
|
+
// They cannot be applied here: module middleware is registered in registerRouter(),
|
|
1075
|
+
// which runs after Nest's own parser middleware, so anything set here never takes effect.
|
|
1076
|
+
export class SolidCoreModule {}
|