@hed-hog/core 0.0.304 → 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.
Files changed (112) hide show
  1. package/README.md +84 -36
  2. package/dist/auth/auth.controller.d.ts +0 -3
  3. package/dist/auth/auth.controller.d.ts.map +1 -1
  4. package/dist/auth/auth.controller.js +4 -7
  5. package/dist/auth/auth.controller.js.map +1 -1
  6. package/dist/auth/auth.service.d.ts.map +1 -1
  7. package/dist/auth/auth.service.js +6 -3
  8. package/dist/auth/auth.service.js.map +1 -1
  9. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +9 -0
  10. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts.map +1 -1
  11. package/dist/dashboard/dashboard-core/dashboard-core.controller.js +12 -0
  12. package/dist/dashboard/dashboard-core/dashboard-core.controller.js.map +1 -1
  13. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +7 -0
  14. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts.map +1 -1
  15. package/dist/dashboard/dashboard-core/dashboard-core.service.js +45 -1
  16. package/dist/dashboard/dashboard-core/dashboard-core.service.js.map +1 -1
  17. package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts +9 -0
  18. package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts.map +1 -0
  19. package/dist/mail-sent/dto/query-mail-sent-list.dto.js +49 -0
  20. package/dist/mail-sent/dto/query-mail-sent-list.dto.js.map +1 -0
  21. package/dist/mail-sent/mail-sent.controller.d.ts +24 -2
  22. package/dist/mail-sent/mail-sent.controller.d.ts.map +1 -1
  23. package/dist/mail-sent/mail-sent.controller.js +5 -3
  24. package/dist/mail-sent/mail-sent.controller.js.map +1 -1
  25. package/dist/mail-sent/mail-sent.service.d.ts +28 -3
  26. package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
  27. package/dist/mail-sent/mail-sent.service.js +108 -3
  28. package/dist/mail-sent/mail-sent.service.js.map +1 -1
  29. package/dist/oauth/oauth-callback-coordinator.service.d.ts +18 -0
  30. package/dist/oauth/oauth-callback-coordinator.service.d.ts.map +1 -0
  31. package/dist/oauth/oauth-callback-coordinator.service.js +137 -0
  32. package/dist/oauth/oauth-callback-coordinator.service.js.map +1 -0
  33. package/dist/oauth/oauth.controller.d.ts +4 -2
  34. package/dist/oauth/oauth.controller.d.ts.map +1 -1
  35. package/dist/oauth/oauth.controller.js +39 -11
  36. package/dist/oauth/oauth.controller.js.map +1 -1
  37. package/dist/oauth/oauth.errors.d.ts +16 -0
  38. package/dist/oauth/oauth.errors.d.ts.map +1 -0
  39. package/dist/oauth/oauth.errors.js +30 -0
  40. package/dist/oauth/oauth.errors.js.map +1 -0
  41. package/dist/oauth/oauth.module.d.ts.map +1 -1
  42. package/dist/oauth/oauth.module.js +10 -1
  43. package/dist/oauth/oauth.module.js.map +1 -1
  44. package/dist/oauth/oauth.service.d.ts +9 -1
  45. package/dist/oauth/oauth.service.d.ts.map +1 -1
  46. package/dist/oauth/oauth.service.js +97 -24
  47. package/dist/oauth/oauth.service.js.map +1 -1
  48. package/dist/oauth/providers/abstract.provider.d.ts +6 -0
  49. package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
  50. package/dist/oauth/providers/abstract.provider.js +34 -0
  51. package/dist/oauth/providers/abstract.provider.js.map +1 -1
  52. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +3 -0
  53. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
  54. package/dist/oauth/providers/microsoft-entra-id.provider.js +40 -22
  55. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  56. package/dist/security/security.service.d.ts +1 -0
  57. package/dist/security/security.service.d.ts.map +1 -1
  58. package/dist/security/security.service.js +9 -10
  59. package/dist/security/security.service.js.map +1 -1
  60. package/dist/session/session.service.d.ts +5 -1
  61. package/dist/session/session.service.d.ts.map +1 -1
  62. package/dist/session/session.service.js +76 -10
  63. package/dist/session/session.service.js.map +1 -1
  64. package/dist/setting/setting.service.js +4 -4
  65. package/dist/setting/setting.service.js.map +1 -1
  66. package/dist/token/token.service.d.ts +1 -0
  67. package/dist/token/token.service.d.ts.map +1 -1
  68. package/dist/token/token.service.js +23 -12
  69. package/dist/token/token.service.js.map +1 -1
  70. package/dist/user/user.service.d.ts +1 -1
  71. package/dist/user/user.service.d.ts.map +1 -1
  72. package/dist/user/user.service.js +3 -0
  73. package/dist/user/user.service.js.map +1 -1
  74. package/hedhog/data/dashboard.yaml +2 -2
  75. package/hedhog/data/route.yaml +8 -0
  76. package/hedhog/frontend/app/account/components/change-email-form.tsx.ejs +87 -8
  77. package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +90 -10
  78. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +145 -31
  79. package/hedhog/frontend/app/configurations/layout.tsx.ejs +1 -1
  80. package/hedhog/frontend/app/dashboard/[slug]/dashboard-content.tsx.ejs +67 -2
  81. package/hedhog/frontend/app/dashboard/components/add-widget-selector-dialog.tsx.ejs +13 -9
  82. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +250 -45
  83. package/hedhog/frontend/app/dashboard/management/tabs/component-roles-tab.tsx.ejs +111 -15
  84. package/hedhog/frontend/app/dashboard/management/tabs/dashboard-roles-tab.tsx.ejs +110 -15
  85. package/hedhog/frontend/app/dashboard/management/tabs/items-tab.tsx.ejs +113 -15
  86. package/hedhog/frontend/app/mail/log/page.tsx.ejs +481 -70
  87. package/hedhog/frontend/app/menu/page.tsx.ejs +202 -30
  88. package/hedhog/frontend/app/roles/menus.tsx.ejs +1 -1
  89. package/hedhog/frontend/app/roles/page.tsx.ejs +288 -104
  90. package/hedhog/frontend/app/users/page.tsx.ejs +168 -23
  91. package/hedhog/frontend/messages/en.json +45 -6
  92. package/hedhog/frontend/messages/pt.json +45 -6
  93. package/package.json +5 -5
  94. package/src/auth/auth.controller.ts +15 -19
  95. package/src/auth/auth.service.ts +13 -8
  96. package/src/dashboard/dashboard-core/dashboard-core.controller.ts +22 -9
  97. package/src/dashboard/dashboard-core/dashboard-core.service.ts +75 -1
  98. package/src/mail-sent/dto/query-mail-sent-list.dto.ts +30 -0
  99. package/src/mail-sent/mail-sent.controller.ts +14 -2
  100. package/src/mail-sent/mail-sent.service.ts +159 -4
  101. package/src/oauth/oauth-callback-coordinator.service.ts +158 -0
  102. package/src/oauth/oauth.controller.ts +58 -1
  103. package/src/oauth/oauth.errors.ts +32 -0
  104. package/src/oauth/oauth.module.ts +10 -1
  105. package/src/oauth/oauth.service.ts +178 -28
  106. package/src/oauth/providers/abstract.provider.ts +64 -0
  107. package/src/oauth/providers/microsoft-entra-id.provider.ts +92 -56
  108. package/src/security/security.service.ts +12 -10
  109. package/src/session/session.service.ts +84 -9
  110. package/src/setting/setting.service.ts +10 -10
  111. package/src/token/token.service.ts +29 -11
  112. 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.prisma.user_session.update({
95
- where: { id: session.id },
96
- data: {
97
- revoked_at: new Date(),
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
- return this.create(locale, session.user_id, ipAddress, userAgent);
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
- return this.prisma.user_session.updateMany({
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
- BadRequestException,
7
- forwardRef,
8
- Inject,
9
- Injectable,
10
- Logger,
11
- NotFoundException
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.warn('Returning settings from cache');
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.warn('Fetching settings from database');
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.warn(`Setting settings cache with ${Object.keys(value).length} items`);
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.warn('Clearing settings cache');
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
- if (settings['url']) {
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' : 'strict',
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' : 'strict',
128
+ sameSite: isLocalhost ? 'lax' : 'none',
111
129
  maxAge,
112
130
  };
113
131
  if (!isLocalhost) {
@@ -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
- BadRequestException,
6
- Inject,
7
- Injectable,
8
- NotFoundException,
9
- forwardRef,
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,