@solidxai/core 0.1.8-beta.1 → 0.1.8-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/authentication.controller.d.ts +32 -2
- package/dist/controllers/authentication.controller.d.ts.map +1 -1
- package/dist/controllers/authentication.controller.js +80 -3
- package/dist/controllers/authentication.controller.js.map +1 -1
- package/dist/dtos/create-api-key.dto.d.ts +5 -0
- package/dist/dtos/create-api-key.dto.d.ts.map +1 -0
- package/dist/dtos/create-api-key.dto.js +34 -0
- package/dist/dtos/create-api-key.dto.js.map +1 -0
- package/dist/dtos/post-chatter-message.dto.d.ts +1 -0
- package/dist/dtos/post-chatter-message.dto.d.ts.map +1 -1
- package/dist/dtos/post-chatter-message.dto.js +6 -1
- package/dist/dtos/post-chatter-message.dto.js.map +1 -1
- package/dist/dtos/register-private.dto.d.ts +3 -5
- package/dist/dtos/register-private.dto.d.ts.map +1 -1
- package/dist/dtos/register-private.dto.js +6 -18
- package/dist/dtos/register-private.dto.js.map +1 -1
- package/dist/dtos/sso-exchange.dto.d.ts +4 -0
- package/dist/dtos/sso-exchange.dto.d.ts.map +1 -0
- package/dist/dtos/sso-exchange.dto.js +26 -0
- package/dist/dtos/sso-exchange.dto.js.map +1 -0
- package/dist/dtos/update-api-key.dto.d.ts +4 -0
- package/dist/dtos/update-api-key.dto.d.ts.map +1 -0
- package/dist/dtos/update-api-key.dto.js +28 -0
- package/dist/dtos/update-api-key.dto.js.map +1 -0
- package/dist/entities/agent-event.entity.d.ts +3 -12
- package/dist/entities/agent-event.entity.d.ts.map +1 -1
- package/dist/entities/agent-event.entity.js +21 -46
- package/dist/entities/agent-event.entity.js.map +1 -1
- package/dist/entities/agent-session.entity.d.ts +2 -11
- package/dist/entities/agent-session.entity.d.ts.map +1 -1
- package/dist/entities/agent-session.entity.js +15 -40
- package/dist/entities/agent-session.entity.js.map +1 -1
- package/dist/entities/field-metadata.entity.js +1 -1
- package/dist/entities/field-metadata.entity.js.map +1 -1
- package/dist/entities/legacy-common.entity.d.ts +9 -9
- package/dist/entities/legacy-common.entity.d.ts.map +1 -1
- package/dist/entities/legacy-common.entity.js +7 -7
- package/dist/entities/legacy-common.entity.js.map +1 -1
- package/dist/entities/sms-template.entity.d.ts.map +1 -1
- package/dist/entities/sms-template.entity.js +2 -1
- package/dist/entities/sms-template.entity.js.map +1 -1
- package/dist/entities/user-api-key.entity.d.ts +12 -0
- package/dist/entities/user-api-key.entity.d.ts.map +1 -0
- package/dist/entities/user-api-key.entity.js +62 -0
- package/dist/entities/user-api-key.entity.js.map +1 -0
- package/dist/entities/user.entity.d.ts +3 -0
- package/dist/entities/user.entity.d.ts.map +1 -1
- package/dist/entities/user.entity.js +12 -1
- package/dist/entities/user.entity.js.map +1 -1
- package/dist/enums/auth-type.enum.d.ts +2 -1
- package/dist/enums/auth-type.enum.d.ts.map +1 -1
- package/dist/enums/auth-type.enum.js +2 -1
- package/dist/enums/auth-type.enum.js.map +1 -1
- package/dist/guards/api-key.guard.d.ts +11 -0
- package/dist/guards/api-key.guard.d.ts.map +1 -0
- package/dist/guards/api-key.guard.js +43 -0
- package/dist/guards/api-key.guard.js.map +1 -0
- package/dist/guards/authentication.guard.d.ts +4 -2
- package/dist/guards/authentication.guard.d.ts.map +1 -1
- package/dist/guards/authentication.guard.js +7 -3
- 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 +12 -1
- package/dist/helpers/bootstrap.helper.js.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.js +15 -6
- package/dist/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/repository/user-api-key.repository.d.ts +12 -0
- package/dist/repository/user-api-key.repository.d.ts.map +1 -0
- package/dist/repository/user-api-key.repository.js +34 -0
- package/dist/repository/user-api-key.repository.js.map +1 -0
- package/dist/seeders/module-test-data.service.d.ts +5 -0
- package/dist/seeders/module-test-data.service.d.ts.map +1 -1
- package/dist/seeders/module-test-data.service.js +131 -4
- package/dist/seeders/module-test-data.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +287 -197
- package/dist/services/api-key.service.d.ts +20 -0
- package/dist/services/api-key.service.d.ts.map +1 -0
- package/dist/services/api-key.service.js +98 -0
- package/dist/services/api-key.service.js.map +1 -0
- package/dist/services/authentication.service.d.ts +19 -1
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +31 -5
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +6 -0
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/export-transaction.service.d.ts.map +1 -1
- package/dist/services/export-transaction.service.js +0 -23
- package/dist/services/export-transaction.service.js.map +1 -1
- package/dist/services/field-metadata.service.d.ts +1 -3
- package/dist/services/field-metadata.service.d.ts.map +1 -1
- package/dist/services/field-metadata.service.js +6 -13
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/file/disk-file.service.d.ts +1 -0
- package/dist/services/file/disk-file.service.d.ts.map +1 -1
- package/dist/services/file/disk-file.service.js +11 -3
- package/dist/services/file/disk-file.service.js.map +1 -1
- package/dist/services/media.service.d.ts +0 -1
- package/dist/services/media.service.d.ts.map +1 -1
- package/dist/services/media.service.js +10 -11
- package/dist/services/media.service.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +5 -2
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/services/sso-code-storage.service.d.ts +15 -0
- package/dist/services/sso-code-storage.service.d.ts.map +1 -0
- package/dist/services/sso-code-storage.service.js +47 -0
- package/dist/services/sso-code-storage.service.js.map +1 -0
- package/dist/services/user.service.d.ts.map +1 -1
- package/dist/services/user.service.js +3 -2
- package/dist/services/user.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +10 -0
- package/dist/solid-core.module.js.map +1 -1
- package/package.json +1 -1
- package/src/controllers/authentication.controller.ts +59 -3
- package/src/dtos/create-api-key.dto.ts +14 -0
- package/src/dtos/post-chatter-message.dto.ts +4 -0
- package/src/dtos/register-private.dto.ts +5 -14
- package/src/dtos/sso-exchange.dto.ts +7 -0
- package/src/dtos/update-api-key.dto.ts +9 -0
- package/src/entities/agent-event.entity.ts +21 -55
- package/src/entities/agent-session.entity.ts +15 -47
- package/src/entities/field-metadata.entity.ts +1 -1
- package/src/entities/legacy-common.entity.ts +15 -15
- package/src/entities/sms-template.entity.ts +3 -2
- package/src/entities/user-api-key.entity.ts +37 -0
- package/src/entities/user.entity.ts +8 -0
- package/src/enums/auth-type.enum.ts +1 -0
- package/src/guards/api-key.guard.ts +32 -0
- package/src/guards/authentication.guard.ts +6 -3
- package/src/helpers/bootstrap.helper.ts +16 -1
- package/src/helpers/field-crud-managers/SelectionDynamicFieldCrudManager.ts +17 -6
- package/src/helpers/typeorm-db-helper.ts +11 -0
- package/src/index.ts +1 -0
- package/src/repository/user-api-key.repository.ts +17 -0
- package/src/seeders/module-test-data.service.ts +165 -6
- package/src/seeders/seed-data/solid-core-metadata.json +287 -197
- package/src/services/api-key.service.ts +111 -0
- package/src/services/authentication.service.ts +35 -3
- package/src/services/chatter-message.service.ts +7 -0
- package/src/services/export-transaction.service.ts +0 -26
- package/src/services/field-metadata.service.ts +5 -12
- package/src/services/file/disk-file.service.ts +15 -7
- package/src/services/media.service.ts +12 -51
- package/src/services/settings/default-settings-provider.service.ts +5 -2
- package/src/services/sso-code-storage.service.ts +36 -0
- package/src/services/user.service.ts +3 -2
- package/src/solid-core.module.ts +10 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ForbiddenException,
|
|
3
|
+
Injectable,
|
|
4
|
+
Logger,
|
|
5
|
+
NotFoundException,
|
|
6
|
+
UnauthorizedException,
|
|
7
|
+
} from '@nestjs/common';
|
|
8
|
+
import { createHash, randomBytes } from 'crypto';
|
|
9
|
+
import { CreateApiKeyDto } from 'src/dtos/create-api-key.dto';
|
|
10
|
+
import { UpdateApiKeyDto } from 'src/dtos/update-api-key.dto';
|
|
11
|
+
import { UserApiKey } from 'src/entities/user-api-key.entity';
|
|
12
|
+
import { User } from 'src/entities/user.entity';
|
|
13
|
+
import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
|
|
14
|
+
import { UserApiKeyRepository } from 'src/repository/user-api-key.repository';
|
|
15
|
+
import { PermissionMetadataService } from 'src/services/permission-metadata.service';
|
|
16
|
+
|
|
17
|
+
@Injectable()
|
|
18
|
+
export class ApiKeyService {
|
|
19
|
+
private readonly logger = new Logger(ApiKeyService.name);
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly apiKeyRepository: UserApiKeyRepository,
|
|
23
|
+
private readonly permissionMetadataService: PermissionMetadataService,
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
async generate(userId: number, dto: CreateApiKeyDto): Promise<{ apiKey: string; record: UserApiKey }> {
|
|
27
|
+
const user = await this.apiKeyRepository.manager.findOne(User, {
|
|
28
|
+
where: { id: userId },
|
|
29
|
+
select: ['id', 'isAllowedToGenerateApiKeys'],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!user?.isAllowedToGenerateApiKeys) {
|
|
33
|
+
throw new ForbiddenException('You are not allowed to generate API keys');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const rawKey = 'sldx_' + randomBytes(32).toString('hex');
|
|
37
|
+
const hashedKey = this.hash(rawKey);
|
|
38
|
+
const maskedKey = 'sldx_****' + rawKey.slice(-4);
|
|
39
|
+
|
|
40
|
+
const record = this.apiKeyRepository.create({
|
|
41
|
+
name: dto.name,
|
|
42
|
+
hashedKey,
|
|
43
|
+
maskedKey,
|
|
44
|
+
isActive: true,
|
|
45
|
+
expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : null,
|
|
46
|
+
user,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await this.apiKeyRepository.save(record);
|
|
50
|
+
|
|
51
|
+
// Strip hashedKey from the returned record — maskedKey is all the UI needs
|
|
52
|
+
delete (record as any).hashedKey;
|
|
53
|
+
|
|
54
|
+
return { apiKey: rawKey, record };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async validate(rawKey: string): Promise<ActiveUserData> {
|
|
58
|
+
const hashedKey = this.hash(rawKey);
|
|
59
|
+
|
|
60
|
+
// Bypass security rules for auth validation — must find the key regardless of caller context
|
|
61
|
+
const keyRecord = await this.apiKeyRepository.findOne({
|
|
62
|
+
where: { hashedKey, isActive: true },
|
|
63
|
+
relations: ['user', 'user.roles'],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!keyRecord) {
|
|
67
|
+
throw new UnauthorizedException();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (keyRecord.expiresAt && keyRecord.expiresAt < new Date()) {
|
|
71
|
+
throw new UnauthorizedException('API key expired');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Fire-and-forget — does not need security rule context
|
|
75
|
+
this.apiKeyRepository.update(keyRecord.id, { lastUsedAt: new Date() }).catch((err) => {
|
|
76
|
+
this.logger.warn(`Failed to update lastUsedAt for key ${keyRecord.id}: ${err.message}`);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const roles = (keyRecord.user.roles ?? []).map((r) => r.name);
|
|
80
|
+
const permissions = await this.permissionMetadataService.findAllUsingRoles(roles);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
sub: keyRecord.user.id,
|
|
84
|
+
username: keyRecord.user.username,
|
|
85
|
+
email: keyRecord.user.email,
|
|
86
|
+
roles,
|
|
87
|
+
permissions: permissions.map((p) => p.name),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async updateKey(id: number, userId: number, dto: UpdateApiKeyDto): Promise<void> {
|
|
92
|
+
const keyRecord = await this.apiKeyRepository.findOne({
|
|
93
|
+
where: { id, user: { id: userId } },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (!keyRecord) {
|
|
97
|
+
throw new NotFoundException('API key not found');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await this.apiKeyRepository.manager
|
|
101
|
+
.createQueryBuilder()
|
|
102
|
+
.update(UserApiKey)
|
|
103
|
+
.set({ isActive: dto.isActive })
|
|
104
|
+
.where('id = :id', { id })
|
|
105
|
+
.execute();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private hash(rawKey: string): string {
|
|
109
|
+
return createHash('sha256').update(rawKey).digest('hex');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -41,6 +41,7 @@ import { EventDetails, EventType } from "../interfaces";
|
|
|
41
41
|
import { ActiveUserData } from '../interfaces/active-user-data.interface';
|
|
42
42
|
import { HashingService } from './hashing.service';
|
|
43
43
|
import { InvalidatedRefreshTokenError, RefreshTokenIdsStorageService } from './refresh-token-ids-storage.service';
|
|
44
|
+
import { SsoCodeStorageService } from './sso-code-storage.service';
|
|
44
45
|
import { RoleMetadataService } from './role-metadata.service';
|
|
45
46
|
import { SettingService } from './setting.service';
|
|
46
47
|
import { UserActivityHistoryService } from './user-activity-history.service';
|
|
@@ -78,6 +79,7 @@ export class AuthenticationService {
|
|
|
78
79
|
private readonly settingService: SettingService,
|
|
79
80
|
private readonly roleMetadataService: RoleMetadataService,
|
|
80
81
|
private readonly userActivityHistoryService: UserActivityHistoryService,
|
|
82
|
+
private readonly ssoCodeStorage: SsoCodeStorageService,
|
|
81
83
|
|
|
82
84
|
@InjectDataSource()
|
|
83
85
|
private readonly dataSource: DataSource,
|
|
@@ -152,6 +154,10 @@ export class AuthenticationService {
|
|
|
152
154
|
const defaultRole = this.settingService.getConfigValue<SolidCoreSetting>('defaultRole');
|
|
153
155
|
|
|
154
156
|
var { user, pwd, autoGeneratedPwd } = await this.populateForSignup(new User(), signUpDto, activateUserOnRegistration, onForcePasswordChange);
|
|
157
|
+
const privateDto = signUpDto as { isAllowedToGenerateApiKeys?: boolean };
|
|
158
|
+
if (privateDto.isAllowedToGenerateApiKeys !== undefined) {
|
|
159
|
+
user.isAllowedToGenerateApiKeys = privateDto.isAllowedToGenerateApiKeys;
|
|
160
|
+
}
|
|
155
161
|
const savedUser = await this.userRepository.save(user);
|
|
156
162
|
// Also assign a default role to the newly created user.
|
|
157
163
|
const userRoles = signUpDto.roles ?? [];
|
|
@@ -879,11 +885,15 @@ export class AuthenticationService {
|
|
|
879
885
|
}
|
|
880
886
|
}
|
|
881
887
|
|
|
882
|
-
private
|
|
883
|
-
const { accessToken, refreshToken } = await this.generateTokens(user);
|
|
888
|
+
private buildUserPayload(user: User) {
|
|
884
889
|
const { id, username, email, mobile, lastLoginProvider } = user;
|
|
885
890
|
const roles = user.roles.map((role) => role.name);
|
|
886
|
-
return {
|
|
891
|
+
return { id, username, email, mobile, lastLoginProvider, roles };
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
private async buildLoginTokenResponse(user: User) {
|
|
895
|
+
const { accessToken, refreshToken } = await this.generateTokens(user);
|
|
896
|
+
return { accessToken, refreshToken, user: this.buildUserPayload(user) };
|
|
887
897
|
}
|
|
888
898
|
|
|
889
899
|
async changePassword(changePasswordDto: ChangePasswordDto, activeUser: ActiveUserData) {
|
|
@@ -1414,6 +1424,28 @@ export class AuthenticationService {
|
|
|
1414
1424
|
return response;
|
|
1415
1425
|
}
|
|
1416
1426
|
|
|
1427
|
+
async generateSsoCode(activeUser: ActiveUserData, rawAccessToken: string): Promise<{ ssoCode: string }> {
|
|
1428
|
+
const refreshTokenState = await this.refreshTokenIdsStorage.getCurrentRefreshTokenState(activeUser.sub);
|
|
1429
|
+
if (!refreshTokenState?.currentRefreshToken) {
|
|
1430
|
+
throw new UnauthorizedException('No active session found');
|
|
1431
|
+
}
|
|
1432
|
+
const ssoCode = await this.ssoCodeStorage.generateCode(
|
|
1433
|
+
activeUser.sub,
|
|
1434
|
+
rawAccessToken,
|
|
1435
|
+
refreshTokenState.currentRefreshToken,
|
|
1436
|
+
);
|
|
1437
|
+
return { ssoCode };
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
async exchangeSsoCode(code: string) {
|
|
1441
|
+
const { userId, accessToken, refreshToken } = await this.ssoCodeStorage.consumeCode(code);
|
|
1442
|
+
const user = await this.userRepository.findOne({ where: { id: userId }, relations: { roles: true } });
|
|
1443
|
+
if (!user) {
|
|
1444
|
+
throw new UnauthorizedException('User not found');
|
|
1445
|
+
}
|
|
1446
|
+
return { accessToken, refreshToken, user: this.buildUserPayload(user) };
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1417
1449
|
}
|
|
1418
1450
|
|
|
1419
1451
|
function parseUniqueConstraintError(detail: string): string {
|
|
@@ -53,6 +53,13 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
|
|
|
53
53
|
chatterMessage.messageBody = postDto.messageBody;
|
|
54
54
|
chatterMessage.coModelEntityId = postDto.coModelEntityId;
|
|
55
55
|
chatterMessage.coModelName = postDto.coModelName;
|
|
56
|
+
chatterMessage.modelUserKey = postDto.modelUserKey ?? null;
|
|
57
|
+
|
|
58
|
+
const model = await this.modelMetadataRepo.findOne({
|
|
59
|
+
where: { singularName: lowerFirst(postDto.coModelName) },
|
|
60
|
+
relations: { userKeyField: true }
|
|
61
|
+
});
|
|
62
|
+
chatterMessage.modelDisplayName = model?.displayName ?? null;
|
|
56
63
|
|
|
57
64
|
const activeUser = this.requestContextService.getActiveUser();
|
|
58
65
|
|
|
@@ -274,32 +274,6 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
// Include userKey from each related field
|
|
278
|
-
for (const [relatedFieldName, userKeyFieldName] of relatedModelsUserKeyMap.entries()) {
|
|
279
|
-
const relatedData = record[relatedFieldName];
|
|
280
|
-
const displayKey = fieldNameToDisplayName.get(relatedFieldName) ?? relatedFieldName;
|
|
281
|
-
|
|
282
|
-
if (Array.isArray(relatedData)) {
|
|
283
|
-
// For many-to-many or one-to-many
|
|
284
|
-
const values = relatedData
|
|
285
|
-
.map(item => {
|
|
286
|
-
let val = item?.[userKeyFieldName];
|
|
287
|
-
const relatedFieldMeta = modelFields.find(f => f.name === relatedFieldName);
|
|
288
|
-
if ((relatedFieldMeta?.type === 'datetime' || relatedFieldMeta?.type === 'date') && val) {
|
|
289
|
-
val = new Date(val).toISOString();
|
|
290
|
-
}
|
|
291
|
-
return val;
|
|
292
|
-
})
|
|
293
|
-
.filter(Boolean);
|
|
294
|
-
newRecord[displayKey] = values.join(', ');
|
|
295
|
-
} else if (relatedData && typeof relatedData === 'object') {
|
|
296
|
-
// For many-to-one or one-to-one
|
|
297
|
-
newRecord[relatedFieldName] = relatedData?.[userKeyFieldName] ?? null;
|
|
298
|
-
} else {
|
|
299
|
-
newRecord[displayKey] = null;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
277
|
// Include userKey from each related field (with displayName)
|
|
304
278
|
for (const [relatedFieldName, userKeyFieldName] of relatedModelsUserKeyMap.entries()) {
|
|
305
279
|
const relatedData = record[relatedFieldName];
|
|
@@ -18,7 +18,6 @@ import { ERROR_MESSAGES } from 'src/constants/error-messages';
|
|
|
18
18
|
import qs from 'qs';
|
|
19
19
|
import { ResolveS3UrlDto } from 'src/dtos/resolve-s3-url.dto';
|
|
20
20
|
import { MediaStorageProviderMetadataRepository } from 'src/repository/media-storage-provider-metadata.repository';
|
|
21
|
-
import { ConfigService } from '@nestjs/config';
|
|
22
21
|
import { S3FileService } from './file';
|
|
23
22
|
import { MediaStorageProviderMetadata } from 'src/entities/media-storage-provider-metadata.entity';
|
|
24
23
|
|
|
@@ -27,7 +26,6 @@ import { MediaStorageProviderMetadata } from 'src/entities/media-storage-provide
|
|
|
27
26
|
export class FieldMetadataService implements OnApplicationBootstrap {
|
|
28
27
|
constructor(
|
|
29
28
|
private readonly fieldMetadataRepo: FieldMetadataRepository,
|
|
30
|
-
private readonly configService: ConfigService,
|
|
31
29
|
private readonly fileService: S3FileService,
|
|
32
30
|
private readonly mediaStorageProviderMetadataRepository: MediaStorageProviderMetadataRepository,
|
|
33
31
|
|
|
@@ -1291,7 +1289,6 @@ export class FieldMetadataService implements OnApplicationBootstrap {
|
|
|
1291
1289
|
}
|
|
1292
1290
|
|
|
1293
1291
|
async resolveS3Url(resolveS3UrlDto: ResolveS3UrlDto) {
|
|
1294
|
-
let url = "";
|
|
1295
1292
|
const normalizedKey = this.normalizeS3Key(resolveS3UrlDto.s3Key);
|
|
1296
1293
|
|
|
1297
1294
|
let resolvedBucketName = resolveS3UrlDto.bucketName;
|
|
@@ -1308,14 +1305,11 @@ export class FieldMetadataService implements OnApplicationBootstrap {
|
|
|
1308
1305
|
}
|
|
1309
1306
|
this.logger.debug(`INSIDE::resolveS3Url:: resolvedBucketName: ${resolvedBucketName}`)
|
|
1310
1307
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
'S3_AWS_REGION_NAME',
|
|
1317
|
-
)}.amazonaws.com/${normalizedKey}`;
|
|
1318
|
-
}
|
|
1308
|
+
const expiryInSeconds = resolveS3UrlDto.isPrivate == "true" ? 60 * 60 : 0;
|
|
1309
|
+
const url = await this.fileService.getUrl(`${resolvedBucketName}:${normalizedKey}`, {
|
|
1310
|
+
expiresIn: expiryInSeconds,
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1319
1313
|
return { url: url }
|
|
1320
1314
|
}
|
|
1321
1315
|
|
|
@@ -1332,4 +1326,3 @@ export class FieldMetadataService implements OnApplicationBootstrap {
|
|
|
1332
1326
|
}
|
|
1333
1327
|
}
|
|
1334
1328
|
|
|
1335
|
-
|
|
@@ -44,7 +44,7 @@ export class DiskFileService implements IFileService {
|
|
|
44
44
|
async write(filePath: string, data: Buffer | string, options?: WriteOptions): Promise<string> {
|
|
45
45
|
await this.ensureDirectoryExists(filePath);
|
|
46
46
|
await fsPromises.writeFile(filePath, data);
|
|
47
|
-
return
|
|
47
|
+
return this.buildUrl(filePath);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -57,7 +57,7 @@ export class DiskFileService implements IFileService {
|
|
|
57
57
|
const writeStream = fs.createWriteStream(filePath);
|
|
58
58
|
await pipeline(stream, writeStream);
|
|
59
59
|
this.logger.debug(`File saved via stream: ${filePath}`);
|
|
60
|
-
return
|
|
60
|
+
return this.buildUrl(filePath);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
@@ -97,13 +97,10 @@ export class DiskFileService implements IFileService {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
|
-
* Get an accessible URL
|
|
101
|
-
* For disk storage, returns the file path as-is
|
|
100
|
+
* Get an accessible URL for the file
|
|
102
101
|
*/
|
|
103
102
|
async getUrl(filePath: string, options?: UrlOptions): Promise<string> {
|
|
104
|
-
|
|
105
|
-
// The caller is responsible for constructing a full URL if needed
|
|
106
|
-
return filePath;
|
|
103
|
+
return this.buildUrl(filePath);
|
|
107
104
|
}
|
|
108
105
|
|
|
109
106
|
/**
|
|
@@ -117,4 +114,15 @@ export class DiskFileService implements IFileService {
|
|
|
117
114
|
await fsPromises.mkdir(dir, { recursive: true });
|
|
118
115
|
}
|
|
119
116
|
}
|
|
117
|
+
|
|
118
|
+
private buildUrl(filePath: string): string {
|
|
119
|
+
const normalizedBaseUrl = this.baseUrl.replace(/\/+$/, '');
|
|
120
|
+
const normalizedPath = filePath.replace(/^\/+/, '');
|
|
121
|
+
|
|
122
|
+
if (!normalizedBaseUrl) {
|
|
123
|
+
return `/${normalizedPath}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return `${normalizedBaseUrl}/${normalizedPath}`;
|
|
127
|
+
}
|
|
120
128
|
}
|
|
@@ -53,51 +53,29 @@ export class MediaService extends CRUDService<Media> {
|
|
|
53
53
|
if (data.records) {
|
|
54
54
|
|
|
55
55
|
for (const media of data.records) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
media.relativeUri = this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
media.mediaStorageProviderMetadata.region
|
|
63
|
-
);
|
|
56
|
+
const mediaStorageProvider = media.mediaStorageProviderMetadata;
|
|
57
|
+
|
|
58
|
+
if (mediaStorageProvider?.type === MediaStorageProviderType.Filesystem) {
|
|
59
|
+
media.relativeUri = await this.diskFileService.getUrl(this.getFullFilePathForDisk(media.relativeUri));
|
|
60
|
+
} else if (mediaStorageProvider?.type === MediaStorageProviderType.AwsS3) {
|
|
61
|
+
media.relativeUri = await this.s3FileService.getUrl(`${mediaStorageProvider.bucketName}:${media.relativeUri}`, { region: mediaStorageProvider.region });
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
|
-
// data.records.forEach((media: Media) => {
|
|
67
|
-
// if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
|
|
68
|
-
// media.relativeUri = `${process.env.BASE_URL}/${this.getFileSysytemFullFilePath(media.relativeUri)}`;
|
|
69
|
-
// } else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
|
|
70
|
-
// media.relativeUri = this.getAwsS3FullFilePath(
|
|
71
|
-
// media.relativeUri,
|
|
72
|
-
// media.mediaStorageProviderMetadata.bucketName,
|
|
73
|
-
// media.mediaStorageProviderMetadata.region
|
|
74
|
-
// );
|
|
75
|
-
// }
|
|
76
|
-
// });
|
|
77
64
|
}
|
|
78
65
|
if (data.groupRecords) {
|
|
79
66
|
|
|
80
67
|
for (const group of data.groupRecords) {
|
|
81
68
|
for (const media of group.groupData.records) {
|
|
82
|
-
|
|
83
|
-
|
|
69
|
+
const mediaStorageProvider = media.mediaStorageProviderMetadata;
|
|
70
|
+
|
|
71
|
+
if (mediaStorageProvider?.type === MediaStorageProviderType.Filesystem) {
|
|
72
|
+
media.relativeUri = await this.diskFileService.getUrl(this.getFullFilePathForDisk(media.relativeUri));
|
|
84
73
|
}
|
|
85
|
-
else if (
|
|
86
|
-
media.relativeUri = this.
|
|
74
|
+
else if (mediaStorageProvider?.type === MediaStorageProviderType.AwsS3) {
|
|
75
|
+
media.relativeUri = await this.s3FileService.getUrl(`${mediaStorageProvider.bucketName}:${media.relativeUri}`, { region: mediaStorageProvider.region });
|
|
87
76
|
}
|
|
88
77
|
}
|
|
89
78
|
}
|
|
90
|
-
|
|
91
|
-
// data.groupRecords.forEach((group) => {
|
|
92
|
-
// group.groupData.records.forEach((media) => {
|
|
93
|
-
// if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
|
|
94
|
-
// media.relativeUri = `${process.env.BASE_URL}/${this.getFileSysytemFullFilePath(media.relativeUri)}`;
|
|
95
|
-
// }
|
|
96
|
-
// else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
|
|
97
|
-
// media.relativeUri = this.getAwsS3FullFilePath(media.relativeUri, media.mediaStorageProviderMetadata.bucketName, media.mediaStorageProviderMetadata.region);
|
|
98
|
-
// }
|
|
99
|
-
// });
|
|
100
|
-
// });
|
|
101
79
|
}
|
|
102
80
|
return data
|
|
103
81
|
}
|
|
@@ -172,24 +150,12 @@ export class MediaService extends CRUDService<Media> {
|
|
|
172
150
|
}
|
|
173
151
|
}
|
|
174
152
|
);
|
|
175
|
-
// if (media.mediaStorageProviderMetadata.type === 'filesystem') {
|
|
176
|
-
// const fileStorageProvider = new FileStorageProvider(this.configService, this.fileService, this);
|
|
177
|
-
|
|
178
|
-
// await fileStorageProvider.delete(media, media.fieldMetadata);
|
|
179
|
-
|
|
180
|
-
// } else if (media.mediaStorageProviderMetadata.type === 'aws-s3') {
|
|
181
|
-
// const fileStorageProvider = new FileS3StorageProvider(this.configService, this.fileService, this);
|
|
182
|
-
// await fileStorageProvider.delete(media, media.fieldMetadata);
|
|
183
|
-
|
|
184
|
-
// } else {
|
|
185
|
-
// }
|
|
186
153
|
const storageProviderType = media.mediaStorageProviderMetadata.type as MediaStorageProviderType;
|
|
187
154
|
const storageProvider = await getMediaStorageProvider(this.moduleRef, storageProviderType);
|
|
188
155
|
await storageProvider.delete(modelEntity, media.fieldMetadata);
|
|
189
156
|
|
|
190
157
|
return this.repo.remove(media);
|
|
191
158
|
}
|
|
192
|
-
//TODO: Move this to a app builder config
|
|
193
159
|
|
|
194
160
|
private getFullFilePathForDisk(fileName: string): string {
|
|
195
161
|
const base = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir")
|
|
@@ -200,11 +166,6 @@ export class MediaService extends CRUDService<Media> {
|
|
|
200
166
|
return `${base}/${fileName}`;
|
|
201
167
|
}
|
|
202
168
|
|
|
203
|
-
private getAwsS3FullFilePath(awsMediaurl: string, bucketName: string, regionName: string): string {
|
|
204
|
-
// https://lunarismedia.s3.ap-south-1.amazonaws.com/LUNARIS_CP_REGISTRATION_CREATIVE.jpg
|
|
205
|
-
return `https://${bucketName}.s3.${regionName}.amazonaws.com/${awsMediaurl}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
169
|
private getFileName(file: Express.Multer.File): string {
|
|
209
170
|
return `${file.filename}-${file.originalname}`;
|
|
210
171
|
}
|
|
@@ -34,8 +34,11 @@ const getSolidCoreSettings = (isProd: boolean) => ([
|
|
|
34
34
|
{ moduleName: "solid-core", key: "authScreenCenterBackgroundImage", value: null, level: SettingLevel.SystemAdminEditable },
|
|
35
35
|
{
|
|
36
36
|
moduleName: "solid-core", key: "solidXGenAiCodeBuilderConfig", value: JSON.stringify({
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
models: {
|
|
38
|
+
default: { providerKey: "", behavior: { streaming: false, custom: "" } },
|
|
39
|
+
fast: { providerKey: "", behavior: { streaming: false, custom: "" } },
|
|
40
|
+
},
|
|
41
|
+
providers: {},
|
|
39
42
|
}), level: SettingLevel.SystemAdminEditable
|
|
40
43
|
},
|
|
41
44
|
{ moduleName: "solid-core", key: "mcpEnabled", value: process.env.MCP_ENABLED || false, level: SettingLevel.SystemAdminReadonly },
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
|
2
|
+
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
3
|
+
import { Cache } from 'cache-manager';
|
|
4
|
+
import { randomBytes } from 'crypto';
|
|
5
|
+
|
|
6
|
+
const SSO_CODE_TTL_MS = 60 * 1000; // 60 seconds
|
|
7
|
+
|
|
8
|
+
interface SsoCodeEntry {
|
|
9
|
+
userId: number;
|
|
10
|
+
accessToken: string;
|
|
11
|
+
refreshToken: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class SsoCodeStorageService {
|
|
16
|
+
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
|
|
17
|
+
|
|
18
|
+
async generateCode(userId: number, accessToken: string, refreshToken: string): Promise<string> {
|
|
19
|
+
const code = randomBytes(32).toString('hex');
|
|
20
|
+
await this.cacheManager.set(this.getKey(code), { userId, accessToken, refreshToken }, SSO_CODE_TTL_MS);
|
|
21
|
+
return code;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async consumeCode(code: string): Promise<SsoCodeEntry> {
|
|
25
|
+
const entry = await this.cacheManager.get<SsoCodeEntry>(this.getKey(code));
|
|
26
|
+
if (!entry) {
|
|
27
|
+
throw new UnauthorizedException('Invalid or expired SSO code');
|
|
28
|
+
}
|
|
29
|
+
await this.cacheManager.del(this.getKey(code));
|
|
30
|
+
return entry;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private getKey(code: string): string {
|
|
34
|
+
return `sso-code-${code}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -102,8 +102,9 @@ export class UserService extends CRUDService<User> {
|
|
|
102
102
|
if (!user) {
|
|
103
103
|
throw new Error(ERROR_MESSAGES.USER_NOT_FOUND);
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
if (updateDto.roles != null) {
|
|
106
|
+
await this.addRolesToUser(user.username, updateDto.roles);
|
|
107
|
+
}
|
|
107
108
|
await this.update(id, updateDto, files, true);
|
|
108
109
|
}
|
|
109
110
|
|
package/src/solid-core.module.ts
CHANGED
|
@@ -78,6 +78,7 @@ import { MqMessageQueue } from './entities/mq-message-queue.entity';
|
|
|
78
78
|
import { MqMessage } from './entities/mq-message.entity';
|
|
79
79
|
import { SmsTemplate } from './entities/sms-template.entity';
|
|
80
80
|
import { AccessTokenGuard } from './guards/access-token.guard';
|
|
81
|
+
import { ApiKeyGuard } from './guards/api-key.guard';
|
|
81
82
|
import { AuthenticationGuard } from './guards/authentication.guard';
|
|
82
83
|
import { PermissionsGuard } from './guards/permissions.guard';
|
|
83
84
|
import { SolidRegistry } from './helpers/solid-registry';
|
|
@@ -126,6 +127,7 @@ import { TwilioSmsQueuePublisherRedis } from './jobs/redis/twilio-sms-publisher-
|
|
|
126
127
|
import { TwilioSmsQueueSubscriberRedis } from './jobs/redis/twilio-sms-subscriber-redis.service';
|
|
127
128
|
import { UserRegistrationListener } from './listeners/user-registration.listener';
|
|
128
129
|
import { GoogleOauthStrategy } from './passport-strategies/google-oauth.strategy';
|
|
130
|
+
import { ApiKeyService } from './services/api-key.service';
|
|
129
131
|
import { AuthenticationService } from './services/authentication.service';
|
|
130
132
|
import { BcryptService } from './services/bcrypt.service';
|
|
131
133
|
import { UuidExternalIdEntityComputedFieldProvider } from './services/computed-fields/entity/uuid-externalid-entity-computed-field-provider.service';
|
|
@@ -140,6 +142,7 @@ import { MqMessageQueueService } from './services/mq-message-queue.service';
|
|
|
140
142
|
import { MqMessageService } from './services/mq-message.service';
|
|
141
143
|
import { PdfService } from './services/pdf.service';
|
|
142
144
|
import { RefreshTokenIdsStorageService } from './services/refresh-token-ids-storage.service';
|
|
145
|
+
import { SsoCodeStorageService } from './services/sso-code-storage.service';
|
|
143
146
|
import { ListOfModelsSelectionProvider } from './services/selection-providers/list-of-models-selection-provider.service';
|
|
144
147
|
import { TinyUrlService } from './services/short-url/tiny-url.service';
|
|
145
148
|
import { SmsTemplateService } from './services/sms-template.service';
|
|
@@ -205,6 +208,7 @@ import { SecurityRule } from './entities/security-rule.entity';
|
|
|
205
208
|
import { Setting } from './entities/setting.entity';
|
|
206
209
|
import { UserActivityHistory } from './entities/user-activity-history.entity';
|
|
207
210
|
import { UserViewMetadata } from './entities/user-view-metadata.entity';
|
|
211
|
+
import { UserApiKey } from './entities/user-api-key.entity';
|
|
208
212
|
import { User } from './entities/user.entity';
|
|
209
213
|
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
|
210
214
|
import { ModelMetadataHelperService } from './helpers/model-metadata-helper.service';
|
|
@@ -284,6 +288,7 @@ import { SettingRepository } from './repository/setting.repository';
|
|
|
284
288
|
import { SmsTemplateRepository } from './repository/sms-template.repository';
|
|
285
289
|
import { UserActivityHistoryRepository } from './repository/user-activity-history.repository';
|
|
286
290
|
import { UserViewMetadataRepository } from './repository/user-view-metadata.repository';
|
|
291
|
+
import { UserApiKeyRepository } from './repository/user-api-key.repository';
|
|
287
292
|
import { UserRepository } from './repository/user.repository';
|
|
288
293
|
import { ViewMetadataRepository } from './repository/view-metadata.repository';
|
|
289
294
|
import { PermissionMetadataSeederService } from './seeders/permission-metadata-seeder.service';
|
|
@@ -415,6 +420,7 @@ import { Entity } from 'typeorm';
|
|
|
415
420
|
Setting,
|
|
416
421
|
SmsTemplate,
|
|
417
422
|
User,
|
|
423
|
+
UserApiKey,
|
|
418
424
|
UserActivityHistory,
|
|
419
425
|
UserViewMetadata,
|
|
420
426
|
ViewMetadata,
|
|
@@ -627,9 +633,12 @@ import { Entity } from 'typeorm';
|
|
|
627
633
|
LocaleListSelectionProvider,
|
|
628
634
|
SoftDeleteAwareEventSubscriber,
|
|
629
635
|
AccessTokenGuard,
|
|
636
|
+
ApiKeyGuard,
|
|
637
|
+
ApiKeyService,
|
|
630
638
|
AuthenticationService,
|
|
631
639
|
GoogleAuthenticationController,
|
|
632
640
|
RefreshTokenIdsStorageService,
|
|
641
|
+
SsoCodeStorageService,
|
|
633
642
|
GoogleOauthStrategy,
|
|
634
643
|
UserRegistrationListener,
|
|
635
644
|
TestQueuePublisher,
|
|
@@ -679,6 +688,7 @@ import { Entity } from 'typeorm';
|
|
|
679
688
|
RoleMetadataService,
|
|
680
689
|
PermissionMetadataSeederService,
|
|
681
690
|
UserService,
|
|
691
|
+
UserApiKeyRepository,
|
|
682
692
|
UserRepository,
|
|
683
693
|
SettingService,
|
|
684
694
|
ConcatComputedFieldProvider,
|