How to update phone number in Firebase when sim card is changed? [duplicate] - flutter

In my Flutter app I use Firebase's phone number authentication as my main form of authentication. After authenticating, I create a user in my users collection with these details:
{
phoneNumber: FirebaseAuth.instance.currentUser().phoneNumber,
displayName: 'Comes from user textbox',
...
}
But say one day a user want's to change their phone number. How do I do this? Because I cannot simply change the user's phone number in the document, because the phone number needs to be authenticated. And after authentication, the user gets a new authUID. Which should then be a new user?
Could someone explain the logic behind a user that wants to keep their profile details but change their number.

In order to achieve this, you can use User.updatePhoneNumber. This allows you to update the phone number of a user.
You would use it in the same manner that you also authenticated with phone number in the first place (using signInWithCredential), i.e. you retrieve a credential using FirebaseAuth.verifyPhoneNumber and pass the credential that you get from either verificationCompleted or your user when they enter the SMS code they received. I will only sketch out what this would look like as I assume that you know how to perform this task:
FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: const Duration(minutes: 2),
verificationCompleted: (credential) async {
await (await FirebaseAuth.instance.currentUser()).updatePhoneNumber(credential);
// either this occurs or the user needs to manually enter the SMS code
},
verificationFailed: null,
codeSent: (verificationId, [forceResendingToken]) async {
String smsCode;
// get the SMS code from the user somehow (probably using a text field)
final AuthCredential credential =
PhoneAuthProvider.getCredential(verificationId: verificationId, smsCode: smsCode);
await (await FirebaseAuth.instance.currentUser()).updatePhoneNumber(credential);
},
codeAutoRetrievalTimeout: null);
When updatePhoneNumber is called, you probably also want to update your database document. Alternatively, you could listen to onAuthStateChanged and update your document this way.

async function save(phone: string, e) {
e.preventDefault();
const { currentUser:fuser } = firebase.auth();
if(fuser && fuser.phoneNumber !== phone) {
try {
const verifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', {
callback: (response) => console.log('callback', response),
size: 'invisible',
});
const phoneProvider = new firebase.auth.PhoneAuthProvider();
const id = await phoneProvider.verifyPhoneNumber(phone, verifier);
const code = window.prompt('Bitte zugeschickten Code eingeben');
const cred = firebase.auth.PhoneAuthProvider.credential(id, code);
await fuser.updatePhoneNumber(cred);
console.log('phone number changed', id, cred, fuser);
setSuccess(true);
} catch(e) {
console.error(e);
}
}
}

Related

How to create push notification one device to another device like chat app?

I am working on a chat application, I want to push notification for specific user when his message send me so i should deliver message with notification bar.
/// Get the token, save it to the database for current user
_saveDeviceToken() async {
// Get the current user
String uid = 'jeffd23';
// FirebaseUser user = await _auth.currentUser();
// Get the token for this device
String fcmToken = await _fcm.getToken();
// Save it to Firestore
if (fcmToken != null) {
var tokens = _db
.collection('users')
.document(uid)
.collection('tokens')
.document(fcmToken);
await tokens.setData({
'token': fcmToken,
'createdAt': FieldValue.serverTimestamp(), // optional
'platform': Platform.operatingSystem // optional
});
}
}

How to link phone number to already logged in email user which is using firebase

I want to link the already login user who has login from their email id when starting the website but after completing the signup process I want to add the phone number of the user but I am getting an error as firebase creates a new id every time when creating a new id after the phone OTP verification.
So, after some digging, I found out that there is a way to LINK already logged in the user with an email with a phone number.
But, the function is not working for me
here is my code for adding a phone number and then linking it with current user credentials.
sendOTP(String phoneNumber) async {
this.phoneNumber = phoneNumber;
FirebaseAuth auth = FirebaseAuth.instance;
print('${phoneCode}');
ConfirmationResult confirmationResult =
await auth.signInWithPhoneNumber('+${phoneCode}${phoneNumber}');
if (kDebugMode) {
print("OTP send to +${phoneCode} ${phoneNumber}");
}
return confirmationResult;
}
authenticateMe(ConfirmationResult confirmationResult, String otp) async {
UserCredential userCredential = await confirmationResult.confirm(otp);
signIn(AuthCredential userCredential) async {
//now link these credentials with the existing user
UserCredential? linkauthresult =
await existingUser?.linkWithCredential(userCredential);
print('linked');
}
firebaseOtp = otp;
}
here is my User existingUser = FirebaseAuth.instance.currentUser!; variable which is i am calling in init state
#override
void initState() {
super.initState();
existingUser;
print('this is current user from otp_container ${existingUser}');
}
and here is my button onPressed function
onPressed: () {
authenticateMe(
temp,
otpCodeController.text,
);
Future.delayed(const Duration(seconds: 3))
.then((value) {
if (!mounted) {
const CircularProgressIndicator();
}
setState(() {
if (otpCodeController.text ==
firebaseOtp) {
isAnimatedContainer =
!isAnimatedContainer;
} else {
setState(() {
verifyOtpcheck = !verifyOtpcheck;
});
}
});
});
},
and I am working on flutter web.

FS Document is not getting created after user is registered

When a user submits their information on the "Signup page", the user should have
an account registered within fire store and
a fire store document created with all of their information inside.
The user account gets registered fine, but the document never gets created. I'm not getting any error messages so I'm trying to use debug prints to find out where things are going wrong.
Debug Prints:
>> login: signUp // this starts the signUp function
>> login: Start hideNewUserOverlay // hide overlay prints before signUp finishes
>> signUp: current user got // the following prints are from signUp
>> signUp: user.id = L8pD6tng5NTAACN7VygK93F6crg1
>> signUp: document creation code Start // then nothing happens after this
Future that is supposed to register the user and create document: // this will eventually pass in first/last names too, that's why I'm using this function
Future<void> signUp(String email, String password) async {
try {
// ignore: unused_local_variable
UserCredential result = await auth.createUserWithEmailAndPassword(email: email, password: password); // <-- user account is created on first press
} catch (e) {
debugPrint('>> Authentication: create new user error');
}
user = auth.currentUser!;
debugPrint('>> signUp: current user got');
String userID = user.uid;
debugPrint('>> signUp: user.id = $userID'); // all debugs print out correctly here, even userID
debugPrint('>> signUp: document creation code Start');
await collectionReference.doc(userID).set({ // code does not run
'userID': userID,
'accountCreated': DateTime.now(),
'email': email,
});
debugPrint('>> Authentication: User Document Created');
}
Signup page:
onPressed: () {
debugPrint('>> login: signUp');
signUp(_email, _password); // this line should finish before the next debug statement is printed but it does not
debugPrint('>> login: Start hideNewUserOverlay'); // prints before signUp() is done
hideNewUserOverlay(); // this will close the Signup page
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => const Nav(),
));
}
At the bottom of the code, the document will get created if I route to a different class. Nav() >> Verify(). The weird part is that Verify does not take in any user information. Verify() has a timer so maybe that has something to do with it? I think it is because the signup() function does not complete before the overlay is hidden. Maybe Nav() needs init state?
Putting await in from of a statement that returns a Future makes that line block the rest of the execution. It does not however make any other call wait.
If you want to wait until signUp is done, use await there too:
await signUp(_email, _password);
That does mean you'll need to mark onPressed as an async method too.
If that is not an option, you can always use then:
onPressed: () {
debugPrint('>> login: signUp');
signUp(_email, _password).then(() {
debugPrint('>> login: Start hideNewUserOverlay'); // prints before signUp() is done
hideNewUserOverlay(); // this will close the Signup page
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => const Nav(),
));
}
}

Flutter Amplify: How to get user id after signup

I am recently using Amplify library for my Flutter project to use Cognito service in AWS. I want to signup a user and get the user id in Cognito so that I can save it in my own database. I have some other attributes for the user which may not be suitable to put in Cognito database.
Currently I confirm user's email by sending them a link rather than a verification code so that the user only need to click the link in email, rather copying verification code over.
I have the following sample code to illustrate my purpose
Future<void> signUp(String username, String password, String email) async {
try {
SignUpResult result = await Amplify.Auth.signUp(
username: username, password: password, options: CognitoSignUpOptions(userAttributes: {'email': email}));
//String userId = await Amplify.Auth.SomeFunction();
//Save userId to my own database
} catch (e) {
print(e);
}
}
Currently, the sign up result is
In this article https://docs.amplify.aws/lib/auth/emailpassword/q/platform/js/#sign-up, it says with js, we can get the CognitoUser after signing up. But could not find equivalent feature for Flutter.
Can anyone please give some advice? Thank you
You can try to use the getCurrentUser Method from the Auth package. This will give you the AuthUser which is a object with two string properties (userId & username) which you can use further more, if you need them.
So for exactly this problem it would mean:
Future<void> signUp(String username, String password, String email) async {
try {
SignUpResult result = await Amplify.Auth.signUp(
username: username, password: password, options: CognitoSignUpOptions(userAttributes: {'email': email}));
final user = await Amplify.Auth.getCurrentUser(); //This will give you the AuthUser
//Mock for save userId to my own database
databaseService.saveUserIdToDatabase(user.userId);
} catch (e) {
print(e);
}
}

Integration Testing Nodejs/Express/Mongoose with Jest/Supertest One Mongoose Model Saves, One Mongoose Model Doesn't

I use Postman and the ReactJS UI to call this registration execution and it works as I expect. Ironically, the Jest and Supertest integration tests do not produce expected results. When integration testing, the Profile is created and the User is not.
The architecture is pretty simple. MongoDB in a Docker container, and Node using nodemon in VSCode.
I have to be doing something wrong, I just can't spot what it is.
// The Integration Test __test__/users/../user.test.js
const app = require('../../app');
const uuidv4 = require('uuid/v4');
const User = require('../../src/models/User');
const Profile = require('../../src/models/Profile');
const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const request = require("supertest");
const {
MONGO_URI,
TEST_DB_NAME
} = process.env;
let DB_URI = MONGO_URI + TEST_DB_NAME;
let NAME = TEST_DB_NAME;
mongoose.connect(DB_URI, {
useNewUrlParser: true,
useCreateIndex: true,
dbName: NAME
});
describe('User Integration Test', () => {
// make sure app is imported without issues
it('Has App Defined', () => {
expect(app).toBeDefined();
});
let server;
beforeAll(async () => {
// Clear Test Data
await User.deleteMany({});
await Profile.deleteMany({});
server = await app.listen(3001);
});
afterAll(async (done) => {
// Clear Test Data
await User.deleteMany({});
await Profile.deleteMany({});
// Close server
await server.close(done);
});
describe('User route tests', () => {
it('Can Register a User', async () => {
const body = {
"username": "User21",
"email": "user21#user.com",
"password": "123456",
"avatar": "image.jpg"
}
await request(server)
.post('/api/v1/users')
.send(body)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.expect(200)
});
});
// THE EXPRESS ROUTE in api/v1/users.js
const express = require('express');
const auth = require('../../middleware/auth');
const router = express.Router();
const { UserService } = require('../../services');
const {
check,
validationResult
} = require('express-validator/check');
// #route POST api/users
// #desc Register User
// #access Public
// #return status message
router.post('/', [
check('email', 'Please provide a valid email address').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }),
check('username', 'Username is Required.').not().isEmpty()
], async (req, res, next) => {
try {
//--Validate
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const message = await UserService.register(req.body);
return res.status(200).json(message)
} catch (err) {
next(err);
}
});
// THE register METHOD found in ../../services/UserService.js
const register = async (data) => {
try {
// Destructure the data
const {
username,
email,
password,
avatar
} = data;
// remove spaces from username and lcase it
let user_name = username.replace(/\s/g, '').toLowerCase();
// Check if the username or email already exists
await doesUserExist(user_name, email);
// Create a new user
const token = uuidv4();
user = new User({
email: email.toLowerCase(),
username: user_name,
avatar: avatar,
verifyEmailToken: token
});
// encrypt the password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
// Save the user
// (Works Unless Running Jest Integration Tests)
await user.save();
// Create and save an empty Profile for the new user
profile = new Profile();
profile.user = user;
// (Always Works)
await profile.save();
// Send verification email
await send(user, 'Verify Your Email', token, 'verify-email.html');
return { message: 'User was registered successfully.' };
} catch (err) {
throw err;
}
}
// Does user exist method found in ./UserService.js
const doesUserExist = async (username, email) => {
// Check if user exists by email
let message = await checkEmail(email);
if (!message.email_available) {
throw new Error('Email already exists');
}
// Check if user exists by username
message = await checkUserName(username.toLowerCase())
if (!message.username_available) {
throw new Error('Username already exists');
}
return false;
}
When I call this code via the UI, Postman, or curl both the User and Profile are created, as expected.
When I run the Integration Test, npm run test:integration or npm test,
Only the Profile is created.
my package.json scripts:
"test": "jest",
"test:integration": "jest --testPathPattern integration.test",
Finally, no errors are reported anywhere. User simply isn't created.
After a few hours of work and testing this issue I found that the afterAll() event was raise randomly. Sometimes after all the tests in the inner describe() ran and sometimes not. Of course, when afterAll() ran all Users where deleted from the data store.
If moved beforeAll() and AfterAll() to the inner describe(). This worked very well until I included other integration tests, like authorization, for example that also used the User table. I noticed that the test suites are not executed synchronously. As one test suit was blowing out the data in another test suite as the other test suite was executing.
I now set up a database per test suite. Clunky, wonky, hacky and wrong I know but I have to have move on. Does anyone know if you can control the synchronous and/or asynchronous behavior of Jest? Please don't suggest Mocha and/or Chai.
I ran into a similar issue, where there appeared to be an intermittent race condition between seeding a document and then retrieving it.
I fixed it by running jest with the --runInBand flag.