Firebase Storage Authentication not working - firebase

I'm just trying to have the rules be that if the user is authenticated then they can write. When I try to upload an image to the gallery from the website I get back an error saying that I'm not authorized. I have authentication set up and working. Here are my rules:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
And here is my image upload code:
imgUploadHandler = () => {
const image = this.props.imageUpload;
const uploadTask = storage.ref(`/Gallery/${image.name}`).put(image);
uploadTask.on('state_changed',
(snapshot) => {
// Progress function
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
this.setState({ progress });
},
(error) => {
// Error function
console.log(error);
},
() => {
// Complete function
storage.ref('Gallery').child(image.name).getDownloadURL()
.then(URL => {
const imageData = { imgURL: URL, name: image.name, width: "400"}
this.props.onImageUploaded(URL);
this.imgSelectedHandler(URL);
Axios.post(`/Gallery.json?auth=${this.props.token}`, imageData);
})
})
}

Related

Firebase auth().onAuthStateChanged not wait until auth().signInWithCredential finish

I have login code in react native using firebase and google signin auth.
So when new user sign in using google account, I set new data. And if user has signed in before, user go to main page.
My problem is when new user sign in > code start to get signInWithCredential > set new data user, before set data finish, onAuthStateChanged was detect there is change in auth and start to get user document / data. But because it's not finish yet, it throw error 'Can Not Get UID / Undefined UID'.
This is my login page code:
const _signIn = async () => {
setInitializing(true);
try {
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
const credential = auth.GoogleAuthProvider.credential(
userInfo.idToken,
userInfo.accessToken,
);
await auth()
.signInWithCredential(credential)
.then(response => {
const uid = response.user.uid;
const data = {
uid: uid,
email: userInfo.user.email,
fullname: userInfo.user.name,
bio: 'Halo!! ..',
username: uid.substring(0, 8),
};
const usersRef = firestore().collection('users');
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
usersRef
.doc(data.uid)
.set(data)
.then(() => {
setInitializing(false); return;
})
.catch(error => {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
});
} else {
setInitializing(false);
return;
}
})
.catch(error => {
Alert.alert(JSON.stringify(error.message));
console.log('Error getting document:', error);
return;
});
});
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
setInitializing(false);
Alert.alert('Sign in canceled');
} else if (error.code === statusCodes.IN_PROGRESS) {
setInitializing(false);
Alert.alert('Signin in progress');
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
setInitializing(false);
Alert.alert('PLAY_SERVICES_NOT_AVAILABLE');
} else {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
}
}};
And this is my index page code to check auth user:
useEffect(() => {
try {
NetInfo.fetch().then(state => {
if(state.isConnected === false){
Alert.alert('No Internet Connection Detected');
setInitializing(false);
return;
}
});
setInitializing(true);
await auth().onAuthStateChanged(user => {
if (user) {
const usersRef = firestore().collection('users');
usersRef
.doc(user.uid)
.get()
.then(document => {
const userData = document.data().uid;
setisLogin(userData);
})
.then(() => {
setInitializing(false);
})
.catch(error => {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
});
} else {
setInitializing(false);
}
});
} catch (error) {
Alert.alert(error);
} }, []);
How to wait auth().signInWithCredential finish? Thankyou.
If you need to perform more actions such read data from database or so after the user logs in, you should ideally unsubscribe from onAuthStateChanged. Essentially it won't trigger when the auth state changes (i.e. user logs in) and let you do your own custom actions. Once your processing is done, then you manually redirect the user to where the onAuthStateChange would have redirected is the user wa s logged in.
const authStateListenter = await auth().onAuthStateChanged(user => {
//...
})
// Unsubscribe auth state observer when _signIn function runs
const _signIn = async () => {
setInitializing(true);
authStateListenter()
}
Calling authStateListener will disable the auth state observer. It's similar to detaching Firestore's listeners.

Get error FirebaseError: Missing or insufficient permissions

I develop web app using Angular 8 and connect to firebase using #angular/fire v5.4.2 and firebase js SDK v7.8.0. Every time I want to get a document in firestore it always shows error
FirebaseError: Missing or insufficient permissions.
at new FirestoreError (https://localhost:4200/vendor.js:130146:28)
at JsonProtoSerializer.push.../../node_modules/#firebase/firestore/dist/index.cjs.js.JsonProtoSerializer.fromRpcStatus (https://localhost:4200/vendor.js:145520:16)
at JsonProtoSerializer.push.../../node_modules/#firebase/firestore/dist/index.cjs.js.JsonProtoSerializer.fromWatchChange (https://localhost:4200/vendor.js:146033:44)
at PersistentListenStream.push.../../node_modules/#firebase/firestore/dist/index.cjs.js.PersistentListenStream.onMessage (https://localhost:4200/vendor.js:142655:43)
at https://localhost:4200/vendor.js:142584:30
at https://localhost:4200/vendor.js:142624:28
at https://localhost:4200/vendor.js:131493:20
at ZoneDelegate.invoke (https://localhost:4200/polyfills.js:3690:26)
at Object.onInvoke (https://localhost:4200/vendor.js:83071:33)
at ZoneDelegate.invoke (https://localhost:4200/polyfills.js:3689:52)
Here is my code when trying to get the document
loginFirebase(): Promise<any> {
return new Promise((resolve, reject) => {
firebase.auth().signInAnonymously().then(res => {
resolve(res);
}).catch(err => {
reject(err);
});
});
}
login(username: string, password: string): Promise<IUsers> {
return new Promise((resolve, reject) => {
this.loginFirebase().then(userLogin => {
this.setUser(res).then(() => {
resolve();
}).catch(err => {
reject(err);
});
}).catch(errorFirebase => {
reject(errorFirebase);
});
}
}
setUser(data: UserLogin): Promise<any> {
return new Promise((resolve, reject) => {
const userData: IUsers = {
userId: data.doctor.id.toString(),
userFullName: `${data.doctor.firstName} ${data.doctor.lastName}`,
userPhoto: data.doctor.profileImage || '',
userStatus: UserStatus.ACTIVE,
userType: data.roles[0].role,
token: data.token,
sex: data.doctor.sex,
email: data.name
};
try {
this.afStore.collection(`${environment.firestoreCollection}users`, ref => ref.where('userId', '==', userData.userId.toString()))
.get()
.subscribe(doc => {
resolve(doc)
}, err => {
console.error(err);
});
} catch (error) {
this.translate.get('error_message', {error: 'catch saveUser'}).subscribe(err => {
if (confirm(err)) {
this.setUser(data);
} else {
reject(error);
}
});
}
And here is the rules for the firestore
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if isAuth(request);
}
function isAuth(req) {
return req.auth != null;
}
}
}
When the web app signed in anonymously, I check the uid it's already in firebase authentication. What possibly I get wrong here? Anyone can help me, please?
You should use an auth state observer to determine when the user is actually signed in and able to make authenticated queries. It turns out that the promise returned by signInAnonymously isn't actually an indicator if the user is fully signed in.

How to setup test data when testing Firestore Rules with Emulator?

I am working on tests for Cloud Firestore Rules, using mocha and Firestore Emulator, and the question is how to initialize some test data before running tests?
To test my rules, I first need to initialize some test data. The problem is that I cannot put any data into a document when working with Emulator, documents only have id.
I didn't find any example of setting up test data for Rules tests in the docs, so I tried to use both
makeDocumentSnapshot from #firebase/testing and document creation via admin app created with initializeAdminApp.
Use case:
To get access to a document at /objects/{object_id}, a User must be authenticated and have read permission: get('/objects/{object_id}/users/{$(request.auth.uid)}').data.read == true. Also, object must be available: get('/objects/{object_id}').data.available == true.
So, to test my rules I need some preset test data with User permissions.
Expected DB structure:
objects collection:
object_id: {
// document fields:
available (bool)
// nested collection:
users collection: {
user_id: {
// document fields:
read (bool)
}
}
}
Example of my rules:
service cloud.firestore {
match /databases/{database}/documents {
match /objects/{object} {
function objectAvailable() {
return resource.data.available;
}
// User has read access.
function userCanReadObject() {
return get(/databases/$(database)/documents/objects/$(object)/users/$(request.auth.uid)).data.read == true;
}
// Objects Permission Rules
allow read: if objectAvailable() && userCanReadObject();
allow write: if false;
// Access forbidden. Used for permission rules only.
match /users/{document=**} {
allow read, write: if false;
}
}
}
}
Example of my tests:
const firebase = require('#firebase/testing');
const fs = require('fs');
// Load Firestore rules from file
const firestoreRules = fs.readFileSync('../firestore.rules', 'utf8');
const projectId = 'test-application';
const test = require('firebase-functions-test')({ projectId, databaseName: projectId });
describe('Tests for Rules', () => {
let adminApp;
const testData = {
myObj: {
id: 'test',
data: {
available: true,
},
},
alice: {
id: 1,
data: {
read: true,
},
},
};
before(async () => {
// Load Rules
await firebase.loadFirestoreRules({ projectId, rules: firestoreRules });
// Initialize admin app.
adminApp = firebase.initializeAdminApp({ projectId }).firestore();
// Create test data
await adminApp.doc(`objects/${testData.myObj.id}`).set(testData.myObj.data);
await adminApp
.doc(`objects/${testData.myObj.id}/users/${testData.alice.id}`)
.set(testData.alice.data);
// Create test data with `firebase-functions-test`
// test.firestore.makeDocumentSnapshot(testData.myObj.data, `objects/${testData.myObj.id}`);
// test.firestore.makeDocumentSnapshot(
// testData.alice.data,
// `objects/${testData.myObj.id}/users/${testData.alice.id}`,
// );
});
beforeEach(async () => {
await firebase.clearFirestoreData({ projectId });
});
after(async () => {
// Shut down all testing Firestore applications after testing is done.
await Promise.all(firebase.apps().map(app => app.delete()));
});
describe('Testing', () => {
it('User with permission can read objects data', async () => {
const db = firebase
.initializeTestApp({ projectId, auth: { uid: testData.alice.id } })
.firestore();
const testObj = db.doc(`objects/${testData.myObj.id}`);
await firebase.assertSucceeds(testObj.get());
});
});
});
Console output for test run:
1) User with permission can read objects data
0 passing (206ms)
1 failing
1) Tests for Rules
Testing
User with permission can read objects data:
FirebaseError:
false for 'get' # L53
To check created test data I added the following code before await firebase.assertSucceeds(testObj.get()); line:
const o = await adminApp.doc(`objects/${testData.myObj.id}`).get();
const u = await adminApp.doc(`objects/${testData.myObj.id}/users/${testData.alice.id}`).get();
console.log('obj data: ', o.id, o.data());
console.log('user data: ', u.id, u.data());
Output is the following:
obj data: test undefined
user data: 1 undefined
I also tried to remove the code from beforeEach, the result is the same.
You can use initializeAdminApp to get admin privilegies (all operations are allowed):
const dbAdmin = firebase.initializeAdminApp({projectId}).firestore();
// Write mock documents
if (data) {
for (const key in data) {
if (data.hasOwnProperty(key)) {
const ref = dbAdmin.doc(key);
await ref.set(data[key]);
}
}
}
Data is supposed to have the following format:
data = {
'user/alice': {
name:'Alice'
},
'user/bob': {
name:'Bob'
},
};
You have to add data before you apply the rules.
Detailed information you can find here
const firebase = require('#firebase/testing');
const fs = require('fs');
let db
let projectId = `my-project-id-${Date.now()}`
async function setup(auth) {
const app = await firebase.initializeTestApp({
projectId: projectId,
auth: auth
});
db = app.firestore();
let data = {
'users/alovelace': {
first: 'Ada',
last: 'Lovelace'
}
}
// Add data before apply rules
for (const key in data) {
const ref = db.doc(key);
await ref.set(data[key]);
}
// Apply rules
await firebase.loadFirestoreRules({
projectId,
rules: fs.readFileSync('firestore.rules', 'utf8')
});
}
test('logged in', async () => {
await setup({ uid: "alovelace" })
let docRef = db.collection('users');
// check if there is data
let users = await docRef.get()
users.forEach(user => {
console.warn(user.id, user.data())
});
let read = await firebase.assertSucceeds(docRef.get());
let write = await firebase.assertFails(docRef.add({}));
await expect(read)
await expect(write)
});
afterAll(async () => {
Promise.all(firebase.apps().map(app => app.delete()))
});
firestore.rules
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read:if request.auth.uid != null;
allow write: if false
}
}
}

firebase auth return null on signInWithPhoneNumber

I am trying to login with phone number with firebase signInWithPhoneNumber() method for login. In which i have checked whether user auth state has been change or not. If user auth is change then login and redirect to home page. but i m getting auth null
onLoginBtnClicked() {
const { contact, password } = this.props;
const error = Validator('password', password) || Validator('contact', contact);
if (error !== null) {
Alert.alert(error);
} else {
console.log('else');
// this.props.loginUser({ contact, password});
const mobileNo = '+91'+contact;
firebase.auth().signInWithPhoneNumber(mobileNo)
.then(data => console.log(data),
firebase.auth().onAuthStateChanged((user) => {
console.log('user'+user);
if (user && !CurrentUser.isFirstTimeUser) {
const userRef = firebase.database().ref(`/users/`);
userRef.on("value", (snapshot) => {
console.log(snapshot.val());
snapshot.forEach(function(item) {
var itemVal = item.val();
if(itemVal.mobile == contact){
NavigationService.navigate('Home');
}
});
}, (errorObject) => {
console.log("The read failed: " + errorObject.code);
});
//NavigationService.navigate('Home');
}
})
)
.catch(error => console(error.message) );
}
}
There are two things to note here
onAuthStateChanged is a listener which listen for the user auth changes.
signInWithPhoneNumber sends the code to the user's phone, you have to confirm it to authenticate the user.
You need to add the listener in the react lifecycle for the component once it is mounted and remove it when it is unmounted
componentDidMount() {
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ user: user.toJSON() });
} else {
// Reset the state since the user has been logged out
}
});
}
componentWillUnmount() {
if (this.unsubscribe) this.unsubscribe();
}
// Send Message here
firebase.auth().signInWithPhoneNumber(mobileNo)
.then(confirmResult => this.setState({ confirmResult })
.catch(error => // handle the error here)
// Authenticate User typed Code here
const { userCode, confirmResult } = this.state;
if (confirmResult && userCode.length > 0) {
confirmResult.confirm(codeInput)
.then((user) => {
// handle user confirmation here or in the listener
})
.catch(error => // handle the error here)
}

Uploading a form posted image buffer to Cloud Storage with Firebase Functions

Here's my cloud function. It's supposed to get an http posted image and upload it to storage, returning the url.
exports.uploadImageToEditor = functions.https.onRequest((req, res) => {
const img = JSON.parse(JSON.stringify(req.body));
const bucket = admin.storage().bucket();
return bucket.file('blog/foo.jpg').save(img.data, {
resumable: false,
metadata: {
contentType: 'image/jpeg'
}
})
.then(() => {
return cors(req, res, () => {
res.status(200).send({ "url": bucket.file('foo.jpg').getSignedUrl()});
});
});
});
This is how the image is actually sent in the client:
uploadImage(file, endPoint) {
if (!endPoint) {
throw new Error('Image Endpoint isn`t provided or invalid');
}
const formData = new FormData();
if (file) {
formData.append('file', file);
const req = new HttpRequest('POST', endPoint, formData, {
reportProgress: true
});
return this._http.request(req);
}
else {
throw new Error('Invalid Image');
}
}
I think you're probably looking for the save() method on File in the Admin SDK.
const bucket = admin.storage().bucket()
.file('my-file.jpg').save(blob)
.then(() => { /* ... */ });
You can also get back information about the file this way.
export const uploadImage = async (destination: string, image: Buffer) => {
const file = storage.bucket().file(destination);
await file.save(image, { contentType: yourContentType });
return file.publicUrl();
};
If you get a permission error, go to Firebase storage Rules and add this rule to allow to write in the directory :
service firebase.storage {
match /b/{bucket}/o {
match /blog/{anyPath=**} {
allow read;
allow write;
}
}
}

Resources