Passing a token through Query? - graphql

I have a Graph QL server running (Apollo Server 2) and the API behind it requires every request to include a token.
Currently the token comes from HTTP Request Cookie. This was simple enough to work. When the request comes in, grab the cookie from the header and pass it along to the HTTP request to be sent to the API server through the resolvers.
I'd like to make it so a GraphQL client can pass this token along through the POST query itself.
Basically wondering if I can define a global GQL variable of some sort. "All queries, this variable is required."

I had a similar implementation in Typescript, and in order to achieve something like this, I've define an object:
const globalInput = {
token: {
type: GraphQLString;
}
}
And then use it in your GraphQLObjectType:
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => ({
myObject: {
type: MyTypeObject,
args: { ...globalInput },
resolve: (source: any, args: any) => {
// global input values can be access in args
// ex: args.token
return {}
}
}
})
})
The problem is that I need to extend it(...globalInput) it in every object type.
But it does the job.

Related

Use Apollo server as a pass through of a query from client

I have a use case where I have apollo-server-express running with a React based apollo-client. I have an external graphql-datasource for some queries. Currently, I've configured apollo-datasource-graphql to be used as a data source for my apollo-server-express. However, this requires duplication of work on the resolver in Apollo as well as the resolver on my external graphql system.
Is there a way for me to pass queries made in the client through the Apollo Server and to the external graphql data source?
Maybe you could access the GraphQL AST from the fourth resolver argument (resolveInfo) and pass it into a GraphQL client?
Here is some prototype code:
import { print } from 'graphql/language/printer';
function forwardOperationResolver(root, args, context, resolveInfo) {
return fetch('https://remote.host/graphql', {
method: 'POST',
body: JSON.stringify({
query: print(resolveInfo.operation),
variables: resolverInfo.variableValues,
}),
})
.then(response => response.json())
.then(response => {
if (response.errors) {
// Handle errors
}
return response.data;
});
}
Downside: This breaks a few things that usually work in GraphQL like partial results and error locations...

Nuxt Apollo with dynamic headers for a session based authentication

Apollo is not storing the header from the query dynamically.
pages/index.vue
methods: {
fetchCars() {
const token = Cookies.get('XSRF-TOKEN')
console.log(token) // 🟢 Token is shown in console
this.$apollo.query({
query: gql`
query {
cars {
uuid
name
}
}
`,
headers: {
'X-XSRF-TOKEN': token, // â­• Fetch without header
},
})
},
},
Is there a way to set the header value new for every Apollo request?
I have a separate Frontend and Backend. For the Frontend I am using Nuxt.js with Apollo. I want to have a session based communication with my server. For this reason I need to send the CSRF-Token with every Request.
Now the problem: On the first load of the page there is no Cookie set on the browser. I do a GET-Request on every initialization of my Nuxt application.
plugins/csrf.js
fetch('http://127.0.0.1:8000/api/csrf-cookie', {
credentials: 'include',
})
Now I have a valid Cookie set on my side and want to communicate with the GraphQL Server but my header is not set dynamically in the query. Does anyone know how I can solve this?
My Laravel Backend is throwing now a 419 Token Mismatch Exception because I did not send a CSRF-Token with my request.
Link to the repository: https://github.com/SuddenlyRust/session-based-auth
[SOLVED] Working solution: https://github.com/SuddenlyRust/session-based-auth/commit/de8fb9c18b00e58655f154f8d0c95a677d9b685b Thanks to the help of kofh in the Nuxt Apollo discord channel 🎉
In order to accomplish this, we need to access the code that gets run every time a fetch happens. This code lives inside your Apollo client's HttpLink. While the #nuxtjs/apollo module gives us many options, we can't quite configure this at such a high level.
Step 1: Creating a client plugin
As noted in the setup section of the Apollo module's docs, we can supply a path to a plugin that will define a clientConfig:
// nuxt.config.js
{
apollo: {
clientConfigs: {
default: '~/plugins/apollo-client.js'
}
}
}
This plugin should export a function which receives the nuxt context. It should return the configuration to be passed to the vue-cli-plugin-apollo's createApolloClient utility. You don't need to worry about that file, but it is how #nuxtjs/apollo creates the client internally.
Step 2: Creating the custom httpLink
In createApolloClient's options, we see we can disable defaultHttpLink and instead supply our own link. link needs to be the output of Apollo's official createHttpLink utility, docs for which can be found here. The option we're most interested in is the fetch option which as the docs state, is
a fetch compatible API for making a request
This boils down to meaning a function that takes uri and options parameters and returns a Promise that represents the network interaction.
Step 3: Creating the custom fetch method
As stated above, we need a function that takes uri and options and returns a promise. This function will be a simple passthrough to the standard fetch method (you may need to add isomorphic-fetch to your dependencies and import it here depending on your setup).
We'll extract your cookie the same as you did in your question, and then set it as a header. The fetch function should look like this:
(uri, options) => {
const token = Cookies.get('XSRF-TOKEN')
options.headers['X-XSRF-TOKEN'] = token
return fetch(uri, options)
}
Putting it all together
Ultimately, your ~/plugins/apollo-client.js file should look something like this:
import { createHttpLink } from 'apollo-link-http'
import fetch from 'isomorphic-fetch'
export default function(context) {
return {
defaultHttpLink: false,
link: createHttpLink({
uri: '/graphql',
credentials: 'include',
fetch: (uri, options) => {
const token = Cookies.get('XSRF-TOKEN')
options.headers['X-XSRF-TOKEN'] = token
return fetch(uri, options)
}
})
}
}

How can I alias specific GraphQL requests in Cypress?

In Cypress, it is well-documented that you can alias specific network requests, which you can then "wait" on. This is especially helpful if you want to do something in Cypress after a specific network request has fired and finished.
Example below from Cypress documentation:
cy.server()
cy.route('POST', '**/users').as('postUser') // ALIASING OCCURS HERE
cy.visit('/users')
cy.get('#first-name').type('Julius{enter}')
cy.wait('#postUser')
However, since I'm using GraphQL in my app, aliasing no longer becomes a straightforward affair. This is because all GraphQL queries share one endpoint /graphql.
Despite it not being possible to differentiate between different graphQL queries using the url endpoint alone, it is possible to differentiate graphQL queries using operationName (refer to following image).
Having dug through the documentation, there doesn't appear to be a way to alias graphQL endpoints using operationName from the request body. I'm also returning the operationName (yellow arrow) as a custom property in my response header; however, I haven't managed to find a way to use it to alias specific graphQL queries either.
FAILED METHOD 1: This method attempts to use the purple arrow shown in image.
cy.server();
cy.route({
method: 'POST',
url: '/graphql',
onResponse(reqObj) {
if (reqObj.request.body.operationName === 'editIpo') {
cy.wrap('editIpo').as('graphqlEditIpo');
}
},
});
cy.wait('#graphqlEditIpo');
This method doesn't work since the graphqlEditIpo alias is registered at runtime and as such, the error I receive is as follows.
CypressError: cy.wait() could not find a registered alias for: '#graphqlEditIpo'. Available aliases are: 'ipoInitial, graphql'.
FAILED METHOD 2: This method attempts to use the yellow arrow shown in image.
cy.server();
cy.route({
method: 'POST',
url: '/graphql',
headers: {
'operation-name': 'editIpo',
},
}).as('graphql');
cy.wait('graphql');
This method doesn't work because the headers property in the options object for cy.route is actually meant to accept response headers for stubbed routes per the docs. Here, I'm trying to use it to identify my specific graphQL query, which obviously won't work.
Which leads me to my question: How can I alias specific graphQL queries/mutations in Cypress? Have I missed something?
The intercept API introduced in 6.0.0 supports this via the request handler function. I used it in my code like so:
cy.intercept('POST', '/graphql', req => {
if (req.body.operationName === 'queryName') {
req.alias = 'queryName';
} else if (req.body.operationName === 'mutationName') {
req.alias = 'mutationName';
} else if (...) {
...
}
});
Where queryName and mutationName are the names of your GQL operations. You can add an additional condition for each request that you would like to alias. You can then wait for them like so:
// Wait on single request
cy.wait('#mutationName');
// Wait on multiple requests.
// Useful if several requests are fired at once, for example on page load.
cy.wait(['#queryName, #mutationName',...]);
The docs have a similar example here: https://docs.cypress.io/api/commands/intercept.html#Aliasing-individual-requests.
This works for me!
Cypress.Commands.add('waitForGraph', operationName => {
const GRAPH_URL = '/api/v2/graph/';
cy.route('POST', GRAPH_URL).as("graphqlRequest");
//This will capture every request
cy.wait('#graphqlRequest').then(({ request }) => {
// If the captured request doesn't match the operation name of your query
// it will wait again for the next one until it gets matched.
if (request.body.operationName !== operationName) {
return cy.waitForGraph(operationName)
}
})
})
Just remember to write your queries with unique names as posible, because the operation name relies on it.
If 'waiting' and not 'aliasing' in itself is the main purpose, the easiest way to do this, as I've encountered thus far, is by aliasing the general graphql requests and then making a recursive function call to 'wait' targeting the newly created alias until you find the specific graphql operation you were looking for.
e.g.
Cypress.Commands.add('waitFor', operationName => {
cy.wait('#graphqlRequest').then(({ request }) => {
if (request.body.operationName !== operationName) {
return cy.waitFor(operationName)
}
})
})
This of course have its caveats and may or may not work in your context. But it works for us.
I hope Cypress enables this in a less hacky way in the future.
PS. I want to give credit to where I got the inspiration to this from, but it seemt to be lost in cyberspace.
Since I was having the same issue and I did not find a real solution for this problem I combined different options and created a workaround that solves my problem. Hopefully this can help someone else too.
I do not really 'wait' for the request to be happen but I catch them all, based on **/graphql url and match the operationName in the request. On a match a function will be executed with the data as parameter. In this function the tests can be defined.
graphQLResponse.js
export const onGraphQLResponse = (resolvers, args) => {
resolvers.forEach((n) => {
const operationName = Object.keys(n).shift();
const nextFn = n[operationName];
if (args.request.body.operationName === operationName) {
handleGraphQLResponse(nextFn)(args.response)(operationName);
}
});
};
const handleGraphQLResponse = (next) => {
return (response) => {
const responseBody = Cypress._.get(response, "body");
return async (alias) => {
await Cypress.Blob.blobToBase64String(responseBody)
.then((blobResponse) => atob(blobResponse))
.then((jsonString) => JSON.parse(jsonString))
.then((jsonResponse) => {
Cypress.log({
name: "wait blob",
displayName: `Wait ${alias}`,
consoleProps: () => {
return jsonResponse.data;
}
}).end();
return jsonResponse.data;
})
.then((data) => {
next(data);
});
};
};
};
In a test file
Bind an array with objects where the key is the operationName and the value is the resolve function.
import { onGraphQLResponse } from "./util/graphQLResponse";
describe("Foo and Bar", function() {
it("Should be able to test GraphQL response data", () => {
cy.server();
cy.route({
method: "POST",
url: "**/graphql",
onResponse: onGraphQLResponse.bind(null, [
{"some operationName": testResponse},
{"some other operationName": testOtherResponse}
])
}).as("graphql");
cy.visit("");
function testResponse(result) {
const foo = result.foo;
expect(foo.label).to.equal("Foo label");
}
function testOtherResponse(result) {
const bar = result.bar;
expect(bar.label).to.equal("Bar label");
}
});
}
Credits
Used the blob command from glebbahmutov.com
This is what you're looking for (New in Cypress 5.6.0):
cy.route2('POST', '/graphql', (req) => {
if (req.body.includes('operationName')) {
req.alias = 'gqlMutation'
}
})
// assert that a matching request has been made
cy.wait('#gqlMutation')
Documentation:
https://docs.cypress.io/api/commands/route2.html#Waiting-on-a-request
I hope that this helps!
I used some of these code examples but had to change it slightly to add the onRequest param to the cy.route and also add the date.Now (could add any auto incrementer, open to other solutions on this) to allow multiple calls to the same GraphQL operation name in the same test. Thanks for pointing me in the right direction!
Cypress.Commands.add('waitForGraph', (operationName) => {
const now = Date.now()
let operationNameFromRequest
cy.route({
method: 'POST',
url: '**graphql',
onRequest: (xhr) => {
operationNameFromRequest = xhr.request.body.operationName
},
}).as(`graphqlRequest${now}`)
//This will capture every request
cy.wait(`#graphqlRequest${now}`).then(({ xhr }) => {
// If the captured request doesn't match the operation name of your query
// it will wait again for the next one until it gets matched.
if (operationNameFromRequest !== operationName) {
return cy.waitForGraph(operationName)
}
})
})
to use:
cy.waitForGraph('QueryAllOrganizations').then((xhr) => { ...
This is how I managed to differentiate each GraphQL request. We use cypress-cucumber-preprocessor so we have a common.js file in /cypress/integration/common/ where we can call a before and beforeEach hook which are called before any feature file.
I tried the solutions here, but couldn't come up with something stable since, in our application, many GraphQL requests are triggered at the same time for some actions.
I ended up storing every GraphQL requests in a global object called graphql_accumulator with a timestamp for each occurence.
It was then easier to manage individual request with cypress command should.
common.js:
beforeEach(() => {
for (const query in graphql_accumulator) {
delete graphql_accumulator[query];
}
cy.server();
cy.route({
method: 'POST',
url: '**/graphql',
onResponse(xhr) {
const queryName = xhr.requestBody.get('query').trim().split(/[({ ]/)[1];
if (!(queryName in graphql_accumulator)) graphql_accumulator[queryName] = [];
graphql_accumulator[queryName].push({timeStamp: nowStamp('HHmmssSS'), data: xhr.responseBody.data})
}
});
});
I have to extract the queryName from the FormData since we don't have (yet) the key operationName in the request header, but this would be where you would use this key.
commands.js
Cypress.Commands.add('waitGraphQL', {prevSubject:false}, (queryName) => {
Cypress.log({
displayName: 'wait gql',
consoleProps() {
return {
'graphQL Accumulator': graphql_accumulator
}
}
});
const timeMark = nowStamp('HHmmssSS');
cy.wrap(graphql_accumulator, {log:false}).should('have.property', queryName)
.and("satisfy", responses => responses.some(response => response['timeStamp'] >= timeMark));
});
It's also important to allow cypress to manage GraphQL requests by adding these settings in /cypress/support/index.js:
Cypress.on('window:before:load', win => {
// unfilters incoming GraphQL requests in cypress so we can see them in the UI
// and track them with cy.server; cy.route
win.fetch = null;
win.Blob = null; // Avoid Blob format for GraphQL responses
});
I use it like this:
cy.waitGraphQL('QueryChannelConfigs');
cy.get(button_edit_market).click();
cy.waitGraphQL will wait for the latest target request, the one that will be stored after the call.
Hope this helps.
Somewhere else this method was suggested.
Btw it all becomes a bit easier once you migrate to Cypress v5.x and make use of the new route (route2) method.
Our use case involved multiple GraphQL calls on one page. We had to use a modified version of the responses from above:
Cypress.Commands.add('createGql', operation => {
cy.route({
method: 'POST',
url: '**/graphql',
}).as(operation);
});
Cypress.Commands.add('waitForGql', (operation, nextOperation) => {
cy.wait(`#${operation}`).then(({ request }) => {
if (request.body.operationName !== operation) {
return cy.waitForGql(operation);
}
cy.route({
method: 'POST',
url: '**/graphql',
}).as(nextOperation || 'gqlRequest');
});
});
The issue is that ALL GraphQL requests share the same URL, so once you create a cy.route() for one GraphQL query, Cypress will match all the following GraphQL queries to that. After it matches, we set cy.route() to just a default label of gqlRequest or the next query.
Our test:
cy.get(someSelector)
.should('be.visible')
.type(someText)
.createGql('gqlOperation1')
.waitForGql('gqlOperation1', 'gqlOperation2') // Create next cy.route() for the next query, or it won't match
.get(someSelector2)
.should('be.visible')
.click();
cy.waitForGql('gqlOperation2')
.get(someSelector3)
.should('be.visible')
.click();

How to send a GraphQL Query from Node.js to Prisma

I just learnt how to create a GraphlQL server using graphql-yoga and prisma-binding based on the HowToGraphQL tutorial.
Question: The only way to query the database so far was to use the Prisma Playground webpage that was started by running the command graphql playground.
Is it possible to perform the same query from a Node.js script? I came across the Apollo client but it seems to be meant for use from a frontend layer like React, Vue, Angular.
This is absolutely possible, in the end the Prisma API is just plain HTTP where you put the query into the body of a POST request.
You therefore can use fetch or prisma-binding inside your Node script as well.
Check out this tutorial to learn more: https://www.prisma.io/docs/tutorials/access-prisma-from-scripts/access-prisma-from-a-node-script-using-prisma-bindings-vbadiyyee9
This might also be helpful as it explains how to use fetch to query the API: https://github.com/nikolasburk/gse/tree/master/3-Use-Prisma-GraphQL-API-from-Code
This is what using fetch looks like:
const fetch = require('node-fetch')
const endpoint = '__YOUR_PRISMA_ENDPOINT__'
const query = `
query {
users {
id
name
posts {
id
title
}
}
}
`
fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: query })
})
.then(response => response.json())
.then(result => console.log(JSON.stringify(result)))
If you want to use a lightweight wrapper around fetch that saves you from writing boilerplate, be sure to check out graphql-request.
And here is how you use Prisma bindings:
const { Prisma } = require('prisma-binding')
const prisma = new Prisma({
typeDefs: 'prisma.graphql',
endpoint: '__YOUR_PRISMA_ENDPOINT__'
})
// send `users` query
prisma.query.users({}, `{ id name }`)
.then(users => console.log(users))
.then(() =>
// send `createUser` mutation
prisma.mutation.createUser(
{
data: { name: `Sarah` },
},
`{ id name }`,
),
)
.then(newUser => {
console.log(newUser)
return newUser
})
.then(newUser =>
// send `user` query
prisma.query.user(
{
where: { id: newUser.id },
},
`{ name }`,
),
)
.then(user => console.log(user))
Since you are using Prisma and want to query it from a NodeJS script, I think you might have overlooked the option to generate a client from your Prisma definitions.
It takes care of handling create/read/update/delete/upsert methods depending on your datamodel.
Also, you can worry less about keeping your models and queries/mutations in sync since it is generated using the Prisma CLI (prisma generate).
I find it to save a lot of coding time compared to using raw GrahQL queries, which I save for more complicated queries/mutations.
Check their official documentation for more details.
Also, note that using the Prisma client is the recommended way of using Prisma in prisma-binding resository, unless:
Unless you explicitly want to use schema delegation
which I can't tell you much about.
I did not know of the prisma-binding package untill I read your question.
EDIT:
Here is another link that puts them both in perspective

Cypress w/graphql - having issues getting AUTH with testing via UI. Better way to stub mutation?

So, if I am testing pages in a vacuum without much interaction with the backend, it works great. I am having issues with actually interacting with my UI if it hits any type of service. Basically, nothing is Auth'd. I try programmatically setCookie, no dice. I try to read the cookie, nope. Btw, my whole site requires a login.
cy.setCookie('sess', ';askjdfa;skdjfa;skdjfa;skdjfa;skfjd');<-- does not work
cy.getCookie('sess').should('exist') <-- does not work
I am having an issue on really the best way to "test" this. For example, I have an account section that a user can "update" their personals. I try, fill out the form (via UI testing), but the submission is rejected, no Auth. EVEN THOUGH I just logged in (via UI testing). - I know I need to remove that since it is bad practice to UI-Login for every section of my site.
So, I don't know how to stub graphql calls with cy.request(). Here is my mutation.
mutation Login($email: Email!, $password: String!) {
login(email: $email, password: $password) {
userID
firstName
lastName
}
}
Right now, I am importing the login spec for each section of the site i am testing. I know this is an anti-pattern. Like to solve this problem.
My AUTH (cookie) is not being set. Even when I try to set it, programmatically, doesn't work.
Maybe I should just stub out my graphql mutations? How?
Lastly, IF I am stubbing out my graphql mututations, how do I update the session ( via my main session query ). If I can get these mutations to work, then refreshing the page will get my my updated data, so I'm not completely needing this part.
Any ideas?
I didn't do the stub and all those, as you were asking how the mutation would work with cy.request in my other post. I did it this way and it just basically works. Hopefully this would help
I created a const first though
export const join_graphQL = (query, extra={}) => {
return `mutation {
${query}(join: { email: "${extra.email}", id: "${extra.id}" }) {
id, name, email
}
}`
};
request config const
export const graphqlReqConfig = (body={}, api=graphQlapi, method='POST') => {
return {
method,
body,
url: api,
failOnStatusCode: false
}
};
mutation query with cy.request
const mutationQuery = join_graphQL('mutationName', {
email: "email",
id: 38293
});
cy.request(graphqlReqConfig({
query: mutationQuery
})).then((res) => {
const data = res.body.data['mutationName']; // your result
});
hopefully it's not too messy to see.
basically the fields need to be string such as "${extra.email}" else it will give you error. Not sure how the graphql works deeply but if I just do ${extra.email} I would get an error which I forgot what error it was.
Here's a simpler way of handling a mutation with cy.request
const mutation = `
mutation {
updateUser(id: 1, firstName: "test") {
firstName
lastName
id
role
}
}`
cy.request({
url: url,
method: 'POST',
body: { query: mutation },
headers: {
Authorization: `Bearer ${token}`,
},
})

Resources