FeathersJS: How to cancel the authentication hook using an error from a hook after it - promise

I used app.authenticate on client.
It called the authentication hook in before create hook on server.
I imported from 'feathers-authentication-manage.hook' as verifyHooks.
Before create hook:
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies),
async context => {
const { app, data } = context;
await app.service('users').find({
query: {
usernameUpperCase: data.username.toUpperCase(),
$limit: 1
}
})
.then(async (user) => {
await user.data.map(async data => {
if(!data.isVerified) {
await console.log('HELLO FROM ABOVE.');
//await v.validationError('Verify Email. A token link has been sent to your email.');
}
});
})
.catch(err => v.validationError(err));
},
verifyHooks.isVerified()
],
The 3 hooks in order were:
1. authentication
2. my hook
3. isVerified() email verify hook from feathers-authentication management
On client, the authenticate promise would be rejected when isVerified() hook activated even if was after the authentication hook.
If I removed the isVerified() hook, the authenticate promise would resolve.
How do I make my hook, the second hook, behave like isVerified() so the authenticate promise on client be rejected?

The first thing is that you are making your life harder by using async/await not as it is intended. The idea is to not having to write all those .then and .catch handlers.
The .catch handler is also probably where actual issue is. If a .catch (in your case v.validationError(err)) does not reject or throw an error, the promise will resolve successfully. Using async/await the right way and Promise.all to wait for the asynchronous validation steps and then re-throwing the validation error should do it:
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies),
async context => {
const { app, data } = context;
const user = await app.service('users').find({
query: {
usernameUpperCase: data.username.toUpperCase(),
$limit: 1
}
});
try {
await Promise.all(user.data.map(async data => {
if(!data.isVerified) {
await console.log('HELLO FROM ABOVE.');
//await v.validationError('Verify Email. A token link has been sent to your email.');
}
});
} catch(err) {
throw v.validationError(err);
}
},

Related

Apollo Graphql Server: Request takes a full minute to reach resolver

I am using Apollo Graphql on a NodeJS server. I recently notices that my requests were taking a lot of time and decided to get into the issue. I added log timestamp and added console logs to various locations in my server to figure out the bottleneck. My server code is as follows:
(async function () {
const app = express();
const httpServer = createServer(app);
const wsServer = new WebSocketServer({
server: httpServer,
path: "/graphql",
});
const serverCleanup = useServer({ schema }, wsServer);
const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
},
};
},
},
myPlugin
],
healthCheckPath: '/health',
async onHealthCheck() {
return
},
});
await server.start();
app.use(
'/',
cors(),
// 50mb is the limit that `startStandaloneServer` uses, but you may configure this to suit your needs
bodyParser.json({ limit: '50mb' }),
// expressMiddleware accepts the same arguments:
// an Apollo Server instance and optional configuration options
expressMiddleware(server, {
context: async ({ req }) => {
let decodedToken
try {
if (env === 'development') {
decodedToken = {
uid: "test"
}
} else {
decodedToken = await verifyIdToken(req.headers?.authorization?.replace('Bearer ', ''))
}
} catch (error) {
decodedToken = null
}
return {
decodedToken,
jwt: decodedToken
}
}
}),
);
await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
console.log(`🚀 Server ready at http://localhost:4000/`);
})()
Then in my graphql resolvers I have code similar to this
const { AuthenticationError } = require('#apollo/server/express4');
const mutations = {
createPost: async(_, { createPostInput }, context) => {
console.log('In graphql mutation')
if (!context.decodedToken || !Object.keys(context.decodedToken).length) {
throw new AuthenticationError('Unauthenticated');
}
console.log('In graphql mutation 2')
return await createPostApi(createPostInput);
}
}
code for "my plugin" passed to apollo server is taken from apollo docs. It only prints logs for various events.
const myPlugin = {
// Fires whenever a GraphQL request is received from a client.
async requestDidStart(requestContext) {
console.log('Request started!');
return {
// Fires whenever Apollo Server will parse a GraphQL
// request to create its associated document AST.
async parsingDidStart(requestContext) {
console.log('Parsing started!');
},
// Fires whenever Apollo Server will validate a
// request's document AST against your GraphQL schema.
async validationDidStart(requestContext) {
console.log('Validation started!');
},
async executionDidStart(requestContext) {
console.log('Execution started!');
},
};
},
};
I have installed log-timestamp package to print timestamp for each log and here is the output
[2023-01-31T18:23:02.428Z] Request started!
[2023-01-31T18:23:02.430Z] Parsing started!
[2023-01-31T18:23:02.432Z] Validation started!
[2023-01-31T18:23:02.450Z] Execution started!
[2023-01-31T18:23:03.081Z] Request started!
[2023-01-31T18:23:03.081Z] Parsing started!
[2023-01-31T18:23:03.081Z] Validation started!
[2023-01-31T18:23:03.083Z] Execution started!
[2023-01-31T18:23:03.380Z] Request started!
[2023-01-31T18:23:03.381Z] Execution started!
[2023-01-31T18:23:18.290Z] Request started!
[2023-01-31T18:23:18.291Z] Execution started!
[2023-01-31T18:23:22.878Z] Request started!
[2023-01-31T18:23:22.878Z] Execution started!
[2023-01-31T18:23:23.878Z] Request started!
[2023-01-31T18:23:23.878Z] Execution started!
[2023-01-31T18:23:24.869Z] Request started!
[2023-01-31T18:23:24.869Z] Execution started!
[2023-01-31T18:23:30.389Z] Request started!
[2023-01-31T18:23:30.390Z] Execution started!
[2023-01-31T18:23:41.372Z] Request started!
[2023-01-31T18:23:41.373Z] Execution started!
[2023-01-31T18:24:01.046Z] Request started!
[2023-01-31T18:24:01.047Z] Execution started!
[2023-01-31T18:24:02.040Z] Request started!
[2023-01-31T18:24:02.041Z] Execution started!
[2023-01-31T18:24:03.180Z] In graphql mutation
[2023-01-31T18:24:03.180Z] In graphql mutation 2
// logs below this point are from my actual mutation. Actual log output has been redacted
[2023-01-31T18:24:03.180Z] Starting ...
[2023-01-31T18:24:03.181Z] Inside function
[2023-01-31T18:24:03.181Z] Sorting ...
[2023-01-31T18:24:03.181Z] Getting from db ...
[2023-01-31T18:24:03.311Z] Got ...
[2023-01-31T18:24:03.311Z] Creating ... input
[2023-01-31T18:24:03.312Z] Creating ... input
[2023-01-31T18:24:03.312Z] Creating ... Input
[2023-01-31T18:24:03.312Z] Creating in db
[2023-01-31T18:24:03.702Z] Fetching fetching from db
[2023-01-31T18:24:03.756Z] parsing
[2023-01-31T18:24:03.756Z] Starting another thing
[2023-01-31T18:24:03.756Z] In that other thing
[2023-01-31T18:24:03.756Z] Starting a third thing
[2023-01-31T18:24:03.760Z] Creating (db call) ...
[2023-01-31T18:24:03.803Z] Finding (db call) ...
[2023-01-31T18:24:03.836Z] Creating (another db call) ...
[2023-01-31T18:24:03.838Z] Creating (db call) ...
[2023-01-31T18:24:03.838Z] Creating (db call) ...
[2023-01-31T18:24:03.839Z] Creating (db call)...
[2023-01-31T18:24:03.840Z] Finishing ...
As you can see, the request started at 18:23:02 and reached the resolver at 18:24:03, a full minute later. There is no middleware involved, this is my local machine so there is no network latency issue or wait for token verification either. The actual business logic gets executed within the same second but overall time becomes 1min+. How can I reduce this lag?
Weirdly enough, a simple machine restart fixed the problem. Although I still do not understand what caused this in the first place. Could be a problem related to MacOS or Apollo server.

Resending a graphql mutation after re-authenticating using Apollo's useMutation

I have an issue where we're using apollo client and specifically the useMutation react hook to perform mutation calls to our GraphQL Server.
At certain times, the server may return a 401 unauthorized response - at which point, we can make a call to special endpoint which re-authenticates the client and refreshes the cookie/token whatever.
I want to be able to re-run the same mutation again once the client is re-authenticated. So basically I would like to know if it is possible to do the following:
useMutation --> Receive 401 Unauthorized --> call to refresh token --> rerun same initial mutation
This is how our useMutation looks like:
const [mutationFunction, { data, ...rest }] = useMutation(query, {
onError(_err: any) {
const networkError = error?.networkError as any;
if (networkError?.statusCode === 401 && !refreshFailed) {
// eslint-disable-next-line prefer-destructuring
loading = true;
error = undefined;
fetch('/authentication/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(token => {
localStorage.setItem(jwtLocalStorageKey, token);
// re fetch here
})
.catch(() => {
refreshFailed = true;
});
} else {
showAlert(_err.message, 'error');
}
}
});
and this is how we call it currently:
const {
mutationFunction: updateTournamentUserMutation,
loading: updateTournamentUserLoading,
error: updateTournamentUserError,
data: updateTournamentUserData
} = useMutationHook(gqlUpdateTournamentUser);
updateTournamentUserMutation({ variables: { input } });
Because we're using hooks and the way we're using it above, I'm not entirely sure how we can save or reuse the same data that is initially sent in the first mutation (that is the mutation parameters)
Is it possible to do so using the current way we're doing it?

rjxjs handle network errors from socket.io-client

I use rxjs and socket.io client.
Id like to solve this problem.
socket is connected
user send data
socket is having any network error during delivery
delivery is faled
socket throw error to user
Here is my code. how to handle network errors indise Observable?
private sendData(data: any, event: SOCKET_EVENTS): Observable<any> {
return new Observable<any>(observer => {
this.socket
.emit(event, data, function(responseData: Result<any>) {
console.log("Data sended", responseData);
if (responseData.success === true) {
observer.next(responseData.data);
observer.complete();
} else {
console.error(" this.socketData not sended", responseData);
observer.error(data);
}
})
});
}
You can handle errors globaly using event error or something else. For every single emit you can use acknowledgements. Anyway, procedure which handle errors should be more complex. For example:
private sendData(data: any, event: SOCKET_EVENTS): Observable<any> {
return new Observable<any>(observer => {
// set timeout before call error
let errorTimeout = setTimeout(() => {
console.error(" this.socketData not sended");
observer.error();
}, 5000);
this.socket
.emit(event, data, (responseData: Result<any>) => {
console.log("Data sended", responseData);
observer.next(responseData.data);
observer.complete();
// reset timer
clearTimeout(errorTimeout);
})
});
}
We wait 5 seconds for response with acknowledgement. Also you should add logic to handle global errors.

Executing code directly throws exception correctly but within a function doesn't

I'm trying to throw exception in the async code. If I throw it directly like this, it works fine. Example:
Query: {
requisitions: async (parent, args, { req }, info) => {
const user = await Auth.findOne({ _id: req.session.userId }).select('roles')
if (!user.roles.some(role => ['driver', 'requestant'].includes(role))) {
throw new ForbiddenError(ErrorCodes.UNAUTHENTICATED)
}
return true
}
}
But if I make a helper function to handle this, it doesn't work. I need the helper function because this roles check is needed at many places in the app.
Query: {
requisitions: async (parent, args, { req }, info) => {
hasRights(req, ['driver', 'requestant'])
return true
}
}
export const hasRights = async (req, allowedRoles) => {
const user = await Auth.findOne({ _id: req.session.userId }).select('roles')
if (!user.roles.some(role => allowedRoles.includes(role))) {
throw new ForbiddenError(ErrorCodes.UNAUTHENTICATED)
}
}
Note: the hasRights is exported from another file.
I have tried all combinations of try/catch blocks in both files but I just get the UnhandlePromiseRejectionWarning in the server console but the requisitons resolver continues in execution. I want it to stop if the exception is thrown. I have no idea why it only works one way and not the other, even though it is the same thing. Any help appreciated. Thanks.
Calling hasRights(req, ['driver', 'requestant']) can return a rejected promise, but you have no code to ever handle that. As such, it's an unhandled rejection.
When the throw is inline, it's caught by the async requsitions() function which then returns a rejected promise so the caller of requisitions() has a chance to catch it.
You can make sure a rejected promise returned from hasRights() is handled the same way by adding await in front of the hasRights() call like this:
Query: {
requisitions: async (parent, args, { req }, info) => {
await hasRights(req, ['driver', 'requestant'])
return true
}
}
This will allow the async requisitions() function to automatically catch a rejection from hasRights() the same way your original inline throw did.
You will, of course, have to make sure you handle rejected promises properly in the callers of requisitions().
It also occurs to me that you need to know that executing a throw inside an async function will be automatically caught by the async function and converted into a rejection of the promise that is returned by the async function. This is a feature of async functions.

How redirect to login page when got 401 error from ajax call in React-Router?

I am using React, React-Router and Superagent. I need authorization feature in my web application. Now, if the token is expired, I need the page redirect to login page.
I have put the ajax call functionality in a separated module and the token will be send on each request's header. In one of my component, I need fetch some data via ajax call, like below.
componentDidMount: function() {
api.getOne(this.props.params.id, function(err, data) {
if (err) {
this.setErrorMessage('System Error!');
} else if (this.isMounted()) {
this.setState({
user: data
});
}
}.bind(this));
},
If I got 401 (Unauthorized) error, maybe caused by token expired or no enough privilege, the page should be redirected to login page. Right now, in my api module, I have to use window.loication="#/login" I don't think this is a good idea.
var endCallback = function(cb, err, res) {
if (err && err.status == 401) {
return window.location('#/login');
}
if (res) {
cb(err, res.body);
} else {
cb(err);
}
};
get: function(cb) {
request
.get(BASE_URL + resources)
.end(endCallback.bind(null, cb));
},
But, I can't easily, call the react-router method in my api module. Is there an elegant way to implemented this easy feature? I don't want to add an error callback in every react components which need authorized.
I would try something like this:
Use the component's context to manipulate the router (this.context.router.transitionTo()).
Pass this method to the API callout as a param.
// component.js
,contextTypes: {
router: React.PropTypes.func
},
componentDidMount: function() {
api.getOne(this.props.params.id, this.context.router, function(err, data) {
if (err) {
this.setErrorMessage('System Error!');
} else if (this.isMounted()) {
this.setState({
user: data
});
}
}.bind(this));
},
// api.js
var endCallback = function(cb, router, err, res) {
if (err && err.status == 401) {
return router.transitionTo('login');
}
if (res) {
cb(err, res.body);
} else {
cb(err);
}
};
get: function(cb, router) {
request
.get(BASE_URL + resources)
.end(endCallback.bind(null, cb, router));
},
I know you didn't want a callback on each authenticated component, but I don't think there's any special react-router shortcuts to transition outside of the router.
The only other thing I could think of would be to spin up a brand new router on the error and manually send it to the login route. But I don't think that would necessarily work since it's outside of the initial render method.

Resources