Part 2: User Roles and Management - FeathersJS
Tags: showdev, javascript, webdev, feathersjs
Published: 2020-09-08 12:24:18

## The Backend - FeathersJS
This article focuses on the backend, which leverages the [FeathersJS framework](https://docs.feathersjs.com/) and several complementary libraries in the FeatherJS Ecosystem: [feathers-authentication-management](https://github.com/feathers-plus/feathers-authentication-management/blob/master/docs.md) and [feathers-permissions](https://github.com/feathersjs-ecosystem/feathers-permissions).

### Getting Started With FeatherJS
[Getting started](https://docs.feathersjs.com/guides/basics/starting.html#our-first-app) with FeathersJS is pretty easy. There's a CLI that generates an application based on several configurable options.  

> Note: I would advise against copying/pasting any code snippets and instead go directly to the repository to view the code. Due to the amount of code involved, I've omitted lines of code in this article for brevity. As this is not written as a tutorial, please view the repository if you want to create a similar project.

### FeathersJS Overview
Feathers has a great [getting started guide](https://docs.feathersjs.com/guides/basics/starting.html), so I'd highly recommend reviewing their guide for a more in-depth overview. I'll highlight a few features that are customized for this starter.

#### Configuration
With Feathers, [configuration](https://docs.feathersjs.com/api/configuration.html#usage) is fairly straightforward. It takes the `NODE_ENV` environment variable to determine which configuration to use. For example, if `NODE_ENV=prod`, then it will merge the `default.json` with `prod.json` configuration settings. We'll add some settings to the configuration file to ensure services have the necessary values to run properly.

#### On Login
I wanted to store a timestamp for when a user logs in, so I used the [app.on('login'...) ](https://docs.feathersjs.com/api/channels.html#app-on-login) connection event. 

```js
  app.on('login', (data) => {
    data.user['lastLoggedIn'] = new Date();
    app.service('users').patch(data.user._id, data.user);
  });
```
#### Feathers Services
Feathers services can be generated using the command line generator with `feathers generate service`. This will begin a prompt sequence that configures the service to your needs. [Services](https://docs.feathersjs.com/api/services.html#service-methods) in feathers consist of a class, hooks, and a service definition. 

#### Feathers Mailer
To send emails, the server uses the [feathers-mailer](https://github.com/feathersjs-ecosystem/feathers-mailer) library, which is a wrapper for [nodemailer](https://nodemailer.com/). For this starter, I configured it for [AWS SES](https://aws.amazon.com/ses/), but you can use any [supported transport](https://nodemailer.com/transports/). To configure for AWS, the following configuration keys will be needed from your AWS account:
 
```json
{
  "smtp_user": "aws_smtp_user",
  "smtp_pw": "aws_smtp_pw",
  "smtp_host": "aws_smtp_host"
}
```

You can add these to the `${env}.json` configuration file or `default.json` configuration file.

I created a custom service using the feathers cli and configured it for the AWS SES Transport. The [email service](https://github.com/meditatingdragon/quasar-feathersjs-user-management/blob/master/server/src/services/email/email.service.js) shows how this is set up. 

```js
module.exports = function (app) {
  app.use(
    '/email',
    Mailer(
      smtpTransport({
        host: app.get('smtp_host'),
        secure: true,
        auth: {
          user: app.get('smtp_user'),
          pass: app.get('smtp_pw'),
        },
      })
    )
  );
};
```
Once the email service is configured, it can be used to verify emails on sign up with the feathers-authentication-management library. 

> Coming Soon: I'll be writing a separate article about creating beautiful templated emails to send out using this service.

### Feathers Authentication Management
[Feathers Authentication Management](https://github.com/feathers-plus/feathers-authentication-management/blob/master/docs.md#using-feathers-method-calls) is a library that enables several useful features during the user signup process:

* email verification
* password reset
* update password
* update to new email with verification

To add it to the user workflow, I created an [auth-management service](https://github.com/meditatingdragon/quasar-feathersjs-user-management/tree/master/server/src/services/auth-management).

```js
const authManagement = require('feathers-authentication-management');
const hooks = require('./auth-management.hooks');
const notifier = require('./notifier');

module.exports = function (app) {
  app.configure(authManagement(notifier(app)));

  // Get our initialized service so that we can register hooks
  const service = app.service('authManagement');

  service.hooks(hooks);
};
```
The [notifier](https://github.com/meditatingdragon/quasar-feathersjs-user-management/blob/master/server/src/services/auth-management/notifier.js) processes the incoming request and handles the case accordingly based on the `action` received from the request. The `resendVerifySignup` case will resend the verification email to the user. 

```js
function sendEmail(email) {
    return app
      .service('email')
      .create(email)
      .catch((err) => {
        console.log('Error sending email', err);
      });
  }

switch (type) {
  case 'resendVerifySignup':
  //sending the user the verification email
  tokenLink = getLink('verify', user.verifyToken);
  email = {
    from: FROM_EMAIL,
    to: user.email,
    subject: 'Verify Email',
    html: tokenLink,
  };
  return sendEmail(email);
}
```
To ensure this service has all the necessary information to generate the correct email, the following configuration keys were also added to the `${env}.json` file. 

```json
{
  "from_email": "no-reply@test.com",
  "client_url": "http://localhost:8080",
  "api_url": "http://localhost:3030/"
}
```
Hooks are used to update the user record before and after various actions. 

```js
module.exports = {
  before: {
    create: [
      // after user is created, add verification fields to user record
      verifyHooks.addVerification(),
    ],
    patch: [
      authenticate('jwt'),
      iff(
        // if request is from external provider
        isProvider('external'),
        // do not allow the following fields to be updating
        preventChanges(
          true,
          'email',
          'isVerified',
          'verifyToken',
          'verifyShortToken',
          'verifyExpires',
          'verifyChanges',
          'resetToken',
          'resetShortToken',
          'resetExpires'
        ),
      ),
    ],
    // don't allow external requests to delete the user
    remove: [disallow('external')],
  },

  after: {
    all: [
      // prevent leak of these user information fields
      protect(
        'password',
        'verifyToken',
        'updatedAt',
        'createdAt',
        'verifyShortToken',
        'verifyExpires',
        'resetToken',
        'resetExpires',
        'verifyChanges',
        '__v'
      ),
    ],
    create: [
      // after a user is created, send the user an email to verify email
      (context) => {
        accountService(context.app).notifier(
          'resendVerifySignup',
          context.data
        );
      },
      // remove the user verification fields before returning user as part of request
      verifyHooks.removeVerification(),
    ],
  },
};
```
After a user is created, the verification fields are added to the user (and later removed before being return as part of a request). For security purposes, fields also should not be directly updated by external requests. After a user is created, another hook sends the user a verification email before removing the verification fields from the user. 

### Feathers Permissions
Finally, the backend implements the role concept using [feathers-permissions](https://github.com/feathersjs-ecosystem/feathers-permissions), using a manually created admin account. Accounts default to a 'guest' role defined in the mongoose model (covered in the next article). 

The admin role can update users into other roles. This starter defines 4 roles: 'guest' (default role), 'user', 'admin', 'inactive'. If an admin deactivates a user, it will update their role to 'inactive'.

Hooks are used to control and limit access to specific admin functions, such as updating a user role.

```js
iff(
   checkPermissions({
     roles: ['super_admin', 'admin'],
     field: 'permissions',
     error: false,
   }),
   validate.mongoose(adminUpdateSchema, joiOptions)
),
iff((context) => !context.params.permitted, [
   // ensure user only updates their own record
   setField({
      from: 'params.user._id',
      as: 'params.query._id',
   }),
   validate.mongoose(updateSchema, joiOptions),
]),
```
The hooks above check if the user is an admin, and if so, check the data against the approved admin schema (which allows more fields to be updated). If the user isn't an admin, make sure the user only updates their own record with the approved user schema.

The permissions can be refined even further. Check the [documentation](https://github.com/feathersjs-ecosystem/feathers-permissions) for details. I kept it simple for this starter. 

### Additional Resources
Special shoutout to these authors who wrote tutorials for setting up email verification. 

* [Setting up Email Verification in FeathersJS](https://hackernoon.com/setting-up-email-verification-in-feathersjs-ce764907e4f2)
* [How to set up email verification in FeathersJS](https://blog.feathersjs.com/how-to-setup-email-verification-in-feathersjs-72ce9882e744)

### Did I miss anything?
This wraps up the backend code. Let me know if you have any questions, comments or suggestions. In the next article, I'll be reviewing the MongoDB setup!