feathersjs/feathers-authentication-oauth2

View on GitHub
lib/verifier.js

Summary

Maintainability
A
1 hr
Test Coverage
const Debug = require('debug');

const debug = Debug('@feathersjs/authentication-oauth2:verify');

class OAuth2Verifier {
  constructor (app, options = {}) {
    this.app = app;
    this.options = options;
    this.service = typeof options.service === 'string' ? app.service(options.service) : options.service;

    if (!this.service) {
      throw new Error(`options.service does not exist.\n\tMake sure you are passing a valid service path or service instance and it is initialized before @feathersjs/authentication-oauth2.`);
    }

    this._createEntity = this._createEntity.bind(this);
    this._updateEntity = this._updateEntity.bind(this);
    this._normalizeResult = this._normalizeResult.bind(this);
    this.verify = this.verify.bind(this);
  }

  _normalizeResult (results) {
    // Paginated services return the array of results in the data attribute.
    let entities = results.data ? results.data : results;
    let entity = entities[0];

    // Handle entity not found.
    if (!entity) {
      return Promise.resolve(null);
    }

    // Handle updating mongoose models
    if (typeof entity.toObject === 'function') {
      entity = entity.toObject();
    } else if (typeof entity.toJSON === 'function') {
      // Handle updating Sequelize models
      entity = entity.toJSON();
    }

    debug(`${this.options.entity} found`);
    return Promise.resolve(entity);
  }

  _updateEntity (entity, data) {
    const options = this.options;
    const name = options.name;
    const id = entity[this.service.id];
    debug(`Updating ${options.entity}: ${id}`);

    const newData = {
      [options.idField]: data.profile.id,
      [name]: data
    };

    return this.service.patch(id, newData, { oauth: { provider: name } });
  }

  _createEntity (data) {
    const options = this.options;
    const name = options.name;
    const entity = {
      [options.idField]: data.profile.id,
      [name]: data
    };

    const id = entity[options.idField];
    debug(`Creating new ${options.entity} with ${options.idField}: ${id}`);

    return this.service.create(entity, { oauth: { provider: name } });
  }

  _setPayloadAndDone (entity, done) {
    const id = entity[this.service.id];
    const payload = { [`${this.options.entity}Id`]: id };
    done(null, entity, payload);
  }

  verify (req, accessToken, refreshToken, profile, done) {
    debug('Checking credentials');
    const options = this.options;
    const query = {
      [options.idField]: profile.id, // facebookId: profile.id
      $limit: 1
    };
    const data = { profile, accessToken, refreshToken };
    let existing;

    if (this.service.id === null || this.service.id === undefined) {
      debug('failed: the service.id was not set');
      return done(new Error('the `id` property must be set on the entity service for authentication'));
    }

    // Check request object for an existing entity
    if (req && req[options.entity]) {
      existing = req[options.entity];
    }

    // Check the request that came from a hook for an existing entity
    if (!existing && req && req.params && req.params[options.entity]) {
      existing = req.params[options.entity];
    }

    // If there is already an entity on the request object (ie. they are
    // already authenticated) attach the profile to the existing entity
    // because they are likely "linking" social accounts/profiles.
    if (existing) {
      return this._updateEntity(existing, data)
        .then(entity => this._setPayloadAndDone(entity, done))
        .catch(error => error ? done(error) : done(null, error));
    }

    // Find or create the user since they could have signed up via facebook.
    this.service
      .find({ query })
      .then(this._normalizeResult)
      .then(entity => entity ? this._updateEntity(entity, data) : this._createEntity(data))
      .then(entity => this._setPayloadAndDone(entity, done))
      .catch(error => error ? done(error) : done(null, error));
  }
}

module.exports = OAuth2Verifier;