Structure restrictToOwner & restrictToRoles in feathersjs - feathersjs

I've read through the documentation, but I can't seem to get it right.
I'm trying to implement a restrictToOwner and restrictToRoles such that users with admin or superadmin role can access every other method in this service
const restrict = [
‎ authenticate('jwt'),
‎ restrictToOwner({
idField: '_id',
ownerField: '_id'
})
]
‎
const restrictUser = [
authenticate('jwt'),
restrictToRoles({
roles: ['admin', 'super-admin'],
fieldName: 'roles'
})
]
before: {
all: [],
find: [ ...restrictUser ],
get: [ ...restrict, ...restrictUser],
create: [ hashPassword() ],
update: [ ...restrict, ...restrictUser, hashPassword() ],
patch: [ ...restrict, ...restrictUser, hashPassword() ],
remove: [ ...restrict, ...restrictUser ]
},

The trick is to not look for pre-done hooks since they are very limited while not doing very much. It is usually makes much more sense to implement custom logic like this in your own hooks.
In your case we first want to check if the user is an admin and if not, either restrict the query to the user id or check if the user is allowed to access the individual entry. This can be done in a few lines of code:
const { Forbidden } = require('#feathersjs/errors');
const restrictUser = async context => {
const { user } = context.params;
// For admin and superadmin allow everything
if(user.roles.includes('admin') || user.roles.includes('superadmin')) {
return context;
}
if(!context.id) {
// When requesting multiple, restrict the query to the user
context.params.query._id = user._id;
} else {
// When acessing a single item, check first if the user is an owner
const item = await context.service.get(context.id);
if(item._id !== user._id) {
throw new Forbidden('You are not allowed to access this');
}
}
return context;
}
before: {
all: [],
find: [ authenticate('jwt'), restrictUser ],
get: [ authenticate('jwt'), restrictUser ],
create: [ hashPassword() ],
update: [ authenticate('jwt'), restrictUser, hashPassword() ],
patch: [ authenticate('jwt'), restrictUser, hashPassword() ],
remove: [ authenticate('jwt'), restrictUser ]
},
This makes it fairly clear what is happening and you have full flexibility over every detail (like property names, how your data is structured or in what order it is being checked).

Related

getting the values from 3 tables in sequelize using nested includes and sequelize.col as single object

I am new to nodejs as well as sequelize and any kind of ORMs
I wish to get all the values from 3 tables linked together through belongsTo associations
3 tables :
item - [id, itemName, itemCategoryID]
itemCategory - [id, itemCategoryName]
itemRequirement - [id, itemID, quantity, requirementDate, requirementStatusID]
requirementStatus - [id, requirementStatusName]
this is my get api req for getting the item requirements
router.get("/", async (req, res) => {
const itemRequirements = await itemRequirement
.findAll({
include: [
{
model: item,
include: [
{
model: itemCategory,
attributes: [],
},
],
attributes: [
//gets error in this line
[Sequelize.col("itemCategory.itemCategoryName"),"itemCategoryName",],
//alternatively this line works fine
['itemCategoryID']
],
},
{ model: requirementStatus, attributes: [] },
],
attributes: [
"id",
"quantity",
"requiredBy",
[Sequelize.col("item.itemName"), "itemName"],
[
Sequelize.col("requirementStatus.requirementStatusName"),
"requirementStatusName",
],
],
})
.then((itemRequirements) => {
console.log(itemRequirements);
res.json(itemRequirements);
});
});
I get error when trying to do a sequelize.col but I am able to get the ID alone if I don't use the sequelize.col in the above code at the mentioned line
code: 'ER_BAD_FIELD_ERROR',
errno: 1054,
sqlState: '42S22',
sqlMessage: "Unknown column 'item.itemCategory.itemCategoryName' in 'field list'",
currently i am getting this if i directly get the id
[
{
"id": 1,
"quantity": 10,
"requiredBy": "2022-02-28T18:30:00.000Z",
"itemName": "vanilla essence",
"requirementStatusName": "pending",
"item": {
"itemCategoryID": 1
}
}
]
i wish to get this
[
{
"id": 1,
"quantity": 10,
"requiredBy": "2022-02-28T18:30:00.000Z",
"itemName": "vanilla essence",
"requirementStatusName": "pending",
"itemCategoryName":"someCategoryName"
}
]
You should use DB column name in Sequelize.col instead of its field counterpart in a model:
// let's say the model field is itemCategoryName and the column name in a table is item_category_name
Sequelize.col("itemCategory.item_category_name")
To query more than 2 tables using joins in sequelize we will have to use reference the table and column name correctly.
Instead of adding [Sequelize.col("itemCategory.itemCategoryName"),"itemCategoryName",] as an attribute to the referencing table and to get the response as a single json object without nesting we need to add this [Sequelize.col("item.itemCategory.itemCategoryName"),"itemCategoryName"] as the attribute to the table from which you are querying now
below is the edited code which returns json as expected
router.get("/", async (req, res) => {
const itemRequirements = await itemRequirement
.findAll({
include: [
{
model: item,
include: [
{model:itemCategory,attributes:[]},
{model:quantityType,attributes:[]}
],
attributes:[]
},
{ model: requirementStatus, attributes: [] },
],
attributes: [
"id",
"quantity",
"requiredBy",
[Sequelize.col("item.itemName"), "itemName"],
[
Sequelize.col("requirementStatus.requirementStatusName"),
"requirementStatusName",
],
//actual way of referencing the different tables to get an object without
//nesting
[Sequelize.col("item.itemCategory.itemCategoryName"),"itemCategoryName"],
[Sequelize.col("item.quantityType.quantityName"),"quantityTypeName"]
],
})
.then((itemRequirements) => {
console.log(JSON.stringify(itemRequirements,null,2));
res.json(itemRequirements);
});
});
module.exports = router;
output
[
{
"id": 4,
"quantity": 10,
"requiredBy": "2022-02-03T00:00:00.000Z",
"itemName": "choco",
"requirementStatusName": "pending",
"itemCategoryName": "Essence",
"quantityTypeName": "ml"
}
]

How To create update API in yii?

I created an api named valid which is not working. Postman says
"name": "Not Found",
"message": "Page not found.",
"code": 0,
"status": 404,
"type": "yii\\web\\NotFoundHttpException",
"previous": {
"name": "Invalid Route",
"message": "Unable to resolve the request \"user/valid/1\".",
"code": 0,
"type": "yii\\base\\InvalidRouteException"
}
}
My controller name is user controller.
Here is my function
public function actionValid($id)
{
return 'example';
}
i called my route as /user/valid/1.
Any reason why this is happening?
This is how i solved this.
If you want to create your own apis in yii, You must follow these steps.
First add your route in UrlManager Like this
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => 'user',
'extraPatterns' => [
'POST valid/<id>' => 'valid',
'GET listing' => 'listing
]
],
],
]
Now, call your route like this users/valid/{your-id} and users/listing. Make sure to use a plural of your controller's name.
For example, a request of POST /users/data would mean accessing the actiondata function in your usercontroller.

Customize the Api response and use it later on other React components

I am from angular background, just started with React.js. This question can be easy for you all, But architecture wise, I need a good solution.
After getting the json response from API, I want to modify the response and then save it somewhere, so i can use modified response on multiple components. (Json modification is not a issue, I want to know, what is the best solution to store the response then where to modify the response and then where to store so other components can use the modified version of it. I am not using redux.)
For example, If i get the below response from axios api
{
"name":"John",
"age":30,
"cars": [
{ "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] },
{ "name":"BMW", "models":[ "320", "X3", "X5" ] },
{ "name":"Fiat", "models":[ "500", "Panda" ] }
]
}
I want to modify it first like
{
"name":"John",
"age":30,
"cars": [
{ "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] },
{ "name":"BMW", "models":[ "320", "X3", "X5" ] },
{ "name":"Fiat", "models":[ "500", "Panda" ] }
],
"newparameter": [
{ "name":"fdf" }
]
}
After modifying this, I want to save it any service or something. So i can use it later on on other components irrespective of there is not relation between of components.
Json modification is not a issue, I want to know, what is the best solution to store the response then where to modify the response and then where to store so other components can use the modified version of it. I am not using redux
You can lift state, use a state manager (redux) or react context, here is an example of Context
const DataContext = React.createContext();
const DataProvider = ({ children }) => {
const [dataResult, setDataResult] = React.useState({
loading: true, //not using error as well
});
//fetch the data when the application loads
React.useEffect(() => {
setTimeout(
() =>
setDataResult({ loading: false, data: [1, 2, 3] }),
2000
);
}, []);
return (
<DataContext.Provider value={dataResult}>
{children}
</DataContext.Provider>
);
};
const data = ['a', 'b', 'c', 'd'];
function App() {
const { loading, data } = React.useContext(DataContext);
return (
<div>
{loading ? (
'loading'
) : (
<pre>{JSON.stringify(data, undefined, 2)}</pre>
)}
</div>
);
}
ReactDOM.render(
<DataProvider>
<App />
</DataProvider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to export the api call results to a csv with the values of single response in one row?

My api response looks like below
[
{
"What time is it?": [
"option_2"
]
},
{
"When will we go home?": [
"option_1"
]
},
{
"When is your birthday?": [
"2050"
]
},
{
"How much do you sleep?": [
"Ajajajsjiskskskskdkdj"
]
}
],
[
{
"What time is it?": [
"option_2"
]
},
{
"When will we go home?": [
"option_1"
]
},
{
"When is your birthday?": [
"10181"
]
},
{
"How much do you sleep?": [
"Ajskossooskdncpqpqpwkdkdkskkskskksksksksks"
]
}
]
Now in react, I want to export the results to a csv. I can do it by export-to-csv but the formatting is the issue here. I want the values of each question of a single response in one row under their labels(questions). So if I have two response like above I want to have export it in two rows, not 8 as there are 8 total questions.
Here is how I want it to get exported.
I have tried so far like this but no luck.
this is my export data function
exp =()=>{
const raw = []
console.log(this.state.data[0].sbm_id)
axios.get(`/dashboard/${this.props.proj_id}/whole_sub/`)
.then(res=>{
// console.log('1')
// console.log(res.data[0][0])
// console.log('2')
for (let i =0;i<this.state.data.length;i++){
for(let j = 0;j<res.data[0].length;j++){
// let sub=[]
//res.data[i][j].ID = this.state.data[i].sbm_id
raw.push(res.data[i][j])
}
}
}
)
let curr = this.state
curr.exp = raw
this.setState({exp:curr.exp})
}
Here is my export function
rawExport=()=>{
const csvExporter = new ExportToCsv(optionsExp);
csvExporter.generateCsv(this.state.exp);
}
First step is to flatten the initial nested array to get a homogeneously shaped array, then you keep on reducing it further.
const data = [
[
{
"What time is it?": [
"option_2"
]
},
{
"When will we go home?": [
"option_1"
]
},
{
"When is your birthday?": [
"2050"
]
},
{
"How much do you sleep?": [
"Ajajajsjiskskskskdkdj"
]
}
],
[
{
"What time is it?": [
"option_2"
]
},
{
"When will we go home?": [
"option_1"
]
},
{
"When is your birthday?": [
"10181"
]
},
{
"How much do you sleep?": [
"Ajskossooskdncpqpqpwkdkdkskkskskksksksksks"
]
}
]
];
const flattenArray = (arr) => [].concat.apply([], arr);
// Flatten the initial array
const flattenedArray = flattenArray(data);
// Keep on reducing the flattened array into an object
var res = flattenedArray.reduce((acc, curr) => {
const [key, val] = flattenArray(Object.entries(curr));
if (!acc[key]) {
acc[key] = [].concat(val);
} else {
val.forEach(x => {
if (!acc[key].includes(x)) {
acc[key].push(x);
}
});
}
return acc;
}, {});
console.log(res);

cakephp 3 rest errors return as html CrudJsonApi

I'm adding a REST API onto an existing cakephp codebase.
It is working as I expect. I hit /api/v1/contacts/12312 and get json data back for contact 12312. If put an id of a contact that doesn't exist then I get the html 404 page error rather than json.
This happens internally on the contacts->get($id) line.
In the api app controller I have
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index',
'Crud.View',
'Crud.Add',
'Crud.Edit',
'Crud.Delete'
],
'listeners' => [
'CrudJsonApi.JsonApi',
'CrudJsonApi.Pagination', // Pagination != ApiPagination
'Crud.ApiQueryLog',
],
'Error' => [
'errorLevel' => E_ALL,
'exceptionRenderer' => 'CrudJsonApi\Error\JsonApiExceptionRenderer',
'skipLog' => [],
'log' => true,
'trace' => true,
],
]);
$this->Crud->config(['listeners.jsonApi.exceptionRenderer' => 'CrudJsonApi\Error\JsonApiExceptionRenderer']);
$this->setJsonResponse();
}
public function beforeRender(Event $event)
{
$this->RequestHandler->renderAs($this, 'json');
$this->response->type('application/json');
$this->set('_serialize', true);
}
I thought using the JsonApiExceptionRenderer 404 errors would be handled with json output.
Also the Pagination works but the Pagination data isnt returned with the response...
{
"viewVar": "crmContacts",
"crmContacts": [
{
"id": 1,
"lastname": "testname1"
},
{
"id": 2,
"lastname": "smith"
}
],
"success": true
}
Any ideas?
Thanks