The first step is to create a user data model for the Notes application. Rather than retrieving data from data files or a database, it will use REST to query the server we just created. We could have created user model code that directly accesses the database but, for reasons already discussed, we've decided to segregate user authentication into a separate service.
Let us now turn to the Notes application, which you may have stored as chap08/notes. We'll be modifying the application, first to access the user authentication REST API, and then to use Passport for authorization and authentication.
For the test/admin scripts that we created earlier, we used the restify-clients module. That package is a companion to the restify library, where restify supports the server side of the REST protocol and restify-clients supports the client side. Their names might give away the purpose.
However nice the restify-clients library is, it doesn't support a Promise-oriented API, as is required to play well with async functions. Another library, superagent, does support a Promise-oriented API, plays well in async functions, and there is a companion to that package, Supertest, that's useful in unit testing. We'll use Supertest in Chapter 11, Unit Testing and Functional Testing, when we talk about unit testing. For documentation, see https://www.npmjs.com/package/superagent:
$ npm install superagent@^3.8.x
Create a new file, models/users-superagent.mjs, containing the following code:
import request from 'superagent';
import util from 'util';
import url from 'url';
const URL = url.URL;
import DBG from 'debug';
const debug = DBG('notes:users-superagent');
const error = DBG('notes:error-superagent');
function reqURL(path) {
const requrl = new URL(process.env.USER_SERVICE_URL);
requrl.pathname = path;
return requrl.toString();
}
The reqURL function replaces the connectXYZZY functions that we wrote in earlier modules. With superagent, we don't leave a connection open to the service, but open a new connection on each request. The common thing to do is to formulate the request URL. The user is expected to provide a base URL, such as http://localhost:3333/, in the USER_SERVICE_URL environment variable. This function modifies that URL, using the new WHATWG URL support in Node.js, to use a given URL path:
export async function create(username, password,
provider, familyName, givenName, middleName,
emails, photos) {
var res = await request
.post(reqURL('/create-user'))
.send({
username, password, provider,
familyName, givenName, middleName, emails, photos
})
.set('Content-Type', 'application/json')
.set('Acccept', 'application/json')
.auth('them', 'D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');
return res.body;
} export async function update(username, password,
provider, familyName, givenName, middleName,
emails, photos) {
var res = await request
.post(reqURL(`/update-user/${username}`))
.send({
username, password, provider,
familyName, givenName, middleName, emails, photos
})
.set('Content-Type', 'application/json')
.set('Acccept', 'application/json')
.auth('them', 'D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');
return res.body;
}
These are our create and update functions. In each case, they take the data provided, construct an anonymous object, and POST it to the server.
The superagent library uses an API style where one chains together method calls to construct a request. The chain of method calls can end in a .then or .end clause, either of which take a callback function. But leave off both, and it will return a Promise.
All through this library, we'll use the .auth clause to set up the required authentication key.
In this case, we've purposely chosen variable names for the parameters to match field names of the object with parameter names used by the server. By doing so, we can use this shortened notation for anonymous objects, and our code is a little cleaner by using consistent variable names from beginning to end:
export async function find(username) {
var res = await request
.get(reqURL(`/find/${username}`))
.set('Content-Type', 'application/json')
.set('Acccept', 'application/json')
.auth('them', 'D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');
return res.body;
}
Our find operation lets us look up user information:
export async function userPasswordCheck(username, password) {
var res = await request
.post(reqURL(`/passwordCheck`))
.send({ username, password })
.set('Content-Type', 'application/json')
.set('Acccept', 'application/json')
.auth('them', 'D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');
return res.body;
}
We're sending the request to check passwords to the server.
A point about this method is useful to note. It could have taken the parameters in the URL, instead of the request body as is done here. But since request URL are routinely logged to files, putting the username and password parameters in the URL means user identity information would be logged to files and part of activity reports. That would obviously be a very bad choice. Putting those parameters in the request body not only avoids that bad result, but if an HTTPS connection to the service were used, the transaction would be encrypted:
export async function findOrCreate(profile) {
var res = await request
.post(reqURL('/find-or-create'))
.send({
username: profile.id, password: profile.password,
provider: profile.provider,
familyName: profile.familyName,
givenName: profile.givenName,
middleName: profile.middleName,
emails: profile.emails, photos: profile.photos
})
.set('Content-Type', 'application/json')
.set('Acccept', 'application/json')
.auth('them', 'D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');
return res.body;
}
The findOrCreate function either discovers the user in the database, or creates a new user. The profile object will come from Passport, but take careful note of what we do with profile.id. The Passport documentation says it will provide the username in the profile.id field. But we want to store it as username, instead:
export async function listUsers() {
var res = await request
.get(reqURL('/list'))
.set('Content-Type', 'application/json')
.set('Acccept', 'application/json')
.auth('them', 'D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');
return res.body;
}
Finally, we can retrieve a list of users.