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]);
  },
});