Create a new Retrieve Salt engine at src/engines/auth/salt/retrieve/index.js. In it, we need to use the Elasticsearch client's search method to find the user's document by email, extract the digest from the document, and then extract the salt from the digest:
const NO_RESULTS_ERROR_MESSAGE = 'no-results';
function retrieveSalt(req, db, getSalt) {
if (!req.query.email) {
return Promise.reject(new Error('Email not specified'));
}
return db.search({
index: process.env.ELASTICSEARCH_INDEX,
type: 'user',
body: {
query: {
match: {
email: req.query.email,
},
},
},
_sourceInclude: 'digest',
}).then((res) => {
const user = res.hits.hits[0];
return user
? user._source.digest
: Promise.reject(new Error(NO_RESULTS_ERROR_MESSAGE));
}).then(getSalt);
}
export default retrieveSalt;
This function requires the getSalt method from the bcrypt library, which would be injected by the handler function. Next, create a file at src/handlers/auth/get-salt/index.js to house the handler function, which simply passes the request on to the engine and generates standard responses based on the result of the engine:
function retrieveSalt(req, res, db, engine, _validator, getSalt) {
return engine(req, db, getSalt).then((result) => {
res.status(200);
res.set('Content-Type', 'text/plain');
return res.send(result);
}, (err) => {
if (err.message === 'Email not specified') {
res.status(400);
res.set('Content-Type', 'application/json');
return res.json({ message: 'The email field must be specified' });
}
throw err;
}).catch(() => {
res.status(500);
res.set('Content-Type', 'application/json');
return res.json({ message: 'Internal Server Error' });
});
}
export default retrieveSalt;
Lastly, in src/index.js, import the engine and handler and use it to create a new endpoint:
import { getSalt } from 'bcryptjs';
import retrieveSaltHandler from './handlers/auth/salt/retrieve';
import retrieveSaltEngine from './engines/auth/salt/retrieve';
const handlerToEngineMap = new Map([
[retrieveSaltHandler, retrieveSaltEngine],
...
]);
app.get('/salt', injectHandlerDependencies(retrieveSaltHandler, client, handlerToEngineMap, handlerToValidatorMap, getSalt));
Since we are now using the bcryptjs package in our implementation code, and not just our test code, we should move it from devDependencies to dependencies:
$ yarn remove bcryptjs
$ yarn add bcryptjs
Lastly, we should also modify the injectHandlerDependencies function to pass through the getSalt dependency:
function injectHandlerDependencies(
handler, db, handlerToEngineMap, handlerToValidatorMap, ...remainingArguments
) {
const engine = handlerToEngineMap.get(handler);
const validator = handlerToValidatorMap.get(handler);
return (req, res) => { handler(req, res, db, engine, validator, ...remainingArguments); };
}
export default injectHandlerDependencies;
Now, when we run the E2E tests, they should all pass.