We have a problem with Sails policies.
Our default policy is:
'*': [
'basicAuth',
'passport',
'sessionAuth',
'ModelPolicy',
'AuditPolicy',
'UpdatedByPolicy',
'OwnerPolicy',
'OmniPolicy'
],
But when I start the server is complains about another policy called CriteriaPolicy, which is NEVER referenced in our policies.js
The CriteriaPolicy comes from the Sails module called sails-permission, which we have installed. However we only want to use some of its polices and not all.
So why is Sails not using our app's config/policies.js as the TRUTH. Why does it allow modules to ADD policies, if I dont want them?
How can I force Sails to only adhere to the policies from the app's config/policies.js?
This the error we get:
TypeError: Cannot read property 'length' of undefined
at /node/ourapp/node_modules/sails-permissions/dist/api/policies/CriteriaPolicy.js:50:12
at arrayMap (/node/ourapp/node_modules/sails-permissions/node_modules/lodash/index.js:1406:25)
at Function.map (/node/ourapp/node_modules/sails-permissions/node_modules/lodash/index.js:6710:14)
at module.exports (/node/ourapp/node_modules/sails-permissions/dist/api/policies/CriteriaPolicy.js:49:96)
at routeTargetFnWrapper (/node/ourapp/node_modules/sails/lib/router/bind.js:181:5)
at callbacks (/node/ourapp/node_modules/#sailshq/express/lib/router/index.js:164:37)
at param (/node/ourapp/node_modules/#sailshq/express/lib/router/index.js:138:11)
at param (/node/ourapp/node_modules/#sailshq/express/lib/router/index.js:135:11)
at pass (/node/ourapp/node_modules/#sailshq/express/lib/router/index.js:145:5)
at nextRoute (/node/ourapp/node_modules/#sailshq/express/lib/router/index.js:100:7)
at callbacks (/node/ourapp/node_modules/#sailshq/express/lib/router/index.js:167:11)
at /node/ourapp/plugins/plugin-ourapp-foundation/api/policies/OmniPolicy.js:184:12
at /node/ourapp/node_modules/sails/node_modules/async/lib/async.js:52:16
at done (/node/ourapp/node_modules/sails/node_modules/async/lib/async.js:246:17)
at /node/ourapp/node_modules/sails/node_modules/async/lib/async.js:44:16
at /node/ourapp/plugins/plugin-ourapp-foundation/api/policies/OmniPolicy.js:169:14
at tryCatcher (/node/ourapp/node_modules/waterline/node_modules/bluebird/js/release/util.js:11:23)
Related
Our application is using fastify and we're registering the openapiGlue plugin to parse/validate http requests. We're using the ajv option to disable all type coercion.
Everything works great...
However, the issue with the ajv coerce option is that it is applied globally. What we'd like to do is enable type coercion only for the query parameters, but since the ajv coerce option is global, there's no way to do that.
We've tried to register another plugin, that we wrote, after the openapiGlue that uses the fastify setValidatorCompiler() to solve this problem for us. We've set up separate ajv options sections that get applied based on the part of the http request getting validated. The issue is the setValidatorCompiler callback function never gets invoked for any of the message parts, and thus we are unable to enable type coercion on just the query parameters.
It's as if the openapiGlue plugin doesn't want to relinquish control of the message parsing and validating.
Anyone have any suggestions?
// this works
await app.register(openapiGlue, {
specification: localSystemApi,
service: handlers,
prefix: apiPrefix,
ajvOptions: {
coerceTypes: false // defaults to true by openapiGlue otherwise
}
});
// new code to add plugin to only coerce query parameters
// has no effect.
await app.register(ajvPlugin);
// Our ajvPlugin
const httpPartSchema = z.union([z.literal('body'), z.literal('headers'), z.literal('querystring'), z.literal('params')]);
export const ajvPlugin: FastifyPluginAsync<FastifyOpenapiGlueOptions> = async (fastify) => {
fastify.setValidatorCompiler(({ schema, httpPart }) => {
// this code never gets invoked
if (httpPart === undefined) {
throw new Error('Missing httpPart');
}
const parseResult = httpPartSchema.parse(httpPart);
const compiler = schemaCompilers[parseResult];
return compiler.compile(schema) as any;
});
};
The issue is the setValidatorCompiler callback function never gets invoked for any of the message parts, and thus we are unable to enable type coercion on just the query parameters
This is a feature 😄
Fastify does not create any ajv instances if your routes do not use the schema option to speed up your server startup time.
You can do what you need by adding a route that adds a schema.query object.
I would suggest to use the fastify-split-validator plugin to change the ajv settings based on the HTTP entity.
As further reading, this issue in the fastify repository explains in detail how it works.
How to perform slot validation in an intent using backend code (webhook).
I have seen how to perform slot filling using the webhooks but I want to know how to validate the slot data and re-prompt the user if validation fails.
Example:
User: I want to know the your services in London.
Bot: We do not provide service in London, please enter some other city name.
In short: If validation fails, reset the dialog contexts, trigger your intent again, and optionally use default values to keep other parameters that were actually valid (so you don't need to re-prompt the user for those again).
You don't need to declare an incoming context on that intent to achieve this. Note that you can use contexts in intents, even though they are not declared as incoming/outgoing contexts on that intent.
In this example, I'm requesting 2 parameters from the user (car make and model). Of course, Enable webhook call for slot filling needs to be set in your intent.
Steps:
On Dialogflow, in the intent, declare an Event. This can be used to trigger this intent from your fulfillment code:
In your parameters, declare a Default Value for each parameter you want to be able to keep after resetting the intent:
Set the Default Value to a parameter in a helper context. If this helper context does exist, the default value will be set, otherwise, it'll be kept empty. This will allow you to reset the intent and keep other parameters you already had. In this example, I'm using the context show-car-details-data, and setting the default value of parameter model to _model in that incoming context:
In your slot-filling fulfillment method, you can validate your parameter and re-prompt the user by reseting the intent. To do that, you need to 1) clear the current dialog contexts, 2) call setFollowupEvent to trigger your intent again, and 3) optionally set up some helper context you can use to assign default values (so you don't need to re-prompt user for those that were valid).
I'm using Dialogflow Fulfillment Node.js Library:
// clear dialog contexts:
agent.contexts.forEach( e => {
if ( e.name.endsWith('_id_dialog_context') ) agent.context.delete(e.name);
});
// workaround bug: https://github.com/dialogflow/dialogflow-fulfillment-nodejs/issues/160
agent.add('');
// set follow up event: this triggers your intent again
agent.setFollowupEvent('show-car-details');
// optionally set helper context to set default values and avoid re-prompt of already valid values
// in this example, keep make since it was a valid parameter
// clear model because it was invalid. User will be prompted again on model, but not on make
agent.context.set('show-car-details-data', 1, { '_make': make, '_model': '' });
Steps
Enable webhook for slot filing in Dialog flow with Entity set as "Required".
Get entity from arguments.
Programmatically check if it is not null and as per your requirement.
If it is correct, proceed further.
Else, RESET THE CONTEXT and ask the question as per the wrong slot input. Programmatically, set the output context same as input so that user can again respond back slot/entity. If the correct context is not present, the slot will not get captured. When a slot is captured as per set Entity, the context for that slot captured is finished and to recapture it you need to increase the lifespan or create it again.
When I create a resource/method in AWS API Gateway API I can create one of the following methods: DELETE, GET, HEAD, OPTIONS, PATCH or POST.
If I choose GET then API Gateway doesn't pass authentication details; but for POST it does.
For GET should I be adding the cognito credentials to the URL of my GET? or just never use GET and use POST for all authenticated calls?
My set-up in API Gateway/Lambda:
I created a Resource and two methods: GET and POST
Under Authorization Settings I set Authorization to AWS_AIM
For this example there is no Request Model
Under Method Execution I set Integration type to Lambda Function and I check Invoke with caller credentials (I also set Lambda Region and Lambda Function)
I leave Credentials cache unchecked.
For Body Mapping Templates, I set Content-Type to `application/json' and the Mapping Template to
{ "identity" : "$input.params('identity')"}
In my Python Lambda function:
def lambda_handler(event, context):
print context.identity
print context.identity.cognito_identity_id
return True
Running the Python function:
For the GET context.identity is None
For the POST context.identity has a value and context.identity.cognito_identity_id has the correct value.
As mentioned in comments: all HTTP methods support authentication. If the method is configured to require authentication, authentication results should be included in the context for you to access via mapping templates to pass down stream as contextual information.
If this is not working for you, please update your question to reflect:
How your API methods are configured.
What your mapping template is.
What results you see in testing.
UPDATE
The code in your lambda function is checking the context of the Lambda function, not the value from API Gateway. To access the value passed in from API Gateway, you would need to use event.identity not context.identity.
This would only half solve your problem as you are not using the correct value to access the identity in API gateway. That would be $context.identity.cognitoIdentityId (assuming you are using Amazon Cognito auth). Please see the mapping template reference for a full guide of supported variables.
Finally, you may want to consider using the template referenced in this question.
if you have a REST API that is hypermedia-driven (HATEOAS) you can easily change a client's behavior by including or omitting links in the response (_links). That enables a client to completely forget about testing permissions for the operations that are possible in the current state of a resource (the link to the operation is present or not).
Additionally you can leave out properties in the response if the current user doesn't have permission to see it.
That way authorization is done entirely on the server (and controls actions and properties that are eligible to execute/view).
But what if I want to a have a read-only property? It is no problem for the REST API to ignore the property if it is present in the request (_POST_ OR _PUT_). it just won't get saved. But how can a client distinguish between write and read-only properties to present the user appropriate controls (like a disabled input field in HTML)?
The goal is to never ever have the client request a user's permissions, but to have a completely resource driven client/frontend.
Any help is greatly appreciated :-)
If I misunderstood your question, I apologize upfront. With that being said...
But how can a client distinguish between write and read-only
properties to present the user appropriate controls (like a disabled
input field in HTML)
Well, there are multiple solutions to this. The simplest one I can personally think of is to make each property an object having a simple structure of something like:
...
someProperty: {
value: 'some value',
access: 'read-only'
},
someOtherProperty: {
value: 'some value',
access: 'write'
}
...
You can obviously get as creative as you want with how you represent the "access" level of the property (using enums, booleans, changing access to be isReadOnly or whatever).
After that, the person using the API now knows they are read-only or not. If they submit a "write" value for a "read-only" property as part of the POST payload, then they should expect nothing less than a 403 response.
Edit:
In case you can't alter the properties in this manner, there are a number of other ways you can still achieve this:
write documentation that explains what access each property has
create a route that the user can submit 1 or more properties to in order to receive a response that indicates the access level of each property (response: { propName: 'read-only', propName2: 'write', etc.)
Return a propertyAccess map as part of the response (mapping properties to access levels).
end of the day, you just need a way to map a property with an access level. however that's done depends on what your restrictions and requirements are for the api, what changes you can make, and what is acceptable to both your client(s) and the business requirements.
What purpose does req.options serve in SailsJs?
From the source, it seems I can somehow use policies to alter existing params on Sails Blueprints, etc. How does this work? I can't find any documentation at all.
req.options was introduced in Sails v0.10 as a way to add options to custom routes. For example, you could always do:
'GET /userList': {
controller: 'UserController',
action: 'find'
}
to point at the default "find" action for a User model, but there was no way to control the query--it would always just return all records. Now you can do:
'GET /userList': {
controller: 'UserController',
action: 'find',
sort: 'name desc',
where: {name: {startsWith: 'j'}}
}
and all of the options (including controller and action) will be added to req.options, which the default blueprint actions use to modify the query. Note the difference to Sails v0.9.x, which used req.target to keep track of the controller and action.
As you pointed out, req.options can also be modified within policies, as opposed to req.params which are reset before every policy and controller action. This can be used to, for example, further restrict the criteria for a query based on a user's role.
Obviously the docs for v0.10.x aren't completely finished (and will always be subject to improvement), but this could definitely find a place in the Route Target Syntax section...