@lenne.tech/nest-server 2.0.4 → 3.1.2

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 (51) hide show
  1. package/dist/config.env.js +14 -1
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/helpers/service.helper.d.ts +3 -1
  4. package/dist/core/common/helpers/service.helper.js +23 -1
  5. package/dist/core/common/helpers/service.helper.js.map +1 -1
  6. package/dist/core/common/interfaces/mailjet-options.interface.d.ts +4 -0
  7. package/dist/core/common/interfaces/mailjet-options.interface.js +3 -0
  8. package/dist/core/common/interfaces/mailjet-options.interface.js.map +1 -0
  9. package/dist/core/common/interfaces/server-options.interface.d.ts +4 -0
  10. package/dist/core/common/services/mailjet.service.d.ts +15 -0
  11. package/dist/core/common/services/mailjet.service.js +64 -0
  12. package/dist/core/common/services/mailjet.service.js.map +1 -0
  13. package/dist/core/modules/user/core-basic-user.service.d.ts +1 -1
  14. package/dist/core/modules/user/core-user.model.d.ts +3 -0
  15. package/dist/core/modules/user/core-user.model.js +17 -0
  16. package/dist/core/modules/user/core-user.model.js.map +1 -1
  17. package/dist/core/modules/user/core-user.service.d.ts +10 -4
  18. package/dist/core/modules/user/core-user.service.js +72 -3
  19. package/dist/core/modules/user/core-user.service.js.map +1 -1
  20. package/dist/core.module.js +3 -1
  21. package/dist/core.module.js.map +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/server/modules/user/user.model.d.ts +1 -1
  26. package/dist/server/modules/user/user.resolver.d.ts +3 -0
  27. package/dist/server/modules/user/user.resolver.js +31 -0
  28. package/dist/server/modules/user/user.resolver.js.map +1 -1
  29. package/dist/server/modules/user/user.service.js +3 -4
  30. package/dist/server/modules/user/user.service.js.map +1 -1
  31. package/dist/server/server.module.js.map +1 -1
  32. package/dist/test/test.helper.js +1 -1
  33. package/dist/test/test.helper.js.map +1 -1
  34. package/dist/tsconfig.build.tsbuildinfo +1 -1
  35. package/package.json +42 -48
  36. package/src/config.env.ts +14 -1
  37. package/src/core/common/helpers/service.helper.ts +34 -4
  38. package/src/core/common/interfaces/mailjet-options.interface.ts +4 -0
  39. package/src/core/common/interfaces/server-options.interface.ts +12 -0
  40. package/src/core/common/services/mailjet.service.ts +84 -0
  41. package/src/core/modules/user/core-basic-user.service.ts +3 -3
  42. package/src/core/modules/user/core-user.model.ts +20 -0
  43. package/src/core/modules/user/core-user.service.ts +131 -10
  44. package/src/core.module.ts +3 -1
  45. package/src/index.ts +1 -0
  46. package/src/server/modules/user/user.resolver.ts +24 -0
  47. package/src/server/modules/user/user.service.ts +6 -4
  48. package/src/server/server.module.ts +0 -1
  49. package/src/templates/password-reset.ejs +3 -0
  50. package/src/templates/welcome.ejs +3 -2
  51. package/src/test/test.helper.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "2.0.4",
3
+ "version": "3.1.2",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -49,48 +49,40 @@
49
49
  "url": "https://github.com/lenneTech/nest-server/issues"
50
50
  },
51
51
  "engines": {
52
- "node": ">= 14.17.0"
53
- },
54
- "peerDependencies": {
55
- "@nestjs/testing": "8.1.2",
56
- "@nestjs/core": "8.1.2",
57
- "@nestjs/common": "8.1.2",
58
- "@nestjs/graphql": "9.1.1",
59
- "@nestjs/jwt": "8.0.0",
60
- "@nestjs/mongoose": "9.0.1",
61
- "@nestjs/passport": "8.0.1",
62
- "@nestjs/platform-express": "8.1.2"
52
+ "node": ">= 16.13.0"
63
53
  },
64
54
  "dependencies": {
65
- "@apollo/federation": "0.33.6",
55
+ "@apollo/federation": "0.33.9",
66
56
  "@apollo/gateway": "0.42.3",
67
- "@nestjs/testing": "8.1.2",
68
- "@nestjs/core": "8.1.2",
69
- "@nestjs/common": "8.1.2",
70
- "@nestjs/graphql": "9.1.1",
57
+ "@nestjs/common": "8.2.6",
58
+ "@nestjs/core": "8.2.6",
59
+ "@nestjs/graphql": "9.1.2",
71
60
  "@nestjs/jwt": "8.0.0",
72
- "@nestjs/mongoose": "9.0.1",
73
- "@nestjs/passport": "8.0.1",
74
- "@nestjs/platform-express": "8.1.2",
61
+ "@nestjs/mongoose": "9.0.2",
62
+ "@nestjs/passport": "8.1.0",
63
+ "@nestjs/platform-express": "8.2.6",
64
+ "@nestjs/testing": "8.2.6",
65
+ "@shelf/jest-mongodb": "2.2.0",
75
66
  "@types/ejs": "3.1.0",
76
- "@types/jest": "27.0.2",
77
- "@types/lodash": "4.14.176",
67
+ "@types/jest": "27.4.0",
68
+ "@types/lodash": "4.14.178",
78
69
  "@types/multer": "1.4.7",
79
- "@types/node": "16.11.6",
70
+ "@types/node": "16.11.21",
71
+ "@types/node-mailjet": "3.3.8",
80
72
  "@types/nodemailer": "6.4.4",
81
73
  "@types/passport": "1.0.7",
82
74
  "@types/supertest": "2.0.11",
83
- "@typescript-eslint/eslint-plugin": "5.3.0",
84
- "@typescript-eslint/parser": "5.3.0",
85
- "apollo-server-core": "3.4.0",
86
- "apollo-server-express": "3.4.0",
75
+ "@typescript-eslint/eslint-plugin": "5.10.0",
76
+ "@typescript-eslint/parser": "5.10.0",
77
+ "apollo-server-core": "3.6.2",
78
+ "apollo-server-express": "3.6.2",
87
79
  "bcrypt": "5.0.1",
88
- "class-transformer": "0.4.0",
89
- "class-validator": "0.13.1",
80
+ "class-transformer": "0.5.1",
81
+ "class-validator": "0.13.2",
90
82
  "coffeescript": "2.6.1",
91
83
  "ejs": "3.1.6",
92
- "fastify": "3.22.1",
93
- "graphql": "15.7.2",
84
+ "fastify": "3.27.0",
85
+ "graphql": "15.8.0",
94
86
  "graphql-subscriptions": "2.0.0",
95
87
  "grunt": "1.4.1",
96
88
  "grunt-bg-shell": "2.3.3",
@@ -98,33 +90,35 @@
98
90
  "grunt-contrib-watch": "1.1.0",
99
91
  "grunt-sync": "0.8.2",
100
92
  "husky": "7.0.4",
101
- "jest": "27.3.1",
102
- "json-to-graphql-query": "2.1.0",
103
- "light-my-request": "4.6.0",
93
+ "jest": "27.4.7",
94
+ "json-to-graphql-query": "2.2.0",
95
+ "light-my-request": "4.7.0",
104
96
  "lodash": "4.17.21",
105
- "mongoose": "6.0.12",
106
- "multer": "1.4.3",
107
- "nodemailer": "6.7.0",
108
- "nodemon": "2.0.14",
109
- "passport": "0.4.1",
97
+ "mongodb": "4.3.0",
98
+ "mongoose": "6.1.7",
99
+ "multer": "1.4.4",
100
+ "node-mailjet": "3.3.5",
101
+ "nodemailer": "6.7.2",
102
+ "nodemon": "2.0.15",
103
+ "passport": "0.5.2",
110
104
  "passport-jwt": "4.0.0",
111
105
  "reflect-metadata": "0.1.13",
112
106
  "rimraf": "3.0.2",
113
- "rxjs": "7.4.0",
114
- "supertest": "6.1.6",
115
- "ts-morph": "12.2.0",
107
+ "rxjs": "7.5.2",
108
+ "supertest": "6.2.2",
109
+ "ts-morph": "13.0.3",
116
110
  "ts-node": "10.4.0",
117
- "tsconfig-paths": "3.11.0"
111
+ "tsconfig-paths": "3.12.0"
118
112
  },
119
113
  "devDependencies": {
120
- "eslint": "8.1.0",
114
+ "eslint": "8.7.0",
121
115
  "eslint-config-prettier": "8.3.0",
122
116
  "find-file-up": "2.0.1",
123
117
  "pm2": "5.1.2",
124
- "prettier": "2.4.1",
125
- "pretty-quick": "3.1.1",
126
- "ts-jest": "27.0.7",
127
- "typescript": "4.4.4"
118
+ "prettier": "2.5.1",
119
+ "pretty-quick": "3.1.3",
120
+ "ts-jest": "27.1.3",
121
+ "typescript": "4.5.5"
128
122
  },
129
123
  "jest": {
130
124
  "collectCoverage": true,
package/src/config.env.ts CHANGED
@@ -19,10 +19,16 @@ const config: { [env: string]: IServerOptions } = {
19
19
  port: 587,
20
20
  secure: false,
21
21
  },
22
+ mailjet: {
23
+ api_key_public: 'MAILJET_API_KEY_PUBLIC',
24
+ api_key_private: 'MAILJET_API_KEY_PRIVATE',
25
+ },
22
26
  defaultSender: {
23
27
  email: 'rebeca68@ethereal.email',
24
28
  name: 'Rebeca Sixtyeight',
25
29
  },
30
+ verificationLink: 'http://localhost:4200/user/verification',
31
+ passwordResetLink: 'http://localhost:4200/user/password-reset',
26
32
  },
27
33
  env: 'development',
28
34
  graphQl: {
@@ -60,10 +66,16 @@ const config: { [env: string]: IServerOptions } = {
60
66
  port: 587,
61
67
  secure: false,
62
68
  },
69
+ mailjet: {
70
+ api_key_public: 'MAILJET_API_KEY_PUBLIC',
71
+ api_key_private: 'MAILJET_API_KEY_PRIVATE',
72
+ },
63
73
  defaultSender: {
64
74
  email: 'rebeca68@ethereal.email',
65
75
  name: 'Rebeca Sixtyeight',
66
76
  },
77
+ verificationLink: 'http://localhost:4200/user/verification',
78
+ passwordResetLink: 'http://localhost:4200/user/password-reset',
67
79
  },
68
80
  env: 'productive',
69
81
  graphQl: {
@@ -93,7 +105,8 @@ const config: { [env: string]: IServerOptions } = {
93
105
  *
94
106
  * default: development
95
107
  */
96
- const envConfig = config[process.env.NODE_ENV || 'development'] || config.development;
108
+ const envConfig = config[process.env['NODE' + '_ENV'] || 'development'] || config.development;
109
+ console.log('Server starts in mode: ', process.env['NODE' + '_ENV'] || 'development');
97
110
 
98
111
  /**
99
112
  * Export envConfig as default
@@ -1,4 +1,7 @@
1
+ import { UnauthorizedException } from '@nestjs/common';
1
2
  import * as bcrypt from 'bcrypt';
3
+ import * as _ from 'lodash';
4
+ import { RoleEnum } from '../enums/role.enum';
2
5
 
3
6
  /**
4
7
  * Helper class for services
@@ -9,13 +12,15 @@ export class ServiceHelper {
9
12
  */
10
13
  static async prepareInput(
11
14
  input: { [key: string]: any },
12
- currentUser: { id: string },
15
+ currentUser: { [key: string]: any; id: string },
13
16
  options: { [key: string]: any; create?: boolean; clone?: boolean } = {},
14
17
  ...args: any[]
15
18
  ) {
16
19
  // Configuration
17
20
  const config = {
21
+ checkRoles: true,
18
22
  clone: false,
23
+ create: false,
19
24
  ...options,
20
25
  };
21
26
 
@@ -24,7 +29,21 @@ export class ServiceHelper {
24
29
  input = JSON.parse(JSON.stringify(input));
25
30
  }
26
31
 
27
- // Has password
32
+ // Process roles
33
+ if (input.roles && config.checkRoles && (!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))) {
34
+ if (!(currentUser as any)?.roles) {
35
+ throw new UnauthorizedException('Missing roles of current user');
36
+ } else {
37
+ const allowedRoles = _.intersection(input.roles, (currentUser as any).roles);
38
+ if (allowedRoles.length !== input.roles.length) {
39
+ const missingRoles = _.difference(input.roles, (currentUser as any).roles);
40
+ throw new UnauthorizedException('Current user not allowed setting roles: ' + missingRoles);
41
+ }
42
+ input.roles = allowedRoles;
43
+ }
44
+ }
45
+
46
+ // Hash password
28
47
  if (input.password) {
29
48
  input.password = await bcrypt.hash((input as any).password, 10);
30
49
  }
@@ -46,11 +65,11 @@ export class ServiceHelper {
46
65
  /**
47
66
  * Prepare output before return
48
67
  */
49
- static async prepareOutput(
68
+ static async prepareOutput<T = Record<string, any>>(
50
69
  output: any,
51
70
  userModel: new () => any,
52
71
  userService: any,
53
- options: { [key: string]: any; clone?: boolean } = {},
72
+ options: { [key: string]: any; clone?: boolean; targetModel?: Partial<T> } = {},
54
73
  ...args: any[]
55
74
  ) {
56
75
  // Configuration
@@ -64,9 +83,20 @@ export class ServiceHelper {
64
83
  output = JSON.parse(JSON.stringify(output));
65
84
  }
66
85
 
86
+ // Map output if target model exist
87
+ if (options.targetModel) {
88
+ (options.targetModel as any).map(output);
89
+ }
90
+
67
91
  // Remove password if exists
68
92
  delete output.password;
69
93
 
94
+ // Remove verification token if exists
95
+ delete output.verificationToken;
96
+
97
+ // Remove password reset token if exists
98
+ delete output.passwordResetToken;
99
+
70
100
  // Return prepared user
71
101
  return output;
72
102
  }
@@ -0,0 +1,4 @@
1
+ export interface MailjetOptions {
2
+ api_key_public: string;
3
+ api_key_private: string;
4
+ }
@@ -3,6 +3,7 @@ import { JwtModuleOptions } from '@nestjs/jwt';
3
3
  import { ServeStaticOptions } from '@nestjs/platform-express/interfaces/serve-static-options.interface';
4
4
  import * as SMTPTransport from 'nodemailer/lib/smtp-transport';
5
5
  import { MongooseModuleOptions } from '@nestjs/mongoose/dist/interfaces/mongoose-options.interface';
6
+ import { MailjetOptions } from './mailjet-options.interface';
6
7
 
7
8
  /**
8
9
  * Options for the server
@@ -101,6 +102,17 @@ export interface IServerOptions {
101
102
  */
102
103
  smtp?: SMTPTransport | SMTPTransport.Options | string;
103
104
 
105
+ mailjet?: MailjetOptions;
106
+ /**
107
+ * Verification link for email
108
+ */
109
+ verificationLink?: string;
110
+
111
+ /**
112
+ * Password reset link for email
113
+ */
114
+ passwordResetLink?: string;
115
+
104
116
  /**
105
117
  * Data for default sender
106
118
  */
@@ -0,0 +1,84 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { ConfigService } from './config.service';
3
+ import * as mailjet from 'node-mailjet';
4
+
5
+ /**
6
+ * Mailjet service
7
+ */
8
+ @Injectable()
9
+ export class MailjetService {
10
+ /**
11
+ * Inject services
12
+ */
13
+ constructor(protected configService: ConfigService) {}
14
+
15
+ /**
16
+ * Send a mail
17
+ */
18
+ public async sendMail(
19
+ recipients: string | string[],
20
+ subject: string,
21
+ templateId: number,
22
+ config: {
23
+ senderEmail?: string;
24
+ senderName?: string;
25
+ attachments?: mailjet.Email.Attachment[];
26
+ templateData?: { [key: string]: any };
27
+ sandbox?: boolean;
28
+ }
29
+ ): Promise<mailjet.Email.PostResponse> {
30
+ // Process config
31
+ const { senderName, senderEmail, templateData, attachments, sandbox } = {
32
+ senderEmail: this.configService.get('email.defaultSender.email'),
33
+ senderName: this.configService.get('email.defaultSender.name'),
34
+ sandbox: false,
35
+ attachments: null,
36
+ templateData: null,
37
+ ...config,
38
+ };
39
+
40
+ // Parse recipients
41
+ let to;
42
+ if (Array.isArray(recipients)) {
43
+ to = [];
44
+ for (const recipient of recipients) {
45
+ to.push({ Email: recipient });
46
+ }
47
+ } else {
48
+ to = [{ Email: recipients }];
49
+ }
50
+
51
+ // Parse body for mailjet request
52
+ const body: mailjet.Email.SendParams = {
53
+ Messages: [
54
+ {
55
+ From: {
56
+ Email: senderEmail,
57
+ Name: senderName,
58
+ },
59
+ To: to,
60
+ TemplateID: templateId,
61
+ TemplateLanguage: true,
62
+ Variables: templateData,
63
+ Subject: subject,
64
+ Attachments: attachments,
65
+ },
66
+ ],
67
+ SandboxMode: sandbox,
68
+ };
69
+
70
+ let connection: mailjet.Email.Client;
71
+ try {
72
+ // Connect to mailjet
73
+ connection = await mailjet.connect(
74
+ this.configService.get('email.mailjet.api_key_public'),
75
+ this.configService.get('email.mailjet.api_key_private')
76
+ );
77
+ } catch (e) {
78
+ throw new Error('Cannot connect to mailjet.');
79
+ }
80
+
81
+ // Send mail with mailjet
82
+ return connection.post('send', { version: 'v3.1' }).request(body);
83
+ }
84
+ }
@@ -16,9 +16,9 @@ const pubSub = new PubSub();
16
16
  * User service
17
17
  */
18
18
  export abstract class CoreBasicUserService<
19
- TUser = CoreUserModel,
20
- TUserInput = CoreUserInput,
21
- TUserCreateInput = CoreUserCreateInput
19
+ TUser extends CoreUserModel,
20
+ TUserInput extends CoreUserInput,
21
+ TUserCreateInput extends CoreUserCreateInput
22
22
  > {
23
23
  protected readonly model: ICorePersistenceModel;
24
24
 
@@ -59,6 +59,26 @@ export abstract class CoreUserModel extends CorePersistenceModel {
59
59
  @Prop()
60
60
  username: string = undefined;
61
61
 
62
+ /**
63
+ * Password reset token of the user
64
+ */
65
+ @IsOptional()
66
+ @Prop()
67
+ passwordResetToken: string = undefined;
68
+
69
+ /**
70
+ * Verification token of the user
71
+ */
72
+ @IsOptional()
73
+ @Prop()
74
+ verificationToken: string = undefined;
75
+
76
+ /**
77
+ * Verification of the user
78
+ */
79
+ @Prop({ type: Boolean })
80
+ verified = false;
81
+
62
82
  // ===================================================================================================================
63
83
  // Methods
64
84
  // ===================================================================================================================
@@ -1,13 +1,23 @@
1
- import { NotFoundException, UnprocessableEntityException } from '@nestjs/common';
1
+ import {
2
+ BadRequestException,
3
+ NotFoundException,
4
+ UnauthorizedException,
5
+ UnprocessableEntityException,
6
+ } from '@nestjs/common';
2
7
  import * as bcrypt from 'bcrypt';
3
8
  import { PubSub } from 'graphql-subscriptions';
4
9
  import { FilterArgs } from '../../common/args/filter.args';
10
+ import { RoleEnum } from '../../common/enums/role.enum';
5
11
  import { Filter } from '../../common/helpers/filter.helper';
6
12
  import { CoreBasicUserService } from './core-basic-user.service';
7
13
  import { CoreUserModel } from './core-user.model';
8
14
  import { CoreUserCreateInput } from './inputs/core-user-create.input';
9
15
  import { CoreUserInput } from './inputs/core-user.input';
10
16
  import { Model } from 'mongoose';
17
+ import * as _ from 'lodash';
18
+ import * as crypto from 'crypto';
19
+ import envConfig from '../../../config.env';
20
+ import { EmailService } from '../../common/services/email.service';
11
21
 
12
22
  // Subscription
13
23
  const pubSub = new PubSub();
@@ -16,11 +26,11 @@ const pubSub = new PubSub();
16
26
  * User service
17
27
  */
18
28
  export abstract class CoreUserService<
19
- TUser = CoreUserModel,
20
- TUserInput = CoreUserInput,
21
- TUserCreateInput = CoreUserCreateInput
29
+ TUser extends CoreUserModel,
30
+ TUserInput extends CoreUserInput,
31
+ TUserCreateInput extends CoreUserCreateInput
22
32
  > extends CoreBasicUserService<TUser, TUserInput, TUserCreateInput> {
23
- protected constructor(protected readonly userModel: Model<any>) {
33
+ protected constructor(protected readonly userModel: Model<any>, protected emailService: EmailService) {
24
34
  super(userModel);
25
35
  }
26
36
 
@@ -35,8 +45,11 @@ export abstract class CoreUserService<
35
45
  // Prepare input
36
46
  await this.prepareInput(input, currentUser, { create: true });
37
47
 
48
+ // Generate verification token
49
+ const newUser = { ...input, ...{ verificationToken: crypto.randomBytes(32).toString('hex') } };
50
+
38
51
  // Create new user
39
- const createdUser = new this.userModel(this.model.map(input));
52
+ const createdUser = new this.userModel(this.model.map(newUser));
40
53
 
41
54
  try {
42
55
  // Save created user
@@ -108,6 +121,93 @@ export abstract class CoreUserService<
108
121
  );
109
122
  }
110
123
 
124
+ /**
125
+ * Verify user with token
126
+ *
127
+ * @param token
128
+ */
129
+ async verify(token: string): Promise<boolean> {
130
+ const user = await this.userModel.findOne({ verificationToken: token }).exec();
131
+
132
+ if (!user) {
133
+ throw new NotFoundException();
134
+ }
135
+
136
+ if (!user.verificationToken) {
137
+ throw new Error('User has no token');
138
+ }
139
+
140
+ if (user.verified) {
141
+ throw new Error('User already verified');
142
+ }
143
+
144
+ await this.userModel.findByIdAndUpdate(user.id, { $set: { verified: true, verificationToken: null } }).exec();
145
+
146
+ return true;
147
+ }
148
+
149
+ /**
150
+ * Set newpassword for user with token
151
+ *
152
+ * @param token
153
+ * @param newPassword
154
+ */
155
+ async resetPassword(token: string, newPassword: string): Promise<boolean> {
156
+ const user = await this.userModel.findOne({ passwordResetToken: token }).exec();
157
+
158
+ if (!user) {
159
+ throw new NotFoundException();
160
+ }
161
+
162
+ const cryptedPassword = await bcrypt.hash(newPassword, 10);
163
+ await this.userModel
164
+ .findByIdAndUpdate(user.id, { $set: { password: cryptedPassword, passwordResetToken: null } })
165
+ .exec();
166
+
167
+ return true;
168
+ }
169
+
170
+ /**
171
+ * Request email with password reset link
172
+ *
173
+ * @param email
174
+ */
175
+ async requestPasswordResetMail(email: string): Promise<boolean> {
176
+ const user = await this.userModel.findOne({ email }).exec();
177
+
178
+ if (!user) {
179
+ throw new NotFoundException();
180
+ }
181
+
182
+ const resetToken = crypto.randomBytes(32).toString('hex');
183
+ await this.userModel.findByIdAndUpdate(user.id, { $set: { passwordResetToken: resetToken } }).exec();
184
+
185
+ await this.emailService.sendMail(user.email, 'Password reset', {
186
+ htmlTemplate: 'password-reset',
187
+ templateData: { name: user.username, link: envConfig.email.passwordResetLink + '/' + resetToken },
188
+ });
189
+
190
+ return true;
191
+ }
192
+
193
+ /**
194
+ * Set roles for specified user
195
+ */
196
+ async setRoles(userId: string, roles: string[]): Promise<TUser> {
197
+ // Check roles
198
+ if (!Array.isArray(roles)) {
199
+ throw new BadRequestException('Missing roles');
200
+ }
201
+
202
+ // Check roles values
203
+ if (roles.some((role) => typeof role !== 'string')) {
204
+ throw new BadRequestException('roles contains invalid values');
205
+ }
206
+
207
+ // Update and return user
208
+ return this.userModel.findByIdAndUpdate(userId, { roles }).exec();
209
+ }
210
+
111
211
  /**
112
212
  * Update user via ID
113
213
  */
@@ -144,22 +244,37 @@ export abstract class CoreUserService<
144
244
  */
145
245
  protected async prepareInput(
146
246
  input: { [key: string]: any },
147
- currentUser: TUser,
148
- options: { [key: string]: any; create?: boolean; clone?: boolean } = {},
247
+ currentUser?: TUser,
248
+ options: { [key: string]: any; checkRoles?: boolean; clone?: boolean } = {},
149
249
  ...args: any[]
150
250
  ) {
151
251
  // Configuration
152
252
  const config = {
253
+ checkRoles: true,
153
254
  clone: false,
154
255
  ...options,
155
256
  };
156
257
 
157
- // Clone output
258
+ // Clone input
158
259
  if (config.clone) {
159
260
  input = JSON.parse(JSON.stringify(input));
160
261
  }
161
262
 
162
- // Has password
263
+ // Process roles
264
+ if (input.roles && config.checkRoles && (!currentUser?.hasRole || !currentUser.hasRole(RoleEnum.ADMIN))) {
265
+ if (!(currentUser as any)?.roles) {
266
+ throw new UnauthorizedException('Missing roles of current user');
267
+ } else {
268
+ const allowedRoles = _.intersection(input.roles, (currentUser as any).roles);
269
+ if (allowedRoles.length !== input.roles.length) {
270
+ const missingRoles = _.difference(input.roles, (currentUser as any).roles);
271
+ throw new UnauthorizedException('Current user not allowed setting roles: ' + missingRoles);
272
+ }
273
+ input.roles = allowedRoles;
274
+ }
275
+ }
276
+
277
+ // Hash password
163
278
  if (input.password) {
164
279
  input.password = await bcrypt.hash((input as any).password, 10);
165
280
  }
@@ -190,6 +305,12 @@ export abstract class CoreUserService<
190
305
  // Remove password if exists
191
306
  delete (user as any).password;
192
307
 
308
+ // Remove verification token if exists
309
+ delete (user as any).verificationToken;
310
+
311
+ // Remove password reset token if exists
312
+ delete (user as any).passwordResetToken;
313
+
193
314
  // Return prepared user
194
315
  return user;
195
316
  }
@@ -9,6 +9,7 @@ import { ConfigService } from './core/common/services/config.service';
9
9
  import { EmailService } from './core/common/services/email.service';
10
10
  import { TemplateService } from './core/common/services/template.service';
11
11
  import { MongooseModule } from '@nestjs/mongoose';
12
+ import { MailjetService } from './core/common/services/mailjet.service';
12
13
 
13
14
  /**
14
15
  * Core module (dynamic)
@@ -91,6 +92,7 @@ export class CoreModule {
91
92
  // Core Services
92
93
  EmailService,
93
94
  TemplateService,
95
+ MailjetService,
94
96
  ];
95
97
 
96
98
  // Return dynamic module
@@ -101,7 +103,7 @@ export class CoreModule {
101
103
  GraphQLModule.forRoot(config.graphQl),
102
104
  ],
103
105
  providers,
104
- exports: [ConfigService, EmailService, TemplateService],
106
+ exports: [ConfigService, EmailService, TemplateService, MailjetService],
105
107
  };
106
108
  }
107
109
  }
package/src/index.ts CHANGED
@@ -41,6 +41,7 @@ export * from './core/common/scalars/json.scalar';
41
41
  export * from './core/common/services/config.service';
42
42
  export * from './core/common/services/email.service';
43
43
  export * from './core/common/services/template.service';
44
+ export * from './core/common/services/mailjet.service';
44
45
 
45
46
  // =====================================================================================================================
46
47
  // Core - Modules - Auth