I'm creating an API where I want to create dynamically multiple graphql endpoints according to the user's preferences without restarting the server.
In general is something very simple, the user will specify the graphql schema and the resolvers, and after the user will push a button to generate a graphql endpoint which will be under the mydomain/randomString
The only code that I can provide so far is how am I creating a graphql endpoint in my expressjs app, which happens on one of the initial files that run when the nodejs server starts.
const {ApolloServer, gql} = require('apollo-server-express');
const typeDefs = gql(fs.readFileSync('./server/graphql/schema.graphql', {encoding: 'utf-8'}));
const resolvers = require ('./server/graphql/resolvers');
const graphqlServer = new ApolloServer({
typeDefs
,resolvers
,engine: {
apiKey: "aRandomString"
}
,introspection: true
// ,context: ({req}) => ({user: req.user && db.users.get(req.user.sub)})
});
After the user generates the endpoint, he/she will be able to query on that graphql endpoint.
Related
I'm looking for a better way to authenticate Google Cloud Function with a service account. Right now I'm storing the credentials json file on the backend. This is the code for my app https://github.com/ChristianOConnor/spheron-react-api-stack. This app could be deployed on any hosting platform, but at the moment the app is built to deploy on a Web3 protocol called Spheron. TLDR, Spheron runs the backend express server on a web3 friendly content serving/hosting platform called Akash. This means that whoever is hosting my backend express server has access to my GCP service account's credentials. You can see all of the code in the link I provided but just for ease of access this is the server.js file which will be on Akash.
server.js
var express = require("express");
var app = express();
require("dotenv").config();
const GoogleAuth = require("google-auth-library").GoogleAuth;
const cors = require("cors");
app.use(
cors({ origin: process.env.ORIGIN, credentials: process.env.CREDENTIALS })
);
app.get("/hello", async function (req, res) {
const keyInJsn = JSON.parse(process.env.CREDENTIALS_STR);
const auth = new GoogleAuth({
credentials: keyInJsn,
});
const url = process.env.RUN_APP_URL;
//Create your client with an Identity token.
const client = await auth.getIdTokenClient(url);
const result = await client.request({ url });
const resData = result.data;
res.send(resData);
});
var server = app.listen(8081, function () {
var host = server.address().address;
var port = server.address().port;
console.log("Example app listening at http://localhost:", port);
});
process.env.CREDENTIALS_STR is the service account credentials set up in this format:
CREDENTIALS_STR={"type": "service_account","project_id": "<PROJECT ID>","private_key_id": "<PRIVATE KEY ID>","private_key": "-----BEGIN PRIVATE KEY-----\<PRIVATE KEY>\n-----END PRIVATE KEY-----\n","client_email": "<SERVICE ACCOUNT NAME>#<PROJECT NAME>.iam.gserviceaccount.com","client_id": "<CLIENT ID>","auth_uri": "https://accounts.google.com/o/oauth2/auth","token_uri": "https://oauth2.googleapis.com/token","auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/<SERVICE ACCOUNT NAME>.iam.gserviceaccount.com"}
The Akash provider can see this string. Is there a better way to do authentication for a GCP service account that doesn't expose the credntials to a hosting/server provider?
Also don't be throw off by the web3 stuff. This app essentially works the same as a traditional web2 app with a backend and a client. If it helps you to think about it different, picture that I'm deploying on Netlify with a static client and a Netlify Function.
The compromise I came to was creating an API Gateway for the function. This allows the function to be called without any credentials and still run from a service account. It creates a separate quasi-vulnerability though, as anyone with the API Gateway link can also call the function unauthenticated.
First, I enabled Service Management APIs, API Gateway API, and Service Control API. Then I made an API Gateway with my service account that runs my referenced cloud function. I uploaded a file like this for the api spec:
swagger: '2.0'
info:
title: api-gateway-cloud-function
description: API Gateway Calling Cloud Function
version: 1.0.0
schemes:
- https
produces:
- application/json
paths:
/whateveryouwanttocallthispath:
get:
summary: My Cloud Function
operationId: whatever
x-google-backend:
address: <CLOUD_RUN_URL>
responses:
'200':
description: OK
You can test it by running the function via curl command in a bash terminal curl {gatewayId}-{hash}.{region_code}.gateway.dev/v1/whateveryouwanttocallthispath. It works with no credential json file.
The problem is that you could achieve a similar result by just allowing the function to be called unauthenticated... Idk if this method has many benefits.
I want to pass the express req and res object to my context because i want to use express-sessions to do session-based-auth because the default requests from the context does not know about sessions. Here is what I have tried
app.use("/graphql", (req, res) => {
return createYoga({
context: ({ params }) => {
return {
req,
params,
res,
prisma,
redis: redisClient,
};
},
graphiql: true,
landingPage: false,
cors: false,
schema,
});
});
But if i try this it seems like the request is not going through.
According to the docs, while running in node.js and express runtime, the context will automatically have the request and response.
Server Context
When creating the server instance, GraphQL Yoga accepts an additional
object from your base server framework or library that will be merged
with the default context. Node.js (standalone, express and Next.js
etc.)
If you are using GraphQL Yoga as a standalone server with createServer
from the http(s) module or exposing it as a middleware as we show in
the express or Next.js integration recipes.
req - Node.js IncomingMessage object
res - Node.js ServerResponse object
The req and res objects are added to the initial context object.
const serverContext = { ...defaultContext, req, res }
Thus, when using #graphql-yoga/node, it is possible to access
context.req and context.res within the GraphQL resolvers or the user
context factory function.
However, we recommend avoiding using context.req and context.res
wherever possible and instead favor context.request, as it is more
future-proof and platform independent (as Node.js HTTP servers adopt
the Fetch Response API).
I did try this out and the context does have the req and res objects.
I'm trying to build SSR application using NextJS and apollo-client on the frontend, and graphql with express using (graphQL Yoga) on the backend.
I came from client side rendering background and things there are simpler than SSR when it comes to authentication, in regular client side rendering my approach to authenticate user was like:
1- once the user login after server validation, sign a JWT with current user data, then send it to the client side, and save it in localstorage or cookies, etc...
2- implement a loadUser() function and call it in the (root) App component's useEffect hook to load the user in every component (page) if the JWT in localstorage is valid.
3- if the JWT isn't there or is invalid just return user as null and redirect to login page.
so in Next.js i know we can't access localstorage cause it works server side, so we just save the token in a cookie, and the approach i implemented is painful and i was wondering if there is an pimplier way, my approach is like:
1- once the user login he calls the login mutation which sets a cookie in the req header, and return a user and any data i want.
2- in each page that requires authentication i need to get the token from the cookie to send it back in the header and i did that in getInitialProps() or getServerSideProps() cause both runs server side and have access to the request cookies in the header like so:
export const getServerSideProps = async ctx => {
const apolloClient = initializeApollo();
// get the cookies from the headers in the request object
const token = ctx.req.headers.cookie ? ctx.req.headers.cookie : null;
return {
props: {
initialApolloState: apolloClient.cache.extract(),
token: token
}
};
};
now i have access to the token in the page props and can send the token back with the req header with my apollo client like so:
let getUserQuery = await apolloClient.query({
query: GET_USER_QUERY,
variables: { id: ctx.params.id },
context: { headers: { token: token } }
});
now i have access to the token in the server side request like req.headers.token
what i wanna achieve:
1- is there an easier way to implement loadUser() that loads the user with every page render that i can implement in next.js custom _app , i found this answer but it doesn't return auth object or user in all components as he mentioned in his answer.
2- i read that if i set cookies httpOnly and credentials: "include" i have access to cookie in every request, but it seems that it doesn't work with apollo client, that would be awesome if there is an alternative approach.
3- there is apollo-link-context provided by apollo team where i can send a token or any value in every request's header using setContext() like so:
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
but since i don't have access to localstorage i can't implement it cause next runs server side, so if anyone has an implementation for this please consider sharing.
PS. i made this thread after searching and reading for like 1 week and it's my last resort to ask you guys, and thanks in advance.
get token by store
store.getState()..path.to.your.token
the problem is that the token doesn't completely update when the blind changes and I'm looking for a solution.
I'm building a FeathersJS service behind an authentication very similar to the messages service that is part of the FeathersJS demo chat app: https://github.com/feathersjs/feathers-chat/
Additionally, I'd like to define an event listener that should store the messages it receives to the app's messages service and call all necessary hooks to notify the client application.
Here's my current approach:
module.exports = function () {
const app = this;
const Model = createModel(app);
const paginate = app.get('paginate');
const options = {
name: 'messages',
Model,
paginate
};
app.use('/messages', createService(options));
const service = app.service('messages');
service.hooks(hooks);
const sender = new MyExternalMessageSender();
sender.on('message', (msg) => {
service.create(msg, {user: {_id: 0}}).then(result => console.log(result));
});
if (service.filter) {
service.filter(filters);
}
};
This sometimes works fine and sometimes it randomly results in an error as soon as MyExternalMessageSender is notified and tries to call the message service's create method.
NotAuthenticated: No auth token
at Error.NotAuthenticated (projects\feathers-chat\node_modules\feathers-errors\lib\index.js:100:17)
at projects\feathers-chat\node_modules\feathers-authentication\lib\hooks\authenticate.js:102:31
How can I store messages the correct way without my application itself needing to use a JWT?
Thanks for your support!
I am not sure what MyExternalMessageSender does but authentication is skipped by default in internal service calls. If it is an internal service call is determined by params.provider being set. So if you pass hook.params from an external call (where provider is normally set to rest or socketio) to subsequent service calls authentication will run (since it thinks it is an external call).
This can be avoided by removing the provider property before passing the original parameters e.g. with Lodash _.omit:
myservice.find(_.omit(params, 'provider'))
I am trying to create a route which will perform some CRUD operations on DynamoDB.
At high level , it can be understood as :
The node js server application is running .(i.e. command 'node server.js' is being triggered)
The user uses POSTMAN of chrome browser to do route requests.
The user does a GET request for 'http://localhost:8080/listtablesofdynamodb'.
The specific route connected with this url gets hit which should do dynamodb specific activity. (like connecting to dynamodb ,fetching table names and showing it in callback method.)
the reason I am asking this question is because I could not find any relevant tutorial of how to do dynamodb activity by using express js of node. All I could find is console applications on aws website which seems not useful for me.
Any kind of help is highly appreciated.
Access key required
All you need to d is make a DynamoDB object to connect too
var ddb = require('dynamodb').ddb({ accessKeyId: '< your_access_key_id >', secretAccessKey: '< your_secret_access_key >' });
put this under your require statements, turn on your server. Then you can just fill out the routes to do the CRUD operations you need.
To test it use
ddb.listTables({}, function(err, res) {console.log(res);});
This will list all the tables in your db.
for full source check here
Best of luck
fortunately, I could manage to use aws-sdk in my route. the solution have two stages:
Run the code in your aws account's EC2 instance and attach an IAM role which allows the ec2 instance to talk to dynamodb. (in this way you don't need to hard-code access key in your code) see this article.
can take reference of the below code for initial code scaffolding.
`
var express = require('express');
var router = express.Router();
var AWS = require("aws-sdk");
AWS.config.update({
region: "us-west-2",
endpoint: "dynamodb endpoint specific to your aws account"
});
var dynamodb = new AWS.DynamoDB();
var params = {
ExclusiveStartTableName: "stringvalue",
Limit: 10
};
/* GET users listing. */
router.get('/', function (req, res) {
console.log("entered into dynadb route");
dynamodb.listTables(params, function (err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
res.send(data);
}
});
});
module.exports = router;
`