Source

authenticators/devise.js

import { Promise } from 'rsvp';
import { isEmpty } from '@ember/utils';
import { run } from '@ember/runloop';
import BaseAuthenticator from './base';
import { waitFor } from '@ember/test-waiters';

const JSON_CONTENT_TYPE = 'application/json';

/**
  Authenticator that works with the Ruby gem
  [devise](https://github.com/plataformatec/devise).

  __As token authentication is not actually part of devise anymore, the server
  needs to implement some customizations__ to work with this authenticator -
  see [this gist](https://gist.github.com/josevalim/fb706b1e933ef01e4fb6).

  @class DeviseAuthenticator
  @extends BaseAuthenticator
  @public
*/
export default BaseAuthenticator.extend({
  /**
    The endpoint on the server that the authentication request is sent to.

    @memberof DeviseAuthenticator
    @property serverTokenEndpoint
    @type String
    @default '/users/sign_in'
    @public
  */
  serverTokenEndpoint: '/users/sign_in',

  /**
    The devise resource name. __This will be used in the request and also be
    expected in the server's response.__

    @memberof DeviseAuthenticator
    @property resourceName
    @type String
    @default 'user'
    @public
  */
  resourceName: 'user',

  /**
    The token attribute name. __This will be used in the request and also be
    expected in the server's response.__

    @memberof DeviseAuthenticator
    @property tokenAttributeName
    @type String
    @default 'token'
    @public
  */
  tokenAttributeName: 'token',

  /**
    The identification attribute name. __This will be used in the request and
    also be expected in the server's response.__

    @memberof DeviseAuthenticator
    @property identificationAttributeName
    @type String
    @default 'email'
    @public
  */
  identificationAttributeName: 'email',

  /**
    Restores the session from a session data object; __returns a resolving
    promise when there are non-empty
    [token]{@linkplain DeviseAuthenticator.tokenAttributeName}
    and
    [identification]{@linkplain DeviseAuthenticator.identificationAttributeName}
    values in `data`__ and a rejecting promise otherwise.

    @memberof DeviseAuthenticator
    @method restore
    @param {Object} data The data to restore the session from
    @return {Ember.RSVP.Promise} A promise that when it resolves results in the session becoming or remaining authenticated
    @public
  */
  restore(data) {
    // eslint-disable-next-line prefer-promise-reject-errors
    return this._validate(data) ? Promise.resolve(data) : Promise.reject();
  },

  /**
    Authenticates the session with the specified `identification` and
    `password`; the credentials are `POST`ed to the
    [server]{@linkplain DeviseAuthenticator.serverTokenEndpoint}.
    If the credentials are valid the server will responds with a
    [token]{@linkplain DeviseAuthenticator.tokenAttributeName}
    and
    [identification]{@linkplain DeviseAuthenticator.identificationAttributeName}.
    __If the credentials are valid and authentication succeeds, a promise that
    resolves with the server's response is returned__, otherwise a promise that
    rejects with the server error is returned.

    @memberof DeviseAuthenticator
    @method authenticate
    @param {String} identification The user's identification
    @param {String} password The user's password
    @return {Ember.RSVP.Promise} A promise that when it resolves results in the session becoming authenticated. If authentication fails, the promise will reject with the server response; however, the authenticator reads that response already so if you need to read it again you need to clone the response object first
    @public
  */
  authenticate(identification, password) {
    return new Promise((resolve, reject) => {
      const { resourceName, identificationAttributeName, tokenAttributeName } = this.getProperties('resourceName', 'identificationAttributeName', 'tokenAttributeName');
      const data = {};
      data[resourceName] = { password };
      data[resourceName][identificationAttributeName] = identification;

      this.makeRequest(data).then((response) => {
        if (response.ok) {
          response.json().then((json) => {
            if (this._validate(json)) {
              const resourceName = this.get('resourceName');
              const _json = json[resourceName] ? json[resourceName] : json;
              run(null, resolve, _json);
            } else {
              run(null, reject, `Check that server response includes ${tokenAttributeName} and ${identificationAttributeName}`);
            }
          });
        } else {
          run(null, reject, response);
        }
      }).catch((error) => run(null, reject, error));
    });
  },

  /**
    Does nothing

    @memberof DeviseAuthenticator
    @method invalidate
    @return {Ember.RSVP.Promise} A resolving promise
    @public
  */
  invalidate() {
    return Promise.resolve();
  },

  /**
    Makes a request to the Devise server using
    [ember-fetch](https://github.com/stefanpenner/ember-fetch).

    @memberof DeviseAuthenticator
    @method makeRequest
    @param {Object} data The request data
    @param {Object} options request options that are passed to `fetch`
    @return {Promise} The promise returned by `fetch`
    @protected
  */
  makeRequest: waitFor(function(data, options = {}) {
    let url = options.url || this.get('serverTokenEndpoint');
    let requestOptions = {};
    let body = JSON.stringify(data);
    Object.assign(requestOptions, {
      body,
      method:   'POST',
      headers:  {
        'accept':       JSON_CONTENT_TYPE,
        'content-type': JSON_CONTENT_TYPE
      }
    });
    Object.assign(requestOptions, options || {});

    return fetch(url, requestOptions);
  }),

  _validate(data) {
    const tokenAttributeName = this.get('tokenAttributeName');
    const identificationAttributeName = this.get('identificationAttributeName');
    const resourceName = this.get('resourceName');
    const _data = data[resourceName] ? data[resourceName] : data;

    return !isEmpty(_data[tokenAttributeName]) && !isEmpty(_data[identificationAttributeName]);
  }
});