Skip to content

Commit ee98e01

Browse files
alainabbastacxou
authored andcommitted
save
1 parent ea293c8 commit ee98e01

20 files changed

+1832
-57
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"dependencies": {
4949
"@kradihsoy/lt-schematics": "^1.0.13",
5050
"@nestjs-modules/ioredis": "^2.0.2",
51+
"@nestjs-modules/mailer": "^2.0.2",
5152
"@nestjs/bullmq": "^10.1.1",
5253
"@nestjs/common": "^10.3.9",
5354
"@nestjs/config": "^3.2.2",
@@ -66,13 +67,16 @@
6667
"class-transformer": "^0.5.1",
6768
"class-validator": "^0.14.1",
6869
"cookie-parser": "^1.4.6",
70+
"handlebars": "^4.7.8",
6971
"helmet": "^7.1.0",
7072
"ioredis": "^5.4.1",
7173
"loglevel": "^1.9.1",
74+
"mjml": "^4.15.3",
7275
"mongoose": "^8.4.3",
7376
"nest-commander": "^3.13.0",
7477
"nest-winston": "^1.10.0",
7578
"nestjs-request-context": "^3.0.0",
79+
"nodemailer": "^6.9.14",
7680
"passport": "^0.7.0",
7781
"passport-jwt": "^4.0.1",
7882
"passport-local": "^1.0.0",
@@ -101,6 +105,7 @@
101105
"@types/inquirer": "^9.0.7",
102106
"@types/jest": "^29.5.12",
103107
"@types/node": "^20.14.8",
108+
"@types/nodemailer": "^6.4.15",
104109
"@types/passport": "^1.0.16",
105110
"@types/passport-http": "^0.3.11",
106111
"@types/passport-jwt": "^4.0.1",

src/app.module.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,34 @@ import { APP_FILTER, APP_GUARD, APP_PIPE } from '@nestjs/core';
1515
import { AuthGuard } from './_common/guards/auth.guard';
1616
import { MongooseValidationFilter } from './_common/filters/mongoose-validation.filter';
1717
import { DtoValidationPipe } from './_common/pipes/dto-validation.pipe';
18+
import {SettingstModule} from "~/settings/settings.module";
19+
import {MailerModule} from "@nestjs-modules/mailer";
20+
import {HandlebarsAdapter} from "@nestjs-modules/mailer/dist/adapters/handlebars.adapter";
21+
import {MjmlAdapter} from "@nestjs-modules/mailer/dist/adapters/mjml.adapter";
1822

1923
@Module({
2024
imports: [
2125
ConfigModule.forRoot({
2226
isGlobal: true,
2327
load: [config],
2428
}),
29+
MailerModule.forRootAsync({
30+
imports: [ConfigModule],
31+
inject: [ConfigService],
32+
useFactory: async (config: ConfigService) => ({
33+
transport: config.get('mailer.host'),
34+
defaults: {
35+
from: config.get('mailer.sender'),
36+
},
37+
template: {
38+
dir: __dirname + '/../templates',
39+
adapter: new HandlebarsAdapter(),
40+
options: {
41+
strict: true,
42+
},
43+
},
44+
}),
45+
}),
2546
MongooseModule.forRootAsync({
2647
imports: [ConfigModule],
2748
inject: [ConfigService],
@@ -59,6 +80,7 @@ import { DtoValidationPipe } from './_common/pipes/dto-validation.pipe';
5980
RequestContextModule,
6081
CoreModule.register(),
6182
ManagementModule.register(),
83+
SettingstModule.register()
6284
],
6385
controllers: [AppController],
6486
providers: [

src/config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ export interface ConfigInstance {
3434
jwt: {
3535
options: JwtModuleOptions;
3636
};
37+
mailer: {
38+
host: string;
39+
port: number;
40+
sender: string;
41+
};
42+
frontPwd: {
43+
url:string;
44+
identityMailAttribute: string;
45+
identityMobileAttribute: string;
46+
};
3747
swagger: {
3848
path: string;
3949
api: string;
@@ -101,4 +111,15 @@ export default (): ConfigInstance => ({
101111
},
102112
},
103113
},
114+
mailer:{
115+
host: process.env['SESAME_SMTP_SERVER'],
116+
port: parseInt(process.env['SESAME_SMTP_PORT']) || 25,
117+
sender: process.env['SESAME_MDP_SENDER'] || 'noreply@mydomain.com'
118+
},
119+
frontPwd:{
120+
url: process.env['SESAME_FRONT_MDP'],
121+
identityMailAttribute: process.env['SESAME_RESET_PWD_MAIL'] || '',
122+
identityMobileAttribute: process.env['SESAME_RESET_PWD_MOBILE'] || ''
123+
}
124+
104125
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsString } from 'class-validator';
3+
4+
export class AskCodeDto {
5+
@IsString()
6+
@ApiProperty({ example: 'paul.bismuth', description: 'User id' })
7+
uid: string;
8+
9+
@ApiProperty({ example: 'monemail@mondomaine.com', description: 'secondary mail' })
10+
@IsString()
11+
mail: string;
12+
13+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ export class AskTokenDto {
99
@ApiProperty({ example: 'monemail@mondomaine.com', description: 'secondary mail' })
1010
@IsString()
1111
mail: string;
12+
1213
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsString } from 'class-validator';
3+
4+
export class InitAccountDto {
5+
6+
@ApiProperty({ example: 'uid', description: 'paul.smith' })
7+
@IsString()
8+
uid: string;
9+
}

src/management/passwd/passwd.controller.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ import { AskTokenDto } from './dto/ask-token.dto';
77
import { VerifyTokenDto } from './dto/verify-token.dto';
88
import { ResetPasswordDto } from './dto/reset-password.dto';
99
import {omit} from "radash";
10+
import {PasswdadmService} from "~/settings/passwdadm/passwdadm.service";
11+
import {PasswordPolicies} from "~/settings/passwdadm/_schemas/PasswordPolicies";
12+
import {InitAccountDto} from "~/management/passwd/dto/init-account.dto";
1013

1114
@Controller('passwd')
1215
@ApiTags('management/passwd')
1316
export class PasswdController {
1417
private readonly logger = new Logger(PasswdController.name);
1518

16-
public constructor(private passwdService: PasswdService) { }
19+
public constructor(private passwdService: PasswdService,private passwdadmService: PasswdadmService) { }
1720

1821
@Post('change')
1922
@ApiOperation({ summary: 'Execute un job de changement de mot de passe sur le/les backends' })
@@ -73,8 +76,19 @@ export class PasswdController {
7376
@ApiOperation({ summary: 'Retourne la politique de mot de passe à appliquer' })
7477
@ApiResponse({ status: HttpStatus.OK })
7578
public async getPolicies(@Res() res: Response): Promise<Response> {
76-
const data = await this.passwdService.getPolicies()
79+
const data = await this.passwdadmService.getPolicies()
7780
//const datax=omit(data.toObject,['_id'])
7881
return res.status(HttpStatus.OK).json({data})
7982
}
83+
@Post('init')
84+
@ApiOperation({ summary: 'Initialise le compte envoi un jeton par mail à l\'identité' })
85+
@ApiResponse({ status: HttpStatus.OK })
86+
public async init(@Body() body: InitAccountDto, @Res() res: Response): Promise<Response> {
87+
const debug = {}
88+
const ok=await this.passwdService.initAccount(body)
89+
return res.status(HttpStatus.OK).json({
90+
message: 'Email envoyé verifiez votre boite mail alternative et vos spam',
91+
...debug,
92+
});
93+
}
8094
}

src/management/passwd/passwd.module.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,13 @@ import { PasswdService } from './passwd.service';
33
import { PasswdController } from './passwd.controller';
44
import { BackendsModule } from '~/core/backends/backends.module';
55
import { IdentitiesModule } from '../identities/identities.module';
6-
import {MongooseModule} from "@nestjs/mongoose";
7-
import {Agents, AgentsSchema} from "~/core/agents/_schemas/agents.schema";
8-
import {PasswordPolicies,PasswordPoliciesSchema} from "~/management/passwd/_schemas/PasswordPolicies";
6+
import { PasswdadmModule} from "~/settings/passwdadm/passwdadm.module";
7+
import {PasswdadmService} from "~/settings/passwdadm/passwdadm.service";
8+
99

1010
@Module({
1111
imports: [BackendsModule,
12-
IdentitiesModule,
13-
MongooseModule.forFeatureAsync([
14-
{
15-
name: PasswordPolicies.name,
16-
useFactory: () => PasswordPoliciesSchema,
17-
},
18-
]),
19-
],
12+
IdentitiesModule, PasswdadmModule],
2013
controllers: [PasswdController],
2114
providers: [PasswdService],
2215
})

src/management/passwd/passwd.service.ts

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import { AskTokenDto } from './dto/ask-token.dto';
1010
import { ChangePasswordDto } from './dto/change-password.dto';
1111
import { ResetPasswordDto } from './dto/reset-password.dto';
1212
import { IdentitiesService } from '../identities/identities.service';
13-
import { pick } from 'radash';
13+
import { pick,get } from 'radash';
1414
import { Identities } from '../identities/_schemas/identities.schema';
15-
import {PasswordPolicies} from "~/management/passwd/_schemas/PasswordPolicies";
16-
import {Model} from "mongoose";
17-
import {InjectModel} from "@nestjs/mongoose";
15+
import {MailerModule, MailerService} from "@nestjs-modules/mailer";
16+
import {InitAccountDto} from "~/management/passwd/dto/init-account.dto";
17+
import {ConfigService} from "@nestjs/config";
1818

1919
interface TokenData {
2020
k: string;
@@ -31,20 +31,63 @@ interface CipherData {
3131
export class PasswdService extends AbstractService {
3232
public static readonly RANDOM_BYTES_K = 16;
3333
public static readonly RANDOM_BYTES_IV = 12;
34+
public static readonly RANDOM_BYTES_CODE = 5;
3435

3536
public static readonly TOKEN_ALGORITHM = 'aes-256-gcm';
3637

37-
public static readonly TOKEN_EXPIRATION = 3600;
38+
public static readonly TOKEN_EXPIRATION = 604800;
39+
public static readonly CODE_EXPIRATION = 900;
3840

3941
public constructor(
4042
protected readonly backends: BackendsService,
4143
protected readonly identities: IdentitiesService,
42-
@InjectRedis() private readonly redis: Redis,
43-
@InjectModel(PasswordPolicies.name) protected passwordPolicies: Model<PasswordPolicies>
44+
protected mailer: MailerService,
45+
protected config: ConfigService,
46+
@InjectRedis() private readonly redis: Redis
4447
) {
4548
super();
4649
}
50+
public async initAccount(initDto: InitAccountDto):Promise<any>{
51+
//recherche de l'identity
52+
try{
53+
const identity = await this.identities.findOne({ 'inetOrgPerson.uid': initDto.uid }) as Identities;
54+
//envoi du mail
55+
const mailAttribute=this.config.get('frontPwd.identityMailAttribute')
56+
this.logger.log("mailer.identityMailAttribute : " +mailAttribute )
57+
if (mailAttribute !== '') {
58+
const mail = <string>get(identity.toObject(), mailAttribute)
59+
//demande du token
60+
const token = await this.askToken({mail: mail, uid: initDto.uid})
61+
//envoi du token
62+
this.mailer.sendMail({
63+
from: this.config.get('mailer.sender'),
64+
to: mail,
65+
subject: 'Activation de votre compte',
66+
template: "initaccount",
67+
context:{
68+
uid: initDto.uid,
69+
url:this.config.get('frontPwd.url')+'/initaccount/'+ token
70+
}
71+
72+
})
73+
.then(() => {
74+
this.logger.log("Init compte envoyé pour uid" +initDto.uid +" à " + mail )
75+
})
76+
.catch((e) => {
77+
this.logger.error("Erreur envoi mail" + e )
78+
})
79+
80+
return true
81+
}else{
82+
this.logger.error("Error while initAccount identityMailAttribute nor defined");
83+
return false
84+
}
85+
}catch(e){
86+
this.logger.error("Error while changing password. " + e + ` (uid=${initDto?.uid})`);
87+
return false
88+
}
4789

90+
}
4891
public async change(passwdDto: ChangePasswordDto): Promise<[Jobs, any]> {
4992
try {
5093
const identity = await this.identities.findOne({ 'inetOrgPerson.uid': passwdDto.uid }) as Identities;
@@ -81,8 +124,40 @@ export class PasswdService extends AbstractService {
81124
});
82125
}
83126
}
127+
/*
128+
public async askCode(askCode: AdkCode):Promise<[string,string]>{
129+
try {
130+
await this.identities.findOne({'inetOrgPerson.uid': askCode.uid});
131+
const code = crypto.randomBytes(PasswdService.RANDOM_BYTES_CODE).toString('utf8')
132+
const k = crypto.randomBytes(PasswdService.RANDOM_BYTES_K).toString('hex');
133+
const iv = crypto.randomBytes(PasswdService.RANDOM_BYTES_IV).toString('base64');
134+
const cipher = crypto.createCipheriv(PasswdService.TOKEN_ALGORITHM, k, iv);
135+
136+
let ciphertext = cipher.update(
137+
JSON.stringify(<CipherData>{ code:code,uid: askToken.uid}),
138+
'utf8',
139+
'base64',
140+
);
141+
ciphertext += cipher.final('base64');
142+
143+
await this.redis.set(
144+
ciphertext,
145+
JSON.stringify(<TokenData>{
146+
k,
147+
iv,
148+
tag: cipher.getAuthTag().toString('base64'),
149+
}),
150+
);
151+
await this.redis.expire(ciphertext, PasswdService.TOKEN_EXPIRATION);
152+
return [k,cipherText]
153+
} catch (e) {
154+
this.logger.error("Error while ask Code. " + e + ` (uid=${askToken?.uid})`);
155+
throw new BadRequestException('Impossible de générer un token, une erreur est survenue');
156+
}
157+
}
158+
*/
84159

85-
public async askToken(askToken: AskTokenDto): Promise<string> {
160+
public async askToken(askToken: AskTokenDto): Promise<string> {
86161
try {
87162
await this.identities.findOne({ 'inetOrgPerson.uid': askToken.uid });
88163

@@ -106,7 +181,7 @@ export class PasswdService extends AbstractService {
106181
}),
107182
);
108183
await this.redis.expire(ciphertext, PasswdService.TOKEN_EXPIRATION);
109-
return ciphertext;
184+
return encodeURIComponent(ciphertext);
110185
} catch (e) {
111186
this.logger.error("Error while ask token. " + e + ` (uid=${askToken?.uid})`);
112187
throw new BadRequestException('Impossible de générer un token, une erreur est survenue');
@@ -115,6 +190,7 @@ export class PasswdService extends AbstractService {
115190

116191
public async decryptToken(token: string): Promise<CipherData> {
117192
try {
193+
token=decodeURIComponent(token)
118194
const result = await this.redis.get(token);
119195
const cypherData: TokenData = JSON.parse(result);
120196

@@ -164,11 +240,5 @@ export class PasswdService extends AbstractService {
164240
}
165241
}
166242

167-
public async getPolicies(): Promise<any>{
168-
const passwordPolicies = await this.passwordPolicies.findOne()
169-
if (passwordPolicies === null){
170-
return new this.passwordPolicies()
171-
}
172-
return passwordPolicies
173-
}
243+
174244
}

src/management/passwd/_schemas/PasswordPolicies.ts renamed to src/settings/passwdadm/_schemas/PasswordPolicies.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export type PasswordPoliciesDocument = Identities & Document;
66

77
@Schema({ versionKey: false })
88
export class PasswordPolicies extends AbstractSchema {
9-
@Prop({ type: Number, default: 8 })
9+
@Prop({ type: Number, default: 10 })
1010
len: Number;
1111

1212
@Prop({ type: Number,default:1 })
@@ -35,7 +35,11 @@ export class PasswordPolicies extends AbstractSchema {
3535

3636
@Prop({ type: Number,default:3600 })
3737
bannedTime: Number;
38+
@Prop({ type: Boolean,default:false })
39+
resetBySms: Boolean;
3840

41+
@Prop({ type: String,default:'https://google.fr' })
42+
redirectUrl: String;
3943
}
4044

4145
export const PasswordPoliciesSchema = SchemaFactory.createForClass(PasswordPolicies);

0 commit comments

Comments
 (0)