Reputation: 355
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:
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
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:
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');
}
}
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]);
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
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
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