How to return error response in apollo link? - apollo

I'm using apollo link in schema stitching as an access control layer. I'm not quite sure how to make the link return error response if a user does not have permissions to access a particular operation. I know about such packages as graphql-shield and graphql-middleware but I'm curious whether it's possible to achieve basic access control using apollo link.
Here's what my link looks like:
const link = setContext((request, previousContext) => merge({
headers: {
...headers,
context: `${JSON.stringify(previousContext.graphqlContext ? _.omit(previousContext.graphqlContext, ['logger', 'models']) : {})}`,
},
})).concat(middlewareLink).concat(new HttpLink({ uri, fetch }));
The middlewareLink has checkPermissions that returns true of false depending on user's role
const middlewareLink = new ApolloLink((operation, forward) => {
const { operationName } = operation;
if (operationName !== 'IntrospectionQuery') {
const { variables } = operation;
const context = operation.getContext().graphqlContext;
const hasAccess = checkPermissions({ operationName, context, variables });
if (!hasAccess) {
// ...
}
}
return forward(operation);
});
What should I do if hasAccess is false. I guess I don't need to forward the operation as at this point it's clear that a user does not have access to it
UPDATE
I guess what I need to do is to extend the ApolloLink class, but so far I didn't manage to return error

Don't know if anyone else needs this, but I was trying to get a NetworkError specifically in the onError callback using Typescript and React. Finally got this working:
const testLink = new ApolloLink((operation, forward) => {
let fetchResult: FetchResult = {
errors: [] // put GraphQL errors here
}
let linkResult = Observable.of(fetchResult).map(_ => {
throw new Error('This is a network error in ApolloClient'); // throw Network errors here
});
return linkResult;
});
Return GraphQL errors in the observable FetchResult response, while throwing an error in the observable callback will produce a NetworkError

After some digging I've actually figured it out. But I'm not quite sure if my approach is correct.
Basically, I've called forward with a subsequent map where I return an object containing errors and data fields. Again, I guess there's a better way of doing this (maybe by extending the ApolloLink class)
const middlewareLink = new ApolloLink((operation, forward) => {
const { operationName } = operation;
if (operationName !== 'IntrospectionQuery') {
const { variables } = operation;
const context = operation.getContext().graphqlContext;
try {
checkPermissions({ operationName, context, variables });
} catch (err) {
return forward(operation).map(() => {
const error = new ForbiddenError('Access denied');
return { errors: [error], data: null };
});
}
}
return forward(operation);
});

Related

How to resolve the type error issue in nestjs unit test?

I'm trying to create a jest test for the below method. And I got errors for two scenarios.
So basically in checkKioskUserPhone method,
Find the user by the phone number( commonService.findKioskUserByPhone)
In findKioskUserByPhone method, we are gonna find the user by the phone number and send error messages if it's unregistered or already registered.
And then return user.
(back to checkKioskUserPhone) if the user doesn't have auth code and pin number we are gonna send him/her auth code and return jwt, and etc.
async checkKioskUserPhone(kioskLoginDto: KioskLoginDto): Promise<ResponseDto<UserAuthDto>> {
const user = await this.commonService.findKioskUserByPhone(kioskLoginDto);
const isConfirmedAuthCode = user.authCode === 'OK' ? true : false;
const isSetPin = user.pin ? true : false;
if (!isConfirmedAuthCode && !isSetPin) {
await this.userService.authenticatePhone(user.id, Builder(AuthorizePhoneDto).phone(user.phone).build());
}
const jwtInfo = await this.createToken(this.removeCredentialField(user));
return Builder<ResponseDto<UserAuthDto>>(ResponseDto)
.result(Builder(UserAuthDto).isConfirmedAuthCode(isConfirmedAuthCode).isSetPin(isSetPin).jwtInfo(jwtInfo).build())
.build();
}
async findKioskUserByPhone(kioskLoginDto: KioskLoginDto): Promise<User> {
const user = await this.userService.findOne({ where: { phone: kioskLoginDto.phone } });
// throw Error message when unregistered phone attempt to login
if (!user) {
throw new NotFoundException('User not found');
}
// throw Error message when registered phone by whatsapp attempt to login
if (user.provider !== Provider.KIOSK) {
throw new ConflictException('You are already joined by Whatsapp.');
}
return user;
}
Jest code
it('when unregistered phone attempt to login', async () => {
const phone = '2212223333';
const kioskLoginDto = Builder(KioskLoginDto).phone(phone).build();
service.commonService.findKioskUserByPhone = jest.fn().mockResolvedValue(null);
try {
await service.checkKioskUserPhone(kioskLoginDto);
expect('here').not.toBe('here');
} catch (error) {
expect(error).toBeInstanceOf(NotFoundException);
expect(error.message).toContain('User not found');
}
});
it('When registered phone by app attempt to login', async () => {
const phone = '2212223333';
const kioskLoginDto = Builder(KioskLoginDto).phone(phone).build();
const user = Builder(User).phone(phone).provider(Provider.WHATSAPP).build();
service.commonService.findKioskUserByPhone = jest.fn().mockResolvedValue(user);
try {
await service.checkKioskUserPhone(kioskLoginDto);
expect('here').not.toBe('here');
} catch (error) {
expect(error).toBeInstanceOf(ConflictException);
expect(error.message).toContain('You are already joined by Whatsapp.');
}
});
Jest Error screenshot
you're overriding the findKioskUserByPhone method to just return null:
service.commonService.findKioskUserByPhone = jest.fn().mockResolvedValue(null);
so findKioskUserByPhone simply is never running, a mock function is just returning null, and is thus never throwing the error you expect. instead, here:
const user = await this.commonService.findKioskUserByPhone(kioskLoginDto);
user is getting set to null and here:
const isConfirmedAuthCode = user.authCode === 'OK' ? true : false;
you're trying access some authCode property of null, which throws the TypeError you're getting.
you probably meant to override the findOne method on the user service:
service.userService.findOne = jest.fn().mockResolvedValue(null);
so the error you want will actually throw in findKioskUserByPhone
(note I don't know if this is actually where you have the user service to provide the mock, I'm just assuming)

Moq; Unit Test - Method is always failing. ISetup is not working

The test method is always failing. After the Setup the method UpdateAsync should return 1 in the result but it remains always 0 which results in exception in the controller method.
Can you tell what I am missing here ?
[Test]
public async Task UpdateImportHeaderAsyncTest()
{
//Arrange
HeaderRequest request = new HeaderRequest()
{
ConfigurationId = 1,
Key = "1",
Status = 1
};
_manager.Setup(a => a.UpdateAsync(_mockData.Header)).Returns(Task.FromResult(1));
//Act
var actual = await Controller.UpdateHeaderAsync(request);
//Assert
Assert.NotNull(actual);
}
//Controller Method
[HttpPut]
public async Task<int> UpdateHeaderAsync(HeaderRequest request)
{
var result = 0;
try
{
result = await _manager.UpdateAsync(new Header()
{
HeaderId = request.Id,
Status = request.Status,
ConfigurationId = request.ConfigurationId
});
if (result == 0)
{
throw new RecordNotFoundException("No records found.", "1", "");
}
}
catch (Exception ex)
{
throw;
}
return result;
}
Loosen the argument match using It.IsAny<Header>()to get the desired behavior.
//...
_manager
.Setup(a => a.UpdateAsync(It.IsAny<Header>()))
.ReturnsAsync(1);
//...
The setup also allows for ReturnsAsync for setting up async members.
What was happening before was that you were setting it up with a specific referenced instance. That instance was not the same one used when exercising the test since you initialized a new Header. This caused the mock to return the default value for the return type.
Reference Moq Quickstart to get a better understanding of how to use the framework

Stripe Error: No signatures found matching the expected signature for payload

I have a stripe webhook that call a Firebase function. In this function I need to verify that this request comes from Stripe servers. Here is the code :
const functions = require('firebase-functions');
const bodyParser = require('body-parser');
const stripe = require("stripe")("sk_test_****");
const endpointSecret = 'whsec_****';
const app = require('express')();
app.use(bodyParser.json({
verify: function (req, res, buf) {
var url = req.originalUrl;
if (url.startsWith('/webhook')) {
req.rawBody = buf.toString()
}
}
}));
app.post('/webhook/example', (req, res) => {
let sig = req.headers["stripe-signature"];
try {
console.log(req.bodyRaw)
let event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
console.log(event);
res.status(200).end()
// Do something with event
}
catch (err) {
console.log(err);
res.status(400).end()
}
});
exports.app = functions.https.onRequest(app);
As mentioned in Stripe Documentation, I have to use raw body to perform this security check.
I have tried with my current code and with :
app.use(require('body-parser').raw({type: '*/*'}));
But I always get this error :
Error: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing
Cloud Functions automatically parses body content of known types. If you're getting JSON, then it's already parsed and available to you in req.body. You shouldn't need to add other body parsing middleware.
If you need to process the raw data, you should use req.rawBody, but I don't think you'll need to do that here.
Here is what is working for me:
add this line:
app.use('/api/subs/stripe-webhook', bodyParser.raw({type: "*/*"}))
(The first argument specifies which route we should use the raw body parser on. See the app.use() reference doc.)
just before this line:
app.use(bodyParser.json());
(it doesn't affect all your operation, just this: '/api/subs/stripe-webhook')
Note: If you are using Express 4.16+ you can replace bodyParser by express:
app.use('/api/subs/stripe-webhook', express.raw({type: "*/*"}));
app.use(express.json());
Then:
const endpointSecret = 'whsec_........'
const stripeWebhook = async (req, res) => {
const sig = req.headers['stripe-signature'];
let eventSecure = {}
try {
eventSecure = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
//console.log('eventSecure :', eventSecure);
}
catch (err) {
console.log('err.message :', err.message);
res.status(400).send(`Webhook Secure Error: ${err.message}`)
return
}
res.status(200).send({ received: true });
}
Here is code which is working for me:
app.use(bodyParser.json({
verify: function (req, res, buf) {
var url = req.originalUrl;
if (url.startsWith('/stripe')) {
req.rawBody = buf.toString();
}
}
}));
And then pass the req.rawBody for verification
stripe.checkWebHook(req.rawBody, signature);
Reference: https://github.com/stripe/stripe-node/issues/341
2 things to note:
pass req.rawBody instead of req.body to constructEvent
const event = stripe.webhooks.constructEvent(
req.rawBody,
sig,
STRIPE_WEBHOOK_SECRET
);
Make sure you're using the correct webhook secret. It's unique per webhook url!
2021 - Solution
I faced that error, and after a lot research I could not figure out the problem easily, but finally I could do it based in my architecture below:
//App.js
this.server.use((req, res, next) => {
if (req.originalUrl.startsWith('/webhook')) {
next();
} else {
express.json()(req, res, next);
}
});
//routes.js
routes.post(
'/webhook-payment-intent-update',
bodyParser.raw({ type: 'application/json' }),
//your stripe logic (Im using a controller, but wherever)
(req, res) => {
stripe.webhooks.constructEvent(...)
}
)
Two big warnings to pay attention:
Make sure to send the req.headers['stripe-signature']
Make sure that your endpointSecret is right, if not it will still saying the same error
Tips:
Test it locally by installing the Stripe CLI: https://stripe.com/docs/webhooks/test
Verify your key on stripe dashboard or you can also make sure if you have the right key by verifying you stripe log as below:
I hope it helps you. :)
// Use JSON parser for all non-webhook routes
app.use(
bodyParser.json({
verify: (req, res, buf) => {
const url = req.originalUrl;
if (url.startsWith('/api/stripe/webhook')) {
req.rawBody = buf.toString();
}
}
})
);
The above code will look fine for the above answers. But even I was made one mistake. After put the same thing I got the same error.
Finally, I've figured it out if you're configured body-parser below the rawBody code then it'll work.
Like this
// Use JSON parser for all non-webhook routes
app.use(
bodyParser.json({
verify: (req, res, buf) => {
const url = req.originalUrl;
if (url.startsWith('/api/stripe/webhook')) {
req.rawBody = buf.toString();
}
}
})
);
// Setup express response and body parser configurations
app.use(express.json());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
Hopefully, it'll help someone.
It is late but will help others
Github answer
const payload = req.body
const sig = req.headers['stripe-signature']
const payloadString = JSON.stringify(payload, null, 2);
const secret = 'webhook_secret';
const header = stripe.webhooks.generateTestHeaderString({
payload: payloadString,
secret,
});
let event;
try {
event = stripe.webhooks.constructEvent(payloadString, header, secret);
} catch (err) {
console.log(`Webhook Error: ${err.message}`)
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'checkout.session.completed': {
......
enter code here
If you are trying to add a stripe webhook into your NextJS API Route, here's how to do so (ref):
import initStripe from "stripe";
import { buffer } from "micro";
import { NextApiRequest, NextApiResponse } from "next";
export const config = { api: { bodyParser: false } };
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const stripe = initStripe(process.env.STRIPE_SECRET_KEY||'');
const signature = req.headers["stripe-signature"];
const signingSecret = process.env.STRIPE_WEBHOOK_SECRET || '';
const reqBuffer = await buffer(req);
let event;
try {
event = stripe.webhooks.constructEvent(reqBuffer, signature, signingSecret);
} catch (error: any) {
console.log(error);
return res.status(400).send(`Webhook error: ${error?.message}`);
}
console.log({ event });
res.send({ received: true });
};
export default handler;
This is using buffer from the micro library, in combination with the modifying the default API request to use request's rawbody. In some frameworks (like NextJs), rawBody doesn't come OOTB, hence the workaround of retrieving the rawbody by reqBuffer, which is needed in the stripe.webhooks.constructEvent event.
I was able to obtain data from one webhook but not from a second one: the problem was that the secret key I used was the same as the one used for the first webhook, but I found out that every webhook has a different key, that's way I got that same message.
AWS API Gateway + Lambda (Express.js CRUD) I'm using this for Stripe webhook endpoint and it works for me:
app.use(require('body-parser').text({ type: "*/*" }));
This happened to me when sending a test webhook from the Stripe dashboard after I had renamed a firebase cloud function. All my other functions were working fine. Solved by re-setting in the terminal
firebase functions:config:set stripe.webhook_signature="Your webhook signing secret"
(if you're using that) and redeploying the functions firebase deploy --only functions
On a second occasion I solved the problem by rolling the stripe signature in the stripe dashboard.
Please use this script
app.use(
bodyParser.json({
verify: (req, res, buf) => {
req.rawBody = buf;
},
})
);
My fave was combining two of above great answers.
Then you can use req.rawbody when you construct the event.
Replace "webhook" with whatever route you wish you have a raw body for.
app.use(
"/webhook",
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString();
},
})
);
BEFORE
app.use(express.json());
Works well if you are using routes and controllers.
To use raw body in express with a specific endpoint in a seperated middleware, my solution is just enabling router to use express.raw for the webhook endpoint.
-node.js v12
-express.js v4.17.1
export const handleBodyRequestParsing = (router: Router): void => {
router.use('/your_webhook_endpoint', express.raw({ type: '*/*' }))
router.use(express.json({ limit: '100mb' }))
router.use(express.urlencoded({ extended: true }))
}
Here is the Quick Tip which may save your hours !
If you are adding express payment to your exciting express app sometimes you may already pass your request as json in the beginning of application by using express middleware app.use(json()); or any other middleware (Bodyparser for example).
If you are doing that then change that to omit your webhook url
Exmaple:
Assume your payment webhook url is /paments/webhhok
app.use((req, res, next) => {
if (req.originalUrl.includes("/payments/webhook")) {
next();
} else {
express.json()(req, res, next);
}
});
When using Stripe in Express, if you have the following line in your code;
app.use(express.json());
it is going to prevent you from providing the raw body to the Stripe even when you explicitly set "bodyParser.raw", which will throw an error. This was the reason my code failed. Finally sorted it out.
I tried all the solutions above and no one worked, and figured out that the only solution was not to use express at all for this endpoint. you just have to create another http function
export const webhook = functions.https.onRequest(async (req, res) => {
try {
const sig = req.headers['stripe-signature']
const endpointSecret = 'web_secret'
const event = stripe.webhooks.constructEvent(
req.rawBody,
sig,
endpointSecret
)
console.log(event.data.object)
res.status(200).send(event.data.object)
} catch (err) {
console.error('ocorreu um erro', err)
res.status(400).send(`Webhook Error: ${err.message}`)
}
})

How to use Promise's reject?

As the documentation says
resolve is
The Promise.resolve(value) method returns a Promise object that is
resolved with the given value.
reject is
The Promise.reject(reason) method returns a Promise object that is
rejected with the given reason.
I understand the uses of resolve but what will be the uses of reject and when to use it ?
Promise.reject is promise's way of throwing errors. Usually you would have a condition inside of your promise:
const user = {
name: 'John',
age: 17
}
const p = new Promise((resolve, reject) => {
if (user.age > 18) {
resolve('Welcome!');
} else {
reject(new Error('Too young!'));
}
});
You can then chain then and catch methods to handle the results of resolve and reject respectively.
p.then(message => {
console.log(message); // 'Welcome!', if promise resolves, won't work with age of 17
})
.catch(err => {
console.error(err); // 'Too young!', because promise was rejected
});
Here are few examples this statement can be used for:
Function defined to return a Promise, however you perform some sync checks and would like to return an error:
function request(data) {
if (!data) return Promise.reject("Empty data!");
// other logic
}
Unit tests, for example you would like to test that default data is used if service returns error (rejected promise):
const mockService = mock(Service);
// mock request method to return rejected promise
when(mockService.performRequest()).thenReturn(Promise.reject("Failed!"));
// inject mock instance and check that default data used if service failed
const sut = new ClassUnderTest(mockService);
expect(sut.getData()).to.eq("Default data");

ReactJs - test multiple calls in redux-saga with expectSaga

I'm using expectSaga ('redux-saga-test-plan') to test one of my sagas and I'm wondering how to test multiple calls made within the same saga.
Sagas.js
export function* fetchSomething(arg){
const response = yield call(executeFetch, arg);
if(response.status === 200){
// trigger success action
} else if (response.status >= 400){
const errResp = yield response.json();
const errorCode = yield call(sharedUtilToExtractErrors, errResp);
yield put(
{ type: 'FETCH_FAILED', errorMessage: UI_ERR_MSG, errorCode }
);
}
}
Unit test
import { expectSaga } from 'redux-saga-test-plan';
describe('fetchSomething', () => {
// positive paths
// ..
// negative paths
it('fetches something and with status code 400 triggers FETCH_FAILED with error message and extracted error code', () => {
const serverError = { message: 'BANG BANG BABY!' };
const koResponse = new Response(
JSON.stringify(serverError),
{ status: 400, headers: { 'Content-type': 'application/json' } }
);
return expectSaga(fetchSomething)
.provide(
{
call: () => koResponse,
call: () => serverError.message,
}
)
.put({
type: 'FETCH_FAILED', errorMessage: UI_ERR_MSG, serverError.message
})
.run();
})
})
Clearly having the "call" attribute twice in the same object passed in to provide() doesn't work but also calling provide() twice doesn't do the trick. Any suggestions?
Thanks
This is how you can provide multiple calls according to the documentation:
.provide([ // this external array is actually optional
[call(executeFetch, arg), koResponse],
[call(sharedUtilToExtractErrors, serverError), serverError.message],
])
or if you're lazy and don't want to specify the arguments:
import * as matchers from 'redux-saga-test-plan/matchers';
.provide(
[matchers.call.fn(executeFetch), koResponse],
[matchers.call.fn(sharedUtilToExtractErrrors), serverError.message],
)
Neither of these two worked for me though as for some reason it was not mocking out the dependencies and still calling them caused errors.
I solved using a dynamic provider:
.provide({
// select(effect, next) { return 'something-for-a-selector' },
call(effect) {
switch(effect.fn.constructor.name) {
case executeFetch.constructor.name: return koResponse;
case sharedUtilToExtractErrors.constructor.name: return serverError.message;
default: throw new Error('Unknown function called in test');
}
}
})