@@ -16,6 +16,12 @@ import { MailerService } from '@nestjs-modules/mailer';
1616import { MailadmService } from '~/settings/mailadm.service' ;
1717import { get } from 'radash' ;
1818import { IdentitiesPasswordExpirationReminderService } from '~/management/identities/identities-password-expiration-reminder.service' ;
19+ import { InitStatesEnum } from '~/management/identities/_enums/init-state.enum' ;
20+ import {
21+ buildExpiredInitInvitationFilter ,
22+ DEFAULT_INIT_TOKEN_TTL_SECONDS ,
23+ getInitInvitationExpirationCutoff ,
24+ } from '~/management/passwd/init-invitation-expiration.helper' ;
1925
2026@SubCommand ( { name : 'fingerprint' } )
2127export class IdentitiesFingerprintCommand extends CommandRunner {
@@ -320,6 +326,90 @@ export class IdentitiesPasswordExpirationReminderCommand extends CommandRunner {
320326 }
321327}
322328
329+ type IdentitiesInitExpireOptions = {
330+ dryRun ?: boolean ;
331+ } ;
332+
333+ @CronConsoleHandler ( {
334+ handler : 'identities-init-invitation-expire' ,
335+ command : 'identities init expire' ,
336+ label : "Expiration des invitations d'initialisation de compte" ,
337+ arguments : [
338+ {
339+ name : 'dryRun' ,
340+ label : 'Simulation' ,
341+ description : 'Compte les invitations expirées sans modifier les identités.' ,
342+ type : 'boolean' ,
343+ default : false ,
344+ } ,
345+ ] ,
346+ } )
347+ @SubCommand ( { name : 'init' } )
348+ export class IdentitiesInitExpireCommand extends CommandRunner {
349+ private readonly logger = new Logger ( IdentitiesInitExpireCommand . name ) ;
350+
351+ public constructor ( protected moduleRef : ModuleRef ) {
352+ super ( ) ;
353+ }
354+
355+ async run ( inputs : string [ ] , options : IdentitiesInitExpireOptions ) : Promise < void > {
356+ const subTask = inputs ?. [ 0 ] ;
357+ if ( subTask !== 'expire' ) {
358+ console . error ( 'Usage: yarn run console identities init expire [--dryRun]' ) ;
359+ return ;
360+ }
361+
362+ // Services singleton : `ModuleRef.resolve()` ne fonctionne que pour les providers transient / request-scoped.
363+ const identities = this . moduleRef . get ( IdentitiesCrudService , { strict : false } ) ;
364+ const passwdadm = this . moduleRef . get ( PasswdadmService , { strict : false } ) ;
365+
366+ const policies = await passwdadm . getPolicies ( ) ;
367+ const ttlSeconds = Number ( policies ?. initTokenTTL ) || DEFAULT_INIT_TOKEN_TTL_SECONDS ;
368+
369+ const now = new Date ( ) ;
370+ const cutoff = getInitInvitationExpirationCutoff ( ttlSeconds , now ) ;
371+ const filter = buildExpiredInitInvitationFilter ( ttlSeconds , now ) ;
372+
373+ this . logger . log (
374+ `Checking outdated init invitations: initTokenTTL=${ ttlSeconds } s cutoff=${ cutoff . toISOString ( ) } dryRun=${ ! ! options ?. dryRun } ` ,
375+ ) ;
376+
377+ const total = await identities . model . countDocuments ( filter ) ;
378+ if ( total === 0 ) {
379+ this . logger . log ( 'No outdated init invitation found.' ) ;
380+ return ;
381+ }
382+
383+ if ( options ?. dryRun ) {
384+ const candidates = await identities . model . find ( filter ) . select ( { _id : 1 , initInfo : 1 } ) . lean ( ) ;
385+ for ( const candidate of candidates ) {
386+ this . logger . warn (
387+ `[dryRun] Identity <${ candidate . _id } > init invitation expired (initDate=${ candidate ?. initInfo ?. initDate ?. toISOString ?.( ) ?? 'n/a' } )` ,
388+ ) ;
389+ }
390+ this . logger . log ( `[dryRun] ${ total } identity(ies) would be flagged as OUTOFDATE.` ) ;
391+ return ;
392+ }
393+
394+ // Seules les identités encore en SENT sont passées en OUTOFDATE (garanti par le filtre).
395+ const result = await identities . model . updateMany ( filter , {
396+ $set : { initState : InitStatesEnum . OUTOFDATE } ,
397+ } ) ;
398+
399+ this . logger . log ( `Outdated init invitations: ${ result . modifiedCount } /${ total } identity(ies) set to OUTOFDATE.` ) ;
400+ }
401+
402+ @Option ( {
403+ flags : '--dryRun [dryRun]' ,
404+ description : 'Compte les invitations expirées sans modifier les identités.' ,
405+ defaultValue : false ,
406+ } )
407+ parseDryRun ( val : string ) : boolean {
408+ if ( val === undefined || val === null || val === '' ) return true ;
409+ return / ^ ( 1 | t r u e | o n | y e s ) $ / i. test ( String ( val ) . trim ( ) ) ;
410+ }
411+ }
412+
323413@Command ( {
324414 name : 'identities' ,
325415 arguments : '<task>' ,
@@ -328,6 +418,7 @@ export class IdentitiesPasswordExpirationReminderCommand extends CommandRunner {
328418 IdentitiesCancelFusionCommand ,
329419 IdentitiesPwnedCommand ,
330420 IdentitiesPasswordExpirationReminderCommand ,
421+ IdentitiesInitExpireCommand ,
331422 ] ,
332423} )
333424export class IdentitiesCommand extends CommandRunner {
0 commit comments