I have order app created on strapi, how can I secure data from other users, but user who create order can view him.
I use relation one-to-many from user to many orders, when ?populate=orders requests other authorized users can view them, and when I turn off find action this field removing from /users/me?populate=orders.
How can I set visibility for order by user who made it?
you would need a middleware for this, the more general approach is to use policy to make users not able to fetch other user data, like here: Why all users in Strapi have access to update all users profile?
So let's mod code a bit to fit your case, the steps we are going to do are following:
Create global middleware.
Inject middleware to user-permissions routes.
Mod middleware to remove orders from request if user aren't the one that is registered.
The result is if we do /api/users?populate=orders or /api/users/:id?populate=orders we would not receive orders.
So:
strapi generate
? Strapi Generators middleware
? Middleware name orders
? Where do you want to add this middleware? Add middleware to root of project
So by default middleware would log in console:
'In orders middleware.'
We need to apply it to find, findOne, update, delete routes of user-permissions plugin:
src/extensions/user-permissions/strapi-server.js
module.exports = (plugin) => {
for (let i = 0; i < plugin.routes["content-api"].routes.length; i++) {
const route = plugin.routes["content-api"].routes[i];
// checks if this one of the routes we target
if (
route.handler === "user.findOne" ||
route.handler === "user.find" ||
route.handler === "user.update" ||
route.handler === "user.destroy"
) {
// if it's append middleware to route
plugin.routes["content-api"].routes[i] = {
...route,
config: {
...route.config,
middlewares: route.config.middlewares
? [...route.config.middlewares, "global::orders"]
: ["global::orders"],
},
};
}
}
return plugin;
};
yarn build
yarn develop
If you did the step correct you should see:
[2023-02-14 14:59:51.472] http: GET /api/users (30 ms) 200
[2023-02-14 15:00:01.852] info: In orders middleware.
every time you hit /api/users
Next step we going to tweak middleware, so middleware has two stages, first stage is before await next() it's stage that executed before controller, the second stage is after await next() is stage executed after controller.
We are going to modify the second stage and if we find attribute orders we are going to remove it from response:
src/middlewares/orders.js
'use strict';
/**
* `orders` middleware
*/
module.exports = (config, { strapi }) => {
return async (ctx, next) => {
// before controller
await next();
// after controller
// we need to check if the reponse is correct,
// otherwise we will have error message in the data
if (ctx.response.status === 200) {
// get the authenticated user, if no user - undefined
const { user } = ctx.state;
// get data from response
let data = ctx.response.body;
// check if data is array
if (Array.isArray(data)) {
// run sanitize function for each element
data = data.map(item => sanitizeItem(item, user))
} else {
// else run for single item
data = sanitizeItem(data, user);
}
// apply result to response
ctx.response.body = data;
}
};
};
// sanitizer function
const sanitizeItem = (item, user) => {
// check if user is not undefined
if (user) {
// check if user id is same as the item.id (user from request)
if (user.id === item.id) {
// if it's same return full object
return item;
}
}
// else extract orders from object
let { orders, ...rest } = item;
return rest;
}
And whoala:
[
{
"id": 1,
"username": "user1",
"email": "user1#email.com",
"provider": "local",
"confirmed": true,
"blocked": false,
"createdAt": "2023-02-14T11:39:43.246Z",
"updatedAt": "2023-02-14T11:39:43.246Z",
"orders": [
{
"id": 1,
"title": "Order 1",
"createdAt": "2023-02-14T11:39:01.990Z",
"updatedAt": "2023-02-14T11:39:01.990Z"
},
{
"id": 2,
"title": "Order 2",
"createdAt": "2023-02-14T11:39:09.182Z",
"updatedAt": "2023-02-14T11:39:09.182Z"
}
]
},
{
"id": 2,
"username": "user2",
"email": "user2#email.com",
"provider": "local",
"confirmed": true,
"blocked": false,
"createdAt": "2023-02-14T11:40:04.793Z",
"updatedAt": "2023-02-14T11:40:04.793Z"
}
]
I'm authenticated as user1
Related
Strapi doesn't have any endpoint to get random data for this purpose you should write some custom code for your endpoint
custom route for that endpoint you want
// path: ./src/api/[your-endpiont]/routes/[custom-route].js
module.exports = {
"routes": [
{
"method": "GET",
"path": "/[your-endpiont]/random", // you can define everything you want for url endpoint
"handler": "[your-endpiont].random", // random is defined as a method
"config": {
"policies": []
}
}
]
}
now you have to run yarn develop or npm ... to display a random method in your strapi panel
Save this setting and retry to reach the random endpoint.
create a function as a service for getting random data in your endpoint API services.
// path: ./src/api/[your-endpiont]/services/[your-endpiont].js
'use strict';
/**
* news-list service.
*/
const { createCoreService } = require('#strapi/strapi').factories;
module.exports = createCoreService('api::news-list.news-list', ({ strapi }) => ({
async serviceGetRandom({ locale, id_nin }) { // these parametrs come from query
function getRandomElementsFromArray(array, numberOfRandomElementsToExtract = 1) {
const elements = [];
function getRandomElement(arr) {
if (elements.length < numberOfRandomElementsToExtract) {
const index = Math.floor(Math.random() * arr.length)
const element = arr.splice(index, 1)[0];
elements.push(element)
return getRandomElement(arr)
} else {
return elements
}
}
return getRandomElement([...array])
}
const newsListArray = await strapi
.db
.query("api::news-list.news-list")
.findMany({
where: {
locale: locale, // if you have multi-language data
$not: {
id: id_nin, // depend on where this endpoint API use
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
},
//? filter object throws an error when you used populate object, everything you want to filter properly best write into where{}
// filters: {
// publishedAt: {
// $notNull: true,
// },
// locale: locale
// }
})
if (!newsListArray.length) {
return null
}
return getRandomElementsFromArray(newsListArray, 2)
}
}));
explain code:
Strapi provides a Query Engine API to interact with the database layer at a lower level
strapi.db.query("api::news-list.news-list").findMany({})
The Query Engine allows operations on database entries,
I wrote this for my purpose probably you should change based on what you needed
{
where: {
locale: locale,
$not: {
id: id_nin
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
}
}
when you get data from your query, passed it to that function getRandomElementsFromArray(newsListArray, 2) to get some random item (how many random items do you want ? pass the second parameter)
At least if your array is null return null otherwise return data
create the controller
Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested route so we going to call our services in this section
// path: ./src/api/[your-endpoint]/controllers/[your-endpoint].js
'use strict';
/**
* news-list controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::news-list.news-list', ({ strapi }) => ({
async random(ctx) { // name of this methods related to something we define in route ("handler": "[your-endpiont].random",)
const entity = await strapi.service('api::news-list.news-list').serviceGetRandom(ctx.query) // call our services, you can send all query you get from url endpoint (notice that you should write your endpoint api in strapi.service("your-endpoint"))
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return this.transformResponse(sanitizedEntity);
// console.log(entity);
}
}));
I call this endpoint in my project nextjs & stapi cms
export const getRandomNewsItem = (id, locale) => {
return API
.get(`/news-list/random?locale=${locale}&id_nin=${id}`)
.then(res => res.data);
};
That's it, I'll hope you all get what to do
all resources you need
https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#creating-custom-routers
https://docs.strapi.io/developer-docs/latest/development/backend-customization/services.html#implementation
https://docs.strapi.io/developer-docs/latest/development/backend-customization/controllers.html#adding-a-new-controller
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.html
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.html#and
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/populating.html
I'm using the Google People API to populate the address book of my dashboard. I have a lot of contacts so the idea is to filter contacts by creating a specific group for this service. What I need is basically the possibility to query all the connections filtered on a specific group id. It seems the official API has only "list" method without the chance to query on. I'm using the client libraries for node.js and here it is my code:
const {google} = require('googleapis');
const getGoogleConnections = async (userToken) => {
const oauth2Client = new google.auth.OAuth2;
oauth2Client.setCredentials({
'access_token': userToken, //Passport session user
});
const googlePeople = await google.people({
version: 'v1',
auth: oauth2Client,
});
const params = {
resourceName: 'people/me',
pageSize: 200,
sortOrder: 'LAST_MODIFIED_DESCENDING',
personFields: ['names', 'emailAddresses', 'memberships', 'organizations'],
};
const connections = await googlePeople.people.connections.list(params);
return connections;
};
My response:
"connections": [
{
... //returning all specified personFieldsValues
"memberships": [
{
"metadata": {
"source": {
"type": "CONTACT",
"id": "id value"
}
},
"contactGroupMembership": {
"contactGroupId": "contact group id",
"contactGroupResourceName": "contactGroups/contact group id"
}
},
{
"metadata": {
"source": {
"type": "CONTACT",
"id": "metadata id "
}
},
"contactGroupMembership": {
"contactGroupId": "myContacts",
"contactGroupResourceName": "contactGroups/myContacts"
}
}
]
}
],
My response is correct and the API returns all the values but I can't find a way to filter these returned values on contact group id. Anyone tried this before?
Issue:
As was mentioned in comments by Tanaike, there is no way to filter the returned connections when calling people.connections.list (there's no optional query parameter or similar, unlike other list methods from Google APIs).
File a feature request:
I'd suggest you to request this on Issue Tracker's People component, if you think this functionality could be useful.
Workaround:
Meanwhile, what you can do is filtering the connections after they have been returned by the API. You could filter them according to the group ID the following way, using filter and some:
const connections = await googlePeople.people.connections.list(params);
const filteredConnections = connections.connections.filter(connection => {
return connection.memberships.some(membership => {
if (membership.contactGroupMembership) {
return membership.contactGroupMembership.contactGroupId === "YOUR_GROUP_ID";
} else return false;
});
});
I have a user model in which there is an array of tokens. Each token is an object having three key value pairs. I want to keep only one token object in tokens array with key 'access'='auth' .The code I have written is not working.Every time I run the code the new token geting pushed without removing existing object with key 'access'='auth'.Please help.
My user object looks like this:-
{
"_id": "5badcc621818710a2a8fcafa",
"email": "example#gmail.com",
"password": "example",
"tokens": [
{
"_id": "5badcc661818710a2a8fcafb",
"access": "auth",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
},
{
"_id": "5badcf6b11a6610a9d5b3f52",
"access": "auth",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
},
{
"_id": "5badcf853776410aa7bdfaba",
"access": "auth",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
}
]
}
I am pushing new object into tokens array when this function get called:-
userSchema.methods.genAuthToken = function () {
var token = jwt.sign(data, SECRET_KEY).toString();
var access = 'auth';
===>this.tokens.pull( { access: 'auth' } ); //NOT WORKING
this.tokens.push({ access, token });
return this.save().then(() => token);
}
I want to remove all existing objects where key 'access' has value 'auth' and then push new object and then save the user object.
Use filter instead. The below code should work:
userSchema.methods.genAuthToken = function () {
var token = jwt.sign(data, SECRET_KEY).toString();
var access = 'auth';
this.tokens = this.tokens.filter(function (token) { return token.access !== 'auth' });
this.tokens.push({ access, token });
return this.save().then(() => token);
Filter will remove all the objects that have access equal to auth.
I created a new collection in my project and followed the same syntax for retrieving all users to retrieve all from this collection but I end up getting an empty array or list for some reason. Can someone please let me know why that is as I am fairly new to MongoDB. below is my code. the service is being called as I put a console.log before the query and it is called. Also saving data to the DB also works its just getAll I am having trouble doing. the collection only has one field which is account number.
In the service I have
async function getAll() {
return await accountdb.find({});
}
controller
function getAll(req, res, next) {
accountdbService.getAll()
.then(account => res.json(account))
.catch(err => next(err));
}
in the front end i have
getAll() {
return this.http.get<account[]>(`${environment.apiUrl}/account`);
}
and finally in the component
ngOnInit() {
this.loadAllAccounts();
}
private loadAllAccounts() {
this.AccountService.getAll().pipe(first()).subscribe(account => {
this.accounts = account;
console.log(this.accounts);
});
console.log(this.accounts);
}
postman response when calling api:
[
{
"_id": "5b89e647e1e6540ac28d68b1",
"logDataInput": "admin changed the status of user test2 to manager",
"__v": 0,
"id": "5b89e647e1e6540ac28d68b1"
},
{
"_id": "5b89e648e1e6540ac28d68b2",
"logDataInput": "admin changed the status of user test2 to User",
"__v": 0,
"id": "5b89e648e1e6540ac28d68b2"
},
{
"_id": "5b8a2a6b16206e0cae8c71ba",
"logDataInput": "admin deactivated user test2",
"__v": 0,
"id": "5b8a2a6b16206e0cae8c71ba"
}
]
network tool 2 api calls picture
Looks like you're sending wrong variable in response from getAll function.
.then(logTrack => res.json(account)
Chage this to
.then(logTrack => res.json(logTrack)
The reason is, the getAll() function is asynchronous, that is to say the code directly after may be executed before the subscribe function is.
It looks like your array is being set but, by checking it outside of the subscribe, you're checking it before it's been set.
{
"_id": {
"$oid": "5a2de0a00d6baa43e8b925d0"
},
"name": "test",
"playList": [
{
"url": "https://p.scdn.co/mp3-preview/8aa799e60164f8a1fb311188d9d85ef65d7782c6?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "Kenny G",
"songName": "My Heart Will Go On (Love Theme from \"Titanic\")",
"_id": {
"$oid": "5a2de0ad0d6baa43e8b925d1"
}
},
{
"url": "https://p.scdn.co/mp3-preview/7c49854f18e6dfda6cd97ab5e8bc139d7ca82b7c?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "PRODUCE 101",
"songName": "PICK ME",
"_id": {
"$oid": "5a2de13b0d6baa43e8b925d2"
}
}
],
"__v": 0
}
I have a database called channels where each channels contain a playList as shown below. I want to delete a single item when a button is clicked. I can handle the onClick event part, but I am not sure how to implement the routes part.
I know that I start by doing something like
router.delete(''/channels/:id', function(req, res){
something here...
})
but how can I access a particular item (probably with a unique id?) and delete it from the DB?
EDIT
By using the GET below
router.get('/channels/:id',
isLoggedIn,
function(req, res) {
channel.findOne({'name':req.params.id},function(err,channeldata){
if(err || channeldata === null){
res.status(404).send({
message: 'Channel Not Found',
data: []
})
}
else {
res.status(200).json({
message: "channel to "+req.params.id+"success",
data:channeldata
})
}
})
});
I get the data for a single channel in my DB.
But since I am new to this stuff, I am not sure how to access each item of the playList and delete a single data.
EDIT2
var mongoose = require('mongoose');
var ChannelSchema = new mongoose.Schema({
name: {type:String,required:true},
playList: [{
songName: { type : String },
artist: { type : String },
url: { type : String }
}]
})
module.exports = mongoose.model('Channel',ChannelSchema);
You can try the following snippet that contains the DELETE (part of CRUD) endpoint for your resource collection (i.e. the channels):
router.delete('/channels/playlist/song', isLoggedIn, (req, res) => {
const channel_id = req.query.channelId;
const song_id = req.query.songId;
// the following query deletes a song form a playlist of a certain channel
channel.update({_id: ObjectId(channel_id)},{$pull:{playList:{_id:ObjectId(song_id)}}})
.exec()
.then(result => {
// for checking if document was found and deleted
// mongodb actually returns special object `result`
// which has its own certain fields
res.status(200).send({
status: "success",
message: result
});
})
.catch(error => {
// here we see if we had any problem with server or db itself
console.log(error)
res.status(500).send({
success: false,
message: "Something went wrong with DELETE /channels/:id"
})
})
});
I assume that you know what ObjectId() function does
if you do not have it declared, declare the following comment
in the beginning of the file (where you require everything)
const mongoose = require('mongoose'); // you must have this
const ObjectId = mongoose.Types.ObjectId; // gets the function
Let me know if this helps, or if you do not understand something - I will make an edit so that you get it.