How do I create custom directives to protect my GraphQL API? - authorization

I want to use custom directives to protect my GraphQL API. More specifically, for specific GraphQL fields, I want to check if users have authorisation to query those fields when a request hits my GraphQL server.
The following links are articles that contain examples on achieving this objective.
Link 1: https://www.prisma.io/blog/graphql-directive-permissions-authorization-made-easy-54c076b5368e/
Link 2: https://codeburst.io/use-custom-directives-to-protect-your-graphql-apis-a78cbbe17355
However, both examples achieve this by first constructing their Schema using the GraphQL Schema Definition Language (below is a snippet from the repo for Link 2) that demonstrates how you can use custom directives to check if users have authorisation to query specific fields (such as "rating").
require('dotenv').config();
const express = require('express');
const graphqlHTTP = require('express-graphql');
const {
makeExecutableSchema
} = require('graphql-tools');
const {directiveResolvers} = require('./directives');
const {allProductsBySupplier, addProduct, product, suppliers} = require('./resolvers');
require('./auth');
const app = express();
const port = 8080;
const typeDefs = `
directive #isAuthenticated on QUERY | FIELD
directive #hasScope(scope: [String]) on QUERY | FIELD
type Product {
id: ID!
supplierId: ID!
sku: String
qty: Int
price: Int
parrot: String
rating: Int #hasScope(scope: ["read:rating"])
}
type Supplier {
id: ID!
name: String!
}
input ProductInput {
supplierId: ID!
sku: String!
qty: Int!
price: Int!
parrot: String!
rating: Int!
}
type Query {
allProductsBySupplier: [Product] #isAuthenticated
product: Product #isAuthenticated
suppliers: [Supplier]
}
type Mutation {
addProduct(input: ProductInput!): Product #hasScope(scope: ["add:product"])
}
`;
const resolvers = {
Query: {
allProductsBySupplier,
product,
suppliers
},
Mutation: {
addProduct
}
};
const schema = makeExecutableSchema({
typeDefs,
resolvers,
directiveResolvers
});
app.use(
'/graphql',
graphqlHTTP({
schema,
graphiql: true
})
);
app.listen(port);
console.log(`server running on localhost:${port}`);
I have constructed my API without using the GraphQL schema definition language as shown below. The following snippet has been extracted from the official graphql docs.
var express = require('express');
var graphqlHTTP = require('express-graphql');
var graphql = require('graphql');
// Maps id to User object
var fakeDatabase = {
'a': {
id: 'a',
name: 'alice',
},
'b': {
id: 'b',
name: 'bob',
},
};
// Define the User type
var userType = new graphql.GraphQLObjectType({
name: 'User',
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
}
});
// Define the Query type
var queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: userType,
// `args` describes the arguments that the `user` query accepts
args: {
id: { type: graphql.GraphQLString }
},
resolve: function (_, {id}) {
return fakeDatabase[id];
}
}
}
});
var schema = new graphql.GraphQLSchema({query: queryType});
var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
How can I create custom directives to check if users have authorisation to query specific fields if I have constructed my Schema without using the GraphQL Schema Definition Language?

Related

Using sofa api with the most simple express server

Hi.
I have this most simple express graphql server.
I want to use sofa-api to make it "rest-able".
Two problems:
when you go to /api/hello it should say "Hello World!", now its null. the /graphql route does work correctly and return "Hello World!".
The rest swagger interface is not loading at /api
you can play with it here: https://stackblitz.com/edit/node-p6jnji?file=index.js,package.json
var schema = buildSchema(`
type Query{
hello:String
}`);
const openApi = OpenAPI({
schema,
info: {
title: 'Example API',
version: '3.0.0',
},
});
var root = {
hello: () => {
return 'Hello World!';
},
};
var app = express();
app.use(
'/graphql',
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
})
);
app.use(
'/api',
useSofa({
schema,
basePath: '/api',
onRoute(info) {
openApi.addRoute(info, {
basePath: '/api',
});
},
})
);
writeFileSync('./swagger.json', JSON.stringify(openApi.get(), null, 2));
app.listen(4400);
Thanks
There is no root value defined in Sofa.
In graphqlHttp call, you provide rootValue as an execution parameter but Sofa is not aware of that
I assume the most elegant way is to build your schema with the resolvers (root value)
something like this:
import { makeExecutableSchema } from '#graphql-tools/schema'
export const schema = makeExecutableSchema({ typeDefs, resolvers })
You can find more info here

How to use Mongoose Populate between 2 different Schema's

I have 2 schema's, Categories and Cards. Each Category has an array of cards, and I want to populate that array with values , but I am unsure how to go about this as the mongoose documentation is somewhat confusing to understand.
// Schemas in seperate files
// Category Schema
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const categorySchema = new Schema({
title: {
type: String,
trim: true,
max: 30,
},
cards: [{ type: Schema.Types.ObjectId, ref: "categoryCard" }],
});
module.exports = mongoose.model("category", categorySchema);
// Category Card Schema
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const categoryCardSchema = new Schema({
category: {
type: String,
trim: true,
},
name: {
type: String,
trim: true,
},
post: {
type: String,
required: true,
trim: true,
},
});
module.exports = mongoose.model("categoryCard", categoryCardSchema);
// Below is the express router file . I want users to be able to create cards for different categories , after the category is already created. It worked in postman, but it doesn't work on the front end for some reason.
router.route("/createCard").post((req, res) => {
const { title, name, post } = req.body;
newCard = new categoryCard({
category: title,
name,
post,
});
newCard.save();
category.findOne({ title }).exec((err, item) => {
if (!err) {
item.cards.push(newCard._id);
item.save();
res.send(item);
} else {
res.send(err);
}
});
});
You can Follow this code...
let categorys= await category.findOne({ title }).populate("cards")

How can I Connect a Postgres Schema in Nodejs and Switching Schemas Dynamically Using Sequelize?

I have to connect to a Postgres database in Node.js and I want to switch schemas dynamically Using Sequelize Library.
Here is my Table Model and Controller Code.
module.exports = function (sequelize, DataTypes) {
const customer = sequelize.define(
'customer',
{
id: {
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
schema_name: {
type: DataTypes.STRING,
allowNull: false,
},
created_on: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW(),
},
},
{
schema: 'public',
}
)
return customer
}
And Controller Code
exports.login = async (req, res, next) => {
const { email, password, domain } = req.body
const domainData = await customer.findOne({
where: { schema_name: domain },
})
console.log('Log: exports.login -> domainData', domainData)
}
Once the response received I have to Switch Schemas Dynamically according to the above result.
Help me Guys Please
I resolved the problem using sequelize.query() function.
for getting Domain...
const domainData = await sequelize.query(
`SELECT schema_name from public.customer_management_client where schema_name = '${domain}'`
)
for switching to dynamic schema
const UserData = await sequelize.query(
`SELECT * from ${domainData[0][0].schema_name}.user_access_user where email = '${email}'`
)

GraphQL. How to write resolver

I just started to get into GraphQL. I am using GraphQL.js and express. Right now I am trying to build a simple example using a hardcoded JSON as the data in my javascript file. I then want to use express middleware to listen to HTTP requests via curl or insomnia. In the middleware I want to extract the query using body-parser. Right now I am having trouble with resolvers.
Please have a look at my code.
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema, graphql } = require('graphql');
var bodyParser = require('body-parser');
var schema = buildSchema(`
type Product {
name: String!
price: Int!
}
type Query {
product(name: String): Product
}
`);
var products = {
'Mango': {
name: 'Mango',
price: 12,
},
'Apfel': {
name: 'Apfel',
price: 3,
},
};
resolvers = {
Query: {
product: (root, { name}) => {
return products[name];
},
},
};
var app = express();
app.use(bodyParser.text({ type: 'application/graphql' }));
app.post('/graphql', (req, res) => {
graphql(schema, req.body)
.then((result) => {
res.send(JSON.stringify(result, null, 2));
});
});
app.listen(4000);
This does not work. When I post a query using curl with
curl -XPOST -H "Content-Type: application/graphql" -d "{product(name: \"Apfel\"){name price}}" http://localhost:4000/graphql
I get the response {"data". {"product": null}}. The resolver doesn't get called. How can I do this correctly?
Can you try this?
var resolvers = {
product: (args) => {
return products[args.name];
},
};
app.post('/graphql', (req, res) => {
graphql(schema, req.body, resolvers)
.then((result) => {
res.send(JSON.stringify(result, null, 2));
});
});
I think this can solve your issue
I recomend watching FunFunFunction series episode focused on GraphQl:
GraphQl Basics
All of his episodes are quite interesting (and really fun)...

Link mongoose schemas using ID's pushed to an array?

I'm trying to link 2 of my mongoose schemas using the ID's assigned by mongoose (_id)
I have a users schema and a servers schema, every server should have 1 user as the owner but every user can have many servers. So I'm saving the req.user.id(from passportJS) in the server schema and there is an array in users schema where I want to push the ID's for servers when they are created. I haven't quite made it to using populate yet because I don't understand how to take the ID from the server that is about to be created and push it to the users server array
Here is my servers schema:
var mongoose = require ('mongoose');
var timestamps = require('mongoose-times');
var Schema = mongoose.Schema;
var ServerSchema = new Schema({
name: String,
imgurl: String,
address: String,
port: String,
tags: Array,
votifier :{
enabled: Boolean,
address: String,
port: String,
pubKey: String
},
ownerID: {type: Schema.ObjectId, ref: 'User'},
upvotes: {type: Number, default: 0}
});
ServerSchema.plugin(timestamps);
module.exports = mongoose.model('Server', ServerSchema);
Here's my users schema:
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var timestamps = require('mongoose-times');
var Schema = mongoose.Schema;
var userSchema = Schema({
local :{
username: {type: String, required: true},
password: {type: String, required: true}
},
email: {type: String, required: true},
imgId: String,
servers: [{type: Schema.ObjectId, ref: 'Server'}]
});
userSchema.plugin(timestamps);
//methods
//gen hash
userSchema.methods.generateHash = function(password, next){
bcrypt.hash(password, bcrypt.genSaltSync(8), null, next);
console.log(password + ': Password has generated hash');
};
userSchema.methods.validPassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.local.password, function(err, isMatch){
if(err) return cb(err);
cb(null, isMatch);
});
};
module.exports = mongoose.model('User', userSchema);
How can I take the _ID of a server that is being created and push it to the array in the User schema which I will findByID using the OwnerID in server schema?
Any help would be appreciated, also if this is the complete wrong way to do this sort of thing please let me know! Thanks
You can create a new server like that:
newServer = new ServerSchema(...);
Then save it:
newServer.save(callback);
The callback should be define to do whatever you want to do with the actual saved result. Lets say that you have a function updateUser that receive userId and serverId and it pushes the serverId to the User of that Id.
Then your callback should be something like...
callback = function(serverSaveErr, serverSavedDoc) {
if (serverSaveErr) { handle errors ... }
else { updateUser(userId, serverSavedDoc._id }
}