@@ -611,4 +611,111 @@ class AuthService {
611611
612612 return (user: permanentUser, token: newToken);
613613 }
614+ 615+ /// Initiates the process of updating a user's email address.
616+ ///
617+ /// This is the first step in a two-step verification process. It checks if
618+ /// the new email is already in use, then generates and sends a verification
619+ /// code to that new email address.
620+ ///
621+ /// - [user] : The currently authenticated user initiating the change.
622+ /// - [newEmail] : The desired new email address.
623+ ///
624+ /// Throws [ConflictException] if the `newEmail` is already taken by another
625+ /// user.
626+ /// Throws [OperationFailedException] for other unexpected errors.
627+ Future <void > initiateEmailUpdate ({
628+ required User user,
629+ required String newEmail,
630+ }) async {
631+ _log.info (
632+ 'User ${user .id } is initiating an email update to "$newEmail ".' ,
633+ );
634+ 635+ try {
636+ // 1. Check if the new email address is already in use.
637+ final existingUser = await _findUserByEmail (newEmail);
638+ if (existingUser != null ) {
639+ _log.warning (
640+ 'Email update failed for user ${user .id }: new email "$newEmail " is already in use by user ${existingUser .id }.' ,
641+ );
642+ throw const ConflictException (
643+ 'This email address is already registered.' ,
644+ );
645+ }
646+ _log.finer ('New email "$newEmail " is available.' );
647+ 648+ // 2. Generate and send a verification code to the new email.
649+ // We reuse the sign-in code mechanism for this verification step.
650+ final code = await _verificationCodeStorageService
651+ .generateAndStoreSignInCode (newEmail);
652+ _log.finer ('Generated verification code for "$newEmail ".' );
653+ 654+ await _emailRepository.sendOtpEmail (
655+ senderEmail: EnvironmentConfig .defaultSenderEmail,
656+ recipientEmail: newEmail,
657+ templateId: EnvironmentConfig .otpTemplateId,
658+ subject: 'Verify your new email address' ,
659+ otpCode: code,
660+ );
661+ _log.info ('Sent email update verification code to "$newEmail ".' );
662+ } on HttpException {
663+ // Propagate known exceptions (like ConflictException).
664+ rethrow ;
665+ } catch (e, s) {
666+ _log.severe (
667+ 'Unexpected error during initiateEmailUpdate for user ${user .id }.' ,
668+ e,
669+ s,
670+ );
671+ throw const OperationFailedException (
672+ 'Failed to initiate email update process.' ,
673+ );
674+ }
675+ }
676+ 677+ /// Completes the email update process by verifying the code and updating
678+ /// the user's record.
679+ ///
680+ /// - [user] : The currently authenticated user.
681+ /// - [newEmail] : The new email address being verified.
682+ /// - [code] : The verification code sent to the new email address.
683+ ///
684+ /// Returns the updated [User] object upon success.
685+ ///
686+ /// Throws [InvalidInputException] if the verification code is invalid.
687+ /// Throws [OperationFailedException] for other unexpected errors.
688+ Future <User > completeEmailUpdate ({
689+ required User user,
690+ required String newEmail,
691+ required String code,
692+ }) async {
693+ _log.info ('User ${user .id } is completing email update to "$newEmail ".' );
694+ 695+ // 1. Validate the verification code for the new email.
696+ final isValid = await _verificationCodeStorageService.validateSignInCode (
697+ newEmail,
698+ code,
699+ );
700+ if (! isValid) {
701+ _log.warning ('Invalid verification code provided for "$newEmail ".' );
702+ throw const InvalidInputException (
703+ 'Invalid or expired verification code.' ,
704+ );
705+ }
706+ _log.finer ('Verification code for "$newEmail " is valid.' );
707+ 708+ // 2. Clear the used code from storage.
709+ await _verificationCodeStorageService.clearSignInCode (newEmail);
710+ 711+ // 3. Update the user's email in the repository.
712+ final updatedUser = user.copyWith (email: newEmail);
713+ final finalUser = await _userRepository.update (
714+ id: user.id,
715+ item: updatedUser,
716+ );
717+ _log.info ('Successfully updated email for user ${user .id } to "$newEmail ".' );
718+ 719+ return finalUser;
720+ }
614721}
0 commit comments