With the normal graphql server we can define the context object like this:
app.use('/graphql', graphqlExpress(async (req) => {
return {
schema,
context: {
app,
params,
}
};
}));
** subscription server **
How can I do the same for the subscription server? (Doing the hybrid http / websocket approach). Can't seem to find a solution from the docs.
new SubscriptionServer({
execute,
subscribe,
schema,
onConnect: (connectionParams, webSocket) => {
console.log(connectionParams);
}
}, {
server,
path: '/subscriptions'
});
You can add a middleware before the execute function and add the required context before resolving the subscription.
It could look like this:
const middleware = (args) => new Promise((resolve, reject) => {
const [schema, document, root, context, variables, operation] = args;
context.app = <your app parameter>;
context.params = <your params>;
resolve(args);
});
SubscriptionServer.create({
schema,
subscribe,
execute: (...args) => middleware(args).then(args => { return execute(...args); }) },
{
server: httpServer,
path: "/subscription",
},
);
As you can see you have all the data from the request in the args of the execute function.
Related
I am trying to run Apollo GraphQL server inside my AWS lambda. I'm using the library from here. I'm also using CDK to deploy my lambda and the REST API Gateway.
My infrastructure is as follows:
const helloFunction = new NodejsFunction(this, 'lambda', {
entry: path.join(__dirname, "lambda.ts"),
handler: "handler"
});
new LambdaRestApi(this, 'apigw', {
handler: helloFunction,
});
The lambda implementation is as follows:
const typeDefs = `#graphql
type Query {
hello: String
}`;
const resolvers = {
Query: {
hello: () => 'world',
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
})
console.log('###? running lambda')
export const handler = startServerAndCreateLambdaHandler(
server,
handlers.createAPIGatewayProxyEventV2RequestHandler(), {
middleware: [
async (event) => {
console.log('###? received event=' + JSON.stringify(event, null, 2))
return async (result) => {
console.log(("###? result=" + JSON.stringify(result, null, 2)))
result
}
}
]
});
When I POST to my endpoint with the appropriate query I get this error:
{
"statusCode": 400,
"body": "Cannot read property 'method' of undefined"
}
I'm seeing my logging inside the lambda as expected and I can confirm the error is being returned in the 'result' from within startServerAndCreateLambdaHandler(). This code is based on the example for the #as-integrations/aws-lambda library. I don't understand why this is failing.
Need to use:
handlers.createAPIGatewayProxyEventRequestHandler()
Instead of:
handlers.createAPIGatewayProxyEventV2RequestHandler()
So final code is:
export const handler = startServerAndCreateLambdaHandler(
server,
handlers.createAPIGatewayProxyEventRequestHandler(),
{
middleware: [
async (event) => {
console.log('###? received event=' + JSON.stringify(event))
}
]
}
);
I am trying to unit test a function which makes an async call using an Axios helper instance. I have attempted multiple ways of trying to unit test this but I can not seem to find any material online which has helped. I've been stuck on this problem for a few days which is frustrating so any help would be appreciated! Below are the Axios Helper file (api.js)
api.js
import axios from 'axios'
const API = (token = null) => {
let headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-key': process.env.NEXT_PUBLIC_API_HEADER_SUBSCRIPTION_KEY
}
if (token) {
const tokenHeader = { Authorization: 'Bearer ' + token }
headers = { ...headers, ...tokenHeader }
}
const url = process.env.NEXT_PUBLIC_API_BASE_URL
const API = axios.create({
baseURL: url,
headers
})
return API
}
export default API
mocked API
export default {
post: jest.fn(() =>
Promise.resolve({
data: {}
})
),
get: jest.fn(() =>
Promise.resolve({
data: {}
})
)
}
action file
export const initiate2FA = (destinationValue) => async () => {
const twoFactorAuth = destinationValue
const res = await API().post('/foo', {
Destination: twoFactorAuth
})
return res
}
Action.test.js
import API from 'api/api'
import { initiate2FA } from 'actions/userActions'
jest.mock('api/api')
const mockedAxios = API
const dispatch = jest.fn()
describe('Initiate2FA function', () => {
it('bar', async () => {
mockedAxios.get.mockImplementationOnce(() => Promise.resolve({ status: 200 }))
const t = await dispatch(initiate2FA('test#test.com'))
console.log(t)
})
})
My issue with the above test file is that it returns an anonymous function and I do not know how to handle this to pass the unit test. The goal of the test is to make sure the function is called. I am not sure if I am approaching this the correct way or should change my approach.
Again, any suggestions would be great!
Mocking an API call is something you can mock on your own React component, instead of a function, and the best option would be to not mock anything on your component. Here you can read all about why you should not mock your API functions. At the end of the article, you're going to find a library called Mock Service Worker which you can use for your purpose.
The way you declare you have an actual HTTP called that needs to be mocked would be something like this:
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
If you just need to unit test a function, you can still use Mock Service Worker to resolve the HTTP request, and then test what happens after that. This would still be your first choice. And the test would look like:
// this could be in another file or on top of your tests.
rest.get('/foo', async (req, res, ctx) => {
const mockedResponse = {bar: ''};
return res(ctx.json(mockedResponse))
}),
// and this would be your test
describe('Initiate2FA function', () => {
it('bar', async () => {
const res = await initiate2FA('test#test.com');
expect(res).toBe({bar: '');
})
})
Okey, this is the repo
What I want to do: Test my protected routes.
Currently, the security of the app is handle by passport, with this strategy: graphql-passport.
I am running my rests with supertest (for the request) and jest
When I build the Apollo Server, i use it to create the context:
import { buildContext } from 'graphql-passport';
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => {
return buildContext({ req, res, User })
},
playground: {
settings: {
'request.credentials': 'same-origin',
},
},
});
This allows me to get the user from the request. Like any authentication with passport works.
passport.use(
new GraphQLLocalStrategy((email, password, next) => {
console.log(`🎫 GraphQLLocalStrategy ${email} 🚔 👮♂`)
User.findOne({ email })
.then(user => !user
? next(null, false, 'Invalid email or password')
: user.checkPassword(password) //bcrypt
.then(match => !match
? next(null, false, 'Invalid email or password')
: next(null, user)
)
)
.catch(error => next(error))
}),
);
So far, it works good enough. For every test that i run, I can see my 🎫 GraphQLLocalStrategy ${email} 🚔 👮♂ being called. Good!.
For some mutations, like login and update user profile, i am able to do this:
user.mutations.test.js
// * Login for add the user in the context
agent
.post("/graphql")
.send({ query: ` mutation {
${loginQuery(email)}
${updateFirstName}
}`})
.set("Accept", "application/json")
.end((err, {body:{data, errors}}) => {
if (err) return done(err);
const {updateUser} = data;
expect(updateUser).toBeInstanceOf(Object);
expect(updateUser.email).toBe(email);
expect(updateUser.firstName).toBe(newName);
expect(updateUser.rol).toBe("patron");
UserFields.map(checkFields(updateUser));
done();
})
So, in one query, I can send the login mutation and then run the update the first name mutation. Both, works good enough, and according to passport I am logged and I can update the user profile.
What is the issue?? I want to run a loging mutation and after that run a query to get all users.
But, ofcourse, I can not run both at the same time in the request(app).post("/graphql").send() It has to be a one or multiple mutations or a queries... but not both.
The other idea, who doesnt work, is run one, and in the response, run the second one, like this:
const agent = request(app);
agent
.post("/graphql")
.send({ query: `mutation { ${loginQuery(email)} }`})
.end((err, {body:{data}}) => {
if (err) return done(err);
agent
.post("/graphql")
.send({ query: `query { getGuestsQuery() }`})
...
If I try to ask in a second request for a protected route, there is not a way to know that i was authenticated, at least not automatically... Can I make an authenticated request here with supertest
**How can I tell to my tested application that I am authenticated???? **
test("fetch all Guests", async (done) => {
const userAdmin = await User.findOne({rol:"admin"}).exec();
if(!userAdmin) return done('no admin users for testing');
const agent = request.agent(app);
agent
.post('/graphql')
.send({ query: ` mutation { ${loginQuery(userAdmin.email)} }`})
.expect(200)
.end((err, res) => {
if (err) return done(err);
agent
.post("/graphql")
.send({query: `{ getGuests { ${GuestInput.join(' ')} } }`})
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end((err, {body:{data}}) => {
if (err) return done(err);
expect(data).toBeInstanceOf(Object);
const {getGuests} = data;
expect(getGuests).toBeInstanceOf(Array);
getGuests.map(user => GuestInput.map(checkFields(user)))
done();
});
});
});
I'm trying to use useEffect in my React app but also refactor things more modularly. Shown below is the heart of actual working code. It resides in a Context Provider file and does the following:
1. Calls AWS Amplify to get the latest Auth Access Token.
2. Uses this token, in the form of an Authorization header, when an Axios GET call is made to an API Endpoint.
This works fine but I thought it would make more sense to move Step #1 into its own useEffect construct above. Furthermore, in doing so, I could then also store the header object as its own Context property, which the GET call could then reference.
Unfortunately, I can now see from console log statements that when the GET call starts, the Auth Access Token has not yet been retrieved. So the refactoring attempt fails.
useEffect(() => {
const fetchData = async () => {
const config = {
headers: { "Authorization":
await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
alert('Error getting authorization token: '.concat(error))
})
}};
await axios.get('http://127.0.0.1:5000/some_path', config)
.then(response => {
// Process the retrieved data and populate in a Context property
})
.catch(error => {
alert('Error getting data from endpoint: '.concat(error));
});
};
fetchData();
}, [myContextObject.some_data]);
Is there a way of refactoring my code into two useEffect instances such that the first one will complete before the second one starts?
You could hold the config object in a state. This way you can separate both fetch calls and trigger the second one once the first one finished:
const MyComponent = props => {
const myContextObject = useContext(myContext);
const [config, setConfig] = useState(null);
useEffect(() => {
const fetchData = async () => {
const config = {
headers: {
Authorization: await Auth.currentSession()
.then(data => {
return data.getAccessToken().getJwtToken();
})
.catch(error => {
alert("Error getting authorization token: ".concat(error));
})
}
};
setConfig(config);
};
fetchData();
}, [myContextObject.some_data]);
useEffect(() => {
if (!config) {
return;
}
const fetchData = async () => {
await axios
.get("http://127.0.0.1:5000/some_path", config)
.then(response => {
// Process the retrieved data and populate in a Context property
})
.catch(error => {
alert("Error getting data from endpoint: ".concat(error));
});
};
fetchData();
// This should work for the first call (not tested) as it goes from null to object.
// If you need subsequent changes then youll have to track some property
// of the object or similar
}, [config]);
return null;
};
I can access the request header in a get or post call
fastify.get('/route1',(req,res,next)=>{
console.log(req.headers.Authorization)
...
}
I am looking for a way to pass it to a plugin register call, specifically fastify-graphql
const { graphqlFastify } = require("fastify-graphql");
fastify.register(graphqlFastify,
{
prefix: "/graphql",
graphql: {
schema: schema,
rootValue: resolvers,
context:{auth:req.headers.Authorization} <-----
}
},
err => {
if (err) {
console.log(err);
throw err;
}
}
);
Is there a way to write a wrapper or any ideas?
I think you can't do that.
If read the code you will find that:
fastify-graphql is calling runHttpQuery
runHttpQuery is calling context without passing the request
So I think that you should check the auth-client with a standard JWT and then use another token server-side.
The final solution could be to check Apollo 2.0 and open the issue on fastify-graphql.
Here a little snippet that explain the idea:
const fastify = require('fastify')({ logger: true })
const { makeExecutableSchema } = require('graphql-tools')
const { graphiqlFastify, graphqlFastify } = require('fastify-graphql');
const typeDefs = `
type Query {
demo: String,
hello: String
}
`
const resolvers = {
Query: {
demo: (parent, args, context) => {
console.log({ args, context });
return 'demo'
},
hello: () => 'world'
}
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
fastify.register(graphqlFastify, {
prefix: '/gr',
graphql: {
schema,
context: function () {
return { serverAuth: 'TOKEN' }
},
},
});
fastify.listen(3000)
// curl -X POST 'http://localhost:3000/gr' -H 'Content-Type: application/json' -d '{"query": "{ demo }"}'
For anyone who need to access request headers in graphql context, try
graphql-fastify
Usage
Create /graphql endpoint like following
const graphqlFastify = require("graphql-fastify");
fastify.register(graphqlFastify, {
prefix: "/graphql",
graphQLOptions
});
graphQLOptions
graphQLOptions can be provided as an object or a function that returns graphql options
graphQLOptions: {
schema: schema,
rootValue: resolver
contextValue?: context
}
If it is a function, you have access to http request and response. This allows you to do authentication and pass authentication scopes to graphql context. See the following pseudo-code
const graphQLOptions = function (request,reply) {
const auth = decodeBearerToken(request.headers.Authorization);
// auth may contain userId, scope permissions
return {
schema: schema,
rootValue: resolver,
contextValue: {auth}
}
});
This way, context.auth is accessible to resolver functions allowing you to check user's scope/permissions before proceeding.