@lenne.tech/nest-server 3.1.0 → 3.2.0
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/core/common/decorators/restricted.decorator.js +2 -0
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/helpers/model.helper.js +1 -0
- package/dist/core/common/helpers/model.helper.js.map +1 -1
- package/dist/core/common/helpers/service.helper.d.ts +2 -1
- package/dist/core/common/helpers/service.helper.js +4 -1
- package/dist/core/common/helpers/service.helper.js.map +1 -1
- package/dist/core/common/pipes/map.pipe.d.ts +4 -0
- package/dist/core/common/pipes/map.pipe.js +25 -0
- package/dist/core/common/pipes/map.pipe.js.map +1 -0
- package/dist/core/modules/user/core-user.model.js +1 -0
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/core/modules/user/core-user.service.d.ts +6 -1
- package/dist/core/modules/user/core-user.service.js +45 -3
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/core/modules/user/inputs/core-user-create.input.js +4 -0
- package/dist/core/modules/user/inputs/core-user-create.input.js.map +1 -1
- package/dist/core/modules/user/inputs/core-user.input.d.ts +2 -1
- package/dist/core/modules/user/inputs/core-user.input.js +12 -2
- package/dist/core/modules/user/inputs/core-user.input.js.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/dist/server/modules/user/user.resolver.d.ts +1 -1
- package/dist/server/modules/user/user.resolver.js +11 -11
- package/dist/server/modules/user/user.resolver.js.map +1 -1
- package/dist/server/modules/user/user.service.d.ts +0 -3
- package/dist/server/modules/user/user.service.js +2 -44
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/common/decorators/restricted.decorator.ts +2 -0
- package/src/core/common/helpers/model.helper.ts +3 -0
- package/src/core/common/helpers/service.helper.ts +8 -3
- package/src/core/common/pipes/map.pipe.ts +16 -0
- package/src/core/modules/user/core-user.model.ts +1 -0
- package/src/core/modules/user/core-user.service.ts +78 -3
- package/src/core/modules/user/inputs/core-user-create.input.ts +1 -1
- package/src/core/modules/user/inputs/core-user.input.ts +9 -8
- package/src/main.ts +4 -0
- package/src/server/modules/user/user.resolver.ts +13 -13
- package/src/server/modules/user/user.service.ts +3 -83
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
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",
|
|
@@ -77,6 +77,9 @@ export class ModelHelper {
|
|
|
77
77
|
// Merge target with prepared source
|
|
78
78
|
Object.assign(target, preparedSource);
|
|
79
79
|
|
|
80
|
+
// Remove all props with undefined
|
|
81
|
+
Object.keys(target).forEach((key) => target[key] === undefined && delete target[key]);
|
|
82
|
+
|
|
80
83
|
// Return target
|
|
81
84
|
return target;
|
|
82
85
|
}
|
|
@@ -18,7 +18,7 @@ export class ServiceHelper {
|
|
|
18
18
|
) {
|
|
19
19
|
// Configuration
|
|
20
20
|
const config = {
|
|
21
|
-
checkRoles:
|
|
21
|
+
checkRoles: false,
|
|
22
22
|
clone: false,
|
|
23
23
|
create: false,
|
|
24
24
|
...options,
|
|
@@ -65,11 +65,11 @@ export class ServiceHelper {
|
|
|
65
65
|
/**
|
|
66
66
|
* Prepare output before return
|
|
67
67
|
*/
|
|
68
|
-
static async prepareOutput(
|
|
68
|
+
static async prepareOutput<T = Record<string, any>>(
|
|
69
69
|
output: any,
|
|
70
70
|
userModel: new () => any,
|
|
71
71
|
userService: any,
|
|
72
|
-
options: { [key: string]: any; clone?: boolean } = {},
|
|
72
|
+
options: { [key: string]: any; clone?: boolean; targetModel?: Partial<T> } = {},
|
|
73
73
|
...args: any[]
|
|
74
74
|
) {
|
|
75
75
|
// Configuration
|
|
@@ -83,6 +83,11 @@ export class ServiceHelper {
|
|
|
83
83
|
output = JSON.parse(JSON.stringify(output));
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// Map output if target model exist
|
|
87
|
+
if (options.targetModel) {
|
|
88
|
+
(options.targetModel as any).map(output);
|
|
89
|
+
}
|
|
90
|
+
|
|
86
91
|
// Remove password if exists
|
|
87
92
|
delete output.password;
|
|
88
93
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
@Injectable()
|
|
4
|
+
export class MapPipe implements PipeTransform {
|
|
5
|
+
transform(value: any, metadata: ArgumentMetadata) {
|
|
6
|
+
let result = value;
|
|
7
|
+
|
|
8
|
+
// Check map function is available
|
|
9
|
+
if ((metadata.metatype as any)?.map) {
|
|
10
|
+
// Map object to correct type
|
|
11
|
+
result = (metadata.metatype as any).map(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -15,6 +15,9 @@ import { CoreUserCreateInput } from './inputs/core-user-create.input';
|
|
|
15
15
|
import { CoreUserInput } from './inputs/core-user.input';
|
|
16
16
|
import { Model } from 'mongoose';
|
|
17
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';
|
|
18
21
|
|
|
19
22
|
// Subscription
|
|
20
23
|
const pubSub = new PubSub();
|
|
@@ -27,7 +30,7 @@ export abstract class CoreUserService<
|
|
|
27
30
|
TUserInput extends CoreUserInput,
|
|
28
31
|
TUserCreateInput extends CoreUserCreateInput
|
|
29
32
|
> extends CoreBasicUserService<TUser, TUserInput, TUserCreateInput> {
|
|
30
|
-
protected constructor(protected readonly userModel: Model<any
|
|
33
|
+
protected constructor(protected readonly userModel: Model<any>, protected emailService: EmailService) {
|
|
31
34
|
super(userModel);
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -42,8 +45,11 @@ export abstract class CoreUserService<
|
|
|
42
45
|
// Prepare input
|
|
43
46
|
await this.prepareInput(input, currentUser, { create: true });
|
|
44
47
|
|
|
48
|
+
// Generate verification token
|
|
49
|
+
const newUser = { ...input, ...{ verificationToken: crypto.randomBytes(32).toString('hex') } };
|
|
50
|
+
|
|
45
51
|
// Create new user
|
|
46
|
-
const createdUser = new this.userModel(this.model.map(
|
|
52
|
+
const createdUser = new this.userModel(this.model.map(newUser));
|
|
47
53
|
|
|
48
54
|
try {
|
|
49
55
|
// Save created user
|
|
@@ -115,6 +121,75 @@ export abstract class CoreUserService<
|
|
|
115
121
|
);
|
|
116
122
|
}
|
|
117
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
|
+
|
|
118
193
|
/**
|
|
119
194
|
* Set roles for specified user
|
|
120
195
|
*/
|
|
@@ -175,7 +250,7 @@ export abstract class CoreUserService<
|
|
|
175
250
|
) {
|
|
176
251
|
// Configuration
|
|
177
252
|
const config = {
|
|
178
|
-
checkRoles:
|
|
253
|
+
checkRoles: false,
|
|
179
254
|
clone: false,
|
|
180
255
|
...options,
|
|
181
256
|
};
|
|
@@ -2,53 +2,54 @@ import { IsEmail, IsOptional } from 'class-validator';
|
|
|
2
2
|
import { Field, InputType } from '@nestjs/graphql';
|
|
3
3
|
import { Restricted } from '../../../common/decorators/restricted.decorator';
|
|
4
4
|
import { RoleEnum } from '../../../common/enums/role.enum';
|
|
5
|
+
import { CoreModel } from '../../../common/models/core-model.model';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* User input to update a user
|
|
8
9
|
*/
|
|
9
10
|
@InputType({ description: 'User input', isAbstract: true })
|
|
10
|
-
export abstract class CoreUserInput {
|
|
11
|
+
export abstract class CoreUserInput extends CoreModel {
|
|
11
12
|
/**
|
|
12
13
|
* Email of the user
|
|
13
14
|
*/
|
|
14
15
|
@Field({ description: 'Email of the user', nullable: true })
|
|
15
16
|
@IsOptional()
|
|
16
17
|
@IsEmail()
|
|
17
|
-
email?: string;
|
|
18
|
+
email?: string = undefined;
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* First name of the user
|
|
21
22
|
*/
|
|
22
23
|
@Field({ description: 'First name of the user', nullable: true })
|
|
23
24
|
@IsOptional()
|
|
24
|
-
firstName?: string;
|
|
25
|
+
firstName?: string = undefined;
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Last name of the user
|
|
28
29
|
*/
|
|
29
30
|
@Field({ description: 'Last name of the user', nullable: true })
|
|
30
31
|
@IsOptional()
|
|
31
|
-
lastName?: string;
|
|
32
|
+
lastName?: string = undefined;
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Roles of the user
|
|
35
36
|
*/
|
|
36
|
-
@Restricted(RoleEnum.ADMIN
|
|
37
|
+
@Restricted(RoleEnum.ADMIN)
|
|
37
38
|
@Field((type) => [String], { description: 'Roles of the user', nullable: true })
|
|
38
39
|
@IsOptional()
|
|
39
|
-
roles?: string[];
|
|
40
|
+
roles?: string[] = [];
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* Username / alias of the user
|
|
43
44
|
*/
|
|
44
45
|
@Field({ description: 'Username / alias of the user', nullable: true })
|
|
45
46
|
@IsOptional()
|
|
46
|
-
username?: string;
|
|
47
|
+
username?: string = undefined;
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Password of the user
|
|
50
51
|
*/
|
|
51
52
|
@Field({ description: 'Password of the user', nullable: true })
|
|
52
53
|
@IsOptional()
|
|
53
|
-
password?: string;
|
|
54
|
+
password?: string = undefined;
|
|
54
55
|
}
|
package/src/main.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core';
|
|
|
2
2
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
|
3
3
|
import envConfig from './config.env';
|
|
4
4
|
import { ServerModule } from './server/server.module';
|
|
5
|
+
import { MapPipe } from './core/common/pipes/map.pipe';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Preparations for server start
|
|
@@ -13,6 +14,9 @@ async function bootstrap() {
|
|
|
13
14
|
ServerModule
|
|
14
15
|
);
|
|
15
16
|
|
|
17
|
+
// Add map pipe for mapping inputs to class
|
|
18
|
+
server.useGlobalPipes(new MapPipe());
|
|
19
|
+
|
|
16
20
|
// Asset directory
|
|
17
21
|
server.useStaticAssets(envConfig.staticAssets.path, envConfig.staticAssets.options);
|
|
18
22
|
|
|
@@ -43,14 +43,6 @@ export class UserResolver {
|
|
|
43
43
|
return await this.usersService.find(args, info);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* Verify user with email
|
|
48
|
-
*/
|
|
49
|
-
@Query((returns) => Boolean, { description: 'Verify user with email' })
|
|
50
|
-
async verifyUser(@Args('token') token: string) {
|
|
51
|
-
return await this.usersService.verify(token);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
46
|
/**
|
|
55
47
|
* Request new password for user with email
|
|
56
48
|
*/
|
|
@@ -59,17 +51,25 @@ export class UserResolver {
|
|
|
59
51
|
return await this.usersService.requestPasswordResetMail(email);
|
|
60
52
|
}
|
|
61
53
|
|
|
54
|
+
// ===========================================================================
|
|
55
|
+
// Mutations
|
|
56
|
+
// ===========================================================================
|
|
57
|
+
/**
|
|
58
|
+
* Verify user with email
|
|
59
|
+
*/
|
|
60
|
+
@Mutation((returns) => Boolean, { description: 'Verify user with email' })
|
|
61
|
+
async verifyUser(@Args('token') token: string) {
|
|
62
|
+
return await this.usersService.verify(token);
|
|
63
|
+
}
|
|
64
|
+
|
|
62
65
|
/**
|
|
63
66
|
* Set new password for user with token
|
|
64
67
|
*/
|
|
65
|
-
@
|
|
68
|
+
@Mutation((returns) => Boolean, { description: 'Set new password for user with token' })
|
|
66
69
|
async resetPassword(@Args('token') token: string, @Args('password') password: string) {
|
|
67
70
|
return await this.usersService.resetPassword(token, password);
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
// ===========================================================================
|
|
71
|
-
// Mutations
|
|
72
|
-
// ===========================================================================
|
|
73
73
|
/**
|
|
74
74
|
* Create new user
|
|
75
75
|
*/
|
|
@@ -88,7 +88,7 @@ export class UserResolver {
|
|
|
88
88
|
@Roles(RoleEnum.ADMIN, RoleEnum.OWNER)
|
|
89
89
|
@Mutation((returns) => User, { description: 'Update existing user' })
|
|
90
90
|
async updateUser(
|
|
91
|
-
@Args('input') input: UserInput,
|
|
91
|
+
@Args('input', { type: () => UserInput }) input: UserInput,
|
|
92
92
|
@Args('id') id: string,
|
|
93
93
|
@GraphQLUser() user: User,
|
|
94
94
|
@Info() info: GraphQLResolveInfo
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Inject,
|
|
3
|
-
Injectable,
|
|
4
|
-
NotFoundException,
|
|
5
|
-
UnauthorizedException,
|
|
6
|
-
UnprocessableEntityException,
|
|
7
|
-
} from '@nestjs/common';
|
|
1
|
+
import { Inject, Injectable, UnauthorizedException, UnprocessableEntityException } from '@nestjs/common';
|
|
8
2
|
import * as fs from 'fs';
|
|
9
3
|
import { GraphQLResolveInfo } from 'graphql';
|
|
10
4
|
import envConfig from '../../../config.env';
|
|
@@ -21,8 +15,6 @@ import { InjectModel } from '@nestjs/mongoose';
|
|
|
21
15
|
import { Model } from 'mongoose';
|
|
22
16
|
import { ICorePersistenceModel } from '../../../core/common/interfaces/core-persistence-model.interface';
|
|
23
17
|
import { PubSub } from 'graphql-subscriptions';
|
|
24
|
-
import * as crypto from 'crypto';
|
|
25
|
-
import * as bcrypt from 'bcrypt';
|
|
26
18
|
|
|
27
19
|
/**
|
|
28
20
|
* User service
|
|
@@ -50,7 +42,7 @@ export class UserService extends CoreUserService<User, UserInput, UserCreateInpu
|
|
|
50
42
|
@InjectModel('User') protected readonly userModel: Model<User>,
|
|
51
43
|
@Inject('PUB_SUB') protected readonly pubSub: PubSub
|
|
52
44
|
) {
|
|
53
|
-
super(userModel);
|
|
45
|
+
super(userModel, emailService);
|
|
54
46
|
this.model = User;
|
|
55
47
|
}
|
|
56
48
|
|
|
@@ -68,86 +60,14 @@ export class UserService extends CoreUserService<User, UserInput, UserCreateInpu
|
|
|
68
60
|
|
|
69
61
|
await this.pubSub.publish('userCreated', User.map(user));
|
|
70
62
|
|
|
71
|
-
const verificationToken = crypto.randomBytes(32).toString('hex');
|
|
72
|
-
await this.userModel.findByIdAndUpdate(user.id, { $set: { verificationToken } }).exec();
|
|
73
|
-
|
|
74
63
|
await this.emailService.sendMail(user.email, 'Welcome', {
|
|
75
64
|
htmlTemplate: 'welcome',
|
|
76
|
-
templateData: { name: user.username, link: envConfig.email.verificationLink + '/' + verificationToken },
|
|
65
|
+
templateData: { name: user.username, link: envConfig.email.verificationLink + '/' + user.verificationToken },
|
|
77
66
|
});
|
|
78
67
|
|
|
79
68
|
return user;
|
|
80
69
|
}
|
|
81
70
|
|
|
82
|
-
/**
|
|
83
|
-
* Verify user with token
|
|
84
|
-
*
|
|
85
|
-
* @param token
|
|
86
|
-
*/
|
|
87
|
-
async verify(token: string): Promise<boolean> {
|
|
88
|
-
const user = await this.userModel.findOne({ verificationToken: token }).exec();
|
|
89
|
-
|
|
90
|
-
if (!user) {
|
|
91
|
-
throw new NotFoundException();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (!user.verificationToken) {
|
|
95
|
-
throw new Error('User has no token');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (user.verified) {
|
|
99
|
-
throw new Error('User already verified');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
await this.userModel.findByIdAndUpdate(user.id, { $set: { verified: true, verificationToken: null } }).exec();
|
|
103
|
-
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Set newpassword for user with token
|
|
109
|
-
*
|
|
110
|
-
* @param token
|
|
111
|
-
* @param newPassword
|
|
112
|
-
*/
|
|
113
|
-
async resetPassword(token: string, newPassword: string): Promise<boolean> {
|
|
114
|
-
const user = await this.userModel.findOne({ passwordResetToken: token }).exec();
|
|
115
|
-
|
|
116
|
-
if (!user) {
|
|
117
|
-
throw new NotFoundException();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const cryptedPassword = await bcrypt.hash(newPassword, 10);
|
|
121
|
-
await this.userModel
|
|
122
|
-
.findByIdAndUpdate(user.id, { $set: { password: cryptedPassword, passwordResetToken: null } })
|
|
123
|
-
.exec();
|
|
124
|
-
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Request email with password reset link
|
|
130
|
-
*
|
|
131
|
-
* @param email
|
|
132
|
-
*/
|
|
133
|
-
async requestPasswordResetMail(email: string): Promise<boolean> {
|
|
134
|
-
const user = await this.userModel.findOne({ email }).exec();
|
|
135
|
-
|
|
136
|
-
if (!user) {
|
|
137
|
-
throw new NotFoundException();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const resetToken = crypto.randomBytes(32).toString('hex');
|
|
141
|
-
await this.userModel.findByIdAndUpdate(user.id, { $set: { passwordResetToken: resetToken } }).exec();
|
|
142
|
-
|
|
143
|
-
await this.emailService.sendMail(user.email, 'Password reset', {
|
|
144
|
-
htmlTemplate: 'password-reset',
|
|
145
|
-
templateData: { name: user.username, link: envConfig.email.passwordResetLink + '/' + resetToken },
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
71
|
/**
|
|
152
72
|
* Get users via filter
|
|
153
73
|
*/
|