Skip to content

Commit 00485c1

Browse files
committed
chore: Add PasswdModule to ManagementModule imports
1 parent 289d2a5 commit 00485c1

File tree

7 files changed

+104
-88
lines changed

7 files changed

+104
-88
lines changed

src/management/management.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { ManagementService } from './management.service';
33
import { ManagementController } from './management.controller';
44
import { RouterModule } from '@nestjs/core';
55
import { IdentitiesModule } from './identities/identities.module';
6+
import { PasswdModule } from './passwd/passwd.module';
67

78
@Module({
8-
imports: [IdentitiesModule],
9+
imports: [IdentitiesModule, PasswdModule],
910
providers: [ManagementService],
1011
controllers: [ManagementController],
1112
})

src/management/passwd/dto/ask-token.dto.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { ApiProperty } from '@nestjs/swagger';
2-
import { IsMongoId, IsString } from 'class-validator';
2+
import { IsString } from 'class-validator';
33

44
export class AskTokenDto {
5-
@IsMongoId()
5+
@IsString()
66
@ApiProperty({ example: 'paul.bismuth', description: 'User id' })
7-
id: string;
7+
uid: string;
8+
89
@ApiProperty({ example: 'monemail@mondomaine.com', description: 'secondary mail' })
910
@IsString()
1011
mail: string;

src/management/passwd/dto/change-password.dto.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { ApiProperty } from '@nestjs/swagger';
2-
import { IsMongoId, IsString } from 'class-validator';
2+
import { IsString } from 'class-validator';
33

44
export class ChangePasswordDto {
5-
@IsMongoId()
5+
@IsString()
66
@ApiProperty({ example: 'paul.bismuth', description: 'User object id', type: String })
7-
public id: string;
7+
public uid: string;
88

99
@IsString()
1010
@ApiProperty({ example: 'MyOldPassword', description: 'Old password', type: String })

src/management/passwd/dto/reset-password.dto.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export class ResetPasswordDto {
88
description: 'Token',
99
})
1010
token: string;
11+
1112
@ApiProperty({ example: 'MyNewPassword', description: 'New Password' })
1213
@IsString()
1314
newPassword: string;
Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Controller, Post, Body, Res, Logger } from '@nestjs/common';
1+
import { Controller, Post, Body, Res, Logger, HttpStatus } from '@nestjs/common';
22
import { PasswdService } from './passwd.service';
33
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
44
import { Response } from 'express';
@@ -15,59 +15,46 @@ export class PasswdController {
1515
public constructor(private passwdService: PasswdService) { }
1616

1717
@Post('change')
18-
@ApiOperation({ summary: 'change password' })
19-
@ApiResponse({ status: 200, description: 'Password has been successfully changed.' })
20-
@ApiResponse({ status: 403, description: 'Old password wrong' })
21-
@ApiResponse({ status: 500, description: 'Backend error' })
22-
public async change(@Body() cpwd: ChangePasswordDto, @Res() res: Response): Promise<Response> {
18+
@ApiOperation({ summary: 'Change password' })
19+
@ApiResponse({ status: HttpStatus.OK, description: 'Password has been successfully changed.' })
20+
@ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Old password wrong' })
21+
public async change(@Body() body: ChangePasswordDto, @Res() res: Response): Promise<Response> {
2322
// eslint-disable-next-line @typescript-eslint/no-unused-vars
24-
const [_, data] = await this.passwdService.change(cpwd);
25-
//TODO: uid ou employeeNumber + employeeType ?
26-
data.data.uid = cpwd.id;
27-
this.logger.log(`call passwd change for : ${cpwd.id}`);
23+
const [_, data] = await this.passwdService.change(body);
24+
this.logger.log(`Call passwd change for : ${body.uid}`);
2825

29-
if (data.data.status === 0) {
30-
return res.status(200).json(data);
31-
} else {
32-
if (data.data.status === 1) {
33-
return res.status(403).json(data);
34-
}
35-
return res.status(200).json(data);
36-
}
26+
return res.status(HttpStatus.OK).json(data);
3727
}
3828

3929
@Post('gettoken')
40-
@ApiOperation({ summary: 'ask token for reseting password' })
41-
@ApiResponse({ status: 200, description: 'Token', content: {} })
42-
@ApiResponse({ status: 500, description: 'Backend error' })
30+
@ApiOperation({ summary: 'Ask token for reseting password' })
31+
@ApiResponse({ status: HttpStatus.OK, description: 'Token' })
32+
@ApiResponse({ status: HttpStatus.BAD_REQUEST })
4333
public async gettoken(@Body() asktoken: AskTokenDto, @Res() res: Response): Promise<Response> {
44-
this.logger.log('GetToken for : ' + asktoken.id);
45-
const data = await this.passwdService.askToken(asktoken);
46-
return res.status(200).json({ token: data });
34+
this.logger.log('GetToken for : ' + asktoken.uid);
35+
const token = await this.passwdService.askToken(asktoken);
36+
37+
return res.status(HttpStatus.OK).json({ token });
4738
}
4839

4940
@Post('verifytoken')
50-
@ApiOperation({ summary: 'ask token for reseting password' })
51-
@ApiResponse({ status: 201, description: 'Token OK' })
52-
@ApiResponse({ status: 500, description: 'Token KO' })
53-
public async verifyToken(@Body() token: VerifyTokenDto, @Res() res: Response): Promise<Response> {
54-
this.logger.log('Verify token : ' + token.token);
55-
if (await this.passwdService.verifyToken(token.token)) {
56-
return res.status(200).json({ status: 0 });
57-
}
58-
return res.status(200).json({ status: 1 });
41+
@ApiOperation({ summary: 'Ask token for reseting password' })
42+
@ApiResponse({ status: HttpStatus.OK })
43+
@ApiResponse({ status: HttpStatus.BAD_REQUEST })
44+
public async verifyToken(@Body() body: VerifyTokenDto, @Res() res: Response): Promise<Response> {
45+
this.logger.log('Verify token : ' + body.token);
46+
const data = await this.passwdService.decryptToken(body.token);
47+
48+
return res.status(HttpStatus.OK).json(data);
5949
}
6050

6151
@Post('reset')
62-
@ApiOperation({ summary: 'reset password' })
63-
@ApiResponse({ status: 200, description: 'Reset OK' })
64-
@ApiResponse({ status: 500, description: 'Reset KO' })
65-
public async reset(@Body() data: ResetPasswordDto, @Res() res: Response): Promise<Response> {
52+
@ApiOperation({ summary: 'Reset password' })
53+
@ApiResponse({ status: HttpStatus.OK })
54+
@ApiResponse({ status: HttpStatus.BAD_REQUEST })
55+
public async reset(@Body() body: ResetPasswordDto, @Res() res: Response): Promise<Response> {
6656
// eslint-disable-next-line @typescript-eslint/no-unused-vars
67-
const [_, resetData] = await this.passwdService.reset(data);
68-
if (resetData.status === 0) {
69-
return res.status(200).json(resetData);
70-
}
71-
return res.status(200).json({ status: 1, error: 'invalid token' });
57+
const [_, data] = await this.passwdService.reset(body);
58+
return res.status(HttpStatus.OK).json(data);
7259
}
7360
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Module } from '@nestjs/common';
22
import { PasswdService } from './passwd.service';
33
import { PasswdController } from './passwd.controller';
4+
import { BackendsModule } from '~/core/backends/backends.module';
5+
import { IdentitiesModule } from '../identities/identities.module';
46

57
@Module({
6-
imports: [PasswdModule],
8+
imports: [BackendsModule, IdentitiesModule],
79
controllers: [PasswdController],
810
providers: [PasswdService],
911
})
10-
export class PasswdModule {}
12+
export class PasswdModule { }

src/management/passwd/passwd.service.ts

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { InjectRedis } from '@nestjs-modules/ioredis';
2-
import { Injectable } from '@nestjs/common';
2+
import { BadRequestException, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
33
import * as crypto from 'crypto';
44
import Redis from 'ioredis';
55
import { AbstractService } from '~/_common/abstracts/abstract.service';
@@ -9,69 +9,93 @@ import { Jobs } from '~/core/jobs/_schemas/jobs.schema';
99
import { AskTokenDto } from './dto/ask-token.dto';
1010
import { ChangePasswordDto } from './dto/change-password.dto';
1111
import { ResetPasswordDto } from './dto/reset-password.dto';
12-
import { Types } from 'mongoose';
12+
import { IdentitiesService } from '../identities/identities.service';
13+
14+
interface TokenData {
15+
k: string;
16+
iv: string;
17+
tag: string;
18+
}
19+
20+
interface CipherData {
21+
uid: string;
22+
mail: string;
23+
}
1324

1425
@Injectable()
1526
export class PasswdService extends AbstractService {
27+
public static readonly RANDOM_BYTES_K = 16;
28+
public static readonly RANDOM_BYTES_IV = 12;
29+
30+
public static readonly TOKEN_ALGORITHM = 'aes-256-gcm';
31+
32+
public static readonly TOKEN_EXPIRATION = 3600;
33+
1634
public constructor(
17-
protected backends: BackendsService,
35+
protected readonly backends: BackendsService,
36+
protected readonly identities: IdentitiesService,
1837
@InjectRedis() private readonly redis: Redis,
1938
) {
2039
super();
2140
}
2241

23-
public async change(passwd: ChangePasswordDto): Promise<[Jobs, any]> {
24-
return await this.backends.executeJob(ActionType.IDENTITY_PASSWORD_CHANGE, new Types.ObjectId(passwd.id), passwd, {
42+
public async change(passwdDto: ChangePasswordDto): Promise<[Jobs, any]> {
43+
const identity = await this.identities.findOne({ 'inetOrgPerson.uid': passwdDto.uid });
44+
45+
return await this.backends.executeJob(ActionType.IDENTITY_PASSWORD_CHANGE, identity._id, passwdDto, {
2546
async: false,
2647
});
2748
}
2849

2950
public async askToken(askToken: AskTokenDto): Promise<string> {
30-
const iv = crypto.randomBytes(12).toString('base64');
31-
const key = crypto.randomBytes(16).toString('hex');
32-
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
33-
//TODO: uid ou employeeNumber + employeeType ?
34-
const dataStruct = { uid: askToken.id, mail: askToken.mail };
35-
let ciphertext = cipher.update(JSON.stringify(dataStruct), 'utf8', 'base64');
51+
await this.identities.findOne({ 'inetOrgPerson.uid': askToken.uid });
52+
53+
const k = crypto.randomBytes(PasswdService.RANDOM_BYTES_K).toString('hex');
54+
const iv = crypto.randomBytes(PasswdService.RANDOM_BYTES_IV).toString('base64');
55+
const cipher = crypto.createCipheriv(PasswdService.TOKEN_ALGORITHM, k, iv);
56+
57+
let ciphertext = cipher.update(
58+
JSON.stringify(<CipherData>{ uid: askToken.uid, mail: askToken.mail }),
59+
'utf8',
60+
'base64',
61+
);
3662
ciphertext += cipher.final('base64');
37-
const tag = cipher.getAuthTag();
38-
const tokenStruct = JSON.stringify({ k: key, iv: iv, tag: tag });
39-
await this.redis.set(ciphertext, tokenStruct);
40-
await this.redis.expire(ciphertext, 3600);
41-
return ciphertext;
42-
}
4363

44-
public async verifyToken(token: string): Promise<boolean> {
45-
const data = await this.decryptToken(token);
46-
return Object.keys(data).length === 0;
64+
await this.redis.set(
65+
ciphertext,
66+
JSON.stringify(<TokenData>{
67+
k,
68+
iv,
69+
tag: cipher.getAuthTag().toString('base64'),
70+
}),
71+
);
72+
await this.redis.expire(ciphertext, PasswdService.TOKEN_EXPIRATION);
73+
return ciphertext;
4774
}
4875

49-
public async decryptToken(token: string): Promise<any> {
50-
if (!(await this.redis.exists(token))) {
51-
throw new Error('Token not found');
52-
}
76+
public async decryptToken(token: string): Promise<CipherData> {
77+
try {
78+
const result = await this.redis.get(token);
79+
const cypherData: TokenData = JSON.parse(result);
5380

54-
const result = await this.redis.get(token);
55-
const cypherData = JSON.parse(result);
56-
const decipher = crypto.createDecipheriv('aes-256-gcm', cypherData.k, cypherData.iv);
57-
decipher.setAuthTag(Buffer.from(cypherData.tag, 'base64'));
58-
const plaintext = decipher.update(token, 'base64', 'ascii');
81+
const decipher = crypto.createDecipheriv(PasswdService.TOKEN_ALGORITHM, cypherData.k, cypherData.iv);
82+
decipher.setAuthTag(Buffer.from(cypherData.tag, 'base64'));
83+
const plaintext = decipher.update(token, 'base64', 'ascii');
5984

60-
return JSON.parse(plaintext);
85+
return JSON.parse(plaintext);
86+
} catch (error) {
87+
throw new BadRequestException('Invalid token');
88+
}
6189
}
6290

6391
public async reset(data: ResetPasswordDto): Promise<[Jobs, any]> {
6492
const tokenData = await this.decryptToken(data.token);
65-
if (Object.keys(tokenData).length === 0) {
66-
throw new Error('Invalid token');
67-
}
93+
const identity = await this.identities.findOne({ 'inetOrgPerson.uid': tokenData.uid });
6894

69-
//TODO: uid ou employeeNumber + employeeType ?
70-
const backendData = { uid: tokenData.uid, newPassword: data.newPassword };
7195
return await this.backends.executeJob(
7296
ActionType.IDENTITY_PASSWORD_RESET,
73-
new Types.ObjectId(`${tokenData.id}`),
74-
backendData,
97+
identity._id,
98+
{ uid: tokenData.uid, newPassword: data.newPassword },
7599
{
76100
async: false,
77101
},

0 commit comments

Comments
 (0)