How can Appsync request headers by passed to a child resolver? - graphql

I have an Appsync Schema which defines a User type which itself contains a List of Notes:
type User {
id: ID!
name: String!
notes: [Note]
}
type Note {
id: ID!
text: String!
}
In the request mapping for the User, I am able to access the headers from the request, to access the Bearer token:
{
"version": "2018-05-29",
"method": "GET",
"resourcePath": $util.toJson("/prod/user/$ctx.args.userId"),
"params":{
"headers":{
"Content-Type": "application/json",
"Authorization": "Bearer $ctx.request.headers.Authorization",
}
}
}
This works well and the header is passed to the data source.
The resolver for the User.notes field is a Lambda resolver which itself iteratively calls a similar API GET /prod/user/{userId}/notes/{noteId}. I am able to access the Parent resolvers response data using $ctx.source.... However, I also need to be able to pass through the Authorization header (e.g. $ctx.request.headers.Authorization).
I have tried this in the User response mapping, but it does not work:
#set($results = $ctx.result.body)
#set($headers = {
"Authorization": "Bearer $ctx.request.headers.Authorization"
})
#set($results.headers = $headers)
$results
My question is, how can I pass the request headers from a Parent resolver to a Child resolver?
Perhaps another way to ask the question would be, given I have a Bearer token on the Graphql request header, how can I ensure it is always passed to any resolvers which are called?

As #Myz mention in their comment, the solution was to use a Pipeline Resolver.
In the end, I pushed the header into the $ctx.stash object (which is available to all functions within a Pipeline Resolver) - I did this using:
$util.qr($ctx.stash.put("bearerToken", "Bearer $ctx.request.headers.Authorization"))
I was then able to access this value in each of the Functions (i.e. the child resolvers) using: "$ctx.stash.bearerToken"

Add in Note user_id
type Note {
id: ID!
user_id: ID!
text: String!
}
and resolve to notes: [Note] by $ctx.source.id
In postgres this appier
{
"version": "2018-05-29",
"statements": [
"select * from notes where user_id = '$ctx.source.id'"
]
}
where $ctx.source.id link to User -> id

Related

Amazon Amplify GraphQL call with API_KEY

I have created a site where an user can login and do things with a GraphQL table. That works fine. But now there is a need to have a public read access to that table.
schema.grapql:
type Garden #model #auth(rules: [{allow: public, operations: [read]}] {
id: ID!
name: String
}
I am aware that allow: public means only Authorized users can access this. So public is not true public.
With the following call (Javascript):
await API.graphql({
query: listGardens,
authMode: 'API_KEY',
authToken: 'token_from_amazon_aplify_backend'
})
But this call only returns _deleted items. What am I missing here?
GraphQL is installed and working.
Auth is installed and working with login with users.

How to access API Service for types and mutations from within lambda functions

We are currently working on an application using AWS Amplify. We have 10/15 Lambda functions defined through our Amplify project along with using several other plugins. The project itself is an Angular Application.
Right now, we are working on interfacing some 3rd party services into our Amplify project that utilizes publishing mutations for real time updates using #aws_subscribe. We can trigger these just fine based on the solution using a HTTP request; however, we are wondering if it is possible to reuse the mutation operations already generated within the API.service.ts file to provide consistent typing for both "clients". This will help maintain type control across both "clients" and catch errors earlier. It also prevents us from re-writing mutations/queries down the road.
Our current solution is working but seems inefficient.
Query Statement
const PublishReportingLocationWorkflowProgressUpdateMutation = `mutation PublishReportingLocationWorkflowProgressUpdate( $id: ID!, $status: WorkflowProgressUpdateStatus!, $reportingLocation_id: String! ) { publishReportingLocationWorkflowProgressUpdate(result: {id: $id, status: $status, reportingLocation_id: $reportingLocation_id}) { id status reportingLocation_id } }`;
HTTP Request Body
{
query: PublishReportingLocationWorkflowProgressUpdateMutation,
operationName: 'PublishReportingLocationWorkflowProgressUpdate',
variables: {
id: event.Payload.executionID,
status: 'COMPLETE',
reportingLocation_id: event.Payload.reportingLocation.id }
HTTP Call
let response = await axios({
method: 'POST',
url: process.env.API_URL,
data: mutation,
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.API_KEY,
}
});

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}`,
},
})

Passing a token through Query?

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.

graphql throws unknown argument error

It seems like I'm not getting something fundamental with graphql.
I am trying to get a user by it's id which is in turn the result of another query. In this case a query on some session data.
I don't understand why the error occurs.
Here's my code:
{
session(key: "558fd6c627267d737d11e758f1ae48cae71fc9b584e2882926ad5470c88d7c3ace08c9c7") {
userId
expires
user(id: userId) {
name
}
}
}
And I get
Unknown argument "id" on field "user" of type "Session"
My schema looks like this:
type Session {
userId: String,
expires: String,
user: User
}
type User {
_id: String
name: String
email: String
firstName: String
lastName: String
}
type Query {
session(key: String!): Session
user(id: String!): User
}
Addendum Feb 23 2017
I apologize that I wasn't sufficiently explicit about the corresponding resolvers in my initial post. Yes, I my resolvers are defined and e. g. the query works for session if I don't add users.
Here's my root:
{
Query: {
async user(parentValue, args, req) {
let user = await adapters.users.byId(args.id);
return user;
},
async session(parentValue, args, req) {
let session = await adapters.session(args.key);
let userId = session.session.userId;
let expires = session.expires;
return {userId: userId, expires: expires};
}
}
}
You need to create some resolver function on field user in type Session, because GraphQL does not know how to return the user. In graphql-js it would look like that
const Session = new GraphQLObjectType({
name: 'Session',
fields: {
userId: { type: GraphQLString },
expires: { type: GraphQLString },
user: {
type: User, // it is your GraphQL type
resolve: function(obj, args, context){
// obj is the session object returned by ID specified in the query
// it contains attributes userId and expires
// however graphql does not know you want to use the userId in order to retrieve user of this session
// that is why you need to specify the value for field user in type Session
return db.User.findById(obj.userId).then(function(user){
return user;
});
}
}
}
});
It is just a simple example from Node.js to show you how you could return the user instance. You have the possibility of specifying how to retrieve values of every field in each GraphQL type (each field can have it's own resolve function).
I suggest you read the GraphQL documentation concerning root and resolvers
EDIT
I will show you an example how you can deal with such a situation. Let's consider two types: User and Session
type User {
_id: String
name: String
}
type Session {
expires: String
user: User
}
If you want your query to return Session object together with it's User, you do not need to include the userId field in Session type.
You can solve this situation in two ways. First one is to use single resolver for the session query.
{
Query: {
async session(parentValue, args, req) {
let session = await adapters.session(args.key);
// now you have your session object with expires and userId attributes
// in order to return session with user from query, you can simply retrieve user with userId
let user = await adapter.users.byId(session.userId);
return { expires: session.expires, user: user }
}
}
}
This is your first option. With that solution you can return session object with user assigned to it via single query and without extra resolve methods on any fields of Session type. The second solution is the one I have shown previously, where you use resolve method on field user of Session type - so you simply tell GraphQL how it should obtain the value for this field.

Resources