I have axios request in my vue application:
async get_banner(id:number) : Promise<any> {
return global.axios.get(`${process.env.VUE_APP_DOMAIN}/banners/${id}`)
}
it works while banner/${id} response exits, but I have situation when I should disable banner in my admin panel so api endpoint becomes empty. (not exits) so I am getting Request failed with status code 404 in my vue app console.
question is how to prevent error and know if url exits or not? what is best practice to do this?
You can't tell whether an API exists or not without trying it (or relying on another API to get status of the former API)
It's usually just a manner of handling the response properly. Usually this would look something like this...
getTheBanner(id){
this.errorMessage = null; // reset message on request init
get_banner(id)
.then(r => {
// handle success
this.results = r;
})
.catch(e => {
// handle error
if (e.status === 404){
// set error message
this.errorMessage = "Invalid banner Id";
}
})
}
then in your template you could have something like this
<div v-if="errorMessage" class="alert danger">{errorMessage}</div>
Explaination:
Yes, you're absolutely right. This is the default behavior of strapi. Whenever the response is empty it throws a 404 error. This is basically because the findOne method in service returns null to the controller and when the controller sends this to the boom module it returns a 404 Not Found error to the front end.
Solution:
Just override the find one method in the controller to return an empty object {} when the response is null.
Implementation
// Path - yourproject/api/banner/controllers/banner.js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
/**
* Retrieve a record.
*
* #return {Object}
*/
async findOne(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.restaurant.findOne({ id });
// in case no entity is found, just return emtpy object here.
if(!entity) return {};
return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
Side Note:
There's no need to make any changes to the browser side axios implementation. You should always handle such cases in controller rather the client side.
Reference:
Backend Customizations
Related
This is my first Google Action project. I have a simple slot after the invocation. User enters the value on prompt and slot invokes the webhook and make a call to API using the user input. All works fine. However the webhook returns to users even before the API call finish processing and returns the value (line 1 conv.add). I do see in the logs that everything from API is logged fine after the webhook returns to user. Below is the code I am using. I am using inline editor. What am I missing? Thanks for help in advance.
const { conversation } = require('#assistant/conversation');
const functions = require('firebase-functions');
var https = require('https');
const fetch = require('node-fetch');
const app = conversation({debug: true});
app.handle('SearchData', conv => {
const body = JSON.stringify({
val: "this is my body"
});
// prepare the header
var postheaders = {
'Content-Type' : 'application/json',
'Auth' : 'MyAuthCreds'
};
fetch('https://host.domain.com/data', {
method: 'post',
body: body,
headers: postheaders,
})
.then(res => res.json())
.then(d => {
console.log(d);
var profile = d;//JSON.parse(d);
console.log(d.entries);
console.log("Length: "+ d.entries.length);
if(d.entries.length > 0)
{
console.log("Data found");
conv.add("Data found"); //line 1
}
else
{
console.log("no data found");
conv.add("no data found"); //line 1
}
})
.catch(function (err) {
// POST failed...
console.log(err);
});
});
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
Your issue is that your handler is making API calls which are asynchronous, but the Assistant Conversation library doesn't know that you're doing so. So as soon as the handler finishes, it tries to send back a response, but your asynchronous responses (the stuff in the then() blocks) haven't executed yet.
To address this, you need to return a Promise object so the library knows to wait till the Promise is fulfilled before it returns.
Fortunately, in your case, this should be pretty straightforward. fetch and all the .then() blocks return a Promise. So all you need to do is add a return statement in front of the call to fetch. So something like this:
return fetch('https://host.domain.com/data', {
I'm calling a Rest API that returns at least 2 success status codes .
A normal 200 OK and a 202 Accepted status code.
Both return a Content in the body.
If I execute in postman my calls I might get something like
Status code: 202 Accepted. With Body "Queued" or some other values
or
Status code: 200 OK. With Body "ValueOfSomeToken"
Making the call with axios in my nuxt app:
this.$axios.$get('/Controller/?id=1')
.then((response)=>{
if(response=='Queued'){
//Do something
}
else if (response=='Expired'){
//Do something
}
else{
//Do something
}
})
.catch((error)=>{
console.log(error);
});
..works, but I actually would like to get the status code (because 202 has other values for the body responses)
I have no idea how to read the status codes.
I tried using (response,code) =>... but code is then nothing.
You can use non $-prefixed functions like this.$axios.get() instead of this.$axios.$get() to get the full response
// Normal usage with axios
let { data } = await $axios.get('...'));
// Fetch Style
let data = await $axios.$get('...');
(source)
You can extract the status codes from the response object in axios
if you print the response object (as shown in below image) you can see the all the objects inside the response object. One among them is status object
response.status will give you the status code that is sent from the server
axios.get("http://localhost:3000/testing").then((response)=>{
console.log("response ",response);
if(response.status == 200){
//do something
}
else if(response.status == 202){
//do something
}
else if(response.status == 301){
//do something
}
}).catch((err)=>{
console.log("err11 ",err);
})
In the server side, you can explicitly send any status code using res.status() method, for more details refer this documentation
app.get('/testing',(req, res)=> {
res.status(202).send({"res" : "hi"});
});
Update:
By default, #nuxtjs/axios returns response.data in the .then((response))
The $axios.onResponse event will have access to the complete response object.
You need to setup an interceptor to intercept the $axios.onResponse event and modify the response object
Under plugin directory create a plugin, plugin/axios.js
Update the plugins section plugins : ['~/plugins/axios']
in nuxt.config.js
export default function ({ $axios, redirect }) {
$axios.onResponse(res=>{
console.log("onResponse ", res);
res.data.status = res.status;
return res;
})
}
In the res object in this interceptor you will have the all the values (as it is shown in my first screenshot). But this res object is not returned as it is, only res.data is returned to our program.
We can update contents inside res.data and then return the res object as shown in my program res.data.status = res.status; .
Now when axios returns res.data we will have access to res.data.status value in response object in the .then((response)) promise
You can access the status using response.status inside this.$axios
this.$axios.$get("url").then((response) =>{
console.log("status ",response.status);
}).catch((err) => {
console.log("res err ",err);
});
In my routes.js file, I have defined a route like this:
'PUT /api/v1/entrance/login': { action: 'entrance/login' },
'POST /api/v1/entrance/signup': { action: 'entrance/signup' },
'POST /api/v1/entrance/send-password-recovery-email': { action: 'entrance/send-password-recovery-email' },
'POST /api/v1/entrance/update-password-and-login': { action: 'entrance/update-password-and-login' },
'POST /api/v1/deliver-contact-form-message': { action: 'deliver-contact-form-message' },
'POST /api/v1/getEventsForICalUrl': 'IcalController.getEvents',
I have just used the default generated code and added the last route for getEventsForIcalUrl.
I created an IcalController inside the controllers directory and it has an action getEvents which simply renders a json like this:
module.exports = {
/**
* `IcalController.getEvents()`
*/
getEvents: async function (req, res) {
console.log("hit here");
let events = [];
for (var i = 0; i < 20; i++) {
events.push({foo: "bar" + i});
}
return res.json({
events: events
});
}
};
My problem is that whenever i try to access this controller from the client side, it gives 403 forbidden error.
When I change the route from POST to GET, it works as expected(ofc I am using proper GET/POST request from client end for the route).
Not sure what is breaking.
I also checked the logs. Its printing "hit here" when I use the GET.
In my policy file looks like this(as it was generated. I did not change it):
module.exports.policies = {
'*': 'is-logged-in',
// Bypass the `is-logged-in` policy for:
'entrance/*': true,
'account/logout': true,
'view-homepage-or-redirect': true,
'deliver-contact-form-message': true,
};
And my "is-logged-in" policy file is this:
module.exports = async function (req, res, proceed) {
// If `req.me` is set, then we know that this request originated
// from a logged-in user. So we can safely proceed to the next policy--
// or, if this is the last policy, the relevant action.
// > For more about where `req.me` comes from, check out this app's
// > custom hook (`api/hooks/custom/index.js`).
console.log("req.me=" + req.me);
if (req.me) {
return proceed();
}
//--•
// Otherwise, this request did not come from a logged-in user.
return res.unauthorized();
};
I just put that console.log in this file. others are as they were by default generation from sails new.
The logs show that using POST, this one does not get hit either.(I dont see the "req.me=".. in console.logs.) But this one gets hit when Using GET.
It seems that the route is not working for POST requests. I wonder if its an error in sails js itself or I am doing something wrong.
There are at least two ways how to solve this.
Probably you are using csrf. If you do, your config probably includes this:
module.exports.security = { // With Sails <1.01 this probably is in another file
csrf: true
};
And (if you are using sails v1.01), you should make this route:
'GET /csrfToken': { action: 'security/grant-csrf-token' },
So, to get data on your frontend, you just:
function get_some_records(object_with_data) {
$.get("/csrfToken", function (data, jwres) {
if (jwres != 'success') { return false; }
msg = {
some_data: object_with_data,
_csrf: data._csrf
};
$.post("get_some_records", msg, function(data, status){});
});
}
But if you are using some background jobs, Sails wont give you csrf easily(there is some way probably). So , you just create a route like this:
'post /get_some_records': {
action: 'home/get_some_records',
csrf: false
}
You probably use a Rest client to test (like Postman). Simply disable the csrf protection. in the /config/security.js file
csrf: false
Here in the blue print says, API gateway will respond with 401: Unauthorized.
I wrote the same raise Exception('Unauthorized') in my lambda and was able to test it from Lambda Console. But in POSTMAN, I'm receiving status 500
with body:
{
message: null`
}
I want to add custom error messages such as "Invalid signature", "TokenExpired", etc., Any documentation or guidance would be appreciated.
This is totally possible but the docs are so bad and confusing.
Here's how you do it:
There is an object called $context.authorizer that you have access to in your gateway responses template. You can read more about it here: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
Here is an examample of populating this authorizer object from your authorizer lambda like so:
// A simple TOKEN authorizer example to demonstrate how to use an authorization token
// to allow or deny a request. In this example, the caller named 'user' is allowed to invoke
// a request if the client-supplied token value is 'allow'. The caller is not allowed to invoke
// the request if the token value is 'deny'. If the token value is 'Unauthorized', the function
// returns the 'Unauthorized' error with an HTTP status code of 401. For any other token value,
// the authorizer returns an 'Invalid token' error.
exports.handler = function(event, context, callback) {
var token = event.authorizationToken;
switch (token.toLowerCase()) {
case 'allow':
callback(null, generatePolicy('user', 'Allow', event.methodArn));
break;
case 'deny':
callback(null, generatePolicy('user', 'Deny', event.methodArn));
break;
case 'unauthorized':
callback("Unauthorized"); // Return a 401 Unauthorized response
break;
default:
callback("Error: Invalid token");
}
};
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = {
"stringKey": "stringval custom anything can go here",
"numberKey": 123,
"booleanKey": true,
};
return authResponse;
}
They key here is adding this part:
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = {
"stringKey": "stringval custom anything can go here",
"numberKey": 123,
"booleanKey": true,
};
This will become available on $context.authorizer
I then set the body mapping template in gateway responses tab like this:
{"message":"$context.authorizer.stringKey"}
NOTE: it must be quoted!
finally - after sending a request in postman with Authorization token set to deny I now get back a payload from postman that looks like this:
{
"message": "stringval custom anything can go here"
}
I used #maxwell solution, using custom resource ResponseTemplates. For deny response see below:
{
"success":false,
"message":"Custom Deny Message"
}
You can check this out here: https://github.com/SeptiyanAndika/serverless-custom-authorizer
I'm not sure what is causing the 500 message: null response. Possibly misconfiguration of the Lambda function permissions.
To customize the Unauthorized error response, you'll set up a Gateway Response for the UNAUTHORIZED error type. You can configure response headers and payload here.
Maxwell is mostly correct. I tried his implementation and noticed that his message should go from :
{"message":"$context.authorizer.stringKey"}
to
{"message":"$context.authorizer.context.stringKey"}
As noted by Connor far as I can see, the answer to the specific question - which mentions 401 related errors - is NO.
You can produce a generic 401 Unauthorized but you cannot alter the error message.
That is you can customise the 403 Forbidden (DENY) messages but not the 401's.
Note that I've used the NodeJS Lambda custom authorizers but not the Python version referenced in the question.
With my testing what i observed is , You cannot customize message when you throw exception from the lambda,
You can have customized messages when you return DENY Policy message from the authorizer
Here is how i am returning custom message when i DENY from the Authorizer, it in the detail field of
authResponse.context returned from custom Authorizer
you can also update status code to 401 instead of 403 .
This can be easily achieved by using the context.fail() function.
Example:
const customAuthorizer: Handler = (event, context: Context, callback: Callback) => {
authenticate(event)
.then((res) => {
// result should be as described in AWS docs
callback(null, res);
})
.catch((err) => {
context.fail("Unauthorized");
});
}
This will return a 401 response with following body.
{
"message": "Unauthorized"
}
This can also be achieved by throwing an error:
throw new Error('Unauthorized');
I am implementing REST API on ZF2. Now I need to check authorization token on Module.php and return with error if authorization failed. But I did not know how to return response from Module.php.
I wrote code to check authorization in DISPATCH Event of onBootstrap. Now how to return error from Module.php without accessing controllers if authorization failed. As only exit function/call make possible to return without accessing controller. But In that case I did not getting any response. Using json_encode(array) is not look like a standard as I am already enabled ViewJsonStrategy and using JsonModel in controllers.
You can shortcircuit the event by having your listener return a response, eg...
public function onBootstrap(EventInterface $e)
{
$eventManager = $e->getApplication()->getEventManager();
// attach dispatch listener
$eventManager->attach('dispatch', function($e) {
// do your auth checks...
if (!$allowed) {
// get response from event
$response = $e->getResponse();
// set status 403 (forbidden)
$response->setStatusCode(403);
// shortcircuit by returning response
return $response;
}
});
}