Divide users by roles in FeathersJs

It's about the underrated feathersjs framework .
In a nutshell, how it works you can read here . One working morning, a new TK came to me in the messenger and it was described as follows: you need to separate users for 2 services (that is, for the back-end to process authentication requests from two different fronts).
That is, we have: 2 divided fronts written in VueJs, located on different domain names, a common backend written in feathers (and, naturally, one users table in the database with the role).
That is, the table may contain 2 such fields
email pass project
+------------+--------+-------
1@gmail.com 123 front_one
1@gmail.com 123 front_two
Naturally, the password is hashed.
As a module for entering the service we use feathers-authentication (a version of passportjs adapted for feathers ).
And so, as for local login, then everything is simple. In order to determine where the request with the login / password pair came to the back of, we can insert one more parameter into the request body at the front, for example, “project”, and search for the right user in the database using this parameter.
A little more detail how to make local auth user. I created a separate file for authentication (it was the file, and did not generate the service) auth.js, connected it to app.js. So auth.js will look something like this for you
const authentication = require('feathers-authentication');
const jwt = require('feathers-authentication-jwt');
const local = require('feathers-authentication-local');
const oauth2 = require('feathers-authentication-oauth2');
const FacebookStrategy = require('passport-facebook');
const commonHooks = require('feathers-hooks-common');
module.exports = function () {
const app = this;
const config = app.get('authentication');
app.configure(authentication(config));
app.configure(jwt());
app.configure(local(config.local));
app.service('authentication').hooks({
before: {
create: [
commonHooks.lowerCase('email'),
authentication.hooks.authenticate(config.strategies)
],
remove: [
authentication.hooks.authenticate('jwt')
]
}
});
};
In general, there is nothing interesting in it now, the only thing I would like to note is that I did not put out the hooks in separate files (as it happens when you call the standard generate service), and added hook commonHooks.lowerCase ('email') - I think it’s clear for what is it for.
Now add some magic. Having rummaged in the documentation, I found the verifier class , which can be expanded and added to my functional. I added to the anonymous function a new configuration for local auth
app.configure(local({ Verifier: CustomVerifier }));
and called my new class
class CustomVerifier extends Verifier {
verify(req, username, password, done) {
return this.app.service('users').find({
query: {
email: username,
roles: req.query.project
}
}).then(res => {
const user = res.data[0];
if (user) {
const userId = res.data[0].id;
this._comparePassword(user, password).then(() => {
if (!user.isVerified) {
done(null)
} else {
done(null, user, { userId: userId });
}
}).catch(err => {
done(null)
})
} else {
done(null)
}
})
}
}
What does this class do? First, we connect to the users service - this.app.service ('users') - and call the find method with the query parameter. That is, we are looking in the database for the user we need in two fields and if we find it, then in the answer (res variable) there will be an array of users found, if no users are found, then the array will return empty. Then we call f-tion
this._comparePassword()
where we pass as a parameter the user found and the password that came from the front. The _comparePassword function hashes the password and compares it with the password that lies in the database and if the password matches, then we call in then ()
done(null, user, { userId: userId });
where the first argument is the error object, the second is the current user, the third is the user id in the database, done () in turn returns the correct token. If we pass the only null argument to done (), then the request status will become 401, and in the response we will get
сlassName:"not-authenticated"
code:401
errors:{}
message:"Error"
name:"NotAuthenticated"
And that would be the end of it, but you can also access our service via facebook. In order for this to be possible, the following should be added to the anonymous function:
app.configure(oauth2(Object.assign({
name: 'facebook',
Strategy: FacebookStrategy,
Verifier: CustomVerifierFB
}, config.facebook)));
In this code, again, we are only interested in one parameter: “Verifier: CustomVerifierFB”. We, as in the case of local registration, extend the built-in Verifier class. When login via fb, the front does not send a request to a specific URL on the back, but clicks the link, that is, on the front it will look like this:
Войти через Fb
In a nutshell, then after clicking on the link a redirect to the back-end will occur, the back-end will redirect to FB, the FB will redirect to the back-end, the back-end will write the generated token to cookies and send to the front page. At the front, you need to parse cookies and send the following requests to the back with a new token.
And there would be no this article, but I spent a lot of time on the question - But how actually to find out where the user came from?
The answer was quite simple. Before registering a component to enter through FB, you need to do this:
app.get('/auth/facebook', (req, res, next) => {
referOrigin = req.headers.referer
next();
})
app.configure(oauth2(Object.assign({
name: 'facebook',
Strategy: FacebookStrategy,
Verifier: CustomVerifierFB
}, config.facebook)));
That is, we catch the transition to '/ auth / facebook', and write the value req.headers.referer into the global variable (referOrigin) and start registering oauth2 (). Thus, we get the host value in the global variable and can use this value in the CustomVerifierFB class, which will look something like this:
class CustomVerifierFB extends Verifier {
verify(req, accessToken, refreshToken, profile, done) {
const refer = referOrigin
let roles = ''
if (refer === 'front_one') {
roles = 'front_one'
} else {
roles = 'front_two'
}
return this.app.service('users').find({
query: {
facebookId: profile.id,
roles: roles
}
}).then(res => {
if (res.data[0]) {
done(null, res.data[0], { userId: res.data[0].id });
} else {
return this.app.service('users').create({
facebookId: profile.id,
email: profile._json.email,
first_name: profile._json.first_name,
last_name: profile._json.last_name,
gender: profile._json.gender,
avatar: profile._json.picture.data.url,
roles: roles,
isVerified: true,
username: profile._json.email + 'whereFromUser'
}).then(createRes => {
done(null, createRes, { userId: createRes.id });
})
}
})
}
}
In verify, we did the following:
- this.app.service ('users'). find () - look for whether there is a user in the database with facebookId, which came to us as an answer with FB
- done (null, res.data [0], {userId: res.data [0] .id}) - if there is, then create a new token and return it to the front
- this.app.service ('users'). create () if not found, then create such a user and then you call done ()
This is how I solved the problem of separating users for two different fronts.
PS - then I will write how I did password recovery for 2 user groups