11import { InjectRedis } from '@nestjs-modules/ioredis' ;
2- import { Injectable } from '@nestjs/common' ;
2+ import { BadRequestException , Injectable , InternalServerErrorException , NotFoundException } from '@nestjs/common' ;
33import * as crypto from 'crypto' ;
44import Redis from 'ioredis' ;
55import { AbstractService } from '~/_common/abstracts/abstract.service' ;
@@ -9,69 +9,93 @@ import { Jobs } from '~/core/jobs/_schemas/jobs.schema';
99import { AskTokenDto } from './dto/ask-token.dto' ;
1010import { ChangePasswordDto } from './dto/change-password.dto' ;
1111import { 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 ( )
1526export 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