Firebase Cloud Functions Call : error : Object message : "Bad Request" status : "INVALID_ARGUMENT" - react-native

first of all i am working with react-native
i wanted to use Custom Claims on my project since it seems to fit the role distribution i expect to use on my app.
after setting my app following the documentation i succeed on creating some functions
but, here's the thing, when i try to call a function by fetching the endpoint i always get this error :
in the console
error
:
Object
message
:
"Bad Request"
status
:
"INVALID_ARGUMENT"
in firebase console
addAdminRoleTest Request body is missing data. { email: 'dev#test.com' }
i couldn't find any answer to that except that i send wrong information from my fetch but i don't understand why.
i even tried to simplify my function only to get the data i sent but i had the exact same error
find below my cloud function & the calling method :
functions/index.js
exports.addAdminRole = functions.https.onCall((data, context) => {
// get user
return admin.auth().getUserByEmail(data.email).then(user => {
// if not already (admin)
if(user.customClaims && (user.customClaims).admin === true) {
return;
}
// add custom claim (admin)
return admin.auth().setCustomUserClaims(user.uid, {
admin: true
});
}).then(() => {
return {
message: `Bravo : ${data.email} fait partie de l'équipe Admins`
}
}).catch(err => {
return err;
});
});
simplified function :
exports.addAdminRoleTest = functions.https.onCall(data => {
console.log("parse data : "+JSON.parse(data));
return (
JSON.parse(data)
);
});
adminScreen.js
function httpAddAdminRole() {
const initRequest = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body:JSON.stringify({
email: 'dev#test.com'
})
}
console.log(initRequest);
return fetch('https://us-central1-my-project.cloudfunctions.net/addAdminRole', initRequest)
.catch(err => console.log(err))
.then(res => res.json())
.then(parsedRes => {
console.log(parsedRes);
});
}

in the end this was mostly json knowledge that missed me
my body should have data included
here's the answer i came to :
functions/index.js
exports.addAdminRole = functions.https.onCall((data, context) => {
const dataParsed = JSON.parse(data);
// get user
return admin.auth().getUserByEmail(dataParsed.email).then(user => {
// if not already (admin)
if(user.customClaims && (user.customClaims).admin === true) {
console.log(dataParsed.email + " is already an Admin");
return;
}
// add custom claim (admin)
return admin.auth().setCustomUserClaims(user.uid, {
admin: true
});
}).then(() => {
return {
message: `Bravo : ${dataParsed.email} is now an Admin`
}
}).catch(err => {
return err;
});
});
adminScreen.js
function httpAddAdminRole(mail) {
const initRequest = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body:JSON.stringify({
data:JSON.stringify({
email: mail
})
})
}
console.log(initRequest);
return fetch('https://us-central1-my-project.cloudfunctions.net/addAdminRole', initRequest)
.catch(err => console.log(err))
.then(res => res.json())
.then(parsedRes => {
console.log(parsedRes);
});
}

Related

react native make accessToken global

im using rn-secure-storage to save authState when using oauth2, then i have class AppHelper to control all network function like below:
import RNSecureStorage, { ACCESSIBLE } from 'rn-secure-storage'
export const accessToken = async() => {
await RNSecureStorage.get("authState").then((value) => {
console.log("authState", value);
return JSON.parse(value).accessToken
}).catch((err) => {
console.log("can not get authState", err);
});
};
export const getData = (url, tag = 'getData') => {
return fetch(url, {
method : 'GET',
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Authorization' : 'Bearer ' + accessToken
}
})
.then((response) => {
console.log(tag, 'Bearer ' + accessToken);
return response.text();
})
.then((json) => {
console.log(tag, json)
return json;
})
.catch((error) => {
handleError(err)
console.log(tag, error, url)
});
}
export const handleError = (error) => {
if (error.response.code == 401){
alert('Unauthorized')
}else{
console.log('network call failed', error)
}
}
im coding native before, so im not familiar with React native syntax. I just want to get access token to apply in network call, but my code show error:
_getMasterInfoApi Bearer function _callee() {
return _regenerator.default.async(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return _regenerator.default.awrap(_rnSecureStorage.default.get("authState").then(function (value) {
console.log("authState", value);
return JSON.parse(value).accessToken;
}).catch(function (err) {
handleError(err);
}));
case 2:
case "end":
return _context.stop();
}
}
}, null, null, null, Promise);
}
Can anyone help? thanks in advance

async / await is not waiting for the response

I am requesting an api using POST method. but after calling the RemoteRequest function using await. It's not waiting for the response return. It directly executes the remaining code in the register.js by this im getting the status = undefind in the console.log.
register.js
const DeviceUniqueId = DeviceInfo.getUniqueId();
const requestBody = { phone: phone, username: username };
const status = await RemoteRequest(URLs.APP_REGISTER,
'POST', DeviceUniqueId, requestBody);
console.log('status====>', status);
this.setState({
loading : false
});
remotereuqest.js
export const RemoteRequest = async (url, method, DeviceUniqueId, requestbody) => {
console.log(url, method, DeviceUniqueId, requestbody);
NetInfo.fetch().then((state) => {
if (state.isConnected) {
fetch(url, {
method : method,
headers : {
Accept : 'application/json',
'Content-Type' : 'application/json',
DEVICEID : DeviceUniqueId,
'Accept-Charset' : 'utf-8'
},
body : JSON.stringify(requestbody)
})
.then((response) => {
console.log('reponse=====>', response);
return response.json();
})
.then((responseData) => {
console.log(responseData);
if (responseData.status == 'OK' && responseData.code == 200) {
return responseData.code;
}
return null;
})
.catch((error) => {
console.log(error);
if (error.message == 'Network request failed') {
showMessage({
floating : true,
message : 'Connection error',
description : 'you have no Internet Connection',
type : 'alert',
backgroundColor : 'red'
});
return null; //503
}
else {
showMessage({
floating : true,
message : 'Internal Server Error',
description : 'please try again after some time',
type : 'alert',
backgroundColor : 'red'
});
throw error;
return null;
}
})
.done();
}
else {
showMessage({
floating : true,
message : 'Connection error',
description : 'you have no Internet Connection',
type : 'alert',
backgroundColor : 'red'
});
return null; //503
}
});
};
You need to await for all promises within the function, otherwise they still get executed asynchronously. Something like this:
await NetInfo.fetch().then( async (state) => {
if (state.isConnected) {
await fetch(url, {
...
}
}
});
When you use .then() the code after it gets executed immediately, so instead you should await the responses and then do the work without .then()-s.
const state = await NetInfo.fetch();
if (state.isConnected) {
const response = fetch(url, ...);
console.log(response);
...
}

Two custom methods/endpoints using loopBack, one works, the other gives a 401

I created two custom endpoints with Loopback.
Account.deleteAllHearingTests = function (req, callback) {
console.log('here comes the req to delete all hearing tests', req);
Account.findById(req.accessToken.userId)
.then(account => {
if (!account) {
throw new Error('cannot find user');
}
return app.models.HearingTest.updateAll({ accountId: account.id }, { isDeleted: new Date() });
})
.then(() => {
callback(null);
})
.catch(error => {
callback(error);
})
}
Account.remoteMethod(
'deleteAllHearingTests', {
http: {
path: '/clearHearingTests',
verb: 'post'
},
accepts: [
{ arg: 'req', type: 'object', http: { source: 'req' } }
],
returns: {}
}
);
the second one looks like this.
Account.deleteSingleHearingTest = function (req, callback) {
// console.log('accounts.js: deleteSingleHearingTest: are we being reached????', req)
Account.findById(req.accessToken.userId)
.then(account => {
if (!account) {
throw new Error('Cannot find user');
}
console.log('account.js: deleteSingleHearingTest: req.body.hearingTestId N: ', req.body.hearingTestId);
return app.models.HearingTest.updateAll({ accountId: account.id, id: req.body.hearingTestId }, { isDeleted: new Date() });
})
.then(() => {
callback(null);
})
.catch(error => {
callback(error);
});
}
Account.remoteMethod(
'deleteSingleHearingTest', {
http: {
path: '/deleteSingleHearingTest',
verb: 'post'
},
accepts: [
{ arg: 'req', type: 'object', description: 'removes a single hearing test', http: { source: 'req' } }
],
description: 'this is the end point for a single delete',
returns: {}
}
);
};
The first custom method returns a 401 status response when I make the initial fetch. The second returns a 200.
Inside my actions file the first method is called with something that looks like this:
export function deleteAllHearingTests() {
return (dispatch, getState) => {
let state = getState();
if (!state.user || !state.user.accessToken || !state.user.accessToken.id || !state.user.accessToken.userId) {
console.debug('deleteAllHearingTests', state.user);
// TODO: ERROR
return;
}
fetch(SERVERCONFIG.BASEURL + '/api/Accounts/clearHearingTests?access_token=' + state.user.accessToken.id, {
method: 'POST',
headers: SERVERCONFIG.HEADERS
})
.then(response => {
console.log('here is your response', response);
if (response.status !== 200) {
throw new Error('Something is wrong');
}
return response.json()
})
the second method is called with
export const deleteSingleHearingTest = (hearingTestNumber) => {
return (dispatch, getState) => {
let state = getState();
if (!state.user || !state.user.accessToken || !state.user.accessToken.id || !state.user.accessToken.userId) {
console.debug('writeTestResult', state.user);
// TODO: ERROR
return;
}
console.log('single delete ', SERVERCONFIG.BASEURL + '/api/Accounts/deleteSingleHearingTest?access_token=' + state.user.accessToken.id)
fetch(SERVERCONFIG.BASEURL + '/api/Accounts/deleteSingleHearingTest?access_token=' + state.user.accessToken.id, {
method: 'POST',
headers: SERVERCONFIG.HEADERS,
body: JSON.stringify({ "hearingTestId": hearingTestNumber })
})
.then(response => {
console.log('getting response from initial fetch inside deleteSingleReqport', response);
They are nearly identical, however, one works..the other fails. What are some possible causes for the 401?
Did you try to call those methods with external tool like a postman, so you would exactly know if you don't miss access_token or something else? Also, when you compare code from one function and another, you can see that you are colling the updateAll with different arguments. It's hard to say without original code, but maybe the issue is there? Compare below:
return app.models.HearingTest.updateAll(
{ accountId: account.id },
{ isDeleted: new Date() });
return app.models.HearingTest.updateAll(
{ accountId: account.id, id: req.body.hearingTestId },
{ isDeleted: new Date() });
Additionally, in fetch method they are also diffferences, you are missing in one case the below:
body: JSON.stringify({ "hearingTestId": hearingTestNumber })
What you could also do to debug and to provide more data is to run server in debug mode by calling:
export DEBUG=*; npm start

AsyncStorage data changing upon app restart

I'm currently calling a JSON api to set an auth token which I'll just be storing in the AsyncStorage to persist between app life so a user doesn't have to log in every single time.
I'm currently setting that token like so:
fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(this.state)
})
.then( resp => {
return resp.json();
})
.then( async (data) => {
if ('error' in data) {
this.setState({
error: data.error,
password: ''
})
this.secondTextInput.focus();
}
if ('access_token' in data) {
try {
await AsyncStorage.setItem('access_token', data.access_token);
} catch (error) {
return error;
}
this.props.navigation.navigate('Main');
}
})
.catch(
error => {
console.error(error)
return error;
}
);
If I then call AsyncStorage.getItem('access_token') After killing the app or reloading it I'm winding up with this output:
{
"_40":0,
"_65":0,
"_55":null,
"_72":null
}
If I then call AsyncStorage.getItem('access_token') Before killing the app or reloading it I'm winding up with the correct access token. I've double checked the code and I'm not using AsyncStorage.setItem('access_token') anywhere else.
This is how I'm retrieving my token:
componentDidMount() {
console.warn('Mounting');
try {
let token = AsyncStorage.getItem('access_token');
console.warn(token);
if(token !== null) {
console.error(token);
}
} catch (error) {}
AsyncStorage.getItem() is a asynchronous action just like setItem(), so you need to wait until the Promise has been resolved before logging.
Edit
Tip: if you see some strange output like that it is always related to a Promise which is not yet resolved or rejected
I've solved my issue by using #dentemm's recommendation of creating an async function.
async _getToken() {
try {
var token = await AsyncStorage.getItem('access_token');
return token;
} catch(e) {
console.error(e);
}
}
componentDidMount() {
let token = null;
this._getToken()
.then( rsp => {
fetch(global.url + '/api/auth/refresh', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + rsp
}
})
.then(rsp => {
return rsp.json();
})
.then(data => {
if('access_token' in data) {
try {
AsyncStorage.setItem('access_token', data.access_token);
} catch (error) {
return error;
}
this.props.navigation.navigate('Main');
}
})
.catch( error => {
return error;
})
});
}
This way I can get my token from the storage then run my refresh function to get an updated token to use for future requests.

Losing connection during a fetch causes crash in React-Native app

If the internet connection is lost during a fetch in my react-native app I get Network request failed and the app crashes.
updateClientData() {
var cachedData = null;
AsyncStorage.getItem('cachedData').then((cachedDataString) => {
cachedData = JSON.parse(cachedDataString);
})
.done(() => {
if (cachedData) {
const base64 = require('base-64');
return fetch('https://...data.json', {
method: 'get',
headers: {
'Authorization': 'Basic '+base64.encode("..."),
}
})
.then( (response) => {
// never called:
return response.json();
})
.catch( (error) => {
//Shouldn't this catch network errors? It never gets called.
console.log('caught network error');
})
.then( (responseJSON) => {
//do something with the JSON
})
}
});
},
I would love to be able to handle this gracefully rather than have it crash. Any ideas?
For some reason, moving the AsyncStorage call out of this function made it work fine. I didn't actually need it until I had the result of the fetch anyway, so I moved it.
This works now:
updateClientData() {
const base64 = require('base-64');
return fetch(clientListURL, {
method: 'get',
headers: {
'Authorization': 'Basic '+base64.encode("..."),
}
})
.then( (response) => {
return response.json();
})
.catch( (error) => {
console.log('error...')
})
.then( (responseJSON) => {
// now do something with the JSON and the data from Async Storage
}
},