Maitredhotel first version

This article is part of a long tutorial.

In a previous article, I defined the need for an authentication plugin for the Leanhub.io project. I am happy to announce the first draft of this plugin is ready.

You can find the sources here. Let’s take a look at this:

Project structure

.
├── index.js # main file: plugin registration logic
├── lib
│ ├── api # routes
│ │ ├── auth.js
│ │ └── user.js
│ ├── index.js
│ ├── models # models
│ │ └── user.model.js
│ └── services # business logic stande here
│ ├── auth.service.js
│ └── user.service.js
├── LICENSE
├── package.json
├── README.md
└── test
├── api
│ ├── auth.test.js
│ └── user.test.js
└── services
├── auth.service.test.js
└── user.service.test.js

As you can see, the business logic and the api related one are separated. Controllers will call the services.

The code

Model

There is only one model defined here. Let’s take a look at this:

A Schema is simply a JSON object defining the form of an object. It can be seen as a class definition.

There is several parameters here, we will describe a few of them:

  • githubid: This is the id of the user on Github. It is a String (type: String), it is required ( required: true), it will not be returned by default queries (select: false). This means that to require it, a specific instruction is to be given to the query: User.find() returns users without githubid when User.find().select(‘+githubid’) will fill this claim. This is an index fiel (index: true)
  • avatar: the url to the Github avatar of the user. As it’s only property is to be a String, it is acceptable to only state: avatar: String.
  • The two last properties we have here are: unique (only one document with such a value) and Default (default value of the property).

As a shameful matter of fact, the ‘unique’ property is not enforced in my tests, I had to add manual checks for now and will correct this issue later.

After defining a schema, we can create a Mongoose model from it with:

Mongoose.model('User', userSchema);

The collection ‘users’ is then created and will be used to store users.

Services

Two services are defined here. We will only describe the ‘user.service’ one. All functions in services return promises.

This service exports 4 methods:

  • save: ensure the username unicity (for now, when I will fix the mongoose issue, it will be handled by MongoDB) and then save a new user. The way to do so is easy:
const user = new User(candidate) // User is the exported model and candidate the content of the user to create
user.save() // is a promise resolved when the insertion in MongoDB is successful.
  • read: it returns the public profile of a user. It uses User.findOne({query}).exec(). The exec() part is to tell Mongoose to return a promise
  • update (unused): it updates a user using ‘findOneAndUpdate’ :
findOneAndUpdate({query}, {update operation ($set here)}, {options}) 

The option we use is { new: true } to return the updated version of the document.

  • translate: gives a user from its username.

Error are raised using Boom. If an error occurs and is not thrown using Boom, it will be considered as a 500 error. If it is a boom error, hapi will return it directly to the client.

API

As for the services, there is an api file for users and another for authentication. Let’s take a look at the authentication one:

This module exports 2 hapi route config objects:

  • logout: it calls a service that drop current’s user token.
  • login: the login route. It has 2 methods (GET and POST). The GET method is used by clients to require access. They will be redirected to Github to allow authentication sharing with Leanhub. Then Github will call the POST route and the credentials will be available in the ‘request.auth.crednetials’. This object will be used to create (or login) the user in the ‘AuthService.githubHandler’ method.

As you can see, we call reply with functions returning promises. This is acceptable for hapi and it will return the result of the resolved or rejected promise.

lib/index.js

This simply defines all the routes in one place and link the controllers with paths and methods. Then the routes are exported.

index.js

In this index, we define a plugin registering two external plugions:

  • hapi-auth-bearer-token’ to handle bearer authentication
  • Bell’ to handle Github authentication. I created an external application on Github to get access tokens. Those are secrets and are given to maitredhotel through configuration.

Tests

The tests are pretty straightforward to read. The only gotcha is when testing Github authentication. Bell provides a mocking solution:

Bell.simulate((request, next) => next(null, credentials));

Documentation for this feature is available here.

Conclusion

There is right now an issue with the unique indexes in mongo. This is being fixed and the article will be updated soon !

Do not hesitate to read the rest of the code. It should be pretty easy if you have followed the other articles of this serie.

I would be happy to have comments, questions and remarks from you !

Vlad

Like what you read? Give Vladimir de Turckheim a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.