hipsterstomper69
hipsterstomper69

Reputation: 355

Amplify's completeNewPassword method throwing TypeError for user data

I am attempting to use a custom UI with aws Amplify, but am running into problems with Auth.completeNewPassword. Any attempt at using this method throws the error Error in v-on handler: "TypeError: Cannot read property 'username' of null.

Having used the given UI before this, I know that when a Cognito user is created by an admin, they are sent to a 'new password' form upon first login. However, the example from the Amplify docs has the signIn method immediately calling the completeNewPassword method once it discovers the user needs a new password.

The following snippet comes straight from amazon's docs:

import { Auth } from 'aws-amplify';

Auth.signIn(username, password)
.then(user => {
    if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
        const { requiredAttributes } = user.challengeParam; // the array of required attributes, e.g ['email', 'phone_number']
        Auth.completeNewPassword(
            user,               // the Cognito User Object
            newPassword,       // the new password
            // OPTIONAL, the required attributes
            {
              email: '[email protected]',
              phone_number: '1234567890'
            }
        ).then(user => {
            // at this time the user is logged in if no MFA required
            console.log(user);
        }).catch(e => {
          console.log(e);
        });
    } else {
        // other situations
    }
}).catch(e => {
    console.log(e);
});

I've attempted to do this a few different ways with the same TypeError result. It doesn't matter if I call the completeNewPassword method separately, store user as a variable and pass that in, try to re-signIn and pass that user to completeNewPassword, etc etc. Same TypeError every time.

The following method, for example, will log the 'user' object correctly when clicking the signIn button the first time, but fails before that line when clicking the same button while attempting to submit a new password.

signIn () {
    Auth.signIn(this.user_name, this.password)
    .then(user => {
        if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
        console.log(user)
        const { requiredAttributes } = user.challengeParam;
        this.$parent.$parent.pageStatus.passwordSetPanel = true;
        Auth.completeNewPassword(
            user,
            this.newPassword
        )
        }
    })
},

Additional Info:

  1. Framework is Vue
  2. Was able to confirm new passwords for new users with the given UI
  3. the 'username' TypeError isn't coming from my js, but refers to the username parameter within the user promise (I have checked and am not trying to pass user_name where it wants username)

EDIT

Managed to find a half-working solution with this following code:

signIn () {
    this.$parent.$parent.pageStatus.loginPanel = false;
    this.$parent.$parent.loading = true;
    Auth.signIn(this.user_name, this.password)
    .then(async user => {
        if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
        const loggedUser = await Auth.completeNewPassword(
            user,
            this.password,
        )
        this.$parent.$parent.pageStatus.passwordSetPanel = true
        }
    })
}

This manages to update the user, but I had to set the password to this.password (the original, to-be-changed one) to get it working. Looks like the problem is that Amplify wants me to use the same function to both signIn, and call completeNewPassword. But the UI for these needs to be split between two different panels.

Upvotes: 5

Views: 9493

Answers (3)

Ahmet Firat Keler
Ahmet Firat Keler

Reputation: 4065

React/NextJs Example

I was trying to do that in my Sign In page. The new signed up user who got a temp password were not be able to sign in with it and needed a password reset. So I generated a new page for it. The steps:

  • When user tried to sign in, if the error message is NEW_PASSWORD_REQUIRED, send the user with query params (email, temp password) to the reset password page
const user = await Auth.signIn(values.email, values.password);
console.log(user)

if (user) {
  if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
    dispatch(setLoading(false));

    Router.push({
      pathname: '/reset-password',
      query: {email: JSON.stringify(values.email), password: JSON.stringify(values.password)}
    }, '/reset-password');
  }
}
  • In the password reset page, get query params as state values
const [email, setEmail] = useState<any>(null);
const [password, setPassword] = useState<any>(null);

useEffect(() => {
  setEmail(JSON.parse(props.router.query.email));
  setPassword(JSON.parse(props.router.query.password));
}, [props.router.query.email, props.router.query.password]);
  • When user submits the form with the new password, call the function again like that
Auth.signIn(email, password)
  .then(async user => {
    if (user.challengeName === 'NEW_PASSWORD_REQUIRED') {
      const loggedUser = await Auth.completeNewPassword(user, values.proposedPassword);
      console.log(loggedUser);

      Successful({ title: 'Success', message: 'You have successfully reset your password. Redirecting..' });
    }
                        
    else {
                            
    }
});

Upvotes: 0

Shank
Shank

Reputation: 414

I managed to solve this issue using RxJS BehaviorSubject.

You can have look example of typical behaviour of BehaviourSubject here.

Declaring BehaviorSubject: Initial value of BehaviorSubject is null.

congitoUser = new BehaviorSubject(null);

Subscribing to value returned from Auth.SignIn(which is cognito user).

 login(user: { username: string; password: string }) {
    Auth.signIn(user)
      .then((data) => {
        this.congitoUser.next(data);
        if (user) {
          if (data.challengeName === 'NEW_PASSWORD_REQUIRED') {
            this.router.navigateByUrl('/change-password');
          } else {
            this.router.navigateByUrl('/home');
          }
        }
      });
  }

Now Subscription would return correct value of Cognito User.

updatePassword(password: string) {
// subscribe to cognito user (behaviour subject) and use the value it returned.
    this.congitoUser.subscribe((value) => {
      Auth.completeNewPassword(value, password, [])
        .then((data) => {
         console.log(data);
        })
        .catch((err) => console.error('Error', err));
    });
}

Upvotes: 1

hipsterstomper69
hipsterstomper69

Reputation: 355

This is a terribly hack, but I ended up finding a workaround by storing the user promise, name, and plaintext password in a temporary js variable in order to preserve them for the completeNewPassword panel. If anyone knows a better solution, feel free to post it.

Upvotes: 1

Related Questions