Context is empty in GraphQL middleware - node.js

I'm sending from frontend authorization token in headers and then I want to check validity of this token in some endpoints using middleware and context, but context is always empty.
I'm using type-graphql.
Frontend code (I check request in 'Network' tab and I can see my additional header):
private async mutate<T>(
mutation: DocumentNode,
data: unknown,
token?: string
) {
const response = await apolloClient.mutate<T>({
mutation: mutation,
context: {
headers: {
'auth-token': token || '',
},
},
variables: {
data: data,
},
});
return response.data;
}
Resolver code:
#Mutation(() => Token)
#UseMiddleware(authMiddleware)
async login(#Ctx() ctx: unknown, #Arg('data') data: LoginInput) {
console.log(ctx);
...
}
Middleware code:
export const authMiddleware: MiddlewareFn = ({ context }, next) => {
console.log(context);
try {
return next();
} catch (error) {
return next();
}
};
console.log is always equal to {}

I found the cause.
In declaration of ApollorServer the context was missing.
const server = new ApolloServer({
schema,
context: ({ req }) => {
const context = {
req,
};
return context;
},
cors: {
origin: '*',
credentials: true,
},
});

Related

Error: Error occurred while verifying paramssubVerifierParams need to equal threshold 1 while using the web3auth/node-sdk

i am recieving Error: Error occurred while verifying paramssubVerifierParams need to equal threshold 1
web3auth.ts
import { Web3Auth } from '#web3auth/node-sdk';
import { CHAIN_NAMESPACES, SafeEventEmitterProvider } from '#web3auth/base';
import { WEB3AUTH } from '../config/globals';
import logger from '../config/logger';
export const web3auth = new Web3Auth({
clientId: WEB3AUTH.clientId,
chainConfig: {
chainNamespace: CHAIN_NAMESPACES.EIP155,
chainId: WEB3AUTH.chainId,
rpcTarget: WEB3AUTH.rpcTarget,
displayName: 'Polygon Mainnet',
blockExplorer: 'https://polygonscan.com',
ticker: 'MATIC',
tickerName: 'Matic',
},
web3AuthNetwork: process.env.NODE_ENV === 'production' ? 'mainnet' : 'testnet',
});
export const connect = async (token: string): Promise<void> => {
try {
const provider = (await web3auth.connect({
verifier: WEB3AUTH.verifier, // replace with your verifier name
verifierId: WEB3AUTH.verifierId, // replace with your verifier id, setup while creating the verifier on Web3Auth's Dashboard
idToken: token, // replace with your newly created unused JWT Token.
subVerifierInfoArray: [
{
verifier: 'ga-google',
idToken: token,
},
{
verifier: 'zt-email',
idToken: token,
},
],
})) as SafeEventEmitterProvider;
if (!provider) logger.error('error');
const ethPrivKey = await provider.request({ method: 'eth_private_key' });
logger.info('ETH Private Key', ethPrivKey);
} catch (error) {
throw error;
}
};
controller.ts
web3authHandler = asyncHandler(async (req: Request, res: Response, next: NextFunction) => {
try {
web3auth.init();
const accessToken: string = res.locals.accessToken;
const data = await connect(accessToken);
console.log('data: ', data);
return new SuccessResponse('Successful web3auth', { data }).send(res);
} catch (error) {
throw error;
}
});
What should i do now? I am completely stuck here. Also provide me with more insights of web3auth. What should I use as the verifier and verifierId?

Apollo client does not send headers when used in nodejs environment

I've got a client-side app that uses apollo/client and I'have a few server-side function where also use the client to do some light queries and mutations.
I've recently implemented jwt integration and now my server-side calls fail because of what I presume is missing headers.
here is my client setup:
const wsLink = new GraphQLWsLink(
createClient({
url: process.env.API_URL_WS,
webSocketImpl: isNode ? ws : null,
connectionParams: async () => {
const { id_token } = await auth.getSession();
return {
headers: {
Authorization: `Bearer ${id_token}`,
},
};
},
})
);
const authLink = setContext(async (_, { headers }) => {
const { id_token } = await auth.getSession();
return {
headers: {
...headers,
Authorization: `Bearer ${id_token}`,
},
};
});
const httpLink = new HttpLink({
uri: process.env.API_URL_HTTP,
});
const splitLink = split(
({ query, ...rest }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
authLink.concat(httpLink)
);
export const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
and here is how I am using it in the server-side function to make a mutation
await client
.mutate({
mutation: gql`
mutation InsertSubscriptionId(
$id: uuid!
$stripe_subscription_id: String!
) {
update_user_by_pk(
pk_columns: { id: $id }
_set: { stripe_subscription_id: $stripe_subscription_id }
) {
id
}
}
`,
variables: {
id: session.metadata.hasura_user_id,
stripe_subscription_id: session.subscription,
},
context: {
headers: {
Authorization: req.cookies.access_token,
},
},
})
.then((result) => console.log(result))
.catch(console.log);
this call fails even if I use the users access token or apply an admin token for access to the DB.
As a workaround I've resorted to using fetch for this mutation (which works). Any idea why headers are not respected?
My workaround with admin access secret:
fetch(process.env.API_URL_HTTP, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-hasura-admin-secret": process.env.GATSBY_HASURA_ADMIN_SECRET,
},
body: JSON.stringify({
query: `
mutation InsertSubscriptionId(
$id: uuid!
$stripe_subscription_id: String!
) {
update_user_by_pk(
pk_columns: { id: $id }
_set: { stripe_subscription_id: $stripe_subscription_id }
) {
id
}
}
`,
variables: {
id: session.metadata.hasura_user_id,
stripe_subscription_id: session.subscription,
},
}),
})
.then((res) => res.json())
.then((result) => console.log(result));
In this case the session was lost after a few jumps between my app and payment provider. As user can't be authenticated when session is lost, I've resorted to authenticate the admin user for this ssr function

Testing Cognito JWKS using mock-jwk?

Auth AWS lambda
export const authorizeFinance = async (event) => {
if (event.headers.authorization) {
const token = event.headers.authorization.substring(7);
try {
const verifier = CognitoJwtVerifier.create({
userPoolId: "xyz",
tokenUse: "access",
clientId: "123",
});
const payload = await verifier.verify(token);
if (authLogic(payload)) {
return {isAuthorized: true, context: {AuthInfo: 'Finance'}}
}
return {isAuthorized: false, context: {}}
} catch (e) {
return {isAuthorized: false, context: {}}
}
}
}
Test case in jest using mock-jwks
import createJWKSMock from "mock-jwks";
import {authorizeFinance} from "#functions/auth-finance/handler";
describe('Authenticate finance', () => {
const jwks = createJWKSMock('https://cognito-idp.ap-southeast-2.amazonaws.com/ap-southeast-xyz.', 'well-known/jwks.json');
beforeEach(() => {
jwks.start();
});
afterEach(() => {
jwks.stop();
});
test('should verify the token', async () => {
const token = jwks.token({
aud: 'https://test.auth-domain.com/',
iss: 'https://cognito-idp.ap-southeast-2.amazonaws.com/ap-southeast-xyz',
});
console.log(token);
const event = {
headers: {
authorization: `Bearer ${token}`,
}
};
jest.unmock('axios');
const basicResponse = await authorizeFinance(event);
console.log('jatin', basicResponse);
expect(basicResponse).toEqual({ isAuthorized: false, context: {} });
});
})
Fails
Authentication failed JWK for kid "BlYCji0Uj6V3LAxmK1JHqYgnPJIUUqeiS8YzUf0vfh0=" not found in the JWKS
Authentication failed Missing Token use. Expected one of: id, access

pass httponly cookies through apollo graphlql server

GOAL: have my api gateway get the httponly cookies being returned from my rest endpoints and pass it along to frontend, also the front end should be able to pass the cookies through.
httpO=httponly
SPA(react) apiGateway(apolloQL) restEndpoint
httpO-cookies----> <-----(httpO)cookies-----> <-----(httpO)cookies
current the resolvers I have are able to see the "set-cookies" in the response from the endpoints but throughout the response lifecycle the header's are lost.
const apolloServer: ApolloServer = new ApolloServer({
context: ({ res }) => {
// console.log(res,"res");
return ({
res
});
},
formatError,
resolvers,
typeDefs,
formatResponse: (response: GraphQLResponse) => {
console.log(response.http?.headers, "header?");
return {
headers: {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Credentials': 'true',
},
data: response.data,
errors: response.errors,
};
}
});
ex. of resolver:
const signupUser = async (parent: any, args: any, context: any, info: any) => {
try {
const response = await UserService.createUser(args);
console.log(response , "response");
return response;
} catch (error) {
console.log(error
}
};
in this example lets assume the UserService.createUser return the entire response Object, not response.data.
const signupUser = async (parent: any, args: any, context: any, info: any) => {
try {
//call get the Express response from the context
const contextResponse = context.res
//or
const {res} = context
//then set the cookies to the response
const response = await UserService.createUser(args);
contextResponse.header('set-cookie', response?.headers['set-cookie']);
//return the original data
return response?.data;
} catch (error) {
console.log(error
}
};

Logging Fastify response body

How can I log the response body in Fastify? The body doesn't seem to be exposed as part of the response object:
const fastify = require('fastify')({
logger: {
serializers: {
res: function (res) {
// No body in req afaik
return { }
}
}
}
})
Try this:
const fastify = require('fastify')({
logger: {
serializers: {
res: function (res) {
return {
statusCode: res.statusCode,
payload: res.payload,
}
},
}
}
})
fastify.addHook('onSend', function (_request, reply, payload, next) {
Object.assign(reply.res, { payload });
next();
})
If some of your payloads are objects and you want to get them in serialize before they are - well, serialized - you can add preSerialization hook as well:
fastify
.addHook('preSerialization', (_request, reply, payload, next) => {
Object.assign(reply.res, { payload });
next();
})
.addHook('onSend', (_request, reply, payload, next) => {
if (!reply.res.payload) Object.assign(reply.res, { payload });
next();
});
here you are a working example. I think that this kind of usage need to be used only for debugging because you are slowing down if you have many req/sec.
I have also added a JSON Schema validation as demo:
const fastify = require('fastify')({ logger: true })
fastify.register(async function (fastify, opts) {
fastify.addHook('onSend', function (request, reply, payload, next) {
console.log(payload);
next()
})
fastify.get('/', {
schema: {
response: {
'2xx': { properties: { this: { type: 'string' } } }
}
}
}, async function () {
return { this: 'is', a: 'json' }
})
})
fastify.listen(3000)
You will get:
curl http://localhost:3000/
{"this":"is"}

Resources