@solidxai/core 0.1.11-beta.9 → 0.1.12-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +456 -0
- package/dist/controllers/authentication.controller.d.ts +1 -1
- package/dist/controllers/chatter-message.controller.d.ts +0 -2
- package/dist/controllers/chatter-message.controller.d.ts.map +1 -1
- package/dist/controllers/chatter-message.controller.js +0 -16
- package/dist/controllers/chatter-message.controller.js.map +1 -1
- package/dist/controllers/datasource-introspection.controller.d.ts +8 -0
- package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -1
- package/dist/controllers/media.controller.js +0 -2
- package/dist/controllers/media.controller.js.map +1 -1
- package/dist/guards/access-token.guard.d.ts +5 -1
- package/dist/guards/access-token.guard.d.ts.map +1 -1
- package/dist/guards/access-token.guard.js +31 -3
- package/dist/guards/access-token.guard.js.map +1 -1
- package/dist/guards/authentication.guard.d.ts +1 -0
- package/dist/guards/authentication.guard.d.ts.map +1 -1
- package/dist/guards/authentication.guard.js +16 -1
- package/dist/guards/authentication.guard.js.map +1 -1
- package/dist/helpers/bootstrap.helper.d.ts.map +1 -1
- package/dist/helpers/bootstrap.helper.js +3 -1
- package/dist/helpers/bootstrap.helper.js.map +1 -1
- package/dist/helpers/environment.helper.d.ts +0 -1
- package/dist/helpers/environment.helper.d.ts.map +1 -1
- package/dist/helpers/environment.helper.js +0 -4
- package/dist/helpers/environment.helper.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.d.ts +1 -2
- package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js +2 -6
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/helpers/module-metadata-helper.service.js +1 -1
- package/dist/helpers/module-metadata-helper.service.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 +19 -0
- package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -1
- package/dist/helpers/solid-registry.d.ts +1 -2
- package/dist/helpers/solid-registry.d.ts.map +1 -1
- package/dist/helpers/solid-registry.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- 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/interfaces.d.ts +1 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/repository/scheduled-job.repository.d.ts +2 -2
- package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
- package/dist/repository/scheduled-job.repository.js +3 -6
- package/dist/repository/scheduled-job.repository.js.map +1 -1
- package/dist/repository/security-rule.repository.d.ts +2 -2
- package/dist/repository/security-rule.repository.d.ts.map +1 -1
- package/dist/repository/security-rule.repository.js +3 -6
- package/dist/repository/security-rule.repository.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +18 -1
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +62 -34
- package/dist/services/active-session-storage.service.d.ts +10 -0
- package/dist/services/active-session-storage.service.d.ts.map +1 -0
- package/dist/services/active-session-storage.service.js +41 -0
- package/dist/services/active-session-storage.service.js.map +1 -0
- package/dist/services/authentication.service.d.ts +8 -3
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +61 -16
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts +2 -0
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +25 -2
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +5 -7
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js.map +1 -1
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +7 -0
- 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 +1 -0
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/datasource-introspection.service.d.ts +10 -0
- package/dist/services/datasource-introspection.service.d.ts.map +1 -1
- package/dist/services/datasource-introspection.service.js +109 -17
- package/dist/services/datasource-introspection.service.js.map +1 -1
- package/dist/services/menu-item-metadata.service.d.ts.map +1 -1
- package/dist/services/menu-item-metadata.service.js +1 -0
- package/dist/services/menu-item-metadata.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts +2 -0
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +49 -16
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +0 -28
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/services/refresh-token-ids-storage.service.d.ts +6 -1
- package/dist/services/refresh-token-ids-storage.service.d.ts.map +1 -1
- package/dist/services/refresh-token-ids-storage.service.js +0 -2
- package/dist/services/refresh-token-ids-storage.service.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts +230 -0
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +116 -0
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/services/solid-ts-morph.service.d.ts +6 -0
- package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
- package/dist/services/solid-ts-morph.service.js +43 -0
- package/dist/services/solid-ts-morph.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +2 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +2 -8
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts +0 -8
- package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.js +3 -36
- package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.d.ts.map +1 -1
- package/dist/subscribers/created-by-updated-by.subscriber.js.map +1 -1
- package/dist/subscribers/field-metadata.subscriber.d.ts.map +1 -1
- package/dist/subscribers/field-metadata.subscriber.js.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.d.ts.map +1 -1
- package/dist/subscribers/scheduled-job.subscriber.js +2 -3
- package/dist/subscribers/scheduled-job.subscriber.js.map +1 -1
- package/dist/subscribers/security-rule.subscriber.d.ts.map +1 -1
- package/dist/subscribers/security-rule.subscriber.js +3 -5
- package/dist/subscribers/security-rule.subscriber.js.map +1 -1
- package/package.json +1 -1
- package/src/controllers/chatter-message.controller.ts +7 -7
- package/src/controllers/media.controller.ts +1 -1
- package/src/guards/access-token.guard.ts +43 -3
- package/src/guards/authentication.guard.ts +22 -1
- package/src/helpers/bootstrap.helper.ts +9 -3
- package/src/helpers/environment.helper.ts +0 -11
- package/src/helpers/model-metadata-helper.service.ts +2 -7
- package/src/helpers/module-metadata-helper.service.ts +1 -1
- package/src/helpers/solid-core-error-codes-provider.service.ts +20 -1
- package/src/helpers/solid-registry.ts +1 -7
- package/src/index.ts +1 -1
- package/src/interfaces/active-user-data.interface.ts +6 -0
- package/src/interfaces.ts +1 -0
- package/src/repository/scheduled-job.repository.ts +4 -10
- package/src/repository/security-rule.repository.ts +4 -10
- package/src/seeders/module-metadata-seeder.service.ts +23 -2
- package/src/seeders/seed-data/solid-core-metadata.json +62 -34
- package/src/services/active-session-storage.service.ts +24 -0
- package/src/services/authentication.service.ts +76 -15
- package/src/services/chatter-message.service.ts +32 -2
- package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +5 -11
- package/src/services/crud-helper.service.ts +7 -0
- package/src/services/crud.service.ts +1 -0
- package/src/services/datasource-introspection.service.ts +176 -16
- package/src/services/menu-item-metadata.service.ts +2 -1
- package/src/services/model-metadata.service.ts +63 -22
- package/src/services/module-metadata.service.ts +0 -36
- package/src/services/refresh-token-ids-storage.service.ts +9 -8
- package/src/services/settings/default-settings-provider.service.ts +116 -1
- package/src/services/solid-ts-morph.service.ts +61 -1
- package/src/solid-core.module.ts +2 -0
- package/src/subscribers/audit.subscriber.ts +2 -11
- package/src/subscribers/computed-entity-field.subscriber.ts +6 -63
- package/src/subscribers/created-by-updated-by.subscriber.ts +0 -3
- package/src/subscribers/field-metadata.subscriber.ts +0 -3
- package/src/subscribers/scheduled-job.subscriber.ts +2 -3
- package/src/subscribers/security-rule.subscriber.ts +3 -8
- package/dist/helpers/nodemon-heartbeat.d.ts +0 -5
- package/dist/helpers/nodemon-heartbeat.d.ts.map +0 -1
- package/dist/helpers/nodemon-heartbeat.js +0 -67
- package/dist/helpers/nodemon-heartbeat.js.map +0 -1
- package/src/helpers/nodemon-heartbeat.ts +0 -59
|
@@ -2,23 +2,26 @@ import type { SolidCoreSetting } from "src/services/settings/default-settings-pr
|
|
|
2
2
|
import {
|
|
3
3
|
CanActivate,
|
|
4
4
|
ExecutionContext,
|
|
5
|
-
Inject,
|
|
6
5
|
Injectable,
|
|
7
6
|
UnauthorizedException,
|
|
8
7
|
} from '@nestjs/common';
|
|
9
8
|
import { JwtService } from '@nestjs/jwt';
|
|
10
9
|
import { Request } from 'express';
|
|
10
|
+
import { ERROR_MESSAGES } from "src/constants/error-messages";
|
|
11
11
|
import { ActiveUserData } from '../interfaces/active-user-data.interface';
|
|
12
12
|
import { REQUEST_USER_KEY } from "../constants";
|
|
13
13
|
import { PermissionMetadataService } from '../services/permission-metadata.service';
|
|
14
14
|
import { ClsService } from 'nestjs-cls';
|
|
15
|
+
import { ActiveSessionStorageService } from "../services/active-session-storage.service";
|
|
15
16
|
import { SettingService } from '../services/setting.service';
|
|
17
|
+
import { createHash } from "crypto";
|
|
16
18
|
|
|
17
19
|
@Injectable()
|
|
18
20
|
export class AccessTokenGuard implements CanActivate {
|
|
19
21
|
constructor(
|
|
20
22
|
private readonly jwtService: JwtService,
|
|
21
23
|
private readonly permissionsService: PermissionMetadataService,
|
|
24
|
+
private readonly activeSessionStorage: ActiveSessionStorageService,
|
|
22
25
|
private readonly settingService: SettingService,
|
|
23
26
|
private readonly cls: ClsService
|
|
24
27
|
) { }
|
|
@@ -44,6 +47,9 @@ export class AccessTokenGuard implements CanActivate {
|
|
|
44
47
|
jwtConfiguration
|
|
45
48
|
);
|
|
46
49
|
|
|
50
|
+
// Prevent Concurrent Login Feature
|
|
51
|
+
await this.validateConcurrentLoginSession(payload, token);
|
|
52
|
+
|
|
47
53
|
// Load permissions given the user.
|
|
48
54
|
const permissions = await this.permissionsService.findAllUsingRoles(payload.roles);
|
|
49
55
|
payload.permissions = permissions.map((permission) => permission.name);
|
|
@@ -52,8 +58,10 @@ export class AccessTokenGuard implements CanActivate {
|
|
|
52
58
|
this.cls.set(REQUEST_USER_KEY, payload);
|
|
53
59
|
// console.log(`About to set payload in the request user key:`);
|
|
54
60
|
// console.log(payload);
|
|
55
|
-
} catch {
|
|
56
|
-
throw
|
|
61
|
+
} catch (err) {
|
|
62
|
+
throw err instanceof UnauthorizedException
|
|
63
|
+
? err
|
|
64
|
+
: new UnauthorizedException();
|
|
57
65
|
}
|
|
58
66
|
return true;
|
|
59
67
|
}
|
|
@@ -64,4 +72,36 @@ export class AccessTokenGuard implements CanActivate {
|
|
|
64
72
|
const [_, token] = request.headers.authorization?.split(' ') ?? [];
|
|
65
73
|
return token;
|
|
66
74
|
}
|
|
75
|
+
|
|
76
|
+
private resolveSessionId(payload: ActiveUserData, token: string): string {
|
|
77
|
+
if (payload.sessionId) {
|
|
78
|
+
return payload.sessionId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Legacy tokens (issued before preventConcurrentLogins was enabled)
|
|
82
|
+
// have no sessionId claim, so derive a stable fallback from the token.
|
|
83
|
+
return createHash('sha256').update(token).digest('hex');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async validateConcurrentLoginSession(payload: ActiveUserData, token: string,): Promise<void> {
|
|
87
|
+
if (!this.settingService.getConfigValue<SolidCoreSetting>("preventConcurrentLogins",)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const activeSessionId = await this.activeSessionStorage.getActiveSession(payload.sub);
|
|
92
|
+
const currentSessionId = this.resolveSessionId(payload, token);
|
|
93
|
+
|
|
94
|
+
if (!activeSessionId) {
|
|
95
|
+
// No active session recorded yet for this user — this is the first
|
|
96
|
+
// request we've seen since the feature was enabled (or storage was
|
|
97
|
+
// cleared). Adopt this request's session as the active one so this
|
|
98
|
+
// browser stays logged in going forward.
|
|
99
|
+
await this.activeSessionStorage.setActiveSession(payload.sub, currentSessionId,);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (currentSessionId !== activeSessionId) {
|
|
104
|
+
throw new UnauthorizedException(ERROR_MESSAGES.SESSION_INVALID);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
67
107
|
}
|
|
@@ -32,6 +32,22 @@ export class AuthenticationGuard implements CanActivate {
|
|
|
32
32
|
private readonly cls: ClsService,
|
|
33
33
|
) { }
|
|
34
34
|
|
|
35
|
+
private isGenericUnauthorizedError(error: unknown): boolean {
|
|
36
|
+
if (!(error instanceof UnauthorizedException)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const response = error.getResponse();
|
|
41
|
+
const message =
|
|
42
|
+
typeof response === 'string'
|
|
43
|
+
? response
|
|
44
|
+
: Array.isArray((response as any)?.message)
|
|
45
|
+
? (response as any).message[0]
|
|
46
|
+
: (response as any)?.message;
|
|
47
|
+
|
|
48
|
+
return !message || message === 'Unauthorized';
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
36
52
|
// If method marked as public, then we return with true, else go ahead and apply the access token guard.
|
|
37
53
|
const contextLog = context.getHandler();
|
|
@@ -72,7 +88,12 @@ export class AuthenticationGuard implements CanActivate {
|
|
|
72
88
|
const canActivate = await Promise.resolve(
|
|
73
89
|
instance.canActivate(context),
|
|
74
90
|
).catch((err) => {
|
|
75
|
-
|
|
91
|
+
if (
|
|
92
|
+
this.isGenericUnauthorizedError(error) ||
|
|
93
|
+
!this.isGenericUnauthorizedError(err)
|
|
94
|
+
) {
|
|
95
|
+
error = err;
|
|
96
|
+
}
|
|
76
97
|
});
|
|
77
98
|
|
|
78
99
|
if (canActivate) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ValidationPipe } from '@nestjs/common';
|
|
2
|
-
import { NestFactory } from '@nestjs/core';
|
|
1
|
+
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
|
|
2
|
+
import { NestFactory, Reflector } from '@nestjs/core';
|
|
3
3
|
import { WsAdapter } from '@nestjs/platform-ws';
|
|
4
4
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
5
5
|
import { NextFunction, Request, Response } from 'express';
|
|
@@ -169,7 +169,13 @@ export async function bootstrapSolidApp(
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
// Global interceptor
|
|
172
|
-
app.useGlobalInterceptors(
|
|
172
|
+
app.useGlobalInterceptors(
|
|
173
|
+
new ClassSerializerInterceptor(app.get(Reflector), {
|
|
174
|
+
enableImplicitConversion: true, // optional but handy
|
|
175
|
+
// groups: ['client'], // if you use groups
|
|
176
|
+
}),
|
|
177
|
+
new WrapResponseInterceptor()
|
|
178
|
+
);
|
|
173
179
|
|
|
174
180
|
// CORS
|
|
175
181
|
app.enableCors(buildDefaultCorsOptions());
|
|
@@ -13,15 +13,4 @@ export function isRedisConfigured(configService: ConfigService): boolean {
|
|
|
13
13
|
const host = configService.get<string>('REDIS_HOST');
|
|
14
14
|
const port = configService.get<string>('REDIS_PORT');
|
|
15
15
|
return host && port && isNumber(parseInt(port));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Returns true when the project is configured to use an embedded PGlite
|
|
20
|
-
* database with a single-connection pool. Several TypeORM subscriber and
|
|
21
|
-
* code-generation workarounds are only necessary (and only applied) under
|
|
22
|
-
* this constraint; on a regular multi-connection Postgres pool the original
|
|
23
|
-
* behaviour is preserved.
|
|
24
|
-
*/
|
|
25
|
-
export function isEmbeddedDb(): boolean {
|
|
26
|
-
return process.env.DEFAULT_DATABASE_DRIVER === 'pglite';
|
|
27
16
|
}
|
|
@@ -4,10 +4,8 @@ import { forwardRef, Inject, Injectable, Logger } from "@nestjs/common";
|
|
|
4
4
|
import { _ } from "lodash";
|
|
5
5
|
import { LEGACY_TABLE_FIELDS_PREFIX } from "src/entities/legacy-common.entity-with-existing-id.entity";
|
|
6
6
|
import { LegacyTableType } from "src/enums/legacy-table-type.enum";
|
|
7
|
-
import { ModelMetadata } from "src/entities/model-metadata.entity";
|
|
8
7
|
import { ModelMetadataRepository } from "src/repository/model-metadata.repository";
|
|
9
8
|
import { SolidRegistry } from "./solid-registry";
|
|
10
|
-
import { EntityManager } from "typeorm";
|
|
11
9
|
|
|
12
10
|
@Injectable()
|
|
13
11
|
export class ModelMetadataHelperService {
|
|
@@ -145,11 +143,8 @@ export class ModelMetadataHelperService {
|
|
|
145
143
|
return systemFieldsMetadata;
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
async loadFieldHierarchy(modelName: any
|
|
149
|
-
const
|
|
150
|
-
? manager.getRepository(ModelMetadata)
|
|
151
|
-
: this.modelMetadataRepo;
|
|
152
|
-
const model = await repo.findOne({
|
|
146
|
+
async loadFieldHierarchy(modelName: any) {
|
|
147
|
+
const model = await this.modelMetadataRepo.findOne({
|
|
153
148
|
where: {
|
|
154
149
|
singularName: modelName,
|
|
155
150
|
},
|
|
@@ -34,7 +34,7 @@ export class ModuleMetadataHelperService {
|
|
|
34
34
|
if (!moduleName) {
|
|
35
35
|
return '';
|
|
36
36
|
}
|
|
37
|
-
return path.resolve(process.cwd(), 'src', moduleName);
|
|
37
|
+
return path.resolve(process.cwd(), 'src', kebabCase(moduleName));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async getSolidUiModulePath(moduleName: string): Promise<string> {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/common/errors/providers/solidcore-error-code.provider.ts
|
|
2
2
|
import { Injectable } from '@nestjs/common';
|
|
3
|
+
import { ERROR_MESSAGES } from 'src/constants/error-messages';
|
|
3
4
|
import { ErrorCodeProvider } from 'src/decorators/error-codes-provider.decorator';
|
|
4
5
|
import { ErrorMeta, ErrorRule, IErrorCodeProvider } from 'src/interfaces';
|
|
5
6
|
|
|
@@ -13,6 +14,24 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
|
|
|
13
14
|
|
|
14
15
|
rules(): ReadonlyArray<ErrorRule> {
|
|
15
16
|
return [
|
|
17
|
+
{
|
|
18
|
+
code: 'solidx-session-invalid',
|
|
19
|
+
priority: 110,
|
|
20
|
+
match: (txt) => txt.includes(ERROR_MESSAGES.SESSION_INVALID.toLowerCase()),
|
|
21
|
+
meta: {
|
|
22
|
+
message: ERROR_MESSAGES.SESSION_INVALID,
|
|
23
|
+
httpStatus: 401,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
code: 'solidx-session-expired',
|
|
28
|
+
priority: 110,
|
|
29
|
+
match: (txt) => txt.includes(ERROR_MESSAGES.SESSION_EXPIRED.toLowerCase()),
|
|
30
|
+
meta: {
|
|
31
|
+
message: ERROR_MESSAGES.SESSION_EXPIRED,
|
|
32
|
+
httpStatus: 401,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
16
35
|
{
|
|
17
36
|
code: 'solidx-mcp-server-unavailable',
|
|
18
37
|
priority: 100,
|
|
@@ -60,4 +79,4 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
|
|
|
60
79
|
const rule = this.rules().find((r) => r.code === code);
|
|
61
80
|
return rule?.meta;
|
|
62
81
|
}
|
|
63
|
-
}
|
|
82
|
+
}
|
|
@@ -8,7 +8,7 @@ import { Locale } from 'src/entities/locale.entity';
|
|
|
8
8
|
import { SecurityRule } from 'src/entities/security-rule.entity';
|
|
9
9
|
import { IScheduledJob } from 'src/services/scheduled-jobs/scheduled-job.interface';
|
|
10
10
|
import { IDashboardWidgetDataProvider, IErrorCodeProvider, ISecurityRuleConfigProvider, ISelectionProvider, ISelectionProviderContext, ISolidDatabaseModule } from "../interfaces";
|
|
11
|
-
import {
|
|
11
|
+
import { ObjectLiteral } from 'typeorm';
|
|
12
12
|
|
|
13
13
|
type ControllerMetadata = {
|
|
14
14
|
name: string;
|
|
@@ -49,12 +49,6 @@ export interface TypeOrmEventContext {
|
|
|
49
49
|
metadataName?: string;
|
|
50
50
|
updatedColumns?: string[];
|
|
51
51
|
updatedRelations?: string[];
|
|
52
|
-
// Transaction-bound EntityManager from the originating TypeORM event. Providers that
|
|
53
|
-
// need to query the DB during a pre-compute hook must use this (not an injected
|
|
54
|
-
// DataSource/EntityManager) so the query runs on the active transaction's connection.
|
|
55
|
-
// On single-threaded embedded engines (PGlite), a second connection mid-transaction
|
|
56
|
-
// can deadlock.
|
|
57
|
-
manager?: EntityManager;
|
|
58
52
|
}
|
|
59
53
|
|
|
60
54
|
export interface ComputedFieldMetadata<TContext = any> {
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './commands/run-tests.command'
|
|
|
8
8
|
export * from './commands/test.command'
|
|
9
9
|
|
|
10
10
|
export * from './config/cache.options'
|
|
11
|
+
export * from './services/active-session-storage.service'
|
|
11
12
|
|
|
12
13
|
export * from './decorators/active-user.decorator'
|
|
13
14
|
export * from './decorators/solid-request-context.decorator'
|
|
@@ -171,7 +172,6 @@ export * from './guards/permissions.guard'
|
|
|
171
172
|
export * from './guards/roles.guard'
|
|
172
173
|
|
|
173
174
|
export * from './helpers/command.service'
|
|
174
|
-
export * from './helpers/nodemon-heartbeat'
|
|
175
175
|
export * from './helpers/module.helper'
|
|
176
176
|
export * from './helpers/schematic.service'
|
|
177
177
|
export * from './helpers/solid-registry'
|
|
@@ -16,6 +16,12 @@ export interface ActiveUserData {
|
|
|
16
16
|
*/
|
|
17
17
|
email: string;
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Logical login session identifier used to invalidate older sessions when
|
|
21
|
+
* concurrent logins are disabled.
|
|
22
|
+
*/
|
|
23
|
+
sessionId?: string;
|
|
24
|
+
|
|
19
25
|
/**
|
|
20
26
|
* The subject's (user) roles.
|
|
21
27
|
* These are part of the JWT token, we simply decode them.
|
package/src/interfaces.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { CreateScheduledJobDto } from "src/dtos/create-scheduled-job.dto";
|
|
|
3
3
|
import { ModuleMetadata } from "src/entities/module-metadata.entity";
|
|
4
4
|
import { ScheduledJob } from "src/entities/scheduled-job.entity";
|
|
5
5
|
import { RequestContextService } from "src/services/request-context.service";
|
|
6
|
-
import { DataSource
|
|
6
|
+
import { DataSource } from "typeorm";
|
|
7
7
|
import { SecurityRuleRepository } from "./security-rule.repository";
|
|
8
8
|
import { SolidBaseRepository } from "./solid-base.repository";
|
|
9
9
|
|
|
@@ -101,25 +101,19 @@ export class ScheduledJobRepository extends SolidBaseRepository<ScheduledJob> {
|
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
103
|
* Converts an entity to a plain DTO object.
|
|
104
|
-
* When manager is provided (e.g. from a TypeORM subscriber event), the
|
|
105
|
-
* fallback relation load uses it so the query runs on the active
|
|
106
|
-
* transaction's connection instead of the default DataSource pool.
|
|
107
104
|
*/
|
|
108
|
-
async toDto(scheduledJob: ScheduledJob
|
|
105
|
+
async toDto(scheduledJob: ScheduledJob): Promise<CreateScheduledJobDto> {
|
|
109
106
|
let populatedScheduledJob: ScheduledJob = scheduledJob;
|
|
110
107
|
// If the scheduled job does not have the module relation loaded, load it
|
|
111
108
|
if (!scheduledJob.module) {
|
|
112
|
-
|
|
113
|
-
? manager.getRepository(ScheduledJob)
|
|
114
|
-
: this;
|
|
115
|
-
populatedScheduledJob = await repo.findOne({
|
|
109
|
+
populatedScheduledJob = await this.findOne({
|
|
116
110
|
where: {
|
|
117
111
|
id: scheduledJob.id,
|
|
118
112
|
},
|
|
119
113
|
relations: {
|
|
120
114
|
module: true,
|
|
121
115
|
},
|
|
122
|
-
})
|
|
116
|
+
});
|
|
123
117
|
}
|
|
124
118
|
|
|
125
119
|
if (!populatedScheduledJob.module) {
|
|
@@ -9,7 +9,7 @@ import { SecurityRule } from 'src/entities/security-rule.entity';
|
|
|
9
9
|
import { SolidRegistry } from 'src/helpers/solid-registry';
|
|
10
10
|
import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
|
|
11
11
|
import { CrudHelperService } from 'src/services/crud-helper.service';
|
|
12
|
-
import { Brackets, DataSource,
|
|
12
|
+
import { Brackets, DataSource, SelectQueryBuilder } from 'typeorm';
|
|
13
13
|
import { SolidBaseRepository } from './solid-base.repository';
|
|
14
14
|
|
|
15
15
|
@Injectable()
|
|
@@ -84,18 +84,12 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
|
|
|
84
84
|
return configString.replace('$activeUserId', activeUser.sub.toString());
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
async toDto(securityRule: SecurityRule
|
|
87
|
+
async toDto(securityRule: SecurityRule): Promise<UpdateSecurityRuleDto> {
|
|
88
88
|
// load the role and model relations for the security rule
|
|
89
89
|
let populatedSecurityRule: SecurityRule = securityRule;
|
|
90
90
|
// If the security rule does not have the role and model relations loaded, load them
|
|
91
91
|
if (!securityRule.role || !securityRule.modelMetadata) {
|
|
92
|
-
|
|
93
|
-
// use it so the query runs on the active transaction's connection
|
|
94
|
-
// instead of trying to acquire a second connection from the pool.
|
|
95
|
-
const repo = manager
|
|
96
|
-
? manager.getRepository(SecurityRule)
|
|
97
|
-
: this;
|
|
98
|
-
populatedSecurityRule = await repo.findOne({
|
|
92
|
+
populatedSecurityRule = await this.findOne({
|
|
99
93
|
where: {
|
|
100
94
|
id: securityRule.id,
|
|
101
95
|
},
|
|
@@ -103,7 +97,7 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
|
|
|
103
97
|
role: true,
|
|
104
98
|
modelMetadata: true,
|
|
105
99
|
},
|
|
106
|
-
})
|
|
100
|
+
});
|
|
107
101
|
}
|
|
108
102
|
|
|
109
103
|
return {
|
|
@@ -1536,8 +1536,9 @@ export class ModuleMetadataSeederService {
|
|
|
1536
1536
|
const { fields: fieldsMetadata, ...modelMetaDataWithoutFields } = modelMetadata;
|
|
1537
1537
|
|
|
1538
1538
|
// Load and set the parent model if it exists.
|
|
1539
|
+
let parentModel: ModelMetadata | null = null;
|
|
1539
1540
|
if (modelMetadata.isChild && modelMetadata.parentModelUserKey) {
|
|
1540
|
-
|
|
1541
|
+
parentModel = await this.getModelByUserKeyCached(modelMetadata.parentModelUserKey, {
|
|
1541
1542
|
moduleName: moduleMetadata.name,
|
|
1542
1543
|
component: 'module-model-fields',
|
|
1543
1544
|
details: `parentModel=${modelMetadata.parentModelUserKey}`,
|
|
@@ -1605,7 +1606,27 @@ export class ModuleMetadataSeederService {
|
|
|
1605
1606
|
}
|
|
1606
1607
|
}
|
|
1607
1608
|
|
|
1608
|
-
//
|
|
1609
|
+
// If userKeyField wasn't found among this model's own fields (e.g. STI "extension" models
|
|
1610
|
+
// whose userKeyField is a column inherited from the parent entity), look it up on the
|
|
1611
|
+
// parent model by the same name.
|
|
1612
|
+
if (!userKeyField && modelMetadata.isChild && parentModel && userKeyFieldName) {
|
|
1613
|
+
const inheritedUserKeyField = await this.timeOperation('field-find-inherited-user-key', () => fieldMetadataRepo.findOne({
|
|
1614
|
+
where: {
|
|
1615
|
+
model: { id: parentModel.id },
|
|
1616
|
+
name: userKeyFieldName,
|
|
1617
|
+
},
|
|
1618
|
+
}), {
|
|
1619
|
+
moduleName: moduleMetadata.name,
|
|
1620
|
+
component: 'module-model-fields',
|
|
1621
|
+
serviceCall: 'fieldMetadataRepo.findOne',
|
|
1622
|
+
details: `model=${modelMetadata.singularName} parentModel=${modelMetadata.parentModelUserKey} field=${userKeyFieldName}`,
|
|
1623
|
+
});
|
|
1624
|
+
if (inheritedUserKeyField) {
|
|
1625
|
+
userKeyField = inheritedUserKeyField;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// Now that we have created fields & model update the model to stamp the userKeyField.
|
|
1609
1630
|
if (userKeyField) {
|
|
1610
1631
|
modelMetaDataWithoutFields['userKeyField'] = userKeyField;
|
|
1611
1632
|
await this.timeOperation('model-user-key-field-upsert', () => this.modelMetadataService.upsert(modelMetaDataWithoutFields), {
|