@@ -419,96 +419,124 @@ class BaseUserRepository extends BaseRepository {
419419 const { createAuditLogEntry } = require ( './baseOrgRepositoryHelpers' )
420420 const registryOrg = await baseOrgRepository . getOrgObject ( orgShortname , false , options )
421421 const originalRegistryOrg = registryOrg . toObject ( )
422+
422423 const legacyUser = await legacyUserRepo . findOneByUserNameAndOrgUUID ( username , registryOrg . UUID , null , options )
423- const registryUser = await this . findOneByUsernameAndOrgShortname ( username , orgShortname , options , true ) // WE always want the registry user
424+ const registryUser = await this . findOneByUsernameAndOrgShortname ( username , orgShortname , options , true )
425+
426+ if ( ! registryUser && ! legacyUser ) {
427+ throw new Error ( 'User not found' )
428+ }
424429
425- registryUser . username = incomingParameters ?. new_username ?? registryUser . username
426- legacyUser . username = incomingParameters ?. new_username ?? legacyUser . username
430+ // Safely assign usernames defensively
431+ if ( registryUser ) {
432+ registryUser . username = incomingParameters ?. new_username ?? registryUser . username
433+ }
434+ if ( legacyUser ) {
435+ legacyUser . username = incomingParameters ?. new_username ?? legacyUser . username
436+ }
427437
428438 if ( incomingParameters ?. active != null ) {
429439 const isConsideredActive = incomingParameters . active === true || String ( incomingParameters . active ) . toLowerCase ( ) === 'true'
430- registryUser . status = isConsideredActive ? 'active' : 'inactive'
431- legacyUser . active = incomingParameters . active ?? legacyUser . active
440+ if ( registryUser ) {
441+ registryUser . status = isConsideredActive ? 'active' : 'inactive'
442+ }
443+ if ( legacyUser ) {
444+ legacyUser . active = incomingParameters . active ?? legacyUser . active
445+ }
432446 }
433447
434448 [ 'name.last' , 'name.first' , 'name.middle' , 'name.suffix' ] . forEach ( field => {
435- _ . set ( registryUser , field , _ . get ( incomingParameters , field , _ . get ( registryUser , field , '' ) ) )
436- _ . set ( legacyUser , field , _ . get ( incomingParameters , field , _ . get ( legacyUser , field , '' ) ) )
449+ if ( registryUser ) _ . set ( registryUser , field , _ . get ( incomingParameters , field , _ . get ( registryUser , field , '' ) ) )
450+ if ( legacyUser ) _ . set ( legacyUser , field , _ . get ( incomingParameters , field , _ . get ( legacyUser , field , '' ) ) )
437451 } )
438452
453+ // Get the UUID from whichever user object actually exists
454+ const userUUID = registryUser ?. UUID ?? legacyUser ?. UUID
455+
439456 const rolesToAdd = _ . flattenDeep ( _ . compact ( _ . get ( incomingParameters , 'active_roles.add' ) ) )
440457 const rolesToRemove = _ . flattenDeep ( _ . compact ( _ . get ( incomingParameters , 'active_roles.remove' ) ) )
441- if ( rolesToRemove . includes ( 'ADMIN' ) ) {
458+
459+ if ( rolesToRemove . includes ( 'ADMIN' ) && userUUID ) {
442460 if ( Array . isArray ( registryOrg . admins ) ) {
443- registryOrg . admins . pull ( registryUser . UUID )
461+ registryOrg . admins . pull ( userUUID )
444462 }
445463 }
446464
447- if ( rolesToAdd . includes ( 'ADMIN' ) && ! incomingParameters ?. org_short_name ) {
448- // Use the already fetched registryOrg instead of querying again
465+ if ( rolesToAdd . includes ( 'ADMIN' ) && ! incomingParameters ?. org_short_name && userUUID ) {
449466 if ( ! Array . isArray ( registryOrg . admins ) ) {
450467 registryOrg . admins = [ ]
451468 }
452- registryOrg . admins . addToSet ( registryUser . UUID )
469+ registryOrg . admins . addToSet ( userUUID )
453470 }
454471
455- const initialRoles = legacyUser . authority ?. active_roles ?? [ ]
472+ // Handle roles calculation fallback
473+ const initialRoles = legacyUser ?. authority ?. active_roles ?? [ ]
456474 const finalRoles = [ ...new Set ( [ ...initialRoles , ...rolesToAdd ] ) ] . filter ( role => ! rolesToRemove . includes ( role ) )
457- registryUser . role = finalRoles [ 0 ] ?? ''
458- _ . set ( legacyUser , 'authority.active_roles' , finalRoles )
459475
460- if ( incomingParameters ?. org_short_name ) {
461- // Remove us from the old users Array
476+ if ( registryUser ) {
477+ registryUser . role = finalRoles [ 0 ] ?? ''
478+ }
479+ if ( legacyUser ) {
480+ _ . set ( legacyUser , 'authority.active_roles' , finalRoles )
481+ }
482+
483+ if ( incomingParameters ?. org_short_name && userUUID ) {
462484 if ( Array . isArray ( registryOrg . users ) ) {
463- registryOrg . users . pull ( registryUser . UUID )
485+ registryOrg . users . pull ( userUUID )
464486 }
465- if ( registryOrg . admins && registryOrg . admins . includes ( registryUser . UUID ) ) {
466- registryOrg . admins . pull ( registryUser . UUID )
487+ if ( registryOrg . admins && registryOrg . admins . includes ( userUUID ) ) {
488+ registryOrg . admins . pull ( userUUID )
467489 }
468- // Add us to the new org (this is a genuine cross-org migration, so we must fetch the new org)
490+
469491 const newOrg = await baseOrgRepository . getOrgObject ( incomingParameters . org_short_name )
470492 const originalNewOrg = newOrg . toObject ( )
471493 if ( ! Array . isArray ( newOrg . users ) ) {
472494 newOrg . users = [ ]
473495 }
474- newOrg . users . addToSet ( registryUser . UUID )
496+ newOrg . users . addToSet ( userUUID )
475497
476- if ( registryUser . role . includes ( 'ADMIN' ) ) {
498+ const isUserAdmin = registryUser ?. role ?. includes ( 'ADMIN' ) || finalRoles . includes ( 'ADMIN' )
499+ if ( isUserAdmin ) {
477500 if ( ! Array . isArray ( newOrg . admins ) ) {
478501 newOrg . admins = [ ]
479502 }
480- newOrg . admins . addToSet ( registryUser . UUID )
503+ newOrg . admins . addToSet ( userUUID )
481504 }
482505
483- legacyUser . org_UUID = newOrg . UUID
506+ if ( legacyUser ) {
507+ legacyUser . org_UUID = newOrg . UUID
508+ }
484509 await newOrg . save ( options )
485510 if ( requestingUserUUID ) {
486511 await createAuditLogEntry ( newOrg , originalNewOrg , requestingUserUUID , options )
487512 }
488513 }
489514
490- delete registryUser . role
491- // Single unified save for the primary org at the end
515+ if ( registryUser ) {
516+ delete registryUser . role
517+ }
518+
492519 await registryOrg . save ( options )
493520 if ( requestingUserUUID ) {
494521 await createAuditLogEntry ( registryOrg , originalRegistryOrg , requestingUserUUID , options )
495522 }
496523
497- await legacyUser . save ( options )
498- await registryUser . save ( options )
524+ // Save only records that exist
525+ if ( legacyUser ) await legacyUser . save ( options )
526+ if ( registryUser ) await registryUser . save ( options )
499527
500528 if ( ! isRegistryObject ) {
529+ if ( ! legacyUser ) throw new Error ( 'Legacy record missing; cannot return legacy format.' )
501530 const plainJavascriptLegacyUser = legacyUser . toObject ( )
502- legacyUser . role = finalRoles [ 0 ] ?? ''
531+ plainJavascriptLegacyUser . role = finalRoles [ 0 ] ?? ''
503532 delete plainJavascriptLegacyUser . __v
504533 delete plainJavascriptLegacyUser . _id
505534 delete plainJavascriptLegacyUser . secret
506- // return deepRemoveEmpty(plainJavascriptLegacyUser)
507535 return plainJavascriptLegacyUser
508536 }
509537
538+ if ( ! registryUser ) throw new Error ( 'Registry record missing; cannot return registry format.' )
510539 const plainJavascriptRegistryUser = registryUser . toObject ( )
511- // Remove private things
512540 delete plainJavascriptRegistryUser . __v
513541 delete plainJavascriptRegistryUser . _id
514542 delete plainJavascriptRegistryUser . __t
@@ -529,117 +557,101 @@ class BaseUserRepository extends BaseRepository {
529557 async updateUserFull ( identifier , incomingUser , options = { } , isRegistryObject = true , requestingUserUUID = null ) {
530558 const legacyUserRepo = new UserRepository ( )
531559
532- // Find registry user by UUID
533560 const registryUser = await this . findUserByUUID ( identifier , options )
534- if ( ! registryUser ) {
535- throw new Error ( 'Registry user not found' )
536- }
537-
538- // Find legacy user
539561 const legacyUser = await legacyUserRepo . findOneByUUID ( identifier )
540- if ( ! legacyUser ) {
541- throw new Error ( 'Legacy user not found' )
562+
563+ // Fail only if completely missing everywhere
564+ if ( ! registryUser && ! legacyUser ) {
565+ throw new Error ( 'User not found in any repository' )
542566 }
543567
544568 const { ...incomingUserBody } = incomingUser
545- let legacyObjectRaw
546- let registryObjectRaw
547-
548- if ( ! isRegistryObject ) {
549- legacyObjectRaw = incomingUserBody
550- registryObjectRaw = this . convertLegacyToRegistry ( incomingUserBody )
551- } else {
552- registryObjectRaw = incomingUserBody
553- legacyObjectRaw = this . convertRegistryToLegacy ( incomingUserBody )
554- }
569+ const legacyObjectRaw = isRegistryObject ? this . convertRegistryToLegacy ( incomingUserBody ) : incomingUserBody
570+ const registryObjectRaw = isRegistryObject ? incomingUserBody : this . convertLegacyToRegistry ( incomingUserBody )
555571
556572 const protectedFieldsRegistry = [ '_id' , 'UUID' , '__v' , 'secret' , 'created' , 'last_updated' ]
557573 const protectedFieldsLegacy = [ '_id' , 'UUID' , '__v' , 'secret' , 'time' , 'org_UUID' ]
558574
559- const updatedRegistryUser = registryUser . overwrite ( _ . mergeWith ( _ . pick ( registryUser . toObject ( ) , protectedFieldsRegistry ) , _ . omit ( registryObjectRaw , protectedFieldsRegistry ) , skipNulls ) )
560- const updatedLegacyUser = legacyUser . overwrite ( _ . mergeWith ( _ . pick ( legacyUser . toObject ( ) , protectedFieldsLegacy ) , _ . omit ( legacyObjectRaw , protectedFieldsLegacy ) , skipNulls ) )
575+ let updatedRegistryUser = null
576+ let updatedLegacyUser = null
561577
562- if ( updatedRegistryUser . status !== 'active' ) {
563- updatedRegistryUser . status = 'inactive'
564- updatedLegacyUser . active = false
565- } else {
566- updatedLegacyUser . active = true
578+ if ( registryUser ) {
579+ updatedRegistryUser = registryUser . overwrite ( _ . mergeWith ( _ . pick ( registryUser . toObject ( ) , protectedFieldsRegistry ) , _ . omit ( registryObjectRaw , protectedFieldsRegistry ) , skipNulls ) )
580+ if ( updatedRegistryUser . status !== 'active' ) {
581+ updatedRegistryUser . status = 'inactive'
582+ }
583+ }
584+
585+ if ( legacyUser ) {
586+ updatedLegacyUser = legacyUser . overwrite ( _ . mergeWith ( _ . pick ( legacyUser . toObject ( ) , protectedFieldsLegacy ) , _ . omit ( legacyObjectRaw , protectedFieldsLegacy ) , skipNulls ) )
587+ // Align status from incoming payload or resolved registry state
588+ const targetStatus = registryUser ? updatedRegistryUser . status : ( isRegistryObject ? registryObjectRaw . status : 'active' )
589+ updatedLegacyUser . active = ( targetStatus === 'active' )
567590 }
568591
569592 try {
570593 if ( incomingUser . org_short_name ) {
571594 const baseOrgRepository = new BaseOrgRepository ( )
572595 const { createAuditLogEntry } = require ( './baseOrgRepositoryHelpers' )
573- const currentOrgUUID = legacyUser . org_UUID
574- const currentOrg = await baseOrgRepository . findOneByUUID ( currentOrgUUID )
575- const originalCurrentOrg = currentOrg . toObject ( )
596+
597+ // Grab current org UUID using whichever document is real
598+ const currentOrgUUID = legacyUser ? legacyUser . org_UUID : registryUser ?. org_UUID // Fallback if schema supports it
599+ const currentOrg = currentOrgUUID ? await baseOrgRepository . findOneByUUID ( currentOrgUUID ) : null
576600 const newOrg = await baseOrgRepository . findOneByShortName ( incomingUser . org_short_name )
577- const originalNewOrg = newOrg . toObject ( )
578601
579602 if ( ! newOrg ) {
580603 throw new Error ( `Organization ${ incomingUser . org_short_name } not found` )
581604 }
582605
583- // 1. Remove user from old org's users list
584- if ( Array . isArray ( currentOrg . users ) ) {
585- currentOrg . users . pull ( identifier )
586- }
587-
588- // 2. Remove user from old org's admins list (if present)
589- if ( currentOrg . admins && currentOrg . admins . includes ( identifier ) ) {
590- currentOrg . admins . pull ( identifier )
606+ // Clean up old org if found
607+ if ( currentOrg ) {
608+ const originalCurrentOrg = currentOrg . toObject ( )
609+ if ( Array . isArray ( currentOrg . users ) ) currentOrg . users . pull ( identifier )
610+ if ( currentOrg . admins && currentOrg . admins . includes ( identifier ) ) currentOrg . admins . pull ( identifier )
611+ await currentOrg . save ( options )
612+ if ( requestingUserUUID ) await createAuditLogEntry ( currentOrg , originalCurrentOrg , requestingUserUUID , options )
591613 }
592614
593- // 3. Add user to new org's users list
594- if ( ! Array . isArray ( newOrg . users ) ) {
595- newOrg . users = [ ]
596- }
615+ // Setup new org
616+ const originalNewOrg = newOrg . toObject ( )
617+ if ( ! Array . isArray ( newOrg . users ) ) newOrg . users = [ ]
597618 newOrg . users . addToSet ( identifier )
598619
599- // 4. Add user to new org's admins list (if they are an admin)
600- const isAdmin = updatedRegistryUser . role === 'ADMIN' || ( updatedLegacyUser . authority && updatedLegacyUser . authority . active_roles && updatedLegacyUser . authority . active_roles . includes ( 'ADMIN' ) )
601-
620+ const isAdmin = updatedRegistryUser ?. role === 'ADMIN' || ( updatedLegacyUser ?. authority ?. active_roles ?. includes ( 'ADMIN' ) )
602621 if ( isAdmin ) {
603622 if ( ! Array . isArray ( newOrg . admins ) ) {
604623 newOrg . admins = [ ]
605624 }
606625 newOrg . admins . addToSet ( identifier )
607626 }
608627
609- // 5. Update user's org_UUID
610- updatedLegacyUser . org_UUID = newOrg . UUID
628+ if ( updatedLegacyUser ) {
629+ updatedLegacyUser . org_UUID = newOrg . UUID
630+ }
611631
612- // Save org changes
613- await currentOrg . save ( options )
614632 await newOrg . save ( options )
615-
616633 if ( requestingUserUUID ) {
617- await createAuditLogEntry ( currentOrg , originalCurrentOrg , requestingUserUUID , options )
618634 await createAuditLogEntry ( newOrg , originalNewOrg , requestingUserUUID , options )
619635 }
620636 }
621637
622- await updatedLegacyUser . save ( options )
623- await updatedRegistryUser . save ( options )
638+ // Conditionally save records
639+ if ( updatedLegacyUser ) await updatedLegacyUser . save ( options )
640+ if ( updatedRegistryUser ) await updatedRegistryUser . save ( options )
624641 } catch ( error ) {
625642 throw new Error ( 'Failed to update user: ' + error . message )
626643 }
627644
628645 if ( ! isRegistryObject ) {
646+ if ( ! updatedLegacyUser ) throw new Error ( 'Legacy record missing; cannot output legacy format.' )
629647 const plain = updatedLegacyUser . toObject ( )
630- delete plain . _id
631- delete plain . __v
632- delete plain . secret
648+ delete plain . _id ; delete plain . __v ; delete plain . secret
633649 return plain
634650 }
635651
636- // Retrieve updated registry user
652+ if ( ! updatedRegistryUser ) throw new Error ( 'Registry record missing; cannot output registry format.' )
637653 const plainJsRegistryUser = updatedRegistryUser . toObject ( )
638- delete plainJsRegistryUser . _id
639- delete plainJsRegistryUser . __v
640- delete plainJsRegistryUser . secret
641- delete plainJsRegistryUser . authority
642-
654+ delete plainJsRegistryUser . _id ; delete plainJsRegistryUser . __v ; delete plainJsRegistryUser . secret ; delete plainJsRegistryUser . authority
643655 return plainJsRegistryUser
644656 }
645657
@@ -661,12 +673,25 @@ class BaseUserRepository extends BaseRepository {
661673 const legUser = await legacyUserRepo . findOneByUserNameAndOrgUUID ( username , legOrgUUID , null , options )
662674 const regUser = await this . findOneByUsernameAndOrgShortname ( username , orgShortName , options , true )
663675
676+ // Fail ONLY if the user is completely missing from both collections
677+ if ( ! legUser && ! regUser ) {
678+ throw new Error ( 'User not found in registry or legacy system.' )
679+ }
680+
664681 const randomKey = cryptoRandomString ( { length : getConstants ( ) . CRYPTO_RANDOM_STRING_LENGTH } )
665682 const secret = await argon2 . hash ( randomKey )
666- legUser . secret = secret
667- regUser . secret = secret
668- await legUser . save ( options )
669- await regUser . save ( options )
683+
684+ // Defensively update legacy if present
685+ if ( legUser ) {
686+ legUser . secret = secret
687+ await legUser . save ( options )
688+ }
689+
690+ // Defensively update registry if present
691+ if ( regUser ) {
692+ regUser . secret = secret
693+ await regUser . save ( options )
694+ }
670695
671696 return randomKey
672697 }
0 commit comments