I need to encode my faunadb instance's id because I use it in an URL of this type
/mission/(id number)/team
I create instance with this:
/* code from functions/todos-create.js */
import faunadb from 'faunadb' /* Import faunaDB sdk */
/* configure faunaDB Client with our secret */
const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SECRET
})
/* export our lambda function as named "handler" export */
exports.handler = (event, context, callback) => {
/* parse the string body into a useable JS object */
const eventBody = JSON.stringify(event.body)
const data = JSON.parse(eventBody)
const mission = {
data: JSON.parse(data)
}
// {"title":"What I had for breakfast ..","completed":true}
/* construct the fauna query */
return client.query(q.Create(q.Ref("classes/missions"), mission))
.then((response) => {
console.log("success", response)
/* Success! return the response with statusCode 200 */
return callback(null, {
statusCode: 200,
body: JSON.stringify(response)
})
}).catch((error) => {
console.log("error", error)
/* Error! return the error with statusCode 400 */
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
})
})
}
I call this lambda with a function in a service:
createMission(data) {
return fetch('/.netlify/functions/mission-create', {
body: JSON.stringify(data),
method: 'POST'
}).then(response => {
return response.json();
});
}
and then I load this at the init of my page with adress '/mission/(id number)/team' :
this._missionService.readById(this.route.snapshot.params.missionId)
with this lambda by a service again:
import faunadb from 'faunadb'
const q = faunadb.query
const client = new faunadb.Client({
secret: process.env.FAUNADB_SECRET
})
exports.handler = (event, context, callback) => {
const id = event.path.match(/([^\/]*)\/*$/)[0];
console.log(`Function 'mission-read' invoked. Read id: ${id}`)
return client.query(q.Get(q.Ref(q.Class("missions"), id)))
.then((response) => {
console.log("success", response)
return callback(null, {
statusCode: 200,
body: JSON.stringify(response)
})
}).catch((error) => {
console.log("error", error)
return callback(null, {
statusCode: 400,
body: JSON.stringify(error)
})
})
}
Actually this is not secure because url handling with id number is possible to access all my missions.
I want to encode in base64 this id, but I don't know how to do this, I begin in dev and I thought first encrypt id in service and decrypt it in service but someone said me front is not secure, then I want to use base64 in back.
Thanks for advice !
The solution to this problem is definitely not to obfuscate the id with base64 (which will protect nothing), but instead you need to associate a user id with your sensitive information in the database.
You then can have them authenticate against your auth/data service to establish identity. Then the service will only serve them records that are associated to them.
The tricky part of this solution is writing an auth protocol for your lambda services.
Related
I have several endpoints in my APIrest and a lot of them pass through the validations of the JWT token.
When I try to update a user with my frontend app always tells me that i am not authorized.
I tried to update the user with postman and everything works fine with it.
With the same Token i can do everything except update a user in my frontend app.
This is the request from my frontend app:
export function updateInfoApi(data){
const url = `${API_HOST}/user`;
const params = {
method: "PUT",
headers:{
Authorization: `Bearer${getTokenApi()}`
},
body: data
}
return fetch(url, params)
.then(response => {
return response;
}).catch(err => {
return err;
});
}
I have several requests in my frontend app with the same variables, and they work fine:
export function addTootApi(message){
const url = `${API_HOST}/toots`
const data = {
message
}
const params ={
method:"POST",
headers:{
"Content-Type":"application/json",
Authorization: `Bearer${getTokenApi()}`
},
body: JSON.stringify(data),
};
return fetch(url, params).then(response => {
if(response.status >=200 && response.status<300){
return {code:response.status, message:"Toot enviado."}
}
return {code:500, message:"Error del servidor."}
}).catch(err=> {return err;});
}
Here is where I process the token... but like i said this works fine cause always works... only donst work when i make the request to update user with my fronted app.
/*ProcessToken process the token*/
func ProcessToken(tk string) (*models.Claim, bool, string, error) {
miClave := []byte("crazyforsnowboards")
claims := &models.Claim{}
splitToken := strings.Split(tk, "Bearer")
if len(splitToken) != 2 {
return claims, false, string(""), errors.New("Invalid format of token")
}
tk = strings.TrimSpace(splitToken[1])
tkn, err := jwt.ParseWithClaims(tk, claims, func(token *jwt.Token) (interface{}, error) {
return miClave, nil
})
if err == nil {
_, found, _ := database.UserAlreadyExist(claims.Email)
if found == true {
Email = claims.Email
UserID = claims.ID.Hex()
}
return claims, found, UserID, nil
}
if !tkn.Valid {
return claims, false, string(""), errors.New("Invalid Token")
}
return claims, false, string(""), err
}
Thanks a lot guys!
Never forget the JSON.stringify
Thanks alot guys for helping this sleepy guy :)
I am trying to pass a query string into my serverless function but it keeps returning an empty object.
search = (searchTerm) => {
// let url = `${URL}${searchTerm}`;
return fetch(`/.netlify/functions/token-hider?search=${searchTerm}`)
.then((response) => response.json())
.then((result) => {
console.log(result.results);
return results;
});
form.addEventListener("submit", (e) => {
e.preventDefault();
let searchTerm = input.value;
search(searchTerm);
});
const axios = require("axios");
const qs = require("qs");
exports.handler = async function (event, context) {
// apply our function to the queryStringParameters and assign it to a variable
const API_PARAMS = qs.stringify(event.queryStringParameters.search);
console.log(event);
// const API_PARAMS = qs.stringify(event.queryStringParameters);
console.log("API_PARAMS", API_PARAMS);
// Get env var values defined in our Netlify site UI
// TODO: customize your URL and API keys set in the Netlify Dashboard
// this is secret too, your frontend won't see this
const { KEY } = process.env;
const URL = `https://api.unsplash.com/search/photos?page=1&per_page=50&client_id=${KEY}&query=${API_PARAMS}`;
console.log("Constructed URL is ...", URL);
try {
const { data } = await axios.get(URL);
// refer to axios docs for other methods if you need them
// for example if you want to POST data:
// axios.post('/user', { firstName: 'Fred' })
return {
statusCode: 200,
body: JSON.stringify(data),
};
} catch (error) {
const { status, statusText, headers, data } = error.response;
return {
statusCode: error.response.status,
body: JSON.stringify({ status, statusText, headers, data }),
};
}
};
it works when i hard code the query string, and i can console log the search term and it is defined.
Since Netlify redirect mechanism is not able to provide you the data of which rule it matched, you could try to match the original request in your function to determine what it should do.
Hope this helps you solve your specific issue!
Here is the reference
I am struggling to execute my first graphQl query with axios.
The app stack is Gatsby on the front-end with Symfony's API-Platform on the back end.
I did check a few the other similar questions at SO and blog posts but no luck.
The thing is that the query works on graphiQl AND it also works if I attempt to execute with fetch API.
This code is within sagas.js
Here's the
const api = (data) => {
return axios({
method: 'POST',
url: API_ROOT_GRAPHQL,
data,
headers: { 'Content-Type': 'application/json' },
}).then((response) => {
console.log('response axios', response);
return response.data;
});
};
function* getUserProfile(action) {
const userId = `"/api/users/${action.payload.userId}"`;
const queryQl = {
query: `{
user(id: ${userId}) {
id
username
name
email
roles
}
}`,
};
try {
yield put({
type: GET_USER_PROFILE_INIT,
});
const data = yield call(api, queryQl);
console.log('data user profile', data);
yield put({
type: GET_USER_PROFILE_OK,
data: data.user,
});
} catch (error) {
As you can see below is throws error: Cannot return null for non nullable field
Thing is that the below fetch query works perfectly - as does graphiql - so I am wondering this must be some config issue on my axios request.
I also checked that the data posted to the API with the same with both axios et fetch, as below from Firefox dev tools Network-> params:
query { user(id: "/api/users/1") { id username name email roles } }
const apiQl = (queryQl) => {
return fetch('http://localhost:8000/api/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(queryQl),
}).then((response) => {
return response.json();
}).then((response) => {
console.log('rsponse fetch', response);
return response.data;
});
};
EDIT: request header for axios
As both graphiQl and fetch() were working and axios was not, I was quick to point fingers at the latter. As it happens axios was the only one working properly.
As #coreyward suggested, there were restrictions on the server side, so that some User properties should only be available for query by their user owner, and username was not among them.
For those familiar with Symfony's API-Platform:
As an example, the email property was available, as it included the get-owner annotation.
/**
* #ORM\Column(type="string", length=180)
* #Groups({"post", "put", "get-admin", "get-owner"})
* #Assert\NotBlank(groups={"post"})
* #Assert\Email(groups={"post", "put"})
* #Assert\Length(min=6, max=180, groups={"post", "put"})
*/
private $email;
But username did not include the get-owner group annotation:
/**
* #ORM\Column(type="string", length=180)
* #Groups({"post", "get-ad-with-user"})
* #Assert\NotBlank(groups={"post"})
* #Assert\Length(min=6, max=50, groups={"post"})
*/
private $username;
So the fix was just to add that annotation to the username property as so:
* #Groups({"post", "get-ad-with-user", "get-owner"})
It is unclear to me at this time on why did fetch() and graphiQl were able to fetch the data.
In my VUE components, I use this async method to fetch data from API:
Components:
methods: {
async fetch() {
// console.log("##### WAIT ####");
const { data } = await staffRepository.getItems(this.teamId)
// console.log("##### END WAIT ####");
this.staffs = data
},
},
As you can see I use a custom repository to have a single axios code, this repository is imported in my previous component.
staffRepository:
export default {
getItems(nationId) {
return Repository.get(`page/${nationId}`)
},
}
And finally the main repository having the axios code:
Repository:
import axios from 'axios/index'
const baseDomain = 'https://my end point'
const baseURL = `${baseDomain}`
...
const headers = {
'X-CSRF-TOKEN': token,
// 'Access-Control-Allow-Origin': '*', // IF you ADD it add 'allowedHeaders' to ai server config/cors.php
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
Authorization: `Bearer ${jwtoken}`,
}
export default axios.create({
baseURL,
withCredentials: withCredentials,
headers: headers,
})
This code works very nice when the jwtoken is a valid and NOT EXIPRED token.
The problem is when the token is expired or not found and my laravel 5.8 API returns the status code 401 (or other).
GET https://api.endpoint 401 (Unauthorized)
A good solution could catch the status code in staffRepository, the one having the get method.
MySolution: (not working)
getItems(nationId) {
return Repository.get(`page/${nationId}`)
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.response.status) // <-- it works!
})
},
This could be nice because in error case the error in console is 401
But I can't use this solution because I have 2 nested promises: this one and the async fetch() into the component.
How can I fix it still using my repository environment?
I would suggest using the returned promise in your component, to make things more explicit:
methods: {
fetch() {
let data = null
staffRepository
.getItems(this.teamId)
.then(data => {
// do something with data
this.staffs = data
})
.catch(e => {
// do something with error, or tell the user
})
},
},
Edit - this will work perfectly fine, as your method in Repository will return a promise by default if you are using axios.
Try this: API code, where HTTP is an axios instance
export const get = (path: string): Promise<any> => {
return new Promise((resolve, reject) => {
HTTP.get(`${path}`)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(handleError(error));
});
});
};
// ***** Handle errors *****/
export function handleError(error) {
if (error.response) {
const status = error.response.status;
switch (status) {
case 400:
// do something
break;
case 401:
// do something, maybe log user out
break;
case 403:
break;
case 500:
// server error...
break;
default:
// handle normal errors here
}
}
return error; // Return the error message, or whatever you want to your components/vue files
}
The best practice solution is to use axios's interceptors:
import axios from "axios";
import Cookies from "js-cookie";
export default (options = {}) => {
let client = options.client || axios.create({ baseURL: process.env.baseUrl });
let token = options.token || Cookies.get("token");
let refreshToken = options.refreshToken || Cookies.get("refreshToken");
let refreshRequest = null;
client.interceptors.request.use(
config => {
if (!token) {
return config;
}
const newConfig = {
headers: {},
...config
};
newConfig.headers.Authorization = `Bearer ${token}`;
return newConfig;
},
e => Promise.reject(e)
);
client.interceptors.response.use(
r => r,
async error => {
if (
!refreshToken ||
error.response.status !== 401 ||
error.config.retry
) {
throw error;
}
if (!refreshRequest) {
refreshRequest = client.post("/auth/refresh", {
refreshToken
});
}
const { data } = await refreshRequest;
const { token: _token, refreshToken: _refreshToken } = data.content;
token = _token;
Cookies.set("token", token);
refreshRequest = _refreshToken;
Cookies.set("refreshToken", _refreshToken);
const newRequest = {
...error.config,
retry: true
};
return client(newRequest);
}
);
return client;
};
Take a look at client.interceptors.response.use. Also you should have a refreshToken. We are intercepting 401 response and sending post request to refresh our token, then waiting for a new fresh token and resending our previous request. It's very elegant and tested solution that fits my company needs, and probably will fit your needs too.
To send request use:
import api from './api'
async function me() {
try {
const res = await api().get('/auth/me')
// api().post('/auth/login', body) <--- POST
if (res.status === 200) { alert('success') }
} catch(e) {
// do whatever you want with the error
}
}
Refresh token: The refresh token is used to generate a new access
token. Typically, if the access token has an expiration date, once it
expires, the user would have to authenticate again to obtain an access
token. With refresh token, this step can be skipped and with a request
to the API get a new access token that allows the user to continue
accessing the application resources.
I'm trying to create a firebase function that makes a HTTP POST request whenever a new document is created.
This is my code:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
const request = require("request");
exports.sendMessage = functions.firestore.document('comms/{comms}').onCreate((snap, context) => {
const newValue = snap.data();
if (newValue) {
//const email = newValue.email;
const msg = newValue.msg;
return request({
uri: "url",
method: 'POST',
body: msg,
json: true,
resolveWithFullResponse: true
}).then((response: { statusCode: number; }) => {
if (response.statusCode >= 400) {
throw new Error(`HTTP Error: ${response.statusCode}`);
}
console.log('SUCCESS! Posted', msg);
});
}
return Promise
});
Error received:
TypeError: request(...).then is not a function
at exports.sendMessage.functions.firestore.document.onCreate (/srv/lib/index.js:25:12)
at cloudFunction (/srv/node_modules/firebase-functions/lib/cloud-functions.js:127:23)
at /worker/worker.js:825:24
at
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
request supports callback interfaces natively but does not return a promise, which is what you must do within a Cloud Function.
This is explained in the official Firebase video series here: https://firebase.google.com/docs/functions/video-series/. In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).
You could use request-promise (https://github.com/request/request-promise) and the rp() method which "returns a regular Promises/A+ compliant promise". You would then adapt your code as follows:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
const rp = require('request-promise');
exports.sendMessage = functions.firestore.document('comms/{comms}').onCreate((snap, context) => {
const newValue = snap.data();
if (newValue) {
const msg = newValue.msg;
var options = {
method: 'POST',
uri: '....',
body: msg,
json: true // Automatically stringifies the body to JSON
};
return rp(options)
.then(parsedBody => {
// POST succeeded...
console.log('SUCCESS! Posted', msg);
return null;
})
.catch(err => {
// POST failed...
console.log(err);
return null;
});
} else {
return null;
}
});
request module doesn't return a Promise instead try using a callback function for response.
return request({
uri: "url",
method: 'POST',
body: msg,
json: true,
resolveWithFullResponse: true
}, function (error, response, body) {
})
As in the documentation already mention you need to pass the callback to your request
var request = require('request');
request('http://www.google.com', function (error, response, body) {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
});
If you want to chain your request you can use pipe
request
.get('url/img.png')
.on('response', function(response) {
console.log(response.statusCode) // 200
console.log(response.headers['content-type']) // 'image/png'
})
.pipe(request.put('url'))
If you want to use promise you can use request-promise
var rp = require('request-promise');
rp('http://www.google.com')
.then(function (htmlString) {
// Process html...
})
.catch(function (err) {
// Crawling failed...
});
The request module work on callbacks only, If you want to make Promisify you need to do like this
const request = require('request');
const webService = {};
webService.callApi = (url, bodyObj, method) => {
return new Promise((resolve, reject) => {
const options = {
method: method || 'POST',
url: url,
headers: {
'Content-Type': 'application/json',
},
body: bodyObj,
json: true,
};
// Error Handler
const errorMessge = { code: 500, error: 'INTERNAL_SERVER_ERROR' };
request(options, (error, response, resBody) => {
if (error) {
return reject(errorMessge);
} else if (response.statusCode !== 200) {
return reject(errorMessge);
}
return resolve(resBody);
});
});
};
module.exports = webService;