I am working on a reactJS website with firebase as a backend. One of the main service that I give to my user is notification everyday. I had implemented a code to ask for permission, if they grant access, store them to my database. But after sometime I noticed that tokens are expiring. I did a research about it and found out about onTokenRefresh() method. I implemented that also. But I believe for some reason, it is not working correctly. I am not getting the new tokens if tokens are expired. I'll paste the snippet below of what I am trying to do.
On the main page,
if (!("Notification" in window)) {
//alert("This browser does not support desktop notification");
}
// Check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
// Refresh logic here
this.checkForRefreshToken();
}
// Otherwise, ask the user for permission
else if (Notification.permission !== "denied") {
//Ask user here, then call another function for storing token
this.addNewToken()
}
If user is accepting notification for the first time, I call addNewToken() method
addNewToken = () => {
this.auth.onAuthStateChanged(authUser => {
const messaging = firebase.messaging();
messaging
.requestPermission()
.then(() => {
return messaging.getToken();
})
.then(token => {
firebase.database().ref('tokens').once('value')
.then(snapshots => {
let tokenExist = false
snapshots.forEach(childSnapshot => {
// console.log("Child snapshot: ", childSnapshot.key);
if (childSnapshot.val().token === token) {
tokenExist = true
return console.log('Device already registered.');
}
})
if (!tokenExist) {
// console.log('Device subscribed successfully');
return firebase.database().ref('tokens').push(token);
}
})
})
.catch(error => {
if (error.code === "messaging/permission-blocked") {
// console.log("Please Unblock Notification Request Manually");
} else {
// console.log("Error Occurred", error);
}
});
})
}
Now if user has already subscribed, I am checking if onTokenRefresh() is called, basically if token needs a refresh.
checkForRefreshToken = () => {
this.auth.onAuthStateChanged(authUser => {
const messaging = firebase.messaging();
messaging.onTokenRefresh(() => {
messaging.getToken().then((refreshedToken) => {
const token = refreshedToken;
firebase.database().ref('tokens').once('value')
.then(snapshots => {
let tokenExist = false
snapshots.forEach(childSnapshot => {
if (childSnapshot.val().token === token) {
tokenExist = true
return console.log('Device already registered.');
}
})
if (!tokenExist) {
return firebase.database().ref('device_ids').push(token);
}
})
})
})
})
}
I don't know what is going wrong here, but I am not able to get the new tokens.
For testing purpose, I have my own device with expired token, I deployed this code and opened my website on the device, refreshed the page etc but I didn't get the new token.
Also, it would be great if anyone can help me how I can test expired tokens locally.
I found different methods for an app, but not for website (javascript).
Thanks
auth.onAuthStateChanged(...) will add an event listener to events signalling changes in the authentication state. In your code above, you expect that the code for addNewToken and checkForRefreshToken are evaluated immediately, but instead they are simply adding new event listeners. The same applies for onTokenRefresh().
So reworking addNewToken() into an on-demand function yields:
requestMessagingToken = () => {
let authUser = this.auth.currentUser;
if (!authUser) {
console.log("Not logged in!");
return Promise.resolve(false); // silently ignore & fail
}
const messaging = firebase.messaging();
return messaging
.requestPermission()
.then((permission) => {
if (permission !== 'granted') {
throw 'insufficient permissions'; // notification permission was denied/ignored
}
return messaging.getToken();
})
.then(token => saveMessagingTokenForUser(authUser.uid, token))
.catch(error => {
if (error && error.code === "messaging/permission-blocked") {
// console.log("Please Unblock Notification Request Manually");
} else {
// console.log("Error Occurred", error);
}
return false; // silently fail
});
};
This has the added benefit of being able to handle auth state changes easily using:
this.auth.onAuthStateChanged(requestMessagingToken);
Once you've been granted permission to send notifications, you can activate the refreshed token listener using:
const messaging = firebase.messaging();
messaging.onTokenRefresh(() => {
let authUser = this.auth.currentUser;
if (!authUser) {
console.log("Not logged in!");
return; // ignore
}
messaging.getToken().then((refreshedToken) => {
return saveMessagingTokenForUser(authUser.uid, refreshedToken);
}).catch((err) => {
console.log('Unable to retrieve/store messaging token ', err);
});
});
Lastly, to save the token to the database, instead of searching through your database for a matching device token as the value of a push ID, you can use the token itself as the key. Furthermore, to make outdated token management easier, it is best to split your tokens by user.
"userData": {
"userId1": {
"tokens": {
"deviceToken1": true,
"deviceToken2": true,
"deviceToken3": true,
},
...
},
"userId2": {
"tokens": {
"deviceToken4": true,
"deviceToken5": true
},
...
},
...
}
Using this structure, you can use transactions to check if the data has been stored in the database and use cloud functions to check for invalid tokens.
saveMessagingTokenForUser = (uid, token) => {
return firebase.database().ref('userData/' + uid + '/tokens/' + token)
.transaction((currentData) => {
if (currentData != null) {
console.log('Device already registered.');
return; // abort transaction, no changes needed
} else {
console.log('Saving token to database.');
return true;
}
})
.then(() => true); // no errors = success
};
On the server you could run a Cloud Function listening to new device tokens added to the database and check that user's other device tokens for expiry.
exports.checkUserForOutdatedTokens = functions.database.ref('/userData/{userId}/tokens/{tokenId}')
.onCreate((newTokenSnapshot, context) => {
return newTokenSnapshot.ref.once('value')
.then((tokensSnapshot) => {
let tokens = Object.keys(tokensSnapshot.val());
const message = {
data: {heartbeat: true},
tokens: tokens,
}
return admin.messaging().sendMulticast(message)
.then((response) => {
if (response.failureCount > 0) {
const dataToDelete = {};
response.responses.forEach((resp, idx) => {
if (!resp.success) {
dataToDelete[tokens[idx]] = null;
}
});
console.log('List of tokens that caused failures: ' + Object.keys(dataToDelete));
return tokensSnapshot.ref.update(dataToDelete); // deletes failed tokens
}
});
});
});
Related
I wish to check if a user is already logged in through email/password auth via mongodb realm web-sdk. Knowing if the user is logged in will allow me to hide the loggin page from site and instead show a log out button.
So far I've successfully created a user and logged in. Using the code/methods below.
async function registerEmailPassword(email, password) {
try {
const user = await app.emailPasswordAuth.registerUser({ email, password });
return user;
} catch (error) {
console.error("Failed to register", error)
}
}
async function loginEmailPassword(email, password) {
// Create an email/password credential
const credentials = Realm.Credentials.emailPassword(email, password);
try {
// Authenticate the user
const user = await app.logIn(credentials);
// `App.currentUser` updates to match the logged in user
console.assert(user.id === app.currentUser.id);
return user;
} catch (error) {
console.error("Failed to log in", error);
}
}
While going through the mongodb class documentation, I wrote the following function which appears to work.
The code is checking for if their is any user in currentUser, if their is no currentUser, their no account logged in. In the event their is a currentUser, the code then checks using currentUser.isLoggedIn if that user is logged and at the end returns a boolean value.
async function isUserLoggedIn() {
try {
const userStatus = await app.currentUser;
if (userStatus == null) {
return false
} else {
const userStatus = await app.currentUser.isLoggedIn;
return userStatus
}
} catch (error) {
console.log("Failed to fetch user", error);
}
}
// Check if user is logged in
isUserLoggedIn().then((value) => {
console.log(value);
}).catch((error) => {
console.log(error);
});
I need to get the jwtToken from the Auth.signUp. Is this possible if i enable autoSignIn:{enabled:true}?
const signUp = async () => {
await Auth.signUp({
username: email,
password,
attributes: {
email, // optional
name,
},
autoSignIn:{
enabled: true
}
})
.then((data) => {
console.log(data.user); //user.signInUserSession is null
})
.catch((err) => {
if (err.message) {
setInvalidMessage(err.message);
}
console.log(err);
});
await Auth.currentAuthenticatedUser()
.then(user =>{
console.log(user)
})
.catch(error => {
console.log(error) //"User is not authenticated"
})
};
I call I want the jwttoken from the userSession data for conditional rendering and I store the token in my router.js. The response object from Auth.signUp contains a CognitoUser which has a signInUserSession value but its's null.
EDIT: Tried to call Auth.currentAuthenticatedUser() after but yields an error that user is not authenticated. But when i restart my app, the user will be authenticated. I still cant authenticate user on the same app "instance"
import { Auth, Hub } from 'aws-amplify';
const listener = (data) => {
switch (data.payload.event) {
case 'autoSignIn':
console.log('auto sign in successful');
console.log(data.payload) //returns user data including session and tokens.
//other logic with user data
break;
}
};
Above is the code to initalize the Hub listener provided by amplify api. Ater user presses sign up, I called to get user session data when user is automatically signed in.
Hub.listen('auth', listener)
i have a react natve app it has a signup whith google button when i click on signin i am getting data in console.log i want to save the data in firebase i sont know how to do it
const googleLogin = async () => {
try {
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
console.log(userInfo);// i am getting user data here
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
// user cancelled the login flow
} else if (error.code === statusCodes.IN_PROGRESS) {
// operation (e.g. sign in) is in progress already
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
// play services not available or outdated
} else {
// some other error happened
}
}
};
You can refer to this LINK for google social auth, this is what you're looking for to save the auth data to firebase:
import auth from '#react-native-firebase/auth';
import { GoogleSignin } from '#react-native-google-signin/google-signin';
async function onGoogleButtonPress() {
// Check if your device supports Google Play
await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true });
// Get the users ID token
const { idToken } = await GoogleSignin.signIn();
// Create a Google credential with the token
const googleCredential = auth.GoogleAuthProvider.credential(idToken);
// Sign-in the user with the credential
return auth().signInWithCredential(googleCredential);
}
This can helpful. Just make sure you have integrated all libraries for firebase authentication.
import {
GoogleSignin,
statusCodes,
} from '#react-native-google-signin/google-signin';
import auth, {FirebaseAuthTypes} from '#react-native-firebase/auth';
// This function will be call on tapping sign in with google button
const signInWithGoogle = async () => {
try {
// This will check whether there is Google play service or not
await GoogleSignin.hasPlayServices();
//This will give you userInformation
const userInfo = await GoogleSignin.signIn();
// This will create new credential which can help to signIn in firebase
const credential = auth.GoogleAuthProvider.credential(userInfo.idToken);
//Here we are trying to return promise so when we call function we can have promise object
return new Promise((resolve, reject) => {
auth()
.signInWithCredential(credential)
.then(response => {
console.log('response in', response);
resolve(response);
})
.catch(error => {
console.log('error in', error);
reject(error);
});
});
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
// user cancelled the login flow
} else if (error.code === statusCodes.IN_PROGRESS) {
// operation (e.g. sign in) is in progress already
alert(JSON.stringify(error));
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
// play services not available or outdated
alert(JSON.stringify(error));
} else {
alert(error);
}
}
};
Now when you call this function on google button on press it will give you promise object and you can do it like below.
onPress={() => {
SignInMethods.signInWithGoogle()
.then(response => {
console.log('user information from firebase authentication', response.user);
})
.catch(error => {
console.log('error in google sign in :', error);
});
}}
Im trying to do a variety of firebase actions in one call in a react-native app using react-native-firebase. the flow goes something like this:
create user in authentication
send image to storage
send data to firestore
During the image-storage phase, the imgRef.putFile() function errors out saying the user isn't authorized. However im using createUserWithEmailAndPassword() (which authenticates a user in on completion) and then using the returned credential to do the rest of the work, image storage and firestore creations.
firebase storage rules are set to allow only authenticated users. here's the ruleset:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
Also, I have enabled anonymous signin in authentication methods.
here's the initial action up until the error point:
return (dispatch) => {
dispatch({ type: types.REGISTER_USER });
console.log('starting registration process...');
firebase
.firestore()
.collection('users')
.where('username', '==', username)
.get()
.then((querySnapshot) => {
console.log(querySnapshot);
if (querySnapshot.empty !== true) {
registrationFail(dispatch, 'Username already taken. Try again.');
console.log("Registrant's username already exists");
} else {
console.log('Registrants username is unique');
firebase
.auth()
.createUserWithEmailAndPassword(email, pass)
.then((userCredential) => {
uploadImg(dispatch, img, userCredential.user.uid)
here's the uploadImg() function:
const uploadImg = async (dispatch, uri, uid) => {
console.log('Starting image upload...');
dispatch({ type: types.UPLOAD_IMG, info: 'Uploading profile image...' });
const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
const imgRef = firebase.storage().ref('profile-images').child(uid);
return imgRef
.putFile(uploadUri, { contentType: 'image/png' })
.then((success) => {
console.log('profile image upload successful.')
return imgRef;
})
.catch((err) => {
console.log('profile image upload failed: ' + err)
uploadImgFail(dispatch, err.message);
});
};
again, the error that logs in the console is:
profile image upload failed: Error: User is not authorized to perform the desired action.
logging the firebase.auth().currentUser right before the uploading() function returns the current user object successfully.
this security rule issue also happens with Firestore, despite my security ruleset for the given collection being:
match /databases/{database}/documents {
match /users/{uid} {
// allow new user to check phone numbers
allow read: if true
allow update, create: if request.auth != null
allow delete: if request.auth.uid == uid
}
}
This is a part of my registration flow. I collect input, send relevant data to redux action, create a user, once the user is created, I add some data to a document in firestore. this makes no sense. im referencing the documentation and it still doesn't work.
How can this be fixed?
It seems to be an issue with firebase that you have to logout() the user once before you can log in after user creation. I faced the same issue and this is my workaround:
firebase.auth().signInWithEmailAndPassword(userEmail,userPass).catch((error) => {
/*console.log('Could not log in user');*/
console.log(error);
firebase.auth().createUserWithEmailAndPassword(userEmail,userPass).catch((error) => {
/*console.log('Could not create user');*/
console.log(error);
}).then(result => {
firebase.auth().signOut().then(() => {
firebase.auth().signInWithEmailAndPassword(userEmail,userPass).then(result => {
/*console.log("here is you logged in user");*/
})
});
});
});
});
I am using the FacebookAuthProvider by firebase to login my users from my platform.
I'm using react native in expo with firestore and it was working fine till I tried to add in some checks to redirect users to the correct screens after login. There are two different roles (administrators and users) which have to be separate right after the login.
if (/* user is administrator */) {
this.props.navigation.navigate('Admin');
} else {
this.props.navigation.navigate('Main');
}
After adding this method to separate users by there roles, I got this error:
react native TypeError: Cannot read property 'navigation' of undefined
Later I will add some more details (log files etc. as soon as I've learned how to grep them from my locale machine).
For better understanding I put my whole code here (sorry for the bad indentations which lesses the readability):
const auth = firebase.auth();
const firebaseUser = '';
const usersRef = firebase.firestore().collection('users');
async handleFacebookButton() {
const { type, token, } = await Facebook.logInWithReadPermissionsAsync(FACEBOOK_APP_ID, {
permissions: ['public_profile', 'email']
});
if (type === 'success') {
//Firebase credential is created with the Facebook access token.
const credential = firebase.auth.FacebookAuthProvider.credential(token);
auth.signInAndRetrieveDataWithCredential(credential)
.then(function(userCredential) {
newUserCheck = userCredential.additionalUserInfo.isNewUser;
console.log('newUserCheck = ', newUserCheck)
});
this.setState({loggedIn: "You are signed in"})
this.setState({signedIn: true})
console.log('you are signed in');
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
firebaseUser = {name: user.displayName, uid: user.uid, email: user.email}
console.log(firebaseUser.name, ' and ', firebaseUser.uid);
var existingRef = usersRef.doc(firebaseUser.uid);
existingRef.get().then(function(documentSnapshot) {
// check if user is registered
if(documentSnapshot) {
data = documentSnapshot.data();
console.log('existing user exists!!');
// check if user is an administrator
if (data.administrator == true) {
console.log('existing administrator exists!!');
this.props.navigation.navigate('Admin');
} else { this.props.navigation.navigate('Main');
}
}
});
(error => {
console.log('user not accessed: ', error);
});
//User is not yet in firebase database and needs to be saved
// double check that user is a new user
if (newUserCheck == true) {
this.ref
.doc(uid)
.set({
id: firebaseUser.uid,
username: firebaseUser.name,
email: firebaseUser.email,
})
this.props.navigation.navigate('ChooseRoute')
}
}
})
}
// If login type is not success:
(error => {
this.setState({loggedIn: "Login failed: log in again"})
this.setState({ errorMessage: error.message });
});
}
I fixed it!! 3 days later - it was a binding issue - after several unsuccessful attempts to work out which were the right parts of the functions to bind I converted both 'auth().onAuthStateChanged' and 'documentSnapshot' into fat arrow functions and the errors are gone!! Thank goodness for ES6...! Hope this helps someone else down the line...