@hed-hog/core 0.0.303 → 0.0.305
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/README.md +84 -36
- package/dist/auth/auth.controller.d.ts +0 -3
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +4 -7
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +6 -3
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +9 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js +12 -0
- package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +7 -0
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js +45 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts +9 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts.map +1 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.js +49 -0
- package/dist/mail-sent/dto/query-mail-sent-list.dto.js.map +1 -0
- package/dist/mail-sent/mail-sent.controller.d.ts +24 -2
- package/dist/mail-sent/mail-sent.controller.d.ts.map +1 -1
- package/dist/mail-sent/mail-sent.controller.js +5 -3
- package/dist/mail-sent/mail-sent.controller.js.map +1 -1
- package/dist/mail-sent/mail-sent.service.d.ts +28 -3
- package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
- package/dist/mail-sent/mail-sent.service.js +108 -3
- package/dist/mail-sent/mail-sent.service.js.map +1 -1
- package/dist/oauth/oauth-callback-coordinator.service.d.ts +18 -0
- package/dist/oauth/oauth-callback-coordinator.service.d.ts.map +1 -0
- package/dist/oauth/oauth-callback-coordinator.service.js +137 -0
- package/dist/oauth/oauth-callback-coordinator.service.js.map +1 -0
- package/dist/oauth/oauth.controller.d.ts +4 -2
- package/dist/oauth/oauth.controller.d.ts.map +1 -1
- package/dist/oauth/oauth.controller.js +39 -11
- package/dist/oauth/oauth.controller.js.map +1 -1
- package/dist/oauth/oauth.errors.d.ts +16 -0
- package/dist/oauth/oauth.errors.d.ts.map +1 -0
- package/dist/oauth/oauth.errors.js +30 -0
- package/dist/oauth/oauth.errors.js.map +1 -0
- package/dist/oauth/oauth.module.d.ts.map +1 -1
- package/dist/oauth/oauth.module.js +10 -1
- package/dist/oauth/oauth.module.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts +9 -1
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +97 -24
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/oauth/providers/abstract.provider.d.ts +6 -0
- package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
- package/dist/oauth/providers/abstract.provider.js +34 -0
- package/dist/oauth/providers/abstract.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +3 -0
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +40 -22
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/security/security.service.d.ts +1 -0
- package/dist/security/security.service.d.ts.map +1 -1
- package/dist/security/security.service.js +9 -10
- package/dist/security/security.service.js.map +1 -1
- package/dist/session/session.service.d.ts +5 -1
- package/dist/session/session.service.d.ts.map +1 -1
- package/dist/session/session.service.js +76 -10
- package/dist/session/session.service.js.map +1 -1
- package/dist/setting/setting.service.js +4 -4
- 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 -12
- package/dist/token/token.service.js.map +1 -1
- package/dist/user/user.service.d.ts +1 -1
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +3 -0
- package/dist/user/user.service.js.map +1 -1
- package/hedhog/data/dashboard.yaml +2 -2
- package/hedhog/data/mail.yaml +147 -0
- package/hedhog/data/route.yaml +8 -0
- package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +87 -8
- package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +90 -10
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +145 -31
- package/hedhog/frontend/app/configurations/layout.tsx.ejs +1 -1
- package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +67 -2
- package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +13 -9
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +250 -45
- package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +111 -15
- package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +110 -15
- package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +113 -15
- package/hedhog/frontend/app/mail/log/page.tsx.ejs +481 -70
- package/hedhog/frontend/app/menu/page.tsx.ejs +202 -30
- package/hedhog/frontend/app/roles/menus.tsx.ejs +1 -1
- package/hedhog/frontend/app/roles/page.tsx.ejs +288 -104
- package/hedhog/frontend/app/users/page.tsx.ejs +168 -23
- package/hedhog/frontend/messages/en.json +45 -6
- package/hedhog/frontend/messages/pt.json +45 -6
- package/package.json +4 -4
- package/src/auth/auth.controller.ts +15 -19
- package/src/auth/auth.service.ts +13 -8
- package/src/dashboard/dashboard-core/dashboard-core.controller.ts +22 -9
- package/src/dashboard/dashboard-core/dashboard-core.service.ts +75 -1
- package/src/index.ts +10 -7
- package/src/mail-sent/dto/query-mail-sent-list.dto.ts +30 -0
- package/src/mail-sent/mail-sent.controller.ts +14 -2
- package/src/mail-sent/mail-sent.service.ts +159 -4
- package/src/oauth/oauth-callback-coordinator.service.ts +158 -0
- package/src/oauth/oauth.controller.ts +58 -1
- package/src/oauth/oauth.errors.ts +32 -0
- package/src/oauth/oauth.module.ts +10 -1
- package/src/oauth/oauth.service.ts +178 -28
- package/src/oauth/providers/abstract.provider.ts +64 -0
- package/src/oauth/providers/microsoft-entra-id.provider.ts +92 -56
- package/src/security/security.service.ts +12 -10
- package/src/session/session.service.ts +84 -9
- package/src/setting/setting.service.ts +10 -10
- package/src/token/token.service.ts +29 -11
- package/src/user/user.service.ts +10 -6
|
@@ -2,7 +2,7 @@ import { getLocaleText } from '@hed-hog/api-locale';
|
|
|
2
2
|
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
3
3
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
4
4
|
import { HttpService } from '@nestjs/axios';
|
|
5
|
-
import { BadRequestException, forwardRef, HttpException, HttpStatus, Inject, Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common';
|
|
5
|
+
import { BadRequestException, forwardRef, HttpException, HttpStatus, Inject, Injectable, Logger, NotFoundException, UnauthorizedException } from '@nestjs/common';
|
|
6
6
|
import { firstValueFrom } from 'rxjs';
|
|
7
7
|
import { SecurityService } from '../security/security.service';
|
|
8
8
|
import { SettingService } from '../setting/setting.service';
|
|
@@ -22,6 +22,7 @@ type GeoIpResult = {
|
|
|
22
22
|
|
|
23
23
|
@Injectable()
|
|
24
24
|
export class SessionService {
|
|
25
|
+
private readonly logger = new Logger(SessionService.name);
|
|
25
26
|
|
|
26
27
|
constructor(
|
|
27
28
|
private prisma: PrismaService,
|
|
@@ -78,33 +79,102 @@ export class SessionService {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
async refresh(locale: string, refreshToken: string, ipAddress: string, userAgent: string) {
|
|
82
|
+
if (!refreshToken) {
|
|
83
|
+
throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
|
|
84
|
+
}
|
|
85
|
+
|
|
81
86
|
const hash = this.security.hashWithPepper(refreshToken);
|
|
82
87
|
const session = await this.prisma.user_session.findFirst({
|
|
83
88
|
where: {
|
|
84
89
|
hash,
|
|
85
90
|
revoked_at: null,
|
|
91
|
+
expires_at: {
|
|
92
|
+
gt: new Date(),
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
select: {
|
|
96
|
+
id: true,
|
|
97
|
+
user_id: true,
|
|
98
|
+
ip_address: true,
|
|
99
|
+
user_agent: true,
|
|
86
100
|
},
|
|
87
|
-
select: { id: true, user_id: true },
|
|
88
101
|
});
|
|
89
102
|
|
|
90
103
|
if (!session) {
|
|
104
|
+
this.logger.warn(`Refresh rejected: session not found or expired for token hash prefix ${hash.slice(0, 8)}`);
|
|
91
105
|
throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
|
|
92
106
|
}
|
|
93
107
|
|
|
94
|
-
await this.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
const settings = await this.setting.getSettingValues([
|
|
109
|
+
'refresh-token-expiration-minutes',
|
|
110
|
+
'max-concurrent-sessions',
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const maxSessions = Number(settings['max-concurrent-sessions']) || 0;
|
|
114
|
+
if (maxSessions > 0) {
|
|
115
|
+
await this.enforceSessionLimit(session.user_id, maxSessions);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const nextRefreshToken = await this.token.createOpaqueToken();
|
|
119
|
+
const nextHash = this.security.hashWithPepper(nextRefreshToken);
|
|
120
|
+
const expiresAt = new Date(Date.now() + ((settings['refresh-token-expiration-minutes'] || 43200) * 60 * 1000));
|
|
121
|
+
const nextIpAddress = ipAddress?.trim() || session.ip_address || 'unknown';
|
|
122
|
+
const nextUserAgent = userAgent?.trim() || session.user_agent || 'unknown';
|
|
123
|
+
|
|
124
|
+
const { updateResult, newSession } = await this.prisma.$transaction(async (tx) => {
|
|
125
|
+
const updateResult = await tx.user_session.updateMany({
|
|
126
|
+
where: {
|
|
127
|
+
id: session.id,
|
|
128
|
+
revoked_at: null,
|
|
129
|
+
},
|
|
130
|
+
data: {
|
|
131
|
+
revoked_at: new Date(),
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const newSession = await tx.user_session.create({
|
|
136
|
+
data: {
|
|
137
|
+
user_id: session.user_id,
|
|
138
|
+
ip_address: nextIpAddress,
|
|
139
|
+
user_agent: nextUserAgent,
|
|
140
|
+
expires_at: expiresAt,
|
|
141
|
+
hash: nextHash,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return { updateResult, newSession };
|
|
99
146
|
});
|
|
100
147
|
|
|
101
|
-
|
|
148
|
+
if (updateResult.count === 0) {
|
|
149
|
+
this.logger.warn(`Refresh rejected: session ${session.id} already rotated`);
|
|
150
|
+
throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
token: nextRefreshToken,
|
|
155
|
+
session: newSession,
|
|
156
|
+
};
|
|
102
157
|
}
|
|
103
158
|
|
|
104
159
|
async deleteRefreshTokenCookie(res, refreshToken: string) {
|
|
105
160
|
await this.token.removeRefreshTokenCookie(res);
|
|
161
|
+
|
|
162
|
+
if (!refreshToken) {
|
|
163
|
+
return { count: 0, userId: null };
|
|
164
|
+
}
|
|
165
|
+
|
|
106
166
|
const hash = this.security.hashWithPepper(refreshToken);
|
|
107
|
-
|
|
167
|
+
const session = await this.prisma.user_session.findFirst({
|
|
168
|
+
where: {
|
|
169
|
+
revoked_at: null,
|
|
170
|
+
hash,
|
|
171
|
+
},
|
|
172
|
+
select: {
|
|
173
|
+
user_id: true,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const result = await this.prisma.user_session.updateMany({
|
|
108
178
|
where: {
|
|
109
179
|
revoked_at: null,
|
|
110
180
|
hash,
|
|
@@ -113,6 +183,11 @@ export class SessionService {
|
|
|
113
183
|
revoked_at: new Date(),
|
|
114
184
|
},
|
|
115
185
|
});
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
count: result.count,
|
|
189
|
+
userId: session?.user_id ?? null,
|
|
190
|
+
};
|
|
116
191
|
}
|
|
117
192
|
|
|
118
193
|
async getUserSessions(paginationParams: PaginationDTO, userId: number, locale: string) {
|
|
@@ -3,12 +3,12 @@ import { getLocaleText, LocaleService } from '@hed-hog/api-locale';
|
|
|
3
3
|
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
4
4
|
import { PrismaService, setting_type_0f055cd5ea_enum } from '@hed-hog/api-prisma';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
BadRequestException,
|
|
7
|
+
forwardRef,
|
|
8
|
+
Inject,
|
|
9
|
+
Injectable,
|
|
10
|
+
Logger,
|
|
11
|
+
NotFoundException
|
|
12
12
|
} from '@nestjs/common';
|
|
13
13
|
import * as pako from 'pako';
|
|
14
14
|
import { IntegrationDeveloperApiService } from '../integration/services/integration-developer-api.service';
|
|
@@ -882,7 +882,7 @@ export class SettingService {
|
|
|
882
882
|
this.cachedSettings &&
|
|
883
883
|
slugs.every(s => Object.prototype.hasOwnProperty.call(this.cachedSettings, s))
|
|
884
884
|
) {
|
|
885
|
-
this.logger.
|
|
885
|
+
this.logger.debug('Returning settings from cache');
|
|
886
886
|
|
|
887
887
|
// Retorna apenas os slugs requisitados do cache
|
|
888
888
|
const cached: Record<string, any> = {};
|
|
@@ -892,7 +892,7 @@ export class SettingService {
|
|
|
892
892
|
return cached;
|
|
893
893
|
}
|
|
894
894
|
|
|
895
|
-
this.logger.
|
|
895
|
+
this.logger.debug('Fetching settings from database');
|
|
896
896
|
|
|
897
897
|
slug = Array.isArray(slug) ? slug : [slug];
|
|
898
898
|
|
|
@@ -972,7 +972,7 @@ export class SettingService {
|
|
|
972
972
|
}
|
|
973
973
|
|
|
974
974
|
setCache(value: Record<string, any>) {
|
|
975
|
-
this.logger.
|
|
975
|
+
this.logger.debug(`Setting settings cache with ${Object.keys(value).length} items`);
|
|
976
976
|
Object.keys(value).forEach(key => {
|
|
977
977
|
this.cachedSettings[key] = value[key];
|
|
978
978
|
});
|
|
@@ -980,7 +980,7 @@ export class SettingService {
|
|
|
980
980
|
}
|
|
981
981
|
|
|
982
982
|
clearCache() {
|
|
983
|
-
this.logger.
|
|
983
|
+
this.logger.debug('Clearing settings cache');
|
|
984
984
|
this.cachedSettings = {};
|
|
985
985
|
}
|
|
986
986
|
|
|
@@ -70,20 +70,38 @@ export class TokenService {
|
|
|
70
70
|
return this.security.randomOpaque(size);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
private normalizeCookieDomain(value?: string | null) {
|
|
74
|
+
const normalized = value?.trim();
|
|
75
|
+
|
|
76
|
+
if (!normalized) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const host = normalized
|
|
81
|
+
.replace(/^https?:\/\//i, '')
|
|
82
|
+
.split('/')[0]
|
|
83
|
+
.split(':')[0]
|
|
84
|
+
.trim();
|
|
85
|
+
|
|
86
|
+
if (!host || host === 'localhost' || /^\d{1,3}(\.\d{1,3}){3}$/.test(host)) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return host;
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
private async getCookieDomain() {
|
|
94
|
+
const explicitDomain = this.normalizeCookieDomain(process.env.COOKIE_DOMAIN);
|
|
95
|
+
|
|
96
|
+
if (explicitDomain) {
|
|
97
|
+
return explicitDomain;
|
|
98
|
+
}
|
|
74
99
|
|
|
75
100
|
const settings = await this.setting.getSettingValues([
|
|
76
|
-
'url'
|
|
101
|
+
'api-url'
|
|
77
102
|
]);
|
|
78
103
|
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
return new URL(settings['url']).hostname;
|
|
82
|
-
} catch {
|
|
83
|
-
return process.env.COOKIE_DOMAIN;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return process.env.COOKIE_DOMAIN;
|
|
104
|
+
return this.normalizeCookieDomain(settings['api-url']);
|
|
87
105
|
}
|
|
88
106
|
|
|
89
107
|
async removeRefreshTokenCookie(res): Promise<void> {
|
|
@@ -92,7 +110,7 @@ export class TokenService {
|
|
|
92
110
|
const isLocalhost = !domain || domain === 'localhost';
|
|
93
111
|
const cookieOptions: any = {
|
|
94
112
|
httpOnly: true,
|
|
95
|
-
sameSite: isLocalhost ? 'lax' : '
|
|
113
|
+
sameSite: isLocalhost ? 'lax' : 'none',
|
|
96
114
|
};
|
|
97
115
|
if (!isLocalhost) {
|
|
98
116
|
cookieOptions.secure = true;
|
|
@@ -107,7 +125,7 @@ export class TokenService {
|
|
|
107
125
|
const maxAge = expires_at.getTime() - Date.now();
|
|
108
126
|
const cookieOptions: any = {
|
|
109
127
|
httpOnly: true,
|
|
110
|
-
sameSite: isLocalhost ? 'lax' : '
|
|
128
|
+
sameSite: isLocalhost ? 'lax' : 'none',
|
|
111
129
|
maxAge,
|
|
112
130
|
};
|
|
113
131
|
if (!isLocalhost) {
|
package/src/user/user.service.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { getLocaleText } from '@hed-hog/api-locale';
|
|
|
2
2
|
import { PaginationService } from '@hed-hog/api-pagination';
|
|
3
3
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
BadRequestException,
|
|
6
|
+
Inject,
|
|
7
|
+
Injectable,
|
|
8
|
+
NotFoundException,
|
|
9
|
+
forwardRef,
|
|
10
10
|
} from '@nestjs/common';
|
|
11
11
|
import { ChallengeService } from '../challenge/challenge.service';
|
|
12
12
|
import { DeleteDTO } from '../dto/delete.dto';
|
|
@@ -415,7 +415,11 @@ export class UserService {
|
|
|
415
415
|
// Activity Tracking
|
|
416
416
|
// ==========================================
|
|
417
417
|
|
|
418
|
-
async registerUserActivity(userId: number, action: string) {
|
|
418
|
+
async registerUserActivity(userId: number | null | undefined, action: string) {
|
|
419
|
+
if (!userId || !Number.isInteger(userId) || userId <= 0) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
419
423
|
return this.prismaService.user_activity.create({
|
|
420
424
|
data: {
|
|
421
425
|
user_id: userId,
|