diff --git a/src/core/backends/backends.service.ts b/src/core/backends/backends.service.ts index 0227b82f..7d2f55dd 100644 --- a/src/core/backends/backends.service.ts +++ b/src/core/backends/backends.service.ts @@ -65,6 +65,7 @@ export class BackendsService extends AbstractQueueProcessor { await this.identitiesService.model.findByIdAndUpdate(isSyncedJob?.concernedTo?.id, { $set: { state: IdentityState.SYNCED, + lastBackendSync: new Date(), }, }); this.logger.warn(`Job already completed, syncing... [${job.id}::COMPLETED]`); diff --git a/src/management/identities/_dto/force-password-dto.ts b/src/management/identities/_dto/force-password-dto.ts new file mode 100644 index 00000000..2636eefa --- /dev/null +++ b/src/management/identities/_dto/force-password-dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class ForcePasswordDto { + @IsString() + @ApiProperty({ example: '66d80ab41821baca9bf965b2', description: 'User object id', type: String }) + public id: string; + + @IsString() + @ApiProperty({ example: 'MyNewPassword', description: 'New password', type: String }) + public newPassword: string; +} diff --git a/src/management/identities/_dto/need-change-password.dto.ts b/src/management/identities/_dto/need-change-password.dto.ts new file mode 100644 index 00000000..21272dd0 --- /dev/null +++ b/src/management/identities/_dto/need-change-password.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class NeedChangePasswordDto { + @IsString() + @ApiProperty({ example: '66d80ab41821baca9bf965b2', description: 'User object id', type: String }) + public id: string; + +} diff --git a/src/management/identities/_enums/data-status.ts b/src/management/identities/_enums/data-status.ts index 7fa94e51..fe2884b5 100644 --- a/src/management/identities/_enums/data-status.ts +++ b/src/management/identities/_enums/data-status.ts @@ -4,6 +4,8 @@ //DELETED : soft delete export enum DataStatusEnum { ACTIVE = 1, - INACTIVE = 0, + NOTINITIALIZED = 0, DELETED = -1, + PASSWORDNEEDTOBECHANGED=-2, + INACTIVE = -3, } diff --git a/src/management/identities/_schemas/identities.schema.ts b/src/management/identities/_schemas/identities.schema.ts index 324bd80e..fbc1fac4 100644 --- a/src/management/identities/_schemas/identities.schema.ts +++ b/src/management/identities/_schemas/identities.schema.ts @@ -22,7 +22,7 @@ export class Identities extends AbstractSchema { @Prop({ type: Number, enum: IdentityLifecycle, default: IdentityLifecycle.INACTIVE }) public lifecycle: IdentityLifecycle; - @Prop({ type: Number, enum: DataStatusEnum, default: DataStatusEnum.INACTIVE }) + @Prop({ type: Number, enum: DataStatusEnum, default: DataStatusEnum.NOTINITIALIZED }) public dataStatus: DataStatusEnum; @Prop({ type: Boolean, default: false }) diff --git a/src/management/identities/abstract-identities.service.ts b/src/management/identities/abstract-identities.service.ts index 01c476b0..5faf0d3f 100644 --- a/src/management/identities/abstract-identities.service.ts +++ b/src/management/identities/abstract-identities.service.ts @@ -1,18 +1,21 @@ -import { BadRequestException, forwardRef, HttpException, Inject, Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Document, Model, ModifyResult, Query, Types } from 'mongoose'; -import { AbstractServiceSchema } from '~/_common/abstracts/abstract.service.schema'; -import { AbstractSchema } from '~/_common/abstracts/schemas/abstract.schema'; -import { ValidationConfigException, ValidationSchemaException } from '~/_common/errors/ValidationException'; -import { IdentitiesUpsertDto } from './_dto/identities.dto'; -import { IdentityState } from './_enums/states.enum'; -import { Identities } from './_schemas/identities.schema'; -import { IdentitiesValidationService } from './validations/identities.validation.service'; -import { FactorydriveService } from '@the-software-compagny/nestjs_module_factorydrive'; -import { BackendsService } from '~/core/backends/backends.service'; -import { construct, omit } from 'radash'; -import { toPlainAndCrush } from '~/_common/functions/to-plain-and-crush'; -import { createHash } from 'node:crypto'; +import {BadRequestException, forwardRef, HttpException, Inject, Injectable} from '@nestjs/common'; +import {InjectModel} from '@nestjs/mongoose'; +import {Document, Model, ModifyResult, Query, Types} from 'mongoose'; +import {AbstractServiceSchema} from '~/_common/abstracts/abstract.service.schema'; +import {AbstractSchema} from '~/_common/abstracts/schemas/abstract.schema'; +import {ValidationConfigException, ValidationSchemaException} from '~/_common/errors/ValidationException'; +import {IdentitiesUpsertDto} from './_dto/identities.dto'; +import {IdentityState} from './_enums/states.enum'; +import {Identities} from './_schemas/identities.schema'; +import {IdentitiesValidationService} from './validations/identities.validation.service'; +import {FactorydriveService} from '@the-software-compagny/nestjs_module_factorydrive'; +import {BackendsService} from '~/core/backends/backends.service'; +import {construct, omit} from 'radash'; +import {toPlainAndCrush} from '~/_common/functions/to-plain-and-crush'; +import {createHash} from 'node:crypto'; +import {PasswdadmService} from "~/settings/passwdadm.service"; +import {DataStatusEnum} from "~/management/identities/_enums/data-status"; +import {JobState} from "~/core/jobs/_enums/state.enum"; @Injectable() export abstract class AbstractIdentitiesService extends AbstractServiceSchema { @@ -20,6 +23,7 @@ export abstract class AbstractIdentitiesService extends AbstractServiceSchema { @InjectModel(Identities.name) protected _model: Model, protected readonly _validation: IdentitiesValidationService, protected readonly storage: FactorydriveService, + protected readonly passwdAdmService: PasswdadmService, @Inject(forwardRef(() => BackendsService)) protected readonly backends: BackendsService, ) { super(); @@ -138,4 +142,51 @@ export abstract class AbstractIdentitiesService extends AbstractServiceSchema { hash.update(data); return hash.digest('hex').toString(); } + public async activation(id: string, status: DataStatusEnum) { + //recherche de l'identité + let identity: Identities = null; + let statusChanged = false; + try { + identity = await this.findById(id); + } catch (error) { + throw new HttpException('Id not found', 400); + } + if (identity.lastBackendSync === null) { + throw new HttpException('Identity has never been synced', 400); + } + if (identity.dataStatus !== DataStatusEnum.DELETED) { + identity.dataStatus = status; + statusChanged = true; + } else { + throw new BadRequestException('Identity is in status deleted'); + } + //sauvegarde de l'identité + if (statusChanged) { + // le dataStaus à changé on envoye l info aux backend et on enregistre l identité + // Envoi du status au backend + let statusBackend=true + if (status == DataStatusEnum.INACTIVE || status == DataStatusEnum.PASSWORDNEEDTOBECHANGED){ + statusBackend= false + } + const result = await this.backends.activationIdentity(identity._id.toString(),statusBackend); + if (result.state === JobState.COMPLETED) { + await super.update(identity._id, identity); + } else { + throw new HttpException('Backend failed', 400); + } + } + } + public async askToChangePassword(id: string){ + try { + const identity = await this.findById(id); + if (identity.dataStatus === DataStatusEnum.ACTIVE) { + identity.dataStatus = DataStatusEnum.PASSWORDNEEDTOBECHANGED + await super.update(identity._id, identity); + } else { + throw new BadRequestException('Identity not in active'); + } + } catch (error) { + throw new HttpException('Id not found', 400); + } + } } diff --git a/src/management/identities/identities-activation.controller.ts b/src/management/identities/identities-activation.controller.ts index a2bc1d27..9ab79b88 100644 --- a/src/management/identities/identities-activation.controller.ts +++ b/src/management/identities/identities-activation.controller.ts @@ -1,9 +1,10 @@ -import { AbstractController } from '~/_common/abstracts/abstract.controller'; -import { IdentitiesActivationService } from '~/management/identities/identities-activation.service'; -import { ApiOperation, ApiResponse, ApiTags} from '@nestjs/swagger'; -import { Body, Controller, HttpStatus, Post, Res} from '@nestjs/common'; -import { Response } from 'express'; -import { ActivationDto } from '~/management/identities/_dto/_parts/activation-dto'; +import {AbstractController} from '~/_common/abstracts/abstract.controller'; +import {IdentitiesActivationService} from '~/management/identities/identities-activation.service'; +import {ApiOperation, ApiResponse, ApiTags} from '@nestjs/swagger'; +import {Body, Controller, HttpStatus, Post, Res} from '@nestjs/common'; +import {Response} from 'express'; +import {ActivationDto} from '~/management/identities/_dto/_parts/activation-dto'; +import {DataStatusEnum} from "~/management/identities/_enums/data-status"; @ApiTags('management/identities') @Controller('identities') @@ -17,7 +18,11 @@ export class IdentitiesActivationController extends AbstractController { @ApiResponse({ status: HttpStatus.OK }) public async activation(@Res() res: Response, @Body() body: ActivationDto): Promise { try { - const data = await this._service.activation(body.id, body.status); + let param = DataStatusEnum.INACTIVE + if ( body.status === true){ + param=DataStatusEnum.ACTIVE + } + const data = await this._service.activation(body.id, param); return res.status(HttpStatus.OK).json({ statusCode: HttpStatus.OK, data, diff --git a/src/management/identities/identities-activation.service.ts b/src/management/identities/identities-activation.service.ts index 0d42c940..c076d9b7 100644 --- a/src/management/identities/identities-activation.service.ts +++ b/src/management/identities/identities-activation.service.ts @@ -1,46 +1,6 @@ import { AbstractIdentitiesService } from '~/management/identities/abstract-identities.service'; -import { Identities } from '~/management/identities/_schemas/identities.schema'; -import { BadRequestException, HttpException } from '@nestjs/common'; -import { DataStatusEnum } from '~/management/identities/_enums/data-status'; -import { JobState } from '~/core/jobs/_enums/state.enum'; + export class IdentitiesActivationService extends AbstractIdentitiesService { - public async activation(id: string, status: boolean) { - //recherche de l'identité - let identity: Identities = null; - let statusChanged = false; - try { - identity = await this.findById(id); - } catch (error) { - throw new HttpException('Id not found', 400); - } - if (identity.lastBackendSync === null) { - throw new HttpException('Identity has never been synced', 400); - } - if (identity.dataStatus !== DataStatusEnum.DELETED) { - if (status) { - if (identity.dataStatus !== DataStatusEnum.ACTIVE) { - identity.dataStatus = DataStatusEnum.ACTIVE; - statusChanged = true; - } - } else { - if (identity.dataStatus !== DataStatusEnum.INACTIVE) { - identity.dataStatus = DataStatusEnum.INACTIVE; - statusChanged = true; - } - } - } else { - throw new BadRequestException('Identity is in status deleted'); - } - //sauvegarde de l'identité - if (statusChanged) { - // le dataStaus à changé on envoye l info aux backend et on enregistre l identité - const result = await this.backends.activationIdentity(identity._id.toString(), status); - if (result.state === JobState.COMPLETED) { - await super.update(identity._id, identity); - } else { - throw new HttpException('Backend failed', 400); - } - } - } + } diff --git a/src/management/identities/identities-forcepassword.controller.ts b/src/management/identities/identities-forcepassword.controller.ts new file mode 100644 index 00000000..b5ecd734 --- /dev/null +++ b/src/management/identities/identities-forcepassword.controller.ts @@ -0,0 +1,51 @@ +import { AbstractController } from '~/_common/abstracts/abstract.controller'; +import { IdentitiesActivationService } from '~/management/identities/identities-activation.service'; +import { ApiOperation, ApiResponse, ApiTags} from '@nestjs/swagger'; +import { Body, Controller, HttpStatus, Post, Res} from '@nestjs/common'; +import { Response } from 'express'; +import { ActivationDto } from '~/management/identities/_dto/_parts/activation-dto'; +import {ForcePasswordDto} from "~/management/identities/_dto/force-password-dto"; +import {IdentitiesForcepasswordService} from "~/management/identities/identities-forcepassword.service"; +import {NeedChangePasswordDto} from "~/management/identities/_dto/need-change-password.dto"; + +@ApiTags('management/identities') +@Controller('identities') +export class IdentitiesForcePasswordController extends AbstractController { + public constructor(protected readonly _service: IdentitiesForcepasswordService) { + super(); + } + @Post('forcepassword') + @ApiOperation({ summary: 'force le mot de passe de l identite' }) + @ApiResponse({ status: HttpStatus.OK }) + public async forcePassword(@Res() res: Response, @Body() body: ForcePasswordDto): Promise { + try { + const data = await this._service.forcePassword(body.id, body.newPassword); + return res.status(HttpStatus.OK).json({ + statusCode: HttpStatus.OK, + data, + }); + } catch (error) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: error.message, + }); + } + } + @Post('needtochangepassword') + @ApiOperation({ summary: "force l'utilisateur a changer son mot de passe" }) + @ApiResponse({ status: HttpStatus.OK }) + public async needToChangePassword(@Res() res: Response, @Body() body: NeedChangePasswordDto): Promise { + try { + const data = await this._service.needToChangePassword(body.id) + return res.status(HttpStatus.OK).json({ + statusCode: HttpStatus.OK, + data, + }); + } catch (error) { + return res.status(HttpStatus.BAD_REQUEST).json({ + statusCode: HttpStatus.BAD_REQUEST, + message: error.message, + }); + } + } +} diff --git a/src/management/identities/identities-forcepassword.service.ts b/src/management/identities/identities-forcepassword.service.ts new file mode 100644 index 00000000..5a71ef24 --- /dev/null +++ b/src/management/identities/identities-forcepassword.service.ts @@ -0,0 +1,84 @@ +import {AbstractIdentitiesService} from '~/management/identities/abstract-identities.service'; +import {Identities} from '~/management/identities/_schemas/identities.schema'; +import {BadRequestException, HttpException, Injectable} from '@nestjs/common'; +import {DataStatusEnum} from '~/management/identities/_enums/data-status'; +import {ActionType} from "~/core/backends/_enum/action-type.enum"; + + +@Injectable() +export class IdentitiesForcepasswordService extends AbstractIdentitiesService { + + public async forcePassword(id: string, newPassword: string) { + //recherche de l'identité + let identity: Identities = null; + try { + identity = await this.findById(id); + } catch (error) { + throw new HttpException('Id not found', 400); + } + if (identity.lastBackendSync === null) { + throw new HttpException('Identity has never been synced', 400); + } + if (identity.dataStatus === DataStatusEnum.DELETED) { + throw new BadRequestException('Identity is in status deleted'); + } + //changement du password check de la policy + if ((await this.passwdAdmService.checkPolicies(newPassword)) === false) { + throw new BadRequestException({ + message: 'Une erreur est survenue : Le mot de passe ne respecte pas la politique des mots de passe', + error: 'Bad Request', + statusCode: 400, + }); + } + //ok on envoie le changement de mdp + try{ + const [_, response] = await this.backends.executeJob( + ActionType.IDENTITY_PASSWORD_RESET, + identity._id, + { uid: identity.inetOrgPerson.uid, newPassword: newPassword, ...identity.toJSON() }, + { + async: false, + timeoutDiscard: true, + disableLogs: false, + switchToProcessing: false, + updateStatus: false, + }, + ); + if (response?.status === 0) { + //activation de l'identité + await this.activation(id,DataStatusEnum.ACTIVE) + return [_, response]; + } + }catch (e) { + this.logger.error('Error while reseting password. ' + e + ` (uid=${identity.inetOrgPerson.uid})`); + throw new BadRequestException( + 'Une erreur est survenue : Tentative de réinitialisation de mot de passe impossible', + ); + } + } + public async needToChangePassword(id: string){ + let identity: Identities = null; + try { + identity = await this.findById(id); + } catch (error) { + throw new HttpException('Id not found', 400); + } + if (identity.lastBackendSync === null) { + throw new HttpException('Identity has never been synced', 400); + } + if (identity.dataStatus === DataStatusEnum.DELETED) { + throw new BadRequestException('Identity is in status deleted'); + } + if (identity.dataStatus === DataStatusEnum.INACTIVE) { + throw new BadRequestException('Identity is in status disabled'); + } + //desactivation du compte + try{ + await this.activation(id,DataStatusEnum.PASSWORDNEEDTOBECHANGED) + }catch{ + throw new BadRequestException('Error changing status'); + } + + } + +} diff --git a/src/management/identities/identities.module.ts b/src/management/identities/identities.module.ts index 8fb20201..b4c08242 100644 --- a/src/management/identities/identities.module.ts +++ b/src/management/identities/identities.module.ts @@ -18,9 +18,13 @@ import { IdentitiesPhotoController } from '~/management/identities/identities-ph import { IdentitiesActivationController } from '~/management/identities/identities-activation.controller'; import { IdentitiesActivationService } from '~/management/identities/identities-activation.service'; import { IdentitiesDoublonController } from '~/management/identities/identities-doublon.controller'; +import {IdentitiesForcePasswordController} from "~/management/identities/identities-forcepassword.controller"; +import {IdentitiesForcepasswordService} from "~/management/identities/identities-forcepassword.service"; +import {SettingsModule} from "~/settings/settings.module"; import { EnsureIdentitiesIndexMiddleware } from './_middlewares/ensure-identities-index.middleware'; import { AgentsModule } from '~/core/agents/agents.module'; + @Module({ imports: [ MongooseModule.forFeatureAsync([ @@ -33,6 +37,7 @@ import { AgentsModule } from '~/core/agents/agents.module'; ]), FilestorageModule, forwardRef(() => BackendsModule), + SettingsModule, AgentsModule, ], providers: [ @@ -46,6 +51,7 @@ import { AgentsModule } from '~/core/agents/agents.module'; useClass: IdentitiesValidationFilter, }, IdentitiesJsonformsService, + IdentitiesForcepasswordService ], controllers: [ IdentitiesCrudController, @@ -53,6 +59,7 @@ import { AgentsModule } from '~/core/agents/agents.module'; IdentitiesPhotoController, IdentitiesDoublonController, IdentitiesActivationController, + IdentitiesForcePasswordController ], exports: [IdentitiesCrudService], }) diff --git a/src/management/passwd/passwd.service.ts b/src/management/passwd/passwd.service.ts index aa9211bf..11d85763 100644 --- a/src/management/passwd/passwd.service.ts +++ b/src/management/passwd/passwd.service.ts @@ -1,4 +1,4 @@ -import { InjectRedis } from '@nestjs-modules/ioredis'; +import {InjectRedis} from '@nestjs-modules/ioredis'; import { BadRequestException, HttpException, @@ -8,29 +8,30 @@ import { NotFoundException, } from '@nestjs/common'; import * as crypto from 'crypto'; -import { randomInt } from 'crypto'; +import {randomInt} from 'crypto'; import Redis from 'ioredis'; -import { AbstractService } from '~/_common/abstracts/abstract.service'; -import { ActionType } from '~/core/backends/_enum/action-type.enum'; -import { BackendsService } from '~/core/backends/backends.service'; -import { Jobs } from '~/core/jobs/_schemas/jobs.schema'; -import { AskTokenDto } from './_dto/ask-token.dto'; -import { ChangePasswordDto } from './_dto/change-password.dto'; -import { ResetPasswordDto } from './_dto/reset-password.dto'; -import { IdentitiesCrudService } from '../identities/identities-crud.service'; -import { get } from 'radash'; -import { Identities } from '../identities/_schemas/identities.schema'; -import { MailerService } from '@nestjs-modules/mailer'; -import { InitAccountDto } from '~/management/passwd/_dto/init-account.dto'; -import { ConfigService } from '@nestjs/config'; -import { ResetByCodeDto } from '~/management/passwd/_dto/reset-by-code.dto'; -import { PasswdadmService } from '~/settings/passwdadm.service'; -import { IdentityState } from '~/management/identities/_enums/states.enum'; -import { InitResetDto } from '~/management/passwd/_dto/init-reset.dto'; -import { SmsadmService } from '~/settings/smsadm.service'; -import { InitManyDto } from '~/management/passwd/_dto/init-many.dto'; -import { InitStatesEnum } from '~/management/identities/_enums/init-state.enum'; -import { MailadmService } from '~/settings/mailadm.service'; +import {AbstractService} from '~/_common/abstracts/abstract.service'; +import {ActionType} from '~/core/backends/_enum/action-type.enum'; +import {BackendsService} from '~/core/backends/backends.service'; +import {Jobs} from '~/core/jobs/_schemas/jobs.schema'; +import {AskTokenDto} from './_dto/ask-token.dto'; +import {ChangePasswordDto} from './_dto/change-password.dto'; +import {ResetPasswordDto} from './_dto/reset-password.dto'; +import {IdentitiesCrudService} from '../identities/identities-crud.service'; +import {get} from 'radash'; +import {Identities} from '../identities/_schemas/identities.schema'; +import {MailerService} from '@nestjs-modules/mailer'; +import {InitAccountDto} from '~/management/passwd/_dto/init-account.dto'; +import {ConfigService} from '@nestjs/config'; +import {ResetByCodeDto} from '~/management/passwd/_dto/reset-by-code.dto'; +import {PasswdadmService} from '~/settings/passwdadm.service'; +import {IdentityState} from '~/management/identities/_enums/states.enum'; +import {InitResetDto} from '~/management/passwd/_dto/init-reset.dto'; +import {SmsadmService} from '~/settings/smsadm.service'; +import {InitManyDto} from '~/management/passwd/_dto/init-many.dto'; +import {InitStatesEnum} from '~/management/identities/_enums/init-state.enum'; +import {MailadmService} from '~/settings/mailadm.service'; +import {DataStatusEnum} from "~/management/identities/_enums/data-status"; interface TokenData { k: string; @@ -141,6 +142,12 @@ export class PasswdService extends AbstractService { //recherche de l'identity try { const identity = (await this.identities.findOne({ 'inetOrgPerson.uid': initDto.uid })) as Identities; + //test si on peu reninitialiser le compte + if ( identity.dataStatus == DataStatusEnum.INACTIVE){ + throw new BadRequestException( + 'Une erreur est survenue : Tentative de réinitialisation de mot de passe impossible', + ); + } //envoi du mail const params = await this.passwdadmService.getPolicies(); const mailAttribute = params.emailAttribute; @@ -200,6 +207,11 @@ export class PasswdService extends AbstractService { 'inetOrgPerson.uid': passwdDto.uid, state: IdentityState.SYNCED, })) as Identities; + if ( identity.dataStatus == DataStatusEnum.INACTIVE){ + throw new BadRequestException( + 'Une erreur est survenue : Tentative de réinitialisation de mot de passe impossible', + ); + } //verification de la police de mdp if ((await this.passwdadmService.checkPolicies(passwdDto.newPassword)) === false) { throw new BadRequestException({