Amplify and React : Code mismatch and fail enable Software Token MFA - reactjs

I have a Cognito user pool that has MFA set to Optional with TOTP only.
I'm trying to set up a page that enables MultiFactorAuthentication for the user for the first time following this AWS Documentation.
As my component mounts, I generate a QR code and show it on screen using qrcode.react
useEffect(() => {
Auth.currentAuthenticatedUser({ bypassCache: true }).then(user => {
setUser(user);
Auth.setupTOTP(user).then(code => {
const authCode = "otpauth://totp/AWSCognito:" + user.username + "?secret=" + code + "&issuer=ivt";
setQrCode(authCode);
});
});
}, []);
Then, when the user puts the input, I verify it and call the setPrefferredMFA. Now here, I checked if "input" is correctly passed and no issues there.
const setupMFA = input => {
Auth.setupTOTP(user).then(() => {
Auth.verifyTotpToken(user, input)
.then(() => {
Auth.setPreferredMFA(user, "TOTP").then(() => {
props.setShowModal(false);
});
})
.catch(e => {
// Token is not verified
});
});
};
I'm still getting Code mismatch and fail enable Software Token MFA error and failing to set a MFA to the user.

I solved it.
Auth.verifyTotpToken() is not supposed to be in the .then() block of setupTOTP.
Pfft. Removing the Auth.setupTOTP in the setupMFA function made it work.

Related

Resetting multiple react Context API states at ones instead of creating resting action for each of them?

I have about 5 separate context api states that I want to rest after logging out from app, since they casing lots of issues any idea how to rest them redirecting doesn't seem to be doing much, and I don't want to refresh, this issue is my log out function will logout the user from server and simply redirect to main page but the state is still in the memory ?
const SignOut = () => {
signOut(auth)
.then(() => {
// Sign-out successful.
})
.catch((error) => {
// An error happened.
const errorCode = error.code;
const errorMessage = error.message;
snackbar.Msg(error, 'A signout error has happened.');
});
history.push('/');
};

Firebase sendPasswordResetEmail in JS SDK, doesn't send confirmation code

I am trying to do the following:
User asks to reset his password
Email is sent with a CODE and an URL to visit to reset his password
User visits the URL inputs the code and the new password and I call the confirmPasswordReset
But this doesn't work, the docs, their examples and also their functionality differs. Here's what happens
Greeting,
Follow this link to reset the example#gmail.com account password for the Example Dev app.
the URL
No Code, nothing, if I follow the URL it will take me to Firebase hosted app that will handle the reset with an ugly looking UI and THEN redirect me to the specified URL in the settings for sendResetEmail...
While the docs specify expected behavior, it differs from the actual one.
Sends a password reset email to the given email address.
#remarks
To complete the password reset, call confirmPasswordReset with the code supplied in the email sent to the user, along with the new password specified by the user.
#example
const actionCodeSettings = {
url: 'https://www.example.com/?email=user#example.com',
iOS: {
bundleId: 'com.example.ios'
},
android: {
packageName: 'com.example.android',
installApp: true,
minimumVersion: '12'
},
handleCodeInApp: true
};
await sendPasswordResetEmail(auth, 'user#example.com', actionCodeSettings);
// Obtain code from user.
await confirmPasswordReset('user#example.com', code);
The docs specify my intended behavior, but when used, it's totally different. What is happening here?
const onSubmit = (data: PasswordResetData) => {
setIsLoading(true);
sendPasswordResetEmail(getAuth(), data.email, {
url: "http://localhost:3002/en/auth/reset/confirm?email=" + data.email,
handleCodeInApp: true,
})
.then(() => {
})
.catch((err) => {
console.error(err);
})
.finally(() => {
setIsLoading(false);
});
};

reCAPTCHA first attempt causes it to reset. Firebase phoneAuthVerifier

Hello I'm having issues implementing recaptcha into our firebase project. I'm using it with firebase.auth.PhoneAuthProvider, to initially enroll a second factor and then use the recaptcha for multi-factor authentication on login.
I've used the https://cloud.google.com/identity-platform/docs/web/mfa doc for the implementation and it does work but it resets on the first successful attempt. The tickbox gets checked but instantly the whole recaptcha rerenders/is reset.
https://imgur.com/PJCJujo (here is a tiny video of that reset behaviour)
The fact that it is rerendering makes me think it is an error in the implementation and possibly to do with React but I am struggling to solve it.
Login Function
const frontendLogin = () => {
const { email, password } = registerDetails
firebaseApp.auth().signInWithEmailAndPassword(email, password)
.then(data => {
// if (data.user.emailVerified) {
axios.get(`/users/${data.user.uid}`)
.then(res => {
// Handle login for users not enrolled with multi-factor
})
.catch(error => {
console.log(error)
if (error.code === 'auth/multi-factor-auth-required') {
setResolver(error.resolver)
setHints(error.resolver.hints[0])
setPhoneVerifyRequired(true)
// Ask user which second factor to use.
if (error.resolver.hints[selectedIndex].factorId ===
firebase.auth.PhoneMultiFactorGenerator.FACTOR_ID) {
const phoneInfoOptions = {
multiFactorHint: error.resolver.hints[selectedIndex],
session: error.resolver.session
};
const phoneAuthProvider = new firebase.auth.PhoneAuthProvider(appAuth);
const recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
captchaRef.current,
{
'size': 'normal',
'callback': function (response) {
console.log('captcha!')
handleSolved()
},
'expired-callback': function () {
console.log('captcha expired')
},
'hl': locale
},
firebaseApp);
// Send SMS verification code
return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier)
.then(function (verificationId) {
setVerificationId(verificationId)
}).catch(err => console.log(err))
} else {
// Unsupported second factor.
}
} else {
console.log(error)
}
})
}
const handleSolved = () => {
setCaptchad(true)
setIsLoading(false)
}
const handleRecaptcha = () => {
var cred = firebase.auth.PhoneAuthProvider.credential(
verificationId, verificationCode);
console.log(cred)
var multiFactorAssertion =
firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
// Complete sign-in.
resolver.resolveSignIn(multiFactorAssertion)
.then(function (data) {
// User successfully signed in with the second factor phone number.
// Handle login for users enrolled with multi-factor
})
}
I am unsure as to why this approach isn't working but I believe it may be to do with React? I have looked into using a React Recaptcha package but as the docs were just written in javascript I've just tried to implement it in this way.
I haven't deviated too far from the docs only to set state with elements including the verification ID that is returned from the successful recaptcha etc.
I also appreciate I am setting state too much and need to refactor the state at the top of the component but as I'm still fleshing this component out it hasn't been refactored yet so I apologise it's a bit messy!
If anyone has any pointers that would be great or if I need to provide more code then let me know, any help is appreciated!

What's the proper way to use Firebase Authentication along with Firestore?

Hi all—I'm building an app using Next.js and Firebase, both brand new technologies for me. It's a simple app where a user creates an account and must log in. If the user doesn't create an account, the app is useless—they can't move on to the next screen, which is a dashboard. Anyway, when they log in, they can then create a trip/vacation itinerary. I'm using Firebase Auth for auth and Firestore (not real-time db) as my db. My goal is that when a user logs in, the user can see every itinerary that they created and no one else's. It should be full CRUD. This is the first time I've done this sort of authentication as well, so that's likely adding to my confusion.
I know that my code isn't right, but it sort of worked. What keeps happening is that it seems like there's a lag when a user logs in and out. I've tested this on my local copy. When I log out and then log back in as a different user, it tells me that the uid is null. Anywhere from 1 - 30 minutes later (seriously), all of a sudden the page loads for the uid that I logged in with! Everything that I've read says that there's a lag with the authentication, but I couldn't really find a solution other than just pointing out the problem—so basically writing a console log that says who's logged in at the time and then the same when they've logged out. Also, I watched / read tons of tutorials, so maybe it's my code? I'm so sorry in advance for this novel of code—I'll organize as best as I can!
Here's my config info, so I'm referring to Firebase as fire. The sign-in method is email and password, and everything looks as it should in Firebase as far as capturing that information on the Authentication screen.
import firebase from 'firebase';
const firebaseConfig = {
apiKey: 'AIzaSyB-xEPETXSfjboKe5H0kPUu-ZdRDGfszmA',
authDomain: "where-to-jess.firebaseapp.com",
databaseURL: 'https://where-to-jess-default-rtdb.firebaseio.com/',
projectId: "where-to-jess",
storageBucket: "where-to-jess.appspot.com",
messagingSenderId: "914509599583",
appId: "1:914509599583:web:80cdf3e4090417b0f35cea"
};
try {
firebase.initializeApp(firebaseConfig);
} catch(err){
if (!/already exists/.test(err.message)) {
console.error('Firebase initialization error', err.stack)}
}
const fire = firebase;
export default fire;
When a user creates an account, they're also added to a collection 'users' in my db. I am using React Hooks (for the first time) as well. Their email is their username to login, but I'm capturing their email in the db. They are also immediately logged in upon account creation. This part also works.
const handleSubmit = (e) => {
e.preventDefault();
setPassword('');
fire.auth().createUserWithEmailAndPassword(userName, password)
.then(() => {
fire.firestore().collection('users').doc(fire.auth().currentUser.uid)
.set({
firstName: firstName,
lastName: lastName,
email: userName
})
.catch(error => {
console.log('user wasn\'t added to db: ', error);
})
})
.catch(error => {
console.log('user wasn\'t able to create an account: ', error);
})
router.push('/users/dashboard')
};
This is my login code:
const handleLogin = (e) => {
e.preventDefault();
fire.auth()
.signInWithEmailAndPassword(username, password)
.catch((error) => {
console.log('user wasn\'t able to login: ', error);
})
setUsername('')
setPassword('')
router.push('/users/dashboard')
};
Now for the fun part! This is my code for form submission for the itinerary. What I'm trying to achieve here is to have this newly created itinerary attached to their uid in the 'users' db. I'm leaving out all the form stuff because it's super long. This also seems to work—I can see it coming in in the db for whichever account I'm using.
const handleSubmit = (e) => {
e.preventDefault();
fire.firestore()
.collection('users').doc(fire.auth().currentUser.uid).collection('itineraries')
.add({
//
})
.catch(error => {
console.log('itinerary not added to db ', error)
})
router.push('/users/dashboard')
}
Here's where it all went to heck! I suspect it's because I'm cutting corners, which I'll explain next. This dashboard should show ONLY itineraries that the current logged-in user created. If the current logged-in user didn't create any itineraries, I'd get an error saying that the uid was null. SO, my workaround was to just create a fake itinerary manually in the db on their account (since I was testing) and give the tripName value as null. This seems to work, but this is where the weird login / logout stuff happens.
export default function Dashboard() {
const router = useRouter();
const [itineraries, setItineraries] = useState([]);
const [loggedIn, setLoggedIn] = useState(false);
fire.auth()
.onAuthStateChanged((user) => {
if (user) {
console.log(user.email + " is logged in!");
setLoggedIn(true)
} else {
setLoggedIn(false)
console.log('User is logged out!');
}
})
useEffect(() => {
const unsubscribe =
fire.firestore()
.collection('users').doc(fire.auth().currentUser.uid).collection('itineraries').where('tripName', '!=', 'null')
.onSnapshot(snap => {
const itineraries = snap.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setItineraries(itineraries);
return () => {
unsubscribe();
};
});
}, []);
const handleLogout = () => {
fire.auth()
.signOut()
router.push('/')
};
Lastly, here is the one rule that I have on the db. I got confused reading the rule docs, and I feel like I cut a corner here.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read
allow write
}
}
}
Again, I'm really sorry for ALL of that code. This is my first time using React Hooks, Next, and Firebase—so it's a mashup of Firebases's docs, tutorials, and my own code. I'd appreciate ANY help or advice here.
That rule will allow all access to all documents in your db at present. You want something like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user_id}{
allow read, write: if request.auth != null && request.auth.uid == user_id;
}
}
}
That will allow access only to users that are authenticated
This was 100% user error on my part, but I wanted to share since some of my issues seem pretty common. In addition to AspiringApollo's advice above, I had my function completely out of order (as I mentioned, hook newbie). The above plus structuring my function like this fixed it:
useEffect(() => {
const unsubscribe =
fire.auth()
.onAuthStateChanged((user) => {
if (user) {
let uid = user.uid
console.log(user.email + ' is logged in!');
setLoggedIn(true)
// all the things you want to do while the user is logged in goes here
} else {
setLoggedIn(false)
console.log('user is logged out!');
}
return () => {
unsubscribe();
};
});
}, []);
Still open to suggestions and more sets of eyes because I know this is a little messy!

Stub a function that runs a promise

I've been attempting to write unit tests for a registration page on a new react application
However I am very new to the concept of Sinon stubs/spies and have been having issues with intercepting a function call and forcing a resolve.
This is my initial test:
test('Success notification is displayed if all information is valid', () => {
wrapper.setState({ username: 'test', password: 'Test1234!#', confirmPassword: 'Test1234!#' });
const stub = sinon.stub(Register.prototype, 'signUp').resolves('Test');
wrapper.find('#submitRegistration').simulate('click');
});
onClick runs this event handler: (Simplified)
public onSubmitHandler(e: React.FormEvent) {
// Perform password match check
if (password === confirmPassword) {
this.signUp(this.state.username, password);
} else {
// Set success state
}
} else {
// Set error state
}
}
And finally, Signup:
public signUp(username: string, password: string) {
// Perform Cognito Signup
return Auth.signUp({
password,
username,
})
.then(() => {
// Set success State
})
.catch((err) => {
// Set error state
});
}
How can I intercept the call to signUp and force it down the resolve path, Currently due to the fact i do not configure my AWS Amplify Auth module it catches every time with "No userPool"
Jest provides many ways of mocking out dependencies, primarily there are 2 flavours:
Manual Mocks - These let you control mocks in far more details.
Mock functions - If you need a simple mock for a common problem, translation for example, these are very helpful.
For your case, you will also need to handle Promises, for this reason, you need to follow the advice on testing async behaviour.
For your case, I am assuming:
Auth is a module named 'auth-module'
you just need to setup the mock once and have 1 test data which resolves to success and 1 for failure.
// telling jest to mock the entire module
jest.mock("auth-module", () => ({
// telling jest which method to mock
signUp: ({ password, username }) =>
new Promise((resolve, reject) => {
// setting the test expectations / test data
process.nextTick(
() =>
username === "SuccessUser"
? resolve(userName)
: reject({
error: "failure message"
})
);
})
}));
Note : Adding the snippet even though it will not run, as the formatting seems to be broken with code block.
With a little thought push from dubes' answer I managed to get the following:
const signUpSpy = jest.spyOn(Auth, 'signUp').mockImplementation((user, password) => {
return new Promise((res, rej) => {
res();
});
});
Instead of spying directly onto the function I wrote and call moved it to the Modules function and resolved the promise from there!

Resources