Customize Strapi default permission error return - javascript

Is there a way to edit the default error return by Strapi for permission / token error?
For example, in roles & permissions under Public role, I uncheck the route for send-email-confirmation. If I use postman and try doing localhost:1337/auth/send-email-confirmation, I would get such error return
{
"statusCode": 403,
"error": "Forbidden",
"message": "Forbidden"
}
I believe this is the default middleware / policy I know where I can get the default send-email-confirmation controller and edit it, but that is only if Roles & Permissions are enabled / checked inside public role.
Same as if a route requires headers of Authorization token but if it is not provided, a default error will be given again which I am not able to find where to customize it.
I don't seem to find it inside strapi documentation or maybe I am using the wrong key words to search.
Thanks in advance for any suggestions + advices.

auth token - permissions validation and errors are managed in this file - https://github.com/strapi/strapi/blob/0c6d39297f6f8a4f983e22fb48256b42da2a8605/packages/strapi-plugin-users-permissions/config/policies/permissions.js#L15
If you want to update this file, you will have to follow the customization concept - https://strapi.io/documentation/3.0.0-beta.x/concepts/customization.html#plugin-extensions
With this, you will be able to change the error message.

Create custom error response files
In folder config/functions/responses/, create corresponding files with relevant filename matching the error code. Inside you can inspect the event and return a custom response. This works for me using Strapi 3.6.8. I'm not sure of latest Strapi. Here's an older doc page with error codes as reference.
Create files in this format for a variety of error codes...
Examples:
config/functions/responses/404.js
'use strict'
module.exports = async (ctx) => {
// Do other stuff
console.log("404 response is: %O, ctx)
// Return a specific error format (e.g. 'notFound') with your own custom message
return ctx.notFound('My custom 404 message')
}
config/functions/responses/403.js
'use strict'
module.exports = async (ctx) => {
// Do other stuff
console.log("403 response is: %O, ctx)
// Return a specific error format (e.g. 'forbidden') with your own custom message
return ctx.forbidden('My custom 403 message')
}

Related

How to validate JWT token from Google pub/sub push (No pem found for envelope)

Context
I'm following Google's RTDNs guide on enabling Real-Time Developer Notifications. I've successfully created the topic and subscription and have received the push notifications sent to the API that I have created. I would now like to authenticate and validate these messages. For that, I'm following this guide on Authentication and Authorization. Their developer documentation here and here has a seemingly useful example.
The Issue
After following the resources outlined above, I get the following error:
Error: No pem found for envelope: {"typ":"JWT","alg":"HS256"}
Relevant Code
const authClient = new OAuth2Client();
// ...
app.post('/pubsub/authenticated-push', jsonBodyParser, async (req, res) => {
// Verify that the push request originates from Cloud Pub/Sub.
try {
// Get the Cloud Pub/Sub-generated JWT in the "Authorization" header.
const bearer = req.header('Authorization');
const [, token] = bearer.match(/Bearer (.*)/);
// Verify and decode the JWT.
// Note: For high volume push requests, it would save some network
// overhead if you verify the tokens offline by decoding them using
// Google's Public Cert; caching already seen tokens works best when
// a large volume of messages have prompted a single push server to
// handle them, in which case they would all share the same token for
// a limited time window.
// verifyIdToken is failing here with the `No pem found for envelope` error
const ticket = await authClient.verifyIdToken({
idToken: token,
audience: 'example.com',
});
// ...
} catch (e) {
res.status(400).send('Invalid token');
return;
}
res.status(200).send();
});
The Questions
From this, I'm assuming I need to have some public key.
Where do I get said public key?
Where do I put said public key so that the google client is initialized with it?
How can I generate an example JWT to test my endpoint?
Edits
I was able to find the source of this error in their code here:
if (!Object.prototype.hasOwnProperty.call(certs, envelope.kid)) {
// If this is not present, then there's no reason to attempt verification
throw new Error('No pem found for envelope: ' + JSON.stringify(envelope));
}
However, I've verified that the kid attribute does indeed exist in the decoded object:
{"alg":"RS256","kid":"7d680d8c70d44e947133cbd499ebc1a61c3d5abc","typ":"JWT"}
Turns out the kid was invalid and therefore threw the No pem found for envelope error. Once a valid kid was supplied, the error no longer persisted.

GCP: Stripe webhook error: No signatures found matching the expected signature for payload

Stripe version: "8.107.0"
I keep getting a Stripe webhook verification error whenever I run my webhook on GCP. I've tried using the raw body in the signature, as the code snippet below mentions, as well as other ways to pass the req.rawBody as other StackOverflow answers mention.
The weird thing is that this error seems to be thrown when I deploy to GCP, and not when I run locally. I tried to manually create the signature (https://stripe.com/docs/webhooks/signatures#verify-manually), and the same result there: locally the signatures match, on GCP it doesn't.
Our server is hosted on GCP GKE, and we serve requests to our server through an Nginx Reverse Proxy. Other stack overflow solutions mentioned Google Cloud Functions and Lambda. As far as I'm aware, we do not parse requests on GCP
I do use bodyParser.json(), but that's setup after this endpoint. These are the ways I've tried creating / using a rawBody:
app.use(express.json({verify: (req,res,buf) => { req.rawBody = buf }}));
bodyParser.json({
verify: (req: any, res, buf) => {
req.rawBody = buf.toString();
},
}),
event = stripe.webhooks.constructEvent(req.rawBody, sig, webhookSecret);
I based my code on the stripe example found here: https://github.com/stripe/stripe-node/blob/master/examples/webhook-signing/node-express/express.js
// Stripe requires the raw body to construct the event
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
} catch (err) {
// On error, log and return the error message
console.log(`❌ Error message: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Successfully constructed event
console.log('✅ Success:', event.id);
// Return a response to acknowledge receipt of the event
res.json({received: true});
});
Any help would be appreciated, thanks.
The issue was with one of our setup files, where basically a space or an \n character was getting added to our webhookSecret
We had the same problem here, and it was fixed by looking for a development webhook secret (as we have a different url for development environment - it is a different webhook secret - consider look that when you have this problem).

SyntaxError: Unexpected token < in JSON Aurelia

I'm using Aurelia to build a web application. I want to display a detail page passing a parameter (id) from the list view. This is my (simplified, "Detail" is basic data class) controler code:
[HttpGet("[action]")]
public IActionResult GetDetail(int _id)
{
var b = new Detail()
var customjson = JsonConvert.SerializeObject(b, Formatting.Indented);
return Ok(customjson);
}
The detail is fetched through the activate() method in the detail.ts class:
constructor(http: HttpClient) {
this.http = http;
}
activate(params: any) {
this.http.fetch("api/Begroting/GetBegroting/" + params.id)
.then(result => result.json as Promise<Begroting>)
.then(data => {
this.begroting = data;
console.log(data);
});
}
However when the detail page is loaded via the list page it gives this error:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0. Testing the API via Swagger yields valid json and a status code 200.
implying that there is something wrong with the API(call).
OP already solved his problem, but to clarify for future readers what was likely going on:
SyntaxError: Unexpected token < in JSON is an error that typically occurs when you request a non-existing resource. The server returns a standard 404 HTML error response.
The client will then fail when it tries to parse the HTML document as JSON (it's the first < of the <html> tag that it fails on).
The client needs a status code instead of an error page
The deeper issue here is that the server should really return a 404 status code response so the client knows that the request failed, and won't try to process it as if it succeeded. A 404 error html page is accompanied by a 200 status code, after all.
So besides fixing the request url on the client side (or the route on the server side), the server and/or client should be configured such that proper status codes are returned as well. I believe Accept: application/json takes care of this although I haven't tested that.
And an alternative solution for OP:
The idea behind RESTful services is that your URI's represent resources; the query string is nice for sorting/filtering/paging parameters and such on lists. An ID kind of belongs in the path, not in the query string.
Change your route attribute to: [HttpGet("[action]/{_id}")] and your initial client code should work again.
We made a call to api/Begroting/GetBegroting/_id instead of specifying id as a GET parameter. The result was that the call to the api rerouted us back to index.html. Hence the unexpected token < in JSON Aurelia error.
To fix it, change the API call to api/Begroting/GetBegroting/?_id= + params.id

Need a way to systematically handle errors/exceptions/rejections in a NodeJS project

Background Info
I have a new project I'm working on that will provide multiple different (optional) packages that can be installed, all of which are in addition to the core package (only manual package). The other packages just interact with the core.
The project is just meant to keep track of lists of data (not very specific, I know, but these details aren't needed). The add-on packages determine HOW the lists of data are interacted with. The core package just consists of all the main JS functionality and database models, and authentication. The other packages tie into those.
Lets say you want to just have it as a standard web page, you can install the webui package, which will tie into the core, and create a web app for it
If you want to create an API, you can install the restapi package, which creates the RESTful interface; You can also install the spaui package which will interact with the RESTful interface, which gets the data from the core
These addon packages I will call "facade" packages. All you really need to extrapolate from the above is that the core is a separate package from the facade packages, and it handles the core functionality (Database stuff, authentication, authorization, etc)
Problem
The core can use promises or callbacks, and it returns exceptions for failures, then whatever facade package is used to interact with the core will handle the exceptions/errors (showing an HTTP error page, returning a RESTful error result, etc).
Since the package that handles the errors is different than the package that returns the errors, there needs to be a systematic way of knowing what type of error was returned, so it can be dealt with properly (EG: The webui/restui packages should know if it needs to show a HTTP 500, a HTTP 403, HTTP 409, etc). Obviously of the core just returns new Error('Something broke'), then the facade packages don't really know what type of error it is, unless they have the text saved somewhere and can match it up with an error code.
Question
Whats the best way to handle this? I haven't been able to find anything that accomplishes this exactly how I want..
I eventually started working on my own attempt.. (below)
My Possible Solution (If this is sufficient, just confirm)
I created a new AppError exception type, and instead of returning AppError exceptions with simple strings, you provide an error code which will associate that exception with the error message, error type, etc.
Here is an example usage of the AppError exception:
exports.createThing = ( name, data ) => {
return new Promise( ( res, rej ) => {
if( doesItExist( name ) )
return rej( new AppError( 'document.create.duplicateName' ) )
// Other stuff...
})
}
Now inside the AppError exception method, it takes the code and looks inside a list of exceptions (the code should be the key inside an object of exception data).
Heres an example of what the exception data object for the above exception would contain:
module.exports = {
'document.create.duplicateName': {
type: 'DocumentConflict',
message: 'Failed to create new document',
detail: 'The document name specified already exists, try another one'
}
}
Example Usage: Lets say we try to execute createThing with an already existing name (From within the webui package):
CorePackage.createThing( 'foobar', 'some data' )
.catch( err => {
/*
The err is now an instance of AppError
err.type -> DocumentConflict
err.message -> Failed to create new document
err.detail -> The document name specified already exists, try another one
*/
})
From here, it's as simple as associating the err.type value with a suitable HTTP error code! (which would probably be HTTP 409 Conflict). Obviously these associations can be kept in an object, making it easy to just retrieve the correct error code for any of the error type values returned. Then the text for the error code is right there in err.message and err.detail
This also makes it easy to introduce some type of locale into the application, as the error, as all that needs to be done is to edit the exception data object.
End of post
So if you think my solution above is a sufficient one, and you cant think of any problems, then please say so. Id like to know if it is or if it isn't. Even if you can't think of a proper solution, but you just know the one I created wont work, share that as well.
If you have an alternative solution, then that would work just as well!
Thanks
I think there are two basic ways to approach this:
code property: Create a new \Error object and assign the code property with information about the error. For example:
var err = new Error('Message');
err.code = "DocumentConflict";
Custom error objects. You could have a seperate Error object per error type that you have. For example, rather than having just AppError, you can have DocumentConflict error.
For projects where I am creating a RESTful API, I like to think in terms of error codes. For most projects, the endpoints will return one of the following codes:
400 (Bad Request)
401 (Credentials Error)
403 (Forbidden)
404 (Not Found).
500 (Internal Server Error).
These then become 'standard' types of Error that I pass around the application. A normal Error object is interpretated as an internal server error, so this will always pass 500 to the endpoint.
For example,
CredentialsError = function (message) {
Error.call(this, arguments);
Error.captureStackTrace(this, this.constructor);
this.message = message;
};
util.inherits(CredentialsError, Error);
CredentialsError.prototype.name = "CredentialsError";
And then just return/throw a new CredentialsError("Invalid password") object as necessary. To check the type of object, you can use instanceof. With Express, for example, you can have an error handler similar to the following:
app.use(function(err, req, res, next) {
var status;
if (err instanceof error.FieldError) {
status = 400;
} else if (err instanceof error.CredentialsError) {
status = 401;
/* etc */
} else {
status = 500;
}
if (status !== 500) {
res.status(status).send(JSON.stringify(
err,
null,
4
));
} else {
// for 500, do not output the error!
console.error(err.stack);
res.status(500).send({
message: "Internal Server Error"
});
}
});
It is also worth noting that you can defined your custom error object constructors to take more than just strings. For example, you can pass objects into a BadRequestError constructor to provide field-level error detail.
Now, in most cases, you can just propagate the errors and the response to the endpoint will make sense. However, there are cases where you want to transmute the type of error. For example, if you have a login endpoint, you might do a request to findUserByEmailAddress(). This could return a NotFoundError object, but you want to capture this in the signIn() function and transmute it to a CredentialsError.

AWS Cognito - Developer Authenticated Identities in JavaScript(Browser)

I have trouble getting credentials in a browser script.
The authentication server returns cognito_identityId and cognito_token.
Then I set a Cookie:
$.cookie('cognito_identityId')
$.cookie('cognito_token')
I tried to get credentials in 4 ways on the browser, and all Failed:
CognitoIdentityCredentials
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxxxxxx'
IdentityId: $.cookie('cognito_identityId'),
Logins: {
'myauth': $.cookie('cognito_token')
}
});
// => Error: Missing required key 'IdentityId' in params
assumeRoleWithWebIdentity
var params = {
RoleArn: 'arn:aws:iam::xxxxxxxxxxxx:role/Cognito_xxxxxxxAuth_Role',
RoleSessionName: 'xxxxxxxxxxx',
WebIdentityToken: $.cookie('cognito_token'),
DurationSeconds: 900,
ProviderId: 'myauth'
};
var sts = new AWS.STS({apiVersion: '2011-06-15'});
sts.assumeRoleWithWebIdentity(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
// => AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
PolicyDocument
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1:xxxxxxxxxxxxx"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
GetCredentialsForIdentity
var params = {
IdentityId: $.cookie('cognito_identityId'),
Logins: {
"myauth": $.cookie('oauth.io_token')
}
};
var cognitoidentity = new AWS.CognitoIdentity({apiVersion: '2014-06-30'});
cognitoidentity.getCredentialsForIdentity(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
}
else {
console.log(data); // successful response
}
});
// => InvalidParameterException: Please provide a valid public provider
WebIdentityCredentials
AWS.config.credentials = new AWS.WebIdentityCredentials({
RoleArn: 'arn:aws:iam::xxxxxxxx:role/Cognito_xxxxxxxxxxAuth_Role',
WebIdentityToken: $.cookie('cognito_token')
});
// => Error: There were 2 validation errors:
// * MissingRequiredParameter: Missing required key 'IdentityPoolId' in params
// * MissingRequiredParameter: Missing required key 'IdentityId' in params
Questions:
What am I doing wrong?
What is the correct way to use this?
Thank you.
Thank you for your kindness.
I tyied your advice, but did not change.
Error messages.
POST https://cognito-identity.us-east-1.amazonaws.com/ 400 (Bad Request)
POST https://cognito-identity.us-east-1.amazonaws.com/ 400 (Bad Request)
Error: Missing required key 'IdentityId' in params
at fail (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2163:37)
at validateStructure (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2084:14)
at validateMember (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2110:21)
at validate (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:2059:10)
at Request.VALIDATE_PARAMETERS (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:800:32)
at Request.callListeners (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:3913:20)
at callNextListener (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:3903:12)
at chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:787:9
at finish (chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:126:7)
at chrome-extension://hmjdjbikinkmjbilihjibcihbkbjdgjf/bower_components/aws-sdk-js/dist/aws-sdk.js:142:9
There are source code below link.
https://github.com/bisque33/my-custom-dictionary
and server side is a AWS Lambda Function.
var aws = require('aws-sdk');
aws.config.region = 'us-east-1';
var cognitoidentity = new aws.CognitoIdentity();
var identityPoolId = 'us-east-1:0dccff0d-5fd7-4d14-b38f-d27204feaecc';
console.log('Loading function');
exports.handler = function(event, context) {
console.log('token: %s', event.token);
var params = {
IdentityPoolId: identityPoolId,
Logins: {
'oauth.io': event.token
}
};
cognitoidentity.getOpenIdTokenForDeveloperIdentity(params,function(err,data){
if(err){
console.log(err);
context.fail('Something went wrong');
}else{
context.succeed(data);
}
});
};
This program is Google-Chrome-Extension.
AWS Lambda Function returns token by getOpenIdTokenForDeveloperIdentity.
app/scripts/popup.js calls Lambda Function and set cookies.
app/scripts/background.js calls AWS.config.credentials.get, and returns error.
Am I using it wrong?
Update for Additional Information
Thank you for the additional information.
Error appears on 104 line on background.js
AWS.config.credentials.get(function(){
and 115 line on background.js
dataset.synchronize(
And, My explaination was not enough. Facebook authentication needs the domain(ex. http :// example.com). However, Google-Chrome-Ext does not have domain. It has a domain 'chrome-extension://xxxxxxxxxxxxxxxxxxxx'. Then, I use https://oauth.io. It proxies any authentication and accepts chrome-extension domain.
Popup.js does Facebook authentication through oauth.io sdk. It gets a facebook token, and gives to getOpenIdTokenForDeveloperIdentity. I think facebook token.substr(0,14) is unique. But, If it is wrong, I use another unique identifier(ex. email-address.)
Sorry, I was wrong. AWS.config.credentials.get gives an Error:
Error: Invalid login token.
And, dataset.synchronize shows this Error:
Error: Missing required key 'IdentityId' in params
The first approach you have, using CognitoIdentityCredentials, is most likely the best approach for you to take. I can't spot exactly what's causing the error for you but lets try a couple things:
When using Developer Authenticated Identities, you do need to specify the IdentityId when initializing CognitoIdentityCredentials. You need to get the IdentityId value from the call to GetOpenIdTokenForDeveloperIdentity. However, you shouldn't need to preserve the IdentityId value in a cookie as CognitoIdentityCredentials will cache the id by default in the browser's local storage.
As for your Logins map: It looks like you're trying to use Developer Authenticated Identities. With the JavaScript SDK, use the key 'cognito-identity.amazonaws.com' and make sure the value is the token returned from your backend's call to getOpenIdTokenForDeveloperIdentity.
If you continue to have problem using the CognitoIdentityCredentials approach, please reply here with some more info such as the exact method/code you're calling when you receive the error message, and the traced output (i.e. with console.log('%o',..)) of the params input just before your call to the CognitoIdentityCredentials constructor.
Update Based on Additional Information Provided
I still need to know exactly which line of code you receive the error on, but based on the information provided I think I can still help...
Based on what I see in background.js, it looks like you're trying to initialize CognitoIdentityCredentials using a Developer Authenticated Identities provider. This is where I'm guessing that you're receiving the error.
However, in Popup.js, it looks like you're trying to authenticate the user with Facebook. If you're authenticating your users with Facebook, you should just pass the facebook access token into your Logins map when using Cognito. Just use graph.facebook.com as the key in the Logins map and the access token from Facebook. More detail on how to do this is in the Facebook Integration topic of the Amazon Cognito developer guide.
Facebook vs Developer Authenticated Identities
We can get Developer Authenticated Identities to work for you, but in this case, it doesn't look like the right solution for you since you're not actually doing any additional authentication on the identity in your Lambda function and the unique user identifier that you're passing into the getOpenIdTokenForDeveloperIdentity operation appears to be the facebook token, which is not good by the way since the token itself will change between user sessions even for the same user. Usually a good unique identifier is an email address or a user id used by an internal system.
Facebook Login & Redirects
Since you're ultimately trying to use Facebook for login and Amazon Cognito has built-in integration for Facebook, the best thing for you to do is get an access token from Facebook and pass in the Facebook token to Cognito's login map directly. I'm not sure if this will work with Auth.io or not (I'm just not familiar with it), but as long as Auth.io gives your JavaScript code a bonefide facebook token and you add the same Facebook App ID to both Auth.io and Amazon Cognito's Console, it should work. However, you mentioned you want to use Auth.io to avoid Facebook doing a redirect to a landing page. I could be mistaken, but I'm pretty sure if you're using Facebook's JavaScript SDK you won't need a redirect page. You should only need the redirect page if you're doing Facebook's Manually Build a Login Flow.

Categories

Resources