As before, let's implement the Login engine first. Like our other engines, we are first using a validator to validate the request object. Once the request is validated, we then use the Elasticsearch client's search method to see how many user documents match the email and digest provided. If there are non-zero documents, then a user with these credentials exists, and the engine should resolve with a token (we are using a placeholder string for now). If there are no users that match these credentials, it means that those credentials are invalid, and the engine should return with a rejected promise:
import specialEscape from 'special-escape';
const specialChars = ['+', '-', '=', '&&', '||', '>', '<', '!', '(', ')', '{', '}', '[', ']', '^', '"', '~', '*', '?', ':', '\\', '/'];
function loginUser(req, db, validator, ValidationError) {
const validationResults = validator(req);
if (validationResults instanceof ValidationError) {
return Promise.reject(validationResults);
}
return db.search({
index: process.env.ELASTICSEARCH_INDEX,
type: 'user',
q: `(email:${specialEscape(req.body.email, specialChars)}) AND (digest:${specialEscape(req.body.digest, specialChars)})`,
defaultOperator: 'AND',
}).then((res) => {
if (res.hits.total > 0) {
return 'IDENTIFIER';
}
return Promise.reject(new Error('Not Found'));
});
}
export default loginUser;
When searching in Elasticsearch, there are certain characters that must be escaped. We are using the special-escape npm package to escape our email and bcrypt digest before passing it to Elasticsearch. Therefore, we must add this package to our repository:
$ yarn add special-escape
Next, we move on to the request handler. Create a new file at src/handlers/auth/loginindex.js with the following function:
function login(req, res, db, engine, validator, ValidationError) {
return engine(req, db, validator, ValidationError)
.then((result) => {
res.status(200);
res.set('Content-Type', 'text/plain');
return res.send(result);
})
.catch((err) => {
res.set('Content-Type', 'application/json');
if (err instanceof ValidationError) {
res.status(400);
return res.json({ message: err.message });
}
if (err.message === 'Not Found') {
res.status(401);
return res.json({ message: 'There are no records of an user with this email and password combination' });
}
res.status(500);
return res.json({ message: 'Internal Server Error' });
});
}
export default login;
Then, we need to define a validator for the Login endpoint payload. Fortunately for us, the Login payload has the same structure as the Create User payload, and so we can simply reuse the Create User validator. However, to make it explicit, let's create a file at src/validators/auth/login.js with the following two lines:
import validate from '../users/create';
export default validate;
Lastly, import the handler, engine, and validator in src/index.js and define a new route:
import loginValidator from './validators/auth/login';
import loginHandler from './handlers/auth/login';
import loginEngine from './engines/auth/login';
const handlerToEngineMap = new Map([
[loginHandler, loginEngine],
...
]);
const handlerToValidatorMap = new Map([
[loginHandler, loginValidator],
]);
app.post('/login', injectHandlerDependencies(
loginHandler, client, handlerToEngineMap, handlerToValidatorMap, ValidationError,
));
Now, run the E2E tests again and they should be green.