@hed-hog/core 0.0.85 → 0.0.87

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 (45) hide show
  1. package/dist/auth/auth.controller.d.ts +1 -0
  2. package/dist/auth/auth.controller.d.ts.map +1 -1
  3. package/dist/auth/auth.service.d.ts +1 -0
  4. package/dist/auth/auth.service.d.ts.map +1 -1
  5. package/dist/auth/auth.service.js +2 -1
  6. package/dist/auth/auth.service.js.map +1 -1
  7. package/dist/challenge/challenge.service.d.ts.map +1 -1
  8. package/dist/challenge/challenge.service.js +10 -6
  9. package/dist/challenge/challenge.service.js.map +1 -1
  10. package/dist/install/install.service.d.ts.map +1 -1
  11. package/dist/install/install.service.js +2 -0
  12. package/dist/install/install.service.js.map +1 -1
  13. package/dist/oauth/oauth.service.d.ts.map +1 -1
  14. package/dist/oauth/oauth.service.js +2 -0
  15. package/dist/oauth/oauth.service.js.map +1 -1
  16. package/dist/profile/profile.controller.d.ts +1 -0
  17. package/dist/profile/profile.controller.d.ts.map +1 -1
  18. package/dist/profile/profile.service.d.ts +1 -0
  19. package/dist/profile/profile.service.d.ts.map +1 -1
  20. package/dist/profile/profile.service.js +59 -5
  21. package/dist/profile/profile.service.js.map +1 -1
  22. package/dist/setting/setting.service.d.ts +1 -0
  23. package/dist/setting/setting.service.d.ts.map +1 -1
  24. package/dist/setting/setting.service.js +8 -2
  25. package/dist/setting/setting.service.js.map +1 -1
  26. package/dist/task/task.service.d.ts.map +1 -1
  27. package/dist/task/task.service.js +25 -1
  28. package/dist/task/task.service.js.map +1 -1
  29. package/dist/user/user.controller.d.ts +1 -0
  30. package/dist/user/user.controller.d.ts.map +1 -1
  31. package/dist/user/user.service.d.ts +2 -0
  32. package/dist/user/user.service.d.ts.map +1 -1
  33. package/dist/user/user.service.js +3 -2
  34. package/dist/user/user.service.js.map +1 -1
  35. package/hedhog/table/user_identifier.yaml +3 -0
  36. package/hedhog/table/user_identifier_challenge.yaml +0 -1
  37. package/package.json +4 -4
  38. package/src/auth/auth.service.ts +2 -2
  39. package/src/challenge/challenge.service.ts +11 -5
  40. package/src/install/install.service.ts +2 -0
  41. package/src/oauth/oauth.service.ts +2 -0
  42. package/src/profile/profile.service.ts +76 -6
  43. package/src/setting/setting.service.ts +8 -0
  44. package/src/task/task.service.ts +26 -2
  45. package/src/user/user.service.ts +3 -2
@@ -28,12 +28,18 @@ export class ChallengeService {
28
28
  user_id: userId,
29
29
  type: 'email',
30
30
  value: email,
31
+ verified_at: null,
31
32
  },
32
33
  select: { id: true }
33
34
  });
34
35
 
36
+ if (!identifier) {
37
+ throw new NotFoundException(getLocaleText('identifierNotFound', locale, 'Email identifier not found or already verified.'));
38
+ }
39
+
35
40
  const settings = await this.setting.getSettingValues([
36
- 'mfa-email-code-length'
41
+ 'mfa-email-code-length',
42
+ 'mfa-challenge-expiration-minutes'
37
43
  ]);
38
44
 
39
45
  const user = await this.prisma.user.findUnique({
@@ -53,15 +59,15 @@ export class ChallengeService {
53
59
 
54
60
  console.log({
55
61
  hash: codeHash,
56
- expires_at: new Date(Date.now() + 15 * 60000),
57
- user_identifier_id: identifier?.id ?? null,
62
+ expires_at: new Date(Date.now() + (settings['mfa-challenge-expiration-minutes'] || 15) * 60000),
63
+ user_identifier_id: identifier.id,
58
64
  })
59
65
 
60
66
  const challenge = await this.prisma.user_identifier_challenge.create({
61
67
  data: {
62
68
  hash: codeHash,
63
- expires_at: new Date(Date.now() + 15 * 60000),
64
- user_identifier_id: identifier?.id ?? null,
69
+ expires_at: new Date(Date.now() + (settings['mfa-challenge-expiration-minutes'] || 15) * 60000),
70
+ user_identifier_id: identifier.id,
65
71
  },
66
72
  select: {
67
73
  id: true,
@@ -208,6 +208,7 @@ export class InstallService {
208
208
  some: {
209
209
  type: 'email',
210
210
  value: email,
211
+ enabled: true
211
212
  }
212
213
  }
213
214
  }
@@ -228,6 +229,7 @@ export class InstallService {
228
229
  create: {
229
230
  type: 'email',
230
231
  value: email,
232
+ enabled: true,
231
233
  }
232
234
  },
233
235
  user_credential: {
@@ -128,6 +128,7 @@ export class OAuthService {
128
128
  where: {
129
129
  type: 'email',
130
130
  value: profile.email,
131
+ enabled: true
131
132
  },
132
133
  include: { user: true },
133
134
  });
@@ -189,6 +190,7 @@ export class OAuthService {
189
190
  type: 'email',
190
191
  value: profile.email,
191
192
  verified_at: new Date(),
193
+ enabled: true
192
194
  },
193
195
  },
194
196
  user_account: {
@@ -45,11 +45,34 @@ export class ProfileService {
45
45
 
46
46
  async changeEmail(locale: string, userId: number, { email, password, pin }: ChangeEmailDto) {
47
47
 
48
+ const checkAlreadyExists = await this.prisma.user_identifier.findFirst({
49
+ where: { type: 'email', value: email, enabled: true},
50
+ });
51
+
52
+ if (checkAlreadyExists) {
53
+ throw new BadRequestException(getLocaleText('emailAlreadyInUse', locale, 'The provided email is already in use.'));
54
+ }
55
+
48
56
  const settings = await this.setting.getSettingValues([
49
57
  'require-email-verification'
50
58
  ]);
51
59
 
52
60
  if (settings['require-email-verification'] === true && !pin) {
61
+
62
+ const checkIfExists = await this.prisma.user_identifier.findFirst({
63
+ where: { type: 'email', value: email, user_id: userId },
64
+ });
65
+
66
+ if (!checkIfExists) {
67
+ await this.prisma.user_identifier.create({
68
+ data: {
69
+ type: 'email',
70
+ value: email,
71
+ user_id: userId,
72
+ },
73
+ })
74
+ }
75
+
53
76
  return {
54
77
  requireEmailVerification: settings['require-email-verification'] === true
55
78
  }
@@ -68,13 +91,33 @@ export class ProfileService {
68
91
 
69
92
  if (!await this.security.validatePassword(locale, credentials, password)) {
70
93
  throw new BadRequestException(getLocaleText('accessDenied', locale, 'Access denied.'));
71
- }
94
+ }
95
+
96
+ await this.prisma.$transaction( async (tx) => {
97
+
98
+ const userIdentifier = await tx.user_identifier.findFirst({
99
+ where: { user_id: userId, type: 'email', enabled: true },
100
+ });
101
+
102
+ if (!userIdentifier) {
103
+ throw new NotFoundException(getLocaleText('emailIdentifierNotFound', locale, 'Email identifier not found.'));
104
+ }
105
+
106
+ await tx.user_identifier.updateMany({
107
+ where: { user_id: userId, type: 'email' },
108
+ data: { value: email, verified_at: null, enabled: true},
109
+ });
110
+
111
+ await tx.user_identifier_challenge.deleteMany({
112
+ where: {
113
+ user_identifier_id: userIdentifier.id
114
+ }
115
+ });
72
116
 
73
- await this.prisma.user_identifier.updateMany({
74
- where: { user_id: userId, type: 'email' },
75
- data: { value: email, verified_at: null},
76
117
  });
77
118
 
119
+
120
+
78
121
  return {
79
122
  requireEmailVerification: settings['require-email-verification'] === true
80
123
  }
@@ -110,15 +153,42 @@ export class ProfileService {
110
153
 
111
154
  await this.prisma.$transaction(async (tx) => {
112
155
 
156
+ const challenge = await tx.user_identifier_challenge.findUnique({
157
+ where: { id: challengeId },
158
+ })
159
+
160
+ let userIdentifierId = challenge.user_identifier_id;
161
+
162
+ if (!userIdentifierId) {
163
+ const userIdentifier = await tx.user_identifier.findFirst({
164
+ where: { user_id: userId, type: 'email' },
165
+ select: { id: true }
166
+ });
167
+ userIdentifierId = userIdentifier.id;
168
+ await tx.user_identifier_challenge.update({
169
+ where: { id: challengeId },
170
+ data: { user_identifier_id: userIdentifierId },
171
+ });
172
+ }
173
+
113
174
  await tx.user_identifier_challenge.update({
114
175
  where: { id: challengeId },
115
176
  data: { verified_at: verifiedAt },
116
177
  });
117
178
 
118
179
  await tx.user_identifier.update({
119
- where: { id: challenge.user_identifier_id },
180
+ where: { id: userIdentifierId },
120
181
  data: {
121
182
  verified_at: verifiedAt,
183
+ enabled: true
184
+ }
185
+ });
186
+
187
+ await tx.user_identifier.deleteMany({
188
+ where: {
189
+ user_id: userId,
190
+ type: 'email',
191
+ id: { not: userIdentifierId }
122
192
  }
123
193
  });
124
194
 
@@ -137,7 +207,7 @@ export class ProfileService {
137
207
 
138
208
  async getEmailIdentifier(userId: number) {
139
209
  return this.prisma.user_identifier.findFirst({
140
- where: { user_id: userId, type: 'email' },
210
+ where: { user_id: userId, type: 'email', enabled: true},
141
211
  });
142
212
  }
143
213
 
@@ -7,6 +7,7 @@ import {
7
7
  forwardRef,
8
8
  Inject,
9
9
  Injectable,
10
+ Logger,
10
11
  } from '@nestjs/common';
11
12
  import * as pako from 'pako';
12
13
  import { CreateDTO } from './dto/create.dto';
@@ -17,6 +18,7 @@ import { UpdateDTO } from './dto/update.dto';
17
18
  @Injectable()
18
19
  export class SettingService {
19
20
 
21
+ private readonly logger = new Logger(SettingService.name);
20
22
  private cachedSettings: Record<string, any> = {};
21
23
 
22
24
  constructor(
@@ -579,6 +581,8 @@ export class SettingService {
579
581
  this.cachedSettings &&
580
582
  slugs.every(s => Object.prototype.hasOwnProperty.call(this.cachedSettings, s))
581
583
  ) {
584
+ this.logger.warn('Returning settings from cache');
585
+
582
586
  // Retorna apenas os slugs requisitados do cache
583
587
  const cached: Record<string, any> = {};
584
588
  slugs.forEach(s => {
@@ -587,6 +591,8 @@ export class SettingService {
587
591
  return cached;
588
592
  }
589
593
 
594
+ this.logger.warn('Fetching settings from database');
595
+
590
596
  slug = Array.isArray(slug) ? slug : [slug];
591
597
 
592
598
  let setting = await this.prismaService.setting.findMany({
@@ -661,6 +667,7 @@ export class SettingService {
661
667
  }
662
668
 
663
669
  setCache(value: Record<string, any>) {
670
+ this.logger.warn(`Setting settings cache with ${Object.keys(value).length} items`);
664
671
  Object.keys(value).forEach(key => {
665
672
  this.cachedSettings[key] = value[key];
666
673
  });
@@ -668,6 +675,7 @@ export class SettingService {
668
675
  }
669
676
 
670
677
  clearCache() {
678
+ this.logger.warn('Clearing settings cache');
671
679
  this.cachedSettings = {};
672
680
  }
673
681
 
@@ -1,13 +1,13 @@
1
+ import { PrismaService } from '@hed-hog/api-prisma';
1
2
  import { Injectable, Logger } from '@nestjs/common';
2
3
  import { Cron, CronExpression } from '@nestjs/schedule';
3
- import { PrismaService } from '@hed-hog/api-prisma';
4
4
 
5
5
  @Injectable()
6
6
  export class TasksService {
7
7
  private readonly logger = new Logger(TasksService.name);
8
8
 
9
9
  constructor(private readonly prismaService: PrismaService) {
10
-
10
+ this.clear();
11
11
  }
12
12
 
13
13
  @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
@@ -49,6 +49,30 @@ export class TasksService {
49
49
  `;
50
50
  this.logger.verbose(`Expired sessions cleaned up in ${Date.now() - startAt}ms`);
51
51
  this.logger.verbose('++++++++++++++++++++++++++++++++++');
52
+ this.logger.verbose('Starting cleanup of expired user identifier challenges');
53
+ startAt = Date.now();
54
+ await this.prismaService.$queryRaw`
55
+ WITH expired_challenges AS (
56
+ SELECT user_identifier_id
57
+ FROM user_identifier_challenge
58
+ WHERE expires_at < now() AND verified_at IS NULL
59
+ ORDER BY user_identifier_id
60
+ LIMIT 20000
61
+ ),
62
+ deleted_challenges AS (
63
+ DELETE FROM user_identifier_challenge c
64
+ USING expired_challenges
65
+ WHERE c.user_identifier_id = expired_challenges.user_identifier_id AND c.verified_at IS NULL
66
+ RETURNING c.user_identifier_id
67
+ )
68
+ DELETE FROM user_identifier ui
69
+ USING deleted_challenges
70
+ WHERE ui.id = deleted_challenges.user_identifier_id
71
+ AND ui.verified_at IS NULL;
72
+ `;
73
+ this.logger.verbose(`Expired user identifier challenges and unverified identifiers cleaned up in ${Date.now() - startAt}ms`);
74
+ this.logger.verbose('++++++++++++++++++++++++++++++++++');
75
+
52
76
  this.logger.verbose('Finished scheduled cleanup tasks');
53
77
  this.logger.verbose('++++++++++++++++++++++++++++++++++');
54
78
  }
@@ -211,7 +211,7 @@ export class UserService {
211
211
  const user = await tx.user.create({
212
212
  data: {
213
213
  name,
214
- user_identifier: { create: { type: 'email', value: email } },
214
+ user_identifier: { create: { type: 'email', value: email, enabled: true } },
215
215
  user_credential: { create: { type: 'password', hash: passwordHash } },
216
216
  user_mfa: {
217
217
  create: {
@@ -261,7 +261,8 @@ export class UserService {
261
261
  user_identifier: {
262
262
  some: {
263
263
  type: 'email',
264
- value: email.toLowerCase().trim()
264
+ value: email.toLowerCase().trim(),
265
+ enabled: true
265
266
  }
266
267
  }
267
268
  },