@hed-hog/core 0.0.360 → 0.0.362
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/access-log/access-log.controller.d.ts +1 -1
- package/dist/access-log/access-log.service.d.ts +1 -1
- package/dist/access-log/access-log.service.d.ts.map +1 -1
- package/dist/access-log/access-log.service.js +43 -1
- package/dist/access-log/access-log.service.js.map +1 -1
- package/dist/ai/ai.service.d.ts +1 -1
- package/dist/auth/auth.controller.d.ts +27 -26
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +6 -2
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +35 -34
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +15 -5
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/auth/guards/auth.guard.d.ts +1 -0
- package/dist/auth/guards/auth.guard.d.ts.map +1 -1
- package/dist/auth/guards/auth.guard.js +11 -4
- package/dist/auth/guards/auth.guard.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +12 -12
- package/dist/core/system.controller.d.ts +2 -2
- package/dist/core/system.service.d.ts +2 -2
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +66 -66
- package/dist/dashboard/dashboard/dashboard.service.d.ts +66 -66
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +26 -26
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +26 -26
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +34 -34
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +34 -34
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +8 -8
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +8 -8
- package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +5 -5
- package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +5 -5
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +25 -25
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +25 -25
- package/dist/file/file.controller.d.ts +6 -6
- package/dist/file/file.module.d.ts.map +1 -1
- package/dist/file/file.module.js +3 -2
- package/dist/file/file.module.js.map +1 -1
- package/dist/file/file.service.d.ts +121 -25
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +217 -32
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/image-optimization.service.d.ts +20 -0
- package/dist/file/image-optimization.service.d.ts.map +1 -0
- package/dist/file/image-optimization.service.js +126 -0
- package/dist/file/image-optimization.service.js.map +1 -0
- package/dist/file/provider/local.provider.d.ts +1 -1
- package/dist/file/provider/local.provider.d.ts.map +1 -1
- package/dist/file/provider/local.provider.js +2 -9
- package/dist/file/provider/local.provider.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/integration-profile/integration-profile.controller.d.ts +15 -15
- package/dist/integration-profile/integration-profile.service.d.ts +15 -15
- package/dist/locale/locale.controller.d.ts +4 -4
- package/dist/locale/locale.service.d.ts +4 -4
- package/dist/mail/mail.controller.d.ts +7 -7
- package/dist/mail/mail.service.d.ts +7 -7
- package/dist/mail-sent/mail-sent.controller.d.ts +12 -12
- package/dist/mail-sent/mail-sent.service.d.ts +12 -12
- package/dist/mcp/mcp-api-key.controller.d.ts +2 -2
- package/dist/mcp/mcp-api-key.service.d.ts +2 -2
- package/dist/mcp/tools/core-users.mcp-tools.d.ts +1 -1
- package/dist/mcp/tools/core-users.mcp-tools.d.ts.map +1 -1
- package/dist/mcp/tools/core-users.mcp-tools.js +2 -2
- package/dist/mcp/tools/core-users.mcp-tools.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.controller.d.ts +8 -8
- package/dist/mcp-chat/mcp-chat.service.d.ts +8 -8
- package/dist/menu/menu.controller.d.ts +10 -10
- package/dist/menu/menu.service.d.ts +10 -10
- package/dist/notification/notification.controller.d.ts +36 -36
- package/dist/notification/notification.service.d.ts +36 -36
- package/dist/profile/profile.controller.d.ts +16 -16
- package/dist/profile/profile.service.d.ts +16 -16
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +44 -23
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/role/role.controller.d.ts +6 -6
- package/dist/role/role.service.d.ts +6 -6
- package/dist/screen/screen.controller.d.ts +3 -3
- package/dist/screen/screen.service.d.ts +3 -3
- package/dist/session/session.controller.d.ts +2 -2
- package/dist/session/session.service.d.ts +6 -6
- package/dist/session/session.service.d.ts.map +1 -1
- package/dist/session/session.service.js +7 -2
- package/dist/session/session.service.js.map +1 -1
- package/dist/setting/setting.controller.d.ts +18 -18
- package/dist/setting/setting.service.d.ts +18 -18
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +0 -1
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/token/token.service.d.ts +1 -0
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +23 -6
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +30 -30
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +4 -3
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +68 -68
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +6 -6
- package/dist/user/user.service.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.d.ts +2 -2
- package/dist/webhook-integration/webhook-integration.service.d.ts +2 -2
- package/hedhog/data/route.yaml +2 -0
- package/hedhog/data/setting_group.yaml +117 -26
- package/hedhog/data/setting_subgroup.yaml +8 -0
- package/hedhog/frontend/app/access-log/page.tsx.ejs +170 -11
- package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +1 -1
- package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +1 -1
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +113 -38
- package/hedhog/frontend/app/users/page.tsx.ejs +7 -7
- package/hedhog/frontend/app/users/permissions.tsx.ejs +137 -83
- package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +28 -26
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +207 -75
- package/hedhog/frontend/messages/en.json +9 -2
- package/hedhog/frontend/messages/pt.json +9 -2
- package/hedhog/table/setting_runtime_state.yaml +9 -0
- package/package.json +12 -6
- package/src/access-log/access-log.service.ts +19 -1
- package/src/auth/auth.controller.ts +5 -0
- package/src/auth/auth.service.ts +14 -4
- package/src/auth/guards/auth.guard.ts +10 -3
- package/src/file/file.module.ts +3 -2
- package/src/file/file.service.ts +448 -46
- package/src/file/image-optimization.service.ts +123 -0
- package/src/file/provider/local.provider.ts +2 -9
- package/src/index.ts +1 -0
- package/src/mcp/tools/core-users.mcp-tools.ts +267 -267
- package/src/profile/profile.service.ts +40 -22
- package/src/session/session.service.ts +10 -2
- package/src/setting/setting.service.ts +0 -1
- package/src/token/token.service.ts +26 -6
- package/src/user/user.controller.ts +5 -2
- package/src/user/user.service.ts +11 -8
- package/dist/menu/menu.service.spec.d.ts +0 -1
- package/dist/menu/menu.service.spec.d.ts.map +0 -1
- package/dist/menu/menu.service.spec.js +0 -208
- package/dist/menu/menu.service.spec.js.map +0 -1
- package/dist/route/route.service.spec.d.ts +0 -1
- package/dist/route/route.service.spec.d.ts.map +0 -1
- package/dist/route/route.service.spec.js +0 -262
- package/dist/route/route.service.spec.js.map +0 -1
- package/dist/screen/screen.service.spec.d.ts +0 -1
- package/dist/screen/screen.service.spec.d.ts.map +0 -1
- package/dist/screen/screen.service.spec.js +0 -259
- package/dist/screen/screen.service.spec.js.map +0 -1
- package/dist/user/user.service.spec.d.ts +0 -1
- package/dist/user/user.service.spec.d.ts.map +0 -1
- package/dist/user/user.service.spec.js +0 -261
- package/dist/user/user.service.spec.js.map +0 -1
|
@@ -49,7 +49,7 @@ export class ProfileService {
|
|
|
49
49
|
async changeEmail(locale: string, userId: number, { email, password, pin }: ChangeEmailDto) {
|
|
50
50
|
|
|
51
51
|
const checkAlreadyExists = await this.prisma.user_identifier.findFirst({
|
|
52
|
-
where: { type: 'email', value: email, enabled: true},
|
|
52
|
+
where: { type: 'email', value: email, enabled: true, verified_at: { not: null } },
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
if (checkAlreadyExists) {
|
|
@@ -98,25 +98,29 @@ export class ProfileService {
|
|
|
98
98
|
|
|
99
99
|
await this.prisma.$transaction( async (tx) => {
|
|
100
100
|
|
|
101
|
-
const
|
|
102
|
-
where: { user_id: userId, type: 'email', enabled: true },
|
|
101
|
+
const currentVerifiedEmail = await tx.user_identifier.findFirst({
|
|
102
|
+
where: { user_id: userId, type: 'email', verified_at: { not: null }, enabled: true },
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
if (!
|
|
105
|
+
if (!currentVerifiedEmail) {
|
|
106
106
|
throw new NotFoundException(getLocaleText('emailIdentifierNotFound', locale, 'Email identifier not found.'));
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
// FIXED: Create new identifier for the new email instead of overwriting the current one
|
|
110
|
+
// This prevents the verified email from becoming orphaned
|
|
111
|
+
const newEmailIdentifier = await tx.user_identifier.create({
|
|
112
|
+
data: {
|
|
113
|
+
type: 'email',
|
|
114
|
+
value: email,
|
|
115
|
+
user_id: userId,
|
|
116
|
+
verified_at: null,
|
|
117
|
+
enabled: true,
|
|
117
118
|
}
|
|
118
119
|
});
|
|
119
120
|
|
|
121
|
+
// Create verification challenge for the new email
|
|
122
|
+
await this.challenge.verifyEmail(locale, userId, email);
|
|
123
|
+
|
|
120
124
|
});
|
|
121
125
|
|
|
122
126
|
|
|
@@ -244,9 +248,14 @@ export class ProfileService {
|
|
|
244
248
|
|
|
245
249
|
if (!userIdentifierId) {
|
|
246
250
|
const userIdentifier = await tx.user_identifier.findFirst({
|
|
247
|
-
where: { user_id: userId, type: 'email' },
|
|
251
|
+
where: { user_id: userId, type: 'email', verified_at: null },
|
|
248
252
|
select: { id: true }
|
|
249
253
|
});
|
|
254
|
+
|
|
255
|
+
if (!userIdentifier) {
|
|
256
|
+
throw new NotFoundException(getLocaleText('identifierNotFound', locale, 'Email identifier not found.'));
|
|
257
|
+
}
|
|
258
|
+
|
|
250
259
|
userIdentifierId = userIdentifier.id;
|
|
251
260
|
await tx.user_identifier_challenge.update({
|
|
252
261
|
where: { id: challengeId },
|
|
@@ -254,6 +263,7 @@ export class ProfileService {
|
|
|
254
263
|
});
|
|
255
264
|
}
|
|
256
265
|
|
|
266
|
+
// Mark challenge and identifier as verified
|
|
257
267
|
await tx.user_identifier_challenge.update({
|
|
258
268
|
where: { id: challengeId },
|
|
259
269
|
data: { verified_at: verifiedAt },
|
|
@@ -267,6 +277,8 @@ export class ProfileService {
|
|
|
267
277
|
}
|
|
268
278
|
});
|
|
269
279
|
|
|
280
|
+
// Delete ALL other email identifiers (both verified and unverified) to maintain single email principle
|
|
281
|
+
// This ensures user has only one verified primary email
|
|
270
282
|
await tx.user_identifier.deleteMany({
|
|
271
283
|
where: {
|
|
272
284
|
user_id: userId,
|
|
@@ -1223,7 +1235,8 @@ export class ProfileService {
|
|
|
1223
1235
|
]);
|
|
1224
1236
|
|
|
1225
1237
|
const rpName = settings['system-name'] || 'HedHog';
|
|
1226
|
-
const
|
|
1238
|
+
const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
|
|
1239
|
+
const rpID = new URL(primaryOrigin).hostname;
|
|
1227
1240
|
const userEmail = user.user_identifier.find(ui => ui.type === 'email')?.value || `user${userId}@example.com`;
|
|
1228
1241
|
const existingAuthenticators = user.user_mfa
|
|
1229
1242
|
.filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
|
|
@@ -1295,8 +1308,9 @@ export class ProfileService {
|
|
|
1295
1308
|
);
|
|
1296
1309
|
}
|
|
1297
1310
|
|
|
1298
|
-
const
|
|
1299
|
-
const
|
|
1311
|
+
const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
|
|
1312
|
+
const rpID = new URL(primaryOrigin).hostname;
|
|
1313
|
+
const expectedOrigin = primaryOrigin;
|
|
1300
1314
|
const verification: VerifiedRegistrationResponse = await verifyRegistrationResponse({
|
|
1301
1315
|
response: attestationResponse,
|
|
1302
1316
|
expectedChallenge: challengeRecord.hash,
|
|
@@ -1356,7 +1370,8 @@ export class ProfileService {
|
|
|
1356
1370
|
);
|
|
1357
1371
|
}
|
|
1358
1372
|
|
|
1359
|
-
const
|
|
1373
|
+
const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
|
|
1374
|
+
const rpID = new URL(primaryOrigin).hostname;
|
|
1360
1375
|
const allowCredentials = user.user_mfa
|
|
1361
1376
|
.filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
|
|
1362
1377
|
.map(mfa => ({
|
|
@@ -1409,8 +1424,9 @@ export class ProfileService {
|
|
|
1409
1424
|
}
|
|
1410
1425
|
|
|
1411
1426
|
const authenticator = challengeRecord.user_mfa.user_mfa_webauthn[0];
|
|
1412
|
-
const
|
|
1413
|
-
const
|
|
1427
|
+
const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
|
|
1428
|
+
const rpID = new URL(primaryOrigin).hostname;
|
|
1429
|
+
const expectedOrigin = primaryOrigin;
|
|
1414
1430
|
const verification: VerifiedAuthenticationResponse = await verifyAuthenticationResponse({
|
|
1415
1431
|
response: assertionResponse,
|
|
1416
1432
|
expectedChallenge: challengeRecord.hash,
|
|
@@ -1493,7 +1509,8 @@ export class ProfileService {
|
|
|
1493
1509
|
throw new NotFoundException(getLocaleText('webauthnNotConfigured', locale, 'WebAuthn not configured.'));
|
|
1494
1510
|
}
|
|
1495
1511
|
|
|
1496
|
-
const
|
|
1512
|
+
const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
|
|
1513
|
+
const rpID = new URL(primaryOrigin).hostname;
|
|
1497
1514
|
const allowCredentials = user.user_mfa
|
|
1498
1515
|
.filter(mfa => mfa.user_mfa_webauthn && mfa.user_mfa_webauthn.length > 0)
|
|
1499
1516
|
.map(mfa => ({
|
|
@@ -1552,8 +1569,9 @@ export class ProfileService {
|
|
|
1552
1569
|
}
|
|
1553
1570
|
|
|
1554
1571
|
const authenticator = challengeRecord.user_mfa.user_mfa_webauthn[0];
|
|
1555
|
-
const
|
|
1556
|
-
const
|
|
1572
|
+
const primaryOrigin = (process.env.CORS_ALLOWED_ORIGINS ?? '').split(/[\n,;]+/).map(u => u.trim()).filter(Boolean)[0] ?? 'http://localhost:3200';
|
|
1573
|
+
const rpID = new URL(primaryOrigin).hostname;
|
|
1574
|
+
const expectedOrigin = primaryOrigin;
|
|
1557
1575
|
const verification: VerifiedAuthenticationResponse = await verifyAuthenticationResponse({
|
|
1558
1576
|
response: assertionResponse,
|
|
1559
1577
|
expectedChallenge: challengeRecord.hash,
|
|
@@ -101,7 +101,7 @@ export class SessionService {
|
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
if (!session) {
|
|
104
|
-
this.logger.warn(`Refresh rejected: session not found or expired for token hash prefix ${hash.slice(0, 8)}`);
|
|
104
|
+
this.logger.warn(`[SessionRefresh] Refresh rejected: session not found or expired for token hash prefix ${hash.slice(0, 8)}`);
|
|
105
105
|
throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -121,6 +121,9 @@ export class SessionService {
|
|
|
121
121
|
const nextIpAddress = ipAddress?.trim() || session.ip_address || 'unknown';
|
|
122
122
|
const nextUserAgent = userAgent?.trim() || session.user_agent || 'unknown';
|
|
123
123
|
|
|
124
|
+
this.logger.debug(`[SessionRefresh] Starting rotation userId=${session.user_id} oldSessionId=${session.id} ip=${nextIpAddress}`);
|
|
125
|
+
const txStart = Date.now();
|
|
126
|
+
|
|
124
127
|
const { updateResult, newSession } = await this.prisma.$transaction(async (tx) => {
|
|
125
128
|
const updateResult = await tx.user_session.updateMany({
|
|
126
129
|
where: {
|
|
@@ -146,10 +149,12 @@ export class SessionService {
|
|
|
146
149
|
});
|
|
147
150
|
|
|
148
151
|
if (updateResult.count === 0) {
|
|
149
|
-
this.logger.warn(`
|
|
152
|
+
this.logger.warn(`[SessionRefresh] Race condition detected: userId=${session.user_id} oldSessionId=${session.id} already rotated by another request (durationMs=${Date.now() - txStart})`);
|
|
150
153
|
throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
|
|
151
154
|
}
|
|
152
155
|
|
|
156
|
+
this.logger.debug(`[SessionRefresh] Rotation successful userId=${session.user_id} oldSessionId=${session.id} newSessionId=${newSession.id} durationMs=${Date.now() - txStart}`);
|
|
157
|
+
|
|
153
158
|
return {
|
|
154
159
|
token: nextRefreshToken,
|
|
155
160
|
session: newSession,
|
|
@@ -390,11 +395,14 @@ export class SessionService {
|
|
|
390
395
|
// During creation a new session is added, so we need one extra free slot (+1).
|
|
391
396
|
const slotsNeeded = isRotation ? 0 : 1;
|
|
392
397
|
const sessionsToExpire = activeSessions.length - maxSessions + slotsNeeded;
|
|
398
|
+
this.logger.debug(`[SessionLimit] userId=${userId} activeSessions=${activeSessions.length} maxSessions=${maxSessions} isRotation=${isRotation} toExpire=${sessionsToExpire}`);
|
|
393
399
|
if (sessionsToExpire > 0) {
|
|
394
400
|
const sessionIdsToExpire = activeSessions
|
|
395
401
|
.slice(0, sessionsToExpire)
|
|
396
402
|
.map(s => s.id);
|
|
397
403
|
|
|
404
|
+
this.logger.warn(`[SessionLimit] Expiring ${sessionsToExpire} oldest session(s) for userId=${userId}: [${sessionIdsToExpire.join(',')}]`);
|
|
405
|
+
|
|
398
406
|
await this.prisma.user_session.updateMany({
|
|
399
407
|
where: {
|
|
400
408
|
id: {
|
|
@@ -471,7 +471,6 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
|
|
|
471
471
|
'oauth-google-enabled',
|
|
472
472
|
'oauth-microsoft-enabled',
|
|
473
473
|
'oauth-microsoft-entra-id-enabled',
|
|
474
|
-
'admin-login-page-oauth-providers',
|
|
475
474
|
'disable-authentication-with-email-and-password',
|
|
476
475
|
'contact-allow-company-registration',
|
|
477
476
|
'operations.comment-edit-window',
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { getLocaleText } from "@hed-hog/api-locale";
|
|
2
2
|
import { PrismaService } from "@hed-hog/api-prisma";
|
|
3
|
-
import { ForbiddenException, forwardRef, Inject, Injectable, UnauthorizedException } from "@nestjs/common";
|
|
3
|
+
import { ForbiddenException, forwardRef, Inject, Injectable, Logger, UnauthorizedException } from "@nestjs/common";
|
|
4
4
|
import { JwtService } from "@nestjs/jwt";
|
|
5
5
|
import { SecurityService } from "../security/security.service";
|
|
6
6
|
import { SettingService } from "../setting/setting.service";
|
|
7
7
|
|
|
8
8
|
@Injectable()
|
|
9
9
|
export class TokenService {
|
|
10
|
+
private readonly logger = new Logger(TokenService.name);
|
|
10
11
|
|
|
11
12
|
constructor(
|
|
12
13
|
private readonly prisma: PrismaService,
|
|
@@ -33,7 +34,22 @@ export class TokenService {
|
|
|
33
34
|
select: { revoked_at: true, expires_at: true }
|
|
34
35
|
});
|
|
35
36
|
|
|
36
|
-
if (!session
|
|
37
|
+
if (!session) {
|
|
38
|
+
this.logger.warn(`[TokenVerify] sessionId=${payload.sessionId} userId=${payload.sub} not found in DB`);
|
|
39
|
+
throw new UnauthorizedException(
|
|
40
|
+
getLocaleText('sessionRevoked', locale, 'Session has been revoked.')
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (session.revoked_at !== null) {
|
|
45
|
+
this.logger.warn(`[TokenVerify] sessionId=${payload.sessionId} userId=${payload.sub} revoked_at=${session.revoked_at.toISOString()}`);
|
|
46
|
+
throw new UnauthorizedException(
|
|
47
|
+
getLocaleText('sessionRevoked', locale, 'Session has been revoked.')
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (session.expires_at <= new Date()) {
|
|
52
|
+
this.logger.debug(`[TokenVerify] sessionId=${payload.sessionId} userId=${payload.sub} expired_at=${session.expires_at.toISOString()}`);
|
|
37
53
|
throw new UnauthorizedException(
|
|
38
54
|
getLocaleText('sessionRevoked', locale, 'Session has been revoked.')
|
|
39
55
|
);
|
|
@@ -43,17 +59,21 @@ export class TokenService {
|
|
|
43
59
|
if (sessionError instanceof UnauthorizedException) {
|
|
44
60
|
throw sessionError;
|
|
45
61
|
}
|
|
46
|
-
//
|
|
47
|
-
|
|
62
|
+
// DB error: log but allow auth to continue to avoid blocking on transient DB issues
|
|
63
|
+
this.logger.error(`[TokenVerify] DB error validating sessionId=${payload.sessionId} userId=${payload.sub}: ${(sessionError as any)?.message}`);
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
return payload;
|
|
52
68
|
} catch (error) {
|
|
53
|
-
|
|
69
|
+
const errName = (error as any)?.name ?? 'UnknownError';
|
|
70
|
+
const errMsg = (error as any)?.message ?? '';
|
|
71
|
+
if (!(error instanceof UnauthorizedException)) {
|
|
72
|
+
this.logger.debug(`[TokenVerify] JWT error=${errName}: ${errMsg}`);
|
|
73
|
+
}
|
|
54
74
|
// Return 401 for JWT errors (expired, invalid, etc.)
|
|
55
75
|
throw new UnauthorizedException(
|
|
56
|
-
|
|
76
|
+
errMsg || getLocaleText('accessDenied', locale, 'Access denied.')
|
|
57
77
|
);
|
|
58
78
|
}
|
|
59
79
|
}
|
|
@@ -112,8 +112,11 @@ export class UserController {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
@Get(':userId/role')
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
async getUserRoles(
|
|
116
|
+
@Param('userId', ParseIntPipe) userId: number,
|
|
117
|
+
@Locale() locale: string,
|
|
118
|
+
) {
|
|
119
|
+
return this.userService.getUserRoles(locale, userId);
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
@Get(':userId/menu')
|
package/src/user/user.service.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
2
1
|
import { getLocaleText } from '@hed-hog/api-locale';
|
|
3
2
|
import { PaginationService } from '@hed-hog/api-pagination';
|
|
4
3
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
@@ -9,6 +8,7 @@ import {
|
|
|
9
8
|
NotFoundException,
|
|
10
9
|
forwardRef,
|
|
11
10
|
} from '@nestjs/common';
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
12
|
import { ChallengeService } from '../challenge/challenge.service';
|
|
13
13
|
import { DeleteDTO } from '../dto/delete.dto';
|
|
14
14
|
import { FileService } from '../file/file.service';
|
|
@@ -31,7 +31,6 @@ const CREDENTIAL_TYPE = {
|
|
|
31
31
|
|
|
32
32
|
const USER_AVATAR_CACHE_MAX_AGE_SETTING = 'user-avatar-cache-max-age';
|
|
33
33
|
const DEFAULT_ROLE_SLUG = 'user';
|
|
34
|
-
const DEFAULT_LOCALE = 'en';
|
|
35
34
|
const DAYS_IN_MS = 24 * 60 * 60 * 1000;
|
|
36
35
|
const NEW_USERS_PERIOD_DAYS = 7;
|
|
37
36
|
const RANDOM_PASSWORD_LENGTH = 16;
|
|
@@ -417,13 +416,13 @@ export class UserService {
|
|
|
417
416
|
// Role Management
|
|
418
417
|
// ==========================================
|
|
419
418
|
|
|
420
|
-
async getUserRoles(userId: number) {
|
|
419
|
+
async getUserRoles(locale: string, userId: number) {
|
|
421
420
|
const [allRoles, userRoles] = await Promise.all([
|
|
422
421
|
this.getAllRolesWithLocale(),
|
|
423
422
|
this.getUserAssignedRoles(userId),
|
|
424
423
|
]);
|
|
425
424
|
|
|
426
|
-
return this.mapRolesWithAssignment(allRoles, userRoles);
|
|
425
|
+
return this.mapRolesWithAssignment(locale, allRoles, userRoles);
|
|
427
426
|
}
|
|
428
427
|
|
|
429
428
|
async getUserMenus(locale: string, userId: number) {
|
|
@@ -730,15 +729,19 @@ export class UserService {
|
|
|
730
729
|
});
|
|
731
730
|
}
|
|
732
731
|
|
|
733
|
-
private mapRolesWithAssignment(
|
|
732
|
+
private mapRolesWithAssignment(
|
|
733
|
+
locale: string,
|
|
734
|
+
allRoles: any[],
|
|
735
|
+
userRoles: any[],
|
|
736
|
+
) {
|
|
734
737
|
return allRoles.map((role) => {
|
|
735
738
|
const isAssigned = userRoles.some(
|
|
736
739
|
(userRole) => userRole.role_id === role.id
|
|
737
740
|
);
|
|
738
741
|
|
|
739
|
-
const localeData =
|
|
740
|
-
(
|
|
741
|
-
|
|
742
|
+
const localeData =
|
|
743
|
+
role.role_locale.find((roleLocale) => roleLocale.locale.code === locale) ??
|
|
744
|
+
role.role_locale[0];
|
|
742
745
|
|
|
743
746
|
return {
|
|
744
747
|
id: role.id,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=menu.service.spec.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"menu.service.spec.d.ts","sourceRoot":"","sources":["../../src/menu/menu.service.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// import {
|
|
3
|
-
// PageOrderDirection,
|
|
4
|
-
// PaginationDTO,
|
|
5
|
-
// PaginationService,
|
|
6
|
-
// } from '@hed-hog/api-pagination';
|
|
7
|
-
// import { PrismaService } from '@hed-hog/api-prisma';
|
|
8
|
-
// import { BadRequestException } from '@nestjs/common';
|
|
9
|
-
// import { Test, TestingModule } from '@nestjs/testing';
|
|
10
|
-
// import { MenuService } from './menu.service';
|
|
11
|
-
// describe('MenuService', () => {
|
|
12
|
-
// let menuService: MenuService;
|
|
13
|
-
// let prismaService: PrismaService;
|
|
14
|
-
// let paginationService: PaginationService;
|
|
15
|
-
// const mockPrismaService = {
|
|
16
|
-
// menu: {
|
|
17
|
-
// create: jest.fn(),
|
|
18
|
-
// update: jest.fn(),
|
|
19
|
-
// deleteMany: jest.fn(),
|
|
20
|
-
// findUnique: jest.fn(),
|
|
21
|
-
// findMany: jest.fn(),
|
|
22
|
-
// count: jest.fn(),
|
|
23
|
-
// },
|
|
24
|
-
// role_menu: {
|
|
25
|
-
// deleteMany: jest.fn(),
|
|
26
|
-
// createMany: jest.fn(),
|
|
27
|
-
// },
|
|
28
|
-
// menu_screen: {
|
|
29
|
-
// deleteMany: jest.fn(),
|
|
30
|
-
// createMany: jest.fn(),
|
|
31
|
-
// },
|
|
32
|
-
// };
|
|
33
|
-
// const mockPaginationService = {
|
|
34
|
-
// paginate: jest.fn(),
|
|
35
|
-
// createInsensitiveSearch: jest.fn(),
|
|
36
|
-
// };
|
|
37
|
-
// beforeEach(async () => {
|
|
38
|
-
// const module: TestingModule = await Test.createTestingModule({
|
|
39
|
-
// providers: [
|
|
40
|
-
// MenuService,
|
|
41
|
-
// { provide: PrismaService, useValue: mockPrismaService },
|
|
42
|
-
// { provide: PaginationService, useValue: mockPaginationService },
|
|
43
|
-
// ],
|
|
44
|
-
// }).compile();
|
|
45
|
-
// menuService = module.get<MenuService>(MenuService);
|
|
46
|
-
// prismaService = module.get<PrismaService>(PrismaService);
|
|
47
|
-
// paginationService = module.get<PaginationService>(PaginationService);
|
|
48
|
-
// });
|
|
49
|
-
// afterEach(() => {
|
|
50
|
-
// jest.clearAllMocks();
|
|
51
|
-
// });
|
|
52
|
-
// describe('create', () => {
|
|
53
|
-
// it('should create a new menu', async () => {
|
|
54
|
-
// const createMenuDto = {
|
|
55
|
-
// name: 'Test Menu',
|
|
56
|
-
// url: '/test',
|
|
57
|
-
// icon: 'test-icon',
|
|
58
|
-
// order: 1,
|
|
59
|
-
// menu_id: undefined,
|
|
60
|
-
// };
|
|
61
|
-
// mockPrismaService.menu.create.mockResolvedValue(createMenuDto);
|
|
62
|
-
// const result = await menuService.create(createMenuDto);
|
|
63
|
-
// expect(result).toEqual(createMenuDto);
|
|
64
|
-
// expect(prismaService.menu.create).toHaveBeenCalledWith({
|
|
65
|
-
// data: createMenuDto,
|
|
66
|
-
// });
|
|
67
|
-
// });
|
|
68
|
-
// });
|
|
69
|
-
// describe('update', () => {
|
|
70
|
-
// it('should update a menu', async () => {
|
|
71
|
-
// const updateDto = { id: 1, data: { name: 'Updated Menu' } };
|
|
72
|
-
// mockPrismaService.menu.update.mockResolvedValue(updateDto.data);
|
|
73
|
-
// const result = await menuService.update(updateDto);
|
|
74
|
-
// expect(result).toEqual(updateDto.data);
|
|
75
|
-
// expect(prismaService.menu.update).toHaveBeenCalledWith({
|
|
76
|
-
// where: { id: updateDto.id },
|
|
77
|
-
// data: updateDto.data,
|
|
78
|
-
// });
|
|
79
|
-
// });
|
|
80
|
-
// });
|
|
81
|
-
// describe('delete', () => {
|
|
82
|
-
// it('should delete menu', async () => {
|
|
83
|
-
// const deleteDto = { ids: [1, 2, 3] };
|
|
84
|
-
// mockPrismaService.menu.deleteMany.mockResolvedValue({ count: 3 });
|
|
85
|
-
// const result = await menuService.delete(deleteDto);
|
|
86
|
-
// expect(result).toEqual({ count: 3 });
|
|
87
|
-
// expect(prismaService.menu.deleteMany).toHaveBeenCalledWith({
|
|
88
|
-
// where: { id: { in: deleteDto.ids } },
|
|
89
|
-
// });
|
|
90
|
-
// });
|
|
91
|
-
// it('should throw BadRequestException if no ids are provided', async () => {
|
|
92
|
-
// await expect(menuService.delete({ ids: null })).rejects.toThrow(
|
|
93
|
-
// BadRequestException,
|
|
94
|
-
// );
|
|
95
|
-
// });
|
|
96
|
-
// });
|
|
97
|
-
// describe('listScreens', () => {
|
|
98
|
-
// it('should paginate screens', async () => {
|
|
99
|
-
// const locale = 'en';
|
|
100
|
-
// const menuId = 1;
|
|
101
|
-
// const paginationParams: PaginationDTO = {
|
|
102
|
-
// page: 1,
|
|
103
|
-
// pageSize: 10,
|
|
104
|
-
// search: '',
|
|
105
|
-
// sortField: '',
|
|
106
|
-
// sortOrder: PageOrderDirection.Asc,
|
|
107
|
-
// fields: '',
|
|
108
|
-
// };
|
|
109
|
-
// const mockScreens = [{ id: 1, name: 'Screen 1' }];
|
|
110
|
-
// mockPaginationService.paginate.mockResolvedValue(mockScreens);
|
|
111
|
-
// const result = await menuService.listScreens(
|
|
112
|
-
// locale,
|
|
113
|
-
// menuId,
|
|
114
|
-
// paginationParams,
|
|
115
|
-
// );
|
|
116
|
-
// expect(result).toEqual(mockScreens);
|
|
117
|
-
// expect(paginationService.paginate).toHaveBeenCalledWith(
|
|
118
|
-
// prismaService.screens,
|
|
119
|
-
// paginationParams,
|
|
120
|
-
// expect.anything(),
|
|
121
|
-
// 'screen_locale',
|
|
122
|
-
// );
|
|
123
|
-
// });
|
|
124
|
-
// });
|
|
125
|
-
// describe('updateScreens', () => {
|
|
126
|
-
// it('should update screens associated with a menu', async () => {
|
|
127
|
-
// const menuId = 1;
|
|
128
|
-
// const updateData = { ids: [1, 2, 3] };
|
|
129
|
-
// jest
|
|
130
|
-
// .spyOn(prismaService.menu_screen, 'deleteMany')
|
|
131
|
-
// .mockResolvedValue(null);
|
|
132
|
-
// jest
|
|
133
|
-
// .spyOn(prismaService.menu_screen, 'createMany')
|
|
134
|
-
// .mockResolvedValue(null);
|
|
135
|
-
// await menuService.updateScreens(menuId, updateData);
|
|
136
|
-
// expect(prismaService.menu_screen.deleteMany).toHaveBeenCalledWith({
|
|
137
|
-
// where: { menu_id: menuId },
|
|
138
|
-
// });
|
|
139
|
-
// expect(prismaService.menu_screen.createMany).toHaveBeenCalledWith({
|
|
140
|
-
// data: updateData.ids.map((screenId) => ({
|
|
141
|
-
// menu_id: menuId,
|
|
142
|
-
// screen_id: screenId,
|
|
143
|
-
// })),
|
|
144
|
-
// skipDuplicates: true,
|
|
145
|
-
// });
|
|
146
|
-
// });
|
|
147
|
-
// });
|
|
148
|
-
// describe('updateRoles', () => {
|
|
149
|
-
// it('should update role associated with a menu', async () => {
|
|
150
|
-
// const menuId = 1;
|
|
151
|
-
// const updateData = { ids: [1, 2] };
|
|
152
|
-
// jest.spyOn(prismaService.role_menu, 'deleteMany').mockResolvedValue(null);
|
|
153
|
-
// jest.spyOn(prismaService.role_menu, 'createMany').mockResolvedValue(null);
|
|
154
|
-
// await menuService.updateRoles(menuId, updateData);
|
|
155
|
-
// expect(prismaService.role_menu.deleteMany).toHaveBeenCalledWith({
|
|
156
|
-
// where: { menu_id: menuId },
|
|
157
|
-
// });
|
|
158
|
-
// expect(prismaService.role_menu.createMany).toHaveBeenCalledWith({
|
|
159
|
-
// data: updateData.ids.map((roleId) => ({
|
|
160
|
-
// menu_id: menuId,
|
|
161
|
-
// role_id: roleId,
|
|
162
|
-
// })),
|
|
163
|
-
// skipDuplicates: true,
|
|
164
|
-
// });
|
|
165
|
-
// });
|
|
166
|
-
// });
|
|
167
|
-
// describe('updateOrder', () => {
|
|
168
|
-
// it('should update the order of menu', async () => {
|
|
169
|
-
// const orderData = { ids: [1, 2, 3] };
|
|
170
|
-
// jest.spyOn(prismaService.menu, 'count').mockResolvedValue(3);
|
|
171
|
-
// jest.spyOn(prismaService.menu, 'update').mockResolvedValue(null);
|
|
172
|
-
// await menuService.updateOrder(orderData);
|
|
173
|
-
// expect(prismaService.menu.count).toHaveBeenCalledWith({
|
|
174
|
-
// where: { id: { in: orderData.ids } },
|
|
175
|
-
// });
|
|
176
|
-
// expect(prismaService.menu.update).toHaveBeenCalledTimes(
|
|
177
|
-
// orderData.ids.length,
|
|
178
|
-
// );
|
|
179
|
-
// orderData.ids.forEach((id, index) => {
|
|
180
|
-
// expect(prismaService.menu.update).toHaveBeenCalledWith({
|
|
181
|
-
// where: { id },
|
|
182
|
-
// data: { order: index + 1 },
|
|
183
|
-
// });
|
|
184
|
-
// });
|
|
185
|
-
// });
|
|
186
|
-
// it('should throw BadRequestException if IDs are invalid', async () => {
|
|
187
|
-
// const orderData = { ids: [1, 2, 3] };
|
|
188
|
-
// jest.spyOn(prismaService.menu, 'count').mockResolvedValue(2); // IDs não batem com o número esperado
|
|
189
|
-
// await expect(menuService.updateOrder(orderData)).rejects.toThrow(
|
|
190
|
-
// BadRequestException,
|
|
191
|
-
// );
|
|
192
|
-
// });
|
|
193
|
-
// });
|
|
194
|
-
// /*
|
|
195
|
-
// describe('getMenus', () => {
|
|
196
|
-
// it('should get menu for a user', async () => {
|
|
197
|
-
// const locale = 'en';
|
|
198
|
-
// const userId = 1;
|
|
199
|
-
// const mockMenus = [{ id: 1, name: 'Menu 1' }];
|
|
200
|
-
// mockPrismaService.menu.findMany.mockResolvedValue(mockMenus);
|
|
201
|
-
// const result = await menuService.getMenus(locale, userId);
|
|
202
|
-
// expect(result).toEqual(mockMenus);
|
|
203
|
-
// expect(prismaService.menu.findMany).toHaveBeenCalled();
|
|
204
|
-
// });
|
|
205
|
-
// });
|
|
206
|
-
// */
|
|
207
|
-
// });
|
|
208
|
-
//# sourceMappingURL=menu.service.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"menu.service.spec.js","sourceRoot":"","sources":["../../src/menu/menu.service.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=route.service.spec.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route.service.spec.d.ts","sourceRoot":"","sources":["../../src/route/route.service.spec.ts"],"names":[],"mappings":""}
|