How to return unauthorized response from before hook in feathersJS - node.js

I have parts of app (modules) that gonna be forbidden for certain people, so I wanna check that in before hook and send unauthorized response if its needed.
I'm successfully throwing error on backend, but on my frontend I still get successful response as if there was no error.
Here is how my code looks like:
1.Function that checks if app is forbidden for user that sent request:
function isAppForbidden(hook) {
let forbiddenApps = [];
hook.app.services.settings.find({
query: {
$limit: 1,
$sort: {
createdAt: -1
}
}
}).then(res => {
let array = hook.params.user.hiddenApps;
if(array.indexOf('qualitydocs') >= 0 || res.data[0].forbiddenApps.indexOf('qualitydocs') >= 0) {
hook.response = Promise.reject({error: '401 Unauthorized'});
//this part is important, the rest not so much
//what im expecting to do here is just to return unauthorized response
}
});
return hook;
}
But this for now just throws error on backend like:
"error: Unhandled Rejection at: Promise Promise {
{ error: { code: '401', message: 'Unauthorized' } } } code=401, message=Unauthorized"
And frontend still gets successful response (200 with requested data)
And I just call this function in before hooks:
before: {
all: [
authenticate('jwt'),
hook => includeBefore(hook),
hook => isAppForbidden(hook) //here, rest is not important
],
find: [],
get: [],
create: [(hook) => {
hook.data.authorId = hook.params.user.id;
}],
update: [],
patch: [],
remove: []
},
the response im expecting to get, looks something like this:

Found the solution... the key was to wrap the content of function in promise... so it now looks like this:
function isAppForbidden(hook) {
return new Promise((resolve, reject) => {
hook.app.services.settings.find({
query: {
$limit: 1,
$sort: {
createdAt: -1
}
}
}).then(res => {
if (hook.params.user.hiddenApps.indexOf('qualitydocs') >= 0 || res.data[0].forbiddenApps.indexOf('qualitydocs') >= 0) {
reject(new errors.NotAuthenticated());
} else {
resolve();
}
})
})
}
and it works as a charm

Related

my api needs time to process a request, how can I use React + SWR to continue checking on the status?

I have an endpoint in my API that initiates a process on AWS. This process takes time. It can last several seconds or minutes depending on the size of the request. As of right now, I'm rebuilding my app to use swr. However, before this new update with swr I created a recursive function that would call itself with a timeout and continuously ping the API to request the status of my AWS process, only exiting once the response had the appropriate type.
I'd like to dump that recursive function because, well ... it was kinda hacky. Though, I'm still getting familiar with swr and I'm not a NodeJS API building master so I'm curious what thoughts come to mind in regards to improving the pattern below.
Ideally, the lowest hanging fruit would be to set up swr in some way to handle the incoming response and keep ping if the response isn't type: "complete" but I'm not sure how I'd do that. It pretty much just pings once and shows me whatever status it found at that time.
any help is appreciated!
tldr;
how can I set up swr to continually ping the API until my content is finished loading?
part of my API that sends out responses based how far along the AWS process is:
if (serviceResponse !== undefined) {
// * task is not complete
const { jobStatus } = serviceResponse.serviceJob;
if (serviceJobStatus.toLowerCase() === 'in_progress') {
return res.status(200).send({ type: 'loading', message: serviceJobStatus });
}
if (serviceJobStatus.toLowerCase() === 'queued') {
return res.status(200).send({ type: 'loading', message: serviceJobStatus });
}
if (serviceJobStatus.toLowerCase() === 'failed') {
return res.status(400).send({ type: 'failed', message: serviceJobStatus });
}
// * task is complete
if (serviceJobStatus.toLowerCase() === 'completed') {
const { serviceFileUri } = serviceResponse.serviceJob?.Data;
const { data } = await axios.get(serviceUri as string);
const formattedData = serviceDataParser(data.results);
return res.status(200).send({ type: 'complete', message: formattedData });
}
} else {
return res.status(400).send({ type: 'error', message: serviceResponse });
}
}
my current useSWR hook:
const { data: rawServiceData } = useSwr(
serviceEndpoint,
url => axios.get(url).then(r => r.data),
{
onSuccess: data => {
if (data.type === 'complete') {
dispatch(
setStatus({
type: 'success',
data: data.message,
message: 'service has been successfully generated.',
display: 'support-both',
})
);
dispatch(setRawService(data.message));
}
if (data.type === 'loading') {
dispatch(
setStatus({
type: 'success',
data: data.message,
message: 'service job is in progress.',
display: 'support-both',
})
);
}
},
}
);
After some digging around, figured I'd use the refreshInterval option that comes with swr. I am changing the state of a boolean on my component.
while the request is 'loading' the boolean in state is false.
once the job is 'complete' the boolean in state is set to true.
there is a ternary within my hook that sets the refreshInterval to 0 (default:off) or 3000.
const [serviceJobComplete, setServiceJobComplete] = useState(false);
const { data: serviceData } = useSwr(
serviceEndpoint,
url => axios.get(url).then(r => r.data),
{
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
refreshInterval: serviceJobComplete ? 0 : 3000,
...
// other options
}
);
helpful resources:
https://github.com/vercel/swr/issues/182
https://swr.vercel.app/docs/options

Return a node js response inside session.withTransaction

I am using session.withTransaction() to execute multiple updates in the mongo db. Please note that promiseArray has multiple Stock.update statements to update stock quantities.
await session.withTransaction(
async () => {
promiseResults = await Promise.all(promiseArray);
for (const result of promiseResults) {
recordCounter++;
if (result.nModified === 1) {
stockItemsNoUpdate.push(goodReturnSummary[recordCounter]);
}
}
if (stockItemsNoUpdate.length > 0) {
return res.status(200).send(response);
}
existingGoodReturnSummary = GoodReturn.build({
_id: sheetId,
goodReturnSummary,
agency,
createdBy,
});
await existingGoodReturnSummary.save({ session: session });
existingGoodReturnSummary = await GoodReturn.calculateTotalGoodReturnAmount(
existingGoodReturnSummary,
session
);
},
{
readPreference: 'primary',
readConcern: { level: 'local' },
writeConcern: { w: 'majority' },
}
);
If stockItemsNoUpdate.length > 0 I need to abort this transaction and send the response. done by below code segment.
if (stockItemsNoUpdate.length > 0) {
return res.status(200).send(response);
}
But I cannot do this because of the below error
Any idea on how to resolve this ??
Cheers
See Nodejs mongodb's Transaction API `withTransaction` always return null and https://jira.mongodb.org/browse/NODE-2014.
https://jira.mongodb.org/browse/NODE-2014?focusedCommentId=2420255&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-2420255 provides a workaround.

AWS PUT request met with "Provided key element does not match schema."

(Edited to incorporate comments)
So I apologize in advance for the long question. I don't know how else to ask it.
I'm trying to finish up a full-stack web app using React, Node, and DynamoDB. POST and GET requests are working fine, but I'm stuck on PUT. My mock PUT request works fine, but once I try it from the front end in React, I get the error mentioned in the title. I'll show the back end code first, then the mock update, and then the front end.
import handler from "./libs/handler-lib";
import dynamoDb from "./libs/dynamodb-lib";
export const main = handler(async (event, context) => {
const data = JSON.parse(event.body);
const params = {
TableName: process.env.tableName,
Key: {
userId: event.requestContext.identity.cognitoIdentityId,
activityId: event.pathParameters.activityId
},
UpdateExpression: "SET title = :title, activityType = :activityType, activityRoutine = :activityRoutine, activityComment = :activityComment",
ExpressionAttributeValues: {
":title": data.title || null,
":activityType": data.activityType || null,
// ":activityRoutine": data.activityRoutine == '' ? "None" : data.activityRoutine,
// ":activityComment": data.activityComment == '' ? "None" : data.activityComment
":activityRoutine": data.activityRoutine || null,
":activityComment": data.activityComment || null
},
ReturnValues: "ALL_NEW"
};
await dynamoDb.update(params);
return { status: true };
This mock update event works without issue:
{
"body": "{\"title\":\"test\",\"activityType\":\"testing\",\"activityRoutine\":\"\",\"activityComment\":\"\"}",
"pathParameters": {
"activityId": "long-alphanumeric-id"
},
"requestContext": {
"identity": {
"cognitoIdentityId": "us-east-and-so-on"
}
}
}
But this code, which produces the exact same Javascript object as the mock, is not okay with AWS:
function saveActivity(activity) {
try {
return API.put("activities", `/activities/${id}`, {
body: activity
});
} catch(e) {
console.log("saveActivity error:", e);
}
}
async function handleSubmit(event) {
event.preventDefault();
setIsLoading(true)
try {
await saveActivity({
title: title, activityType: activityType, activityRoutine: activityRoutine, activityComment: activityComment
// "key": {userId: userId, activityId: activityId}
// "pathParameters": {"id": activityId},
// "requestContext": {"identity": {"cognitoIdentityId": userId}}
});
} catch(e) {
console.log(e)
setIsLoading(false)
}
}
If anyone needs to see more of the code, I'm happy to share, but I figured this question is already getting very long. Any code you see commented out has been tried before without success.
I'd also be happy if someone could point me in the right direction as far as the AWS documentation is concerned. I've been going off of a tutorial and modifying it where need be.
Any help is appreciated!

Mock multiple api call inside one function using Moxios

I am writing a test case for my service class. I want to mock multiple calls inside one function as I am making two API calls from one function. I tried following but it is not working
it('should get store info', async done => {
const store: any = DealersAPIFixture.generateStoreInfo();
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: store
});
const nextRequest = moxios.requests.at(1);
nextRequest.respondWith({
status: 200,
response: DealersAPIFixture.generateLocation()
});
});
const params = {
dealerId: store.dealerId,
storeId: store.storeId,
uid: 'h0pw1p20'
};
return DealerServices.retrieveStoreInfo(params).then((data: IStore) => {
const expectedOutput = DealersFixture.generateStoreInfo(data);
expect(data).toMatchObject(expectedOutput);
});
});
const nextRequest is always undefined
it throw error TypeError: Cannot read property 'respondWith' of undefined
here is my service class
static async retrieveStoreInfo(
queryParam: IStoreQueryString
): Promise<IStore> {
const res = await request(getDealerStoreParams(queryParam));
try {
const locationResponse = await graphQlRequest({
query: locationQuery,
variables: { storeId: res.data.storeId }
});
res.data['inventoryLocationCode'] =
locationResponse.data?.location?.inventoryLocationCode;
} catch (e) {
res.data['inventoryLocationCode'] = 'N/A';
}
return res.data;
}
Late for the party, but I had to resolve this same problem just today.
My (not ideal) solution is to use moxios.stubRequest for each request except for the last one. This solution is based on the fact that moxios.stubRequest pushes requests to moxios.requests, so, you'll be able to analyze all requests after responding to the last call.
The code will look something like this (considering you have 3 requests to do):
moxios.stubRequest("get-dealer-store-params", {
status: 200,
response: {
name: "Audi",
location: "Berlin",
}
});
moxios.stubRequest("graph-ql-request", {
status: 204,
});
moxios.wait(() => {
const lastRequest = moxios.requests.mostRecent();
lastRequest.respondWith({
status: 200,
response: {
isEverythingWentFine: true,
},
});
// Here you can analyze any request you want
// Assert getDealerStoreParams's request
const dealerStoreParamsRequest = moxios.requests.first();
expect(dealerStoreParamsRequest.config.headers.Accept).toBe("application/x-www-form-urlencoded");
// Assert graphQlRequest
const graphQlRequest = moxios.requests.get("POST", "graph-ql-request");
...
// Assert last request
expect(lastRequest.config.url).toBe("status");
});

Conflict in _local

Occasionally I am getting something along the lines of:
http://myhosted.couchdb.com:5984/userdb-6938/_local/H73d9PBjwCczSUMy4NZ6bA%3D%3D
409 (Conflict)
The header response is: {"error":"conflict","reason":"Document update conflict."}
The request payload is:
{
"_id": "_local/f.kaEuoL4i41KgUTkCHAyg==",
"_rev": "0-10",
"session_id": "735AF8E9-BF62-191F-9A63-760C922E1259",
"history": [{
"last_seq": 77,
"session_id": "735AF8E9-BF62-191F-9A63-760C922E1259"
},
{
"last_seq": 67,
"session_id": "908B46C9-06C4-A60D-9189-CCE33ECEB252"
},
{
"last_seq": 59,
"session_id": "7B7A1737-D6AB-23F2-B3C3-954DFAC3F91A"
},
{
"last_seq": 52,
"session_id": "A7B639E5-CCEF-E1DE-91B2-8E27AB4AFB5B"
},
{
"last_seq": 45,
"session_id": "D8A322C2-5421-D493-91FB-DD85258D2194"
}],
"replicator": "pouchdb",
"version": 1,
"last_seq": 77
}
This is happening when performing some actions using pouchDb i.e This code will sometimes trigger it:
try {
await db.bulkDocs(toBeSaved)
} catch (err) {
errorCallback(err, importObj)
}
I understand local docs are docs that don't get replicated but do I need to worry about the error message that shows? The docs do explain about handling conflicts but this appears to be one I can't do anything about. Also, as this is a bulk operation, which document exactly is causing the conflict?
EDIT
My getLocalDb call (which sorts the connection to pouch / couch):
getLocalDb: function (onSyncCompleteDispatch) {
var localDb = new PouchDb(this.databaseName, {auto_compaction: true})
var liveDb = this.getLiveDb()
var self = this
// Only send a db object once it has sync'd with the remote host.
return new Promise(function (resolve, reject) {
resolve(localDb)
// We're not using continuous sync because the loading bars on the app will go wild
// We don't need to get data without users clicking anything at this point.
localDb.sync(liveDb, {
live: false,
retry: false
}).on('complete', function (info) {
var requiresReload = info.pull.docs_written > 0 || info.pull.docs_read > 0 || info.push.docs_written > 0 || info.push.docs_read > 0
if (requiresReload) {
typeof self.onSyncCompleteCallback === 'function' && self.onSyncCompleteCallback(info)
}
}).on('error', function (err) {
if (err.message === 'getCheckpoint rejected with ') {
// Don't need to resolve anything here as we resolved it above and nothing will have
// changed as no connection is available.
console.log('No live connection available, return local (without sync).')
} else {
typeof self.onSyncErrorCallback === 'function' && self.onSyncErrorCallback(err)
reject(err)
}
})
})
}

Resources