Skip to content

Commit b866215

Browse files
alainabbastacxou
authored andcommitted
envoi par sms
1 parent 438404c commit b866215

File tree

10 files changed

+263
-62
lines changed

10 files changed

+263
-62
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,15 @@
6363
"ajv": "^8.16.0",
6464
"ajv-errors": "^3.0.0",
6565
"argon2": "^0.40.3",
66+
"awesome-phonenumber": "^6.10.0",
6667
"bullmq": "^5.8.2",
6768
"class-transformer": "^0.5.1",
6869
"class-validator": "^0.14.1",
6970
"cookie-parser": "^1.4.6",
7071
"fast-password-entropy": "^1.1.1",
7172
"handlebars": "^4.7.8",
7273
"helmet": "^7.1.0",
74+
"hibp": "^14.1.2",
7375
"ioredis": "^5.4.1",
7476
"loglevel": "^1.9.1",
7577
"mjml": "^4.15.3",
@@ -86,6 +88,7 @@
8688
"request-ip": "^3.3.0",
8789
"rxjs": "^7.8.1",
8890
"schema-to-yup": "^1.12.18",
91+
"smpp": "^0.6.0-rc.4",
8992
"swagger-themes": "^1.4.3",
9093
"types-package-json": "^2.0.39",
9194
"winston": "^3.13.0",

src/config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ export interface ConfigInstance {
3939
port: number;
4040
sender: string;
4141
};
42+
sms:{
43+
host: string,
44+
systemId: string,
45+
password: string,
46+
sourceAddr: string,
47+
regionCode: string
48+
},
4249
frontPwd: {
4350
url:string;
4451
identityMailAttribute: string;
@@ -120,6 +127,13 @@ export default (): ConfigInstance => ({
120127
url: process.env['SESAME_FRONT_MDP'],
121128
identityMailAttribute: process.env['SESAME_RESET_PWD_MAIL'] || '',
122129
identityMobileAttribute: process.env['SESAME_RESET_PWD_MOBILE'] || ''
130+
},
131+
sms:{
132+
host: process.env['SESAME_SMPP_SERVER'] || '',
133+
systemId: process.env['SESAME_SMPP_SYSTEMID'] || '',
134+
password: process.env['SESAME_SMPP_PASSWORD'] || '',
135+
sourceAddr: process.env['SESAME_SMPP_SOURCEADDR'] || '',
136+
regionCode: process.env['SESAME_SMPP_REGIONCODE'] || 'FR'
123137
}
124138

125139
});
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { ApiProperty } from '@nestjs/swagger';
2-
import { IsString } from 'class-validator';
2+
import {IsNumber, IsString} from 'class-validator';
33

44
export class InitResetDto {
55
@IsString()
66
@ApiProperty({ example: 'paul.bismuth', description: 'User id' })
7-
uid: string;
7+
uid: string
8+
9+
@IsNumber()
10+
@ApiProperty({ example: 0, description: '0 = send by mail, 1 = send by SMS' })
11+
type: number
12+
813
}

src/management/passwd/passwd.controller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export class PasswdController {
2727
@ApiResponse({status: HttpStatus.OK, description: 'Mot de passe synchronisé sur le/les backends'})
2828
public async change(@Body() body: ChangePasswordDto, @Res() res: Response): Promise<Response> {
2929
const debug = {}
30+
3031
const [_, data] = await this.passwdService.change(body);
3132
this.logger.log(`Call passwd change for : ${body.uid}`);
3233

@@ -40,6 +41,7 @@ export class PasswdController {
4041
});
4142
}
4243

44+
/*
4345
@Post('gettoken')
4446
@ApiOperation({summary: 'Récupère un jeton de réinitialisation de mot de passe'})
4547
@ApiResponse({status: HttpStatus.OK, description: 'Retourne un jeton de réinitialisation de mot de passe'})
@@ -60,7 +62,7 @@ export class PasswdController {
6062
6163
return res.status(HttpStatus.OK).json({data});
6264
}
63-
65+
*/
6466
@Post('resetbycode')
6567
@ApiOperation({summary: 'reinitialise le mot de passe avec le code reçu'})
6668
@ApiResponse({status: HttpStatus.OK})

src/management/passwd/passwd.module.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import { BackendsModule } from '~/core/backends/backends.module';
55
import { IdentitiesModule } from '../identities/identities.module';
66
import { PasswdadmModule} from "~/settings/passwdadm/passwdadm.module";
77
import {PasswdadmService} from "~/settings/passwdadm/passwdadm.service";
8+
import {SmsService} from "~/management/passwd/sms-service";
89

910

1011
@Module({
1112
imports: [BackendsModule,
12-
IdentitiesModule, PasswdadmModule],
13+
IdentitiesModule, PasswdadmModule ],
1314
controllers: [PasswdController],
14-
providers: [PasswdService],
15+
providers: [PasswdService,SmsService],
1516
})
1617
export class PasswdModule { }

src/management/passwd/passwd.service.ts

Lines changed: 84 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import {InitAccountDto} from "~/management/passwd/dto/init-account.dto";
1717
import {ConfigService} from "@nestjs/config";
1818
import {randomInt} from "crypto";
1919
import {ResetByCodeDto} from "~/management/passwd/dto/reset-by-code-dto";
20+
import {PasswdadmService} from "~/settings/passwdadm/passwdadm.service";
21+
import {IdentityState} from "~/management/identities/_enums/states.enum";
22+
import {InitResetDto} from "~/management/passwd/dto/init-reset.dto";
23+
import {SmsService} from "~/management/passwd/sms-service";
2024

2125
interface TokenData {
2226
k: string;
@@ -43,51 +47,70 @@ export class PasswdService extends AbstractService {
4347
protected readonly identities: IdentitiesService,
4448
protected mailer: MailerService,
4549
protected config: ConfigService,
50+
private passwdadmService: PasswdadmService,
51+
private smsService: SmsService,
4652
@InjectRedis() private readonly redis: Redis
4753
) {
4854
super();
4955
}
50-
public async initReset(initDto: InitAccountDto):Promise<any>{
56+
//Initialisation du reset de mot de passe envoie un email ou par sms un code et fourni un token au front.
57+
// Le code est la clé du token
58+
public async initReset(initDto: InitResetDto):Promise<any>{
5159
//envoi du mail
5260
try{
5361
const identity = await this.identities.findOne({ 'inetOrgPerson.uid': initDto.uid }) as Identities;
62+
const k = randomInt(100000, 999999);
63+
//asking for padding
64+
const padd = await this.getPaddingForCode()
5465
const mailAttribute=this.config.get('frontPwd.identityMailAttribute')
55-
this.logger.log("Reset passord asked for : " + initDto.uid )
56-
if (mailAttribute !== '') {
57-
const mail = <string>get(identity.toObject(), mailAttribute)
58-
const displayName=identity.inetOrgPerson.displayName
59-
//const k=crypto.randomBytes(PasswdService.RANDOM_BYTES_CODE).toString('hex')
60-
const k = randomInt(100000, 999999);
61-
//asking for padding
62-
const padd = await this.getPaddingForCode()
63-
const token = await this.askToken({mail: mail, uid: initDto.uid}, padd + k.toString(16),PasswdService.CODE_EXPIRATION)
64-
this.logger.log("Token :" + token + ' int : ' + k.toString(10))
65-
this.mailer.sendMail({
66-
from: this.config.get('mailer.sender'),
67-
to: mail,
68-
subject: 'Reinitialisation de votre mot de passe',
69-
template: "resetaccount",
70-
context:{
71-
uid: identity.inetOrgPerson.uid,
72-
displayName: displayName,
73-
code: k
74-
}
75-
76-
})
77-
.then(() => {
78-
this.logger.log("reset compte envoyé pour uid" +initDto.uid +" à " + mail )
79-
})
80-
.catch((e) => {
81-
throw new BadRequestException({
82-
message: 'Erreur serveur lors de l envoi du mail',
83-
error: "Bad Request",
84-
statusCode: 400
85-
});
66+
const mail = <string>get(identity.toObject(), mailAttribute)
67+
const token = await this.askToken({mail: mail, uid: initDto.uid}, padd + k.toString(16),PasswdService.CODE_EXPIRATION)
68+
this.logger.log("Token :" + token + ' int : ' + k.toString(10))
69+
if (initDto.type === 0){
70+
this.logger.log("Reset password asked by mail for : " + initDto.uid )
71+
if (mailAttribute !== '') {
72+
const displayName=identity.inetOrgPerson.displayName
73+
this.mailer.sendMail({
74+
from: this.config.get('mailer.sender'),
75+
to: mail,
76+
subject: 'Reinitialisation de votre mot de passe',
77+
template: "resetaccount",
78+
context:{
79+
uid: identity.inetOrgPerson.uid,
80+
displayName: displayName,
81+
code: k
82+
}
8683
})
87-
return token
84+
.then(() => {
85+
this.logger.log("reset compte envoyé pour uid" +initDto.uid +" à " + mail )
86+
})
87+
.catch((e) => {
88+
throw new BadRequestException({
89+
message: 'Erreur serveur lors de l envoi du mail',
90+
error: "Bad Request",
91+
statusCode: 400
92+
});
93+
})
94+
return token
95+
}else{
96+
return false
97+
}
8898
}else{
89-
return false
99+
//envoi par SMS si c est possible
100+
const policies=await this.passwdadmService.getPolicies()
101+
if (policies.resetBySms === true){
102+
this.logger.log("Reset password asked by SMS for : " + initDto.uid )
103+
const smsAttribute=this.config.get('frontPwd.identityMobileAttribute')
104+
if (smsAttribute !== ''){
105+
const numTel = <string>get(identity.toObject(), smsAttribute)
106+
this.smsService.send(numTel,"Votre code de reinitialisation : " + k.toString(10))
107+
}
108+
return token
109+
}else{
110+
return false
111+
}
90112
}
113+
91114
}catch(e){
92115
this.logger.error("Error while reseting password. " + e + ` (uid=${initDto?.uid})`);
93116
//on retoune un token qui ne sert à rien pour ne pas divulguer que l uid n existe pas
@@ -98,6 +121,7 @@ export class PasswdService extends AbstractService {
98121

99122

100123
}
124+
//Initialisation du compte. Envoi d' un mail avec un token pour l'init du compte
101125
public async initAccount(initDto: InitAccountDto):Promise<any>{
102126
//recherche de l'identity
103127
try{
@@ -144,10 +168,19 @@ export class PasswdService extends AbstractService {
144168
}
145169

146170
}
171+
//Changement du password
147172
public async change(passwdDto: ChangePasswordDto): Promise<[Jobs, any]> {
148173
try {
149-
const identity = await this.identities.findOne({ 'inetOrgPerson.uid': passwdDto.uid }) as Identities;
150-
174+
const identity = await this.identities.findOne({ 'inetOrgPerson.uid': passwdDto.uid,'state':IdentityState.SYNCED }) as Identities;
175+
//verification de la police de mdp
176+
if (await this.passwdadmService.checkPolicies(passwdDto.newPassword) === false){
177+
throw new BadRequestException({
178+
message: 'Une erreur est survenue : Le mot de passe ne respecte pas la politique des mots de passe',
179+
error: "Bad Request",
180+
statusCode: 400,
181+
});
182+
}
183+
//tout est ok en envoie au backend
151184
return await this.backends.executeJob(ActionType.IDENTITY_PASSWORD_CHANGE, identity._id, {
152185
...passwdDto,
153186
...pick(identity.toJSON(), ['inetOrgPerson']),
@@ -181,7 +214,8 @@ export class PasswdService extends AbstractService {
181214
}
182215
}
183216

184-
public async askToken(askToken: AskTokenDto,k,ttl): Promise<string> {
217+
// Genere un token pour les autres methodes
218+
public async askToken(askToken: AskTokenDto,k, ttl: number): Promise<string> {
185219
try {
186220
/*
187221
if (ttl >0){
@@ -217,6 +251,7 @@ export class PasswdService extends AbstractService {
217251
throw new BadRequestException('Impossible de générer un token, une erreur est survenue');
218252
}
219253
}
254+
// decrypte le token à l aide du code
220255
public async decryptTokenWithCode(token: string,code: number): Promise<CipherData> {
221256
try {
222257
token=decodeURIComponent(token)
@@ -238,6 +273,7 @@ export class PasswdService extends AbstractService {
238273
throw new BadRequestException('Invalid token xx');
239274
}
240275
}
276+
// decrypte le token d'initialisation du compte
241277
public async decryptToken(token: string): Promise<CipherData> {
242278
try {
243279
token=decodeURIComponent(token)
@@ -258,8 +294,17 @@ export class PasswdService extends AbstractService {
258294
throw new BadRequestException('Invalid token');
259295
}
260296
}
297+
// reset du password
261298
public async resetByCode(data:ResetByCodeDto):Promise<[Jobs,any]>{
262299
this.logger.log('resetByCode : ' + data.token+ ' '+ data.code )
300+
//verification du passwordPolicies
301+
if (await this.passwdadmService.checkPolicies(data.newpassword) === false){
302+
throw new BadRequestException({
303+
message: 'Une erreur est survenue : Le mot de passe ne respecte pas la politique de securité',
304+
error: "Bad Request",
305+
statusCode: 400,
306+
});
307+
}
263308
const tokenData=await this.decryptTokenWithCode(data.token,data.code)
264309
this.logger.log( 'dataToken :' + tokenData)
265310
try{
@@ -288,11 +333,12 @@ export class PasswdService extends AbstractService {
288333
throw new BadRequestException('Une erreur est survenue : Tentative de réinitialisation de mot de passe impossible');
289334
}
290335
}
336+
// methode pour le reset par token (pour l initialisation du compte)
291337
public async reset(data: ResetPasswordDto): Promise<[Jobs, any]> {
292338
const tokenData = await this.decryptToken(data.token);
293339

294340
try {
295-
const identity = await this.identities.findOne({ 'inetOrgPerson.uid': tokenData.uid }) as Identities;
341+
const identity = await this.identities.findOne({ 'inetOrgPerson.uid': tokenData.uid,'state':IdentityState.SYNCED }) as Identities;
296342

297343
const [_, response] = await this.backends.executeJob(
298344
ActionType.IDENTITY_PASSWORD_RESET,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {Injectable} from "@nestjs/common";
2+
import {AbstractService} from "~/_common/abstracts/abstract.service";
3+
import {ConfigService} from "@nestjs/config";
4+
import { parsePhoneNumber } from 'awesome-phonenumber'
5+
6+
@Injectable()
7+
export class SmsService extends AbstractService {
8+
public constructor(protected config: ConfigService){
9+
super()
10+
}
11+
public send(telNumber: string , message:string){
12+
this.logger.log('Envoi SMS : ' +telNumber + ' message :' + message)
13+
const host = this.config.get('sms.host')
14+
const systemId = this.config.get('sms.systemId')
15+
const password = this.config.get('sms.password')
16+
const sourceAddr=this.config.get('sms.sourceAddr')
17+
const smpp = require('smpp');
18+
const logger=this.logger
19+
//normalisation du numero de telephone
20+
const pTelNumber=parsePhoneNumber(telNumber,{ regionCode: this.config.get('sms.regionCode') })
21+
this.logger.log('phone number parsed :' +pTelNumber.number.e164)
22+
if (pTelNumber.valid === true){
23+
const session = smpp.connect({
24+
url: host,
25+
debug: true
26+
}, function() {
27+
session.bind_transmitter({
28+
system_id: systemId,
29+
password: password,
30+
}, function(pdu) {
31+
if (pdu.command_status === 0) {
32+
// Successfully bound
33+
session.submit_sm({
34+
source_addr_ton: 5,
35+
source_addr_npi:0,
36+
source_addr : sourceAddr,
37+
destination_addr: pTelNumber.number.e164,
38+
dest_addr_ton:1,
39+
dest_addr_npi:1,
40+
data_coding: 'GSM 03.38',
41+
short_message: message
42+
}, function(pdu) {
43+
if (pdu.command_status === 0) {
44+
// Message successfully sent
45+
console.log('SUCCESS : ' + pdu.message_id);
46+
logger.log('SMS SUCCESS ')
47+
session.unbind()
48+
session.close()
49+
}else{
50+
console.log('FAIL : ' + pdu.command_status)
51+
session.unbind()
52+
session.close()
53+
}
54+
});
55+
}
56+
session.on('error', function(error){
57+
console.log('smpp error', error)
58+
logger.log('SMS ERREUR ' + error)
59+
session.unbind()
60+
session.close()
61+
})
62+
});
63+
});
64+
}else{
65+
this.logger.error( 'Numero invalide :' + telNumber)
66+
}
67+
68+
69+
}
70+
}

0 commit comments

Comments
 (0)