diff --git a/addons/auth/addon/.gitkeep b/addons/auth/addon/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/addons/auth/addon/authenticators/password.js b/addons/auth/addon/authenticators/password.js new file mode 100644 index 000000000..f4dc5668c --- /dev/null +++ b/addons/auth/addon/authenticators/password.js @@ -0,0 +1,87 @@ +import BaseAuthenticator from 'ember-simple-auth/authenticators/base'; +import { resolve, reject } from 'rsvp'; +import fetch from 'fetch'; + +/** + * + * This authenticator is intended for use with Ember Simple Auth. + * + * Simple id/password base authenticator sends credentials to an endpoint + * specified in the `authEndpoint` URL. If the HTTP response code is in the + * success range, authentication resolves. Otherwise it rejects. + * + * Upon session invalidation, deauthentication is attempted at the + * `deauthEndpoint`, but is not guaranteed. + * + * This authenticator should not be used directly because it does not specify + * an `authEndpoint` or `deauthEndpoint` of its own. To use, generate an + * application authenticator in your app `authenticators/application.js` and + * extend this class. + * + * @example + * + * import PasswordAuthenticator from 'auth/authenticators/password'; + * export default class ApplicationAuthenticator extends PasswordAuthenticator { + * authEndpoint = '/api/authenticate'; + * deauthEndpoint = '/api/deauthenticate'; + * } + * + */ +export default class PasswordAuthenticator extends BaseAuthenticator { + // =methods + + /** + * Restores the session if data is present. Otherwise rejects. + * @override + * @param {object} data + * @return {Promise} + */ + restore(data) { + return data ? resolve(data) : reject(); + } + + /** + * Posts credentials to the URL specified in `authEndpoint` and resolves + * if a success HTTP status code is received, otherwise rejects. + * + * If requesting cookies, be sure to use the cookie session store. Otherwise + * use the local storage session store. + * + * @override + * @param {object} creds + * @param {string} creds.identification is sent to server as `name` + * @param {string} creds.password + * @param {string} authMethodId ID of the auth method to use + * @param {boolean} requestCookies request cookie tokens (default `true`) + * @return {Promise} + */ + async authenticate( + { identification: name, password }, + authMethodID, + requestCookies = true + ) { + const body = JSON.stringify({ + auth_method_id: authMethodID, + token_type: requestCookies ? 'cookie' : null, + credentials: { name, password }, + }); + const response = await fetch(this.authEndpoint, { method: 'post', body }); + const json = await response.json(); + return response.status < 400 ? resolve(json) : reject(); + } + + /** + * Posts to the `deauthEntpoint` on a best-effort basis and then returns. + * Deauthentication with the server is not guaranteed and request failures + * are ignored. + * + * @override + * @return {Promise} + */ + invalidate() { + fetch(this.deauthEndpoint, { method: 'post' }).catch(() => { + /* no op */ + }); + return super.invalidate(...arguments); + } +} diff --git a/addons/auth/addon/session-stores/cookie.js b/addons/auth/addon/session-stores/cookie.js new file mode 100644 index 000000000..ea0501ad5 --- /dev/null +++ b/addons/auth/addon/session-stores/cookie.js @@ -0,0 +1,134 @@ +import BaseSessionStore from 'ember-simple-auth/session-stores/base'; +import { resolve, reject } from 'rsvp'; +import { get } from '@ember/object'; +import { inject as service } from '@ember/service'; + +/** + * + * This session store is intended for use with Ember Simple Auth when requesting + * that the server send cookies (see authenticators). + * + * The cookie session store is used to maintain session state for auth servers + * that send cookies. In this strategy, the server sets any cookies needed for + * future authorization. This store determines if a session is authenticated + * by looking for the presence of a cookie with name specified in `cookieName`. + * + * This session store should not be used directly because it does not specify + * `cookiePath` or `cookieName`. To use, create an application session store + * and extend this class, being sure to specify these attributes. + * + * @example + * + * import CookieSessionStore from 'auth/session-stores/cookie'; + * export default class ApplicationSessionStore extends CookieSessionStore { + * cookiePath = '/'; + * cookieName = 'auth-token'; + * } + * + */ +export default class CookieSessionStore extends BaseSessionStore { + // =services + + @service cookies; + + // =attributes + + /** + * The name of the cookie that stores the name of the authenticator, derived + * from the name of the session cookie. The authenticator name is used + * internally by Ember Simple Auth, since it supports simultaneous + * authentication schemes within a single application. + * @type {string} + */ + get authenticatorCookieName() { + return `${this.cookieName}-authenticator`; + } + + /** + * Name of the authenticator used, if any. + * @type {?string} + */ + get authenticatorName() { + return this.cookies.read(this.authenticatorCookieName); + } + + /** + * True if both session and authenticator cookies are present. + * False otherwise. + * @type {boolean} + */ + get sessionExists() { + const tokenExists = this.cookies.exists(this.cookieName); + const authenticatorExists = this.cookies.exists( + this.authenticatorCookieName + ); + return tokenExists && authenticatorExists; + } + + // =methods + + /** + * This method is called automatically on initialization to perform cleanup + * if necessary. + * + * Sessions exist only if both the session and authenticator cookies are + * present. If either one is missing, we want to cleanup to be sure they + * are both absent. + */ + init() { + if (!this.sessionExists) this.clear(); + } + + /** + * While the primary session cookie is persisted by the server, not the + * application, we still need to "remember" the name of the authenticator + * used to authenticate to properly restore a session. Thus when this method + * is called with an authenticator name, we save it into a cookie with name + * `authenticatorCookieName`. If this method is called without an + * authenticator, all related cookies are cleared. + * @param {object} data + * @return {Promise} + */ + persist(data) { + const authenticator = get(data, 'authenticated.authenticator'); + const path = this.cookiePath; + if (authenticator) { + this.cookies.write(this.authenticatorCookieName, authenticator, { path }); + } else { + this.clear(); + } + return resolve(); + } + + /** + * If a session exists (as reported by `sessionExists`), then this method + * resolves with the authentication object expected by Ember Simple Auth, + * which must specify the name of the authenticator used to authenticate. + * + * If the session does not exist, all related cookies are cleared and this + * method rejects. + * + * @return {Promise} + */ + restore() { + if (this.sessionExists) { + return resolve({ + authenticated: { + authenticator: this.authenticatorName, + }, + }); + } + this.clear(); + return reject(); + } + + /** + * Clears both cookies related to the session: the server specified session + * cookie as well as the authenticator name cookie. + */ + clear() { + const path = this.cookiePath; + this.cookies.clear(this.cookieName, { path }); + this.cookies.clear(this.authenticatorCookieName, { path }); + } +} diff --git a/addons/auth/addon/session-stores/local-storage.js b/addons/auth/addon/session-stores/local-storage.js new file mode 100644 index 000000000..7c216fa55 --- /dev/null +++ b/addons/auth/addon/session-stores/local-storage.js @@ -0,0 +1,20 @@ +import LocalStorageSessionStore from 'ember-simple-auth/session-stores/local-storage'; + +/** + * + * This session store is intended for use with Ember Simple Auth when requesting + * that the server send a full token (e.g. cookies are not requested, + * see authenticators). + * + * NOTE: when using a local storage scheme, authorization is not provided + * automatically. In order to achieve authorization... TODO + * + * @example + * + * import LocalStorageSessionStore from 'auth/session-stores/local-storage'; + * export default class ApplicationSessionStore extends LocalStorageSessionStore { + * + * } + * + */ +export default class AuthLocalStorageSessionStore extends LocalStorageSessionStore {} diff --git a/addons/auth/package.json b/addons/auth/package.json index cdd1f5623..33199987a 100644 --- a/addons/auth/package.json +++ b/addons/auth/package.json @@ -33,16 +33,20 @@ "ember-cli-dependency-checker": "^3.2.0", "ember-cli-eslint": "^5.1.0", "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-mirage": "^1.1.7", "ember-cli-sri": "^2.1.1", "ember-cli-template-lint": "^1.0.0-beta.3", "ember-cli-uglify": "^3.0.0", + "ember-cookies": "^0.5.2", "ember-data": "^3.16.0", "ember-disable-prototype-extensions": "^1.1.3", "ember-export-application-global": "^2.0.1", + "ember-fetch": "^8.0.1", "ember-load-initializers": "^2.1.1", "ember-maybe-import-regenerator": "^0.1.6", "ember-qunit": "^4.6.0", "ember-resolver": "^7.0.0", + "ember-simple-auth": "^3.0.0", "ember-source": "~3.16.0", "ember-source-channel-url": "^2.0.1", "ember-try": "^1.4.0", diff --git a/addons/auth/tests/dummy/app/authenticators/password.js b/addons/auth/tests/dummy/app/authenticators/password.js new file mode 100644 index 000000000..2f312a7e4 --- /dev/null +++ b/addons/auth/tests/dummy/app/authenticators/password.js @@ -0,0 +1,6 @@ +import PasswordAuthenticator from 'auth/authenticators/password'; + +export default class ApplicationPasswordAuthenticator extends PasswordAuthenticator { + authEndpoint = '/authenticate'; + deauthEndpoint = '/deauthenticate'; +} diff --git a/addons/auth/tests/dummy/app/session-stores/cookie.js b/addons/auth/tests/dummy/app/session-stores/cookie.js new file mode 100644 index 000000000..50c1a4441 --- /dev/null +++ b/addons/auth/tests/dummy/app/session-stores/cookie.js @@ -0,0 +1,6 @@ +import CookieSessionStore from 'auth/session-stores/cookie'; + +export default class ApplicationCookieSessionStore extends CookieSessionStore { + cookiePath = '/'; + cookieName = 'my-cookie'; +} diff --git a/addons/auth/tests/dummy/app/session-stores/local-storage.js b/addons/auth/tests/dummy/app/session-stores/local-storage.js new file mode 100644 index 000000000..b7546d920 --- /dev/null +++ b/addons/auth/tests/dummy/app/session-stores/local-storage.js @@ -0,0 +1,3 @@ +import LocalStorageSessionStore from 'auth/session-stores/local-storage'; + +export default class ApplicationLocalStorageSessionStore extends LocalStorageSessionStore {} diff --git a/addons/auth/tests/dummy/mirage/config.js b/addons/auth/tests/dummy/mirage/config.js new file mode 100644 index 000000000..3e74fe7c0 --- /dev/null +++ b/addons/auth/tests/dummy/mirage/config.js @@ -0,0 +1,22 @@ +export default function () { + // These comments are here to help you get started. Feel free to delete them. + /* + Config (with defaults). + + Note: these only affect routes defined *after* them! + */ + // this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server + // this.namespace = ''; // make this `/api`, for example, if your API is namespaced + // this.timing = 400; // delay for each request, automatically set to 0 during testing + /* + Shorthand cheatsheet: + + this.get('/posts'); + this.post('/posts'); + this.get('/posts/:id'); + this.put('/posts/:id'); // or this.patch + this.del('/posts/:id'); + + https://www.ember-cli-mirage.com/docs/route-handlers/shorthands + */ +} diff --git a/addons/auth/tests/dummy/mirage/scenarios/default.js b/addons/auth/tests/dummy/mirage/scenarios/default.js new file mode 100644 index 000000000..d388296b5 --- /dev/null +++ b/addons/auth/tests/dummy/mirage/scenarios/default.js @@ -0,0 +1,7 @@ +export default function (/* server */) { + /* + Seed your development database using your factories. + This data will not be loaded in your tests. + */ + // server.createList('post', 10); +} diff --git a/addons/auth/tests/dummy/mirage/serializers/application.js b/addons/auth/tests/dummy/mirage/serializers/application.js new file mode 100644 index 000000000..6224ab8c9 --- /dev/null +++ b/addons/auth/tests/dummy/mirage/serializers/application.js @@ -0,0 +1,3 @@ +import { JSONAPISerializer } from 'ember-cli-mirage'; + +export default JSONAPISerializer.extend({}); diff --git a/addons/auth/tests/unit/authenticators/password-test.js b/addons/auth/tests/unit/authenticators/password-test.js new file mode 100644 index 000000000..bf516dd43 --- /dev/null +++ b/addons/auth/tests/unit/authenticators/password-test.js @@ -0,0 +1,92 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { Response } from 'miragejs'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; + +module('Unit | Authenticator | password', function (hooks) { + setupTest(hooks); + setupMirage(hooks); + + test('it authenticates to the specified authEndpoint', async function (assert) { + assert.expect(2); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.authEndpoint, (schema, request) => { + const json = JSON.parse(request.requestBody); + assert.ok(json.token_type, 'Requested token cookies by default'); + return new Response(200); + }); + await authenticator.authenticate({}, '').then(() => { + assert.ok(true, 'authentication succeeded'); + }); + }); + + test('it can authenticate without requesting cookies', async function (assert) { + assert.expect(2); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.authEndpoint, (schema, request) => { + const json = JSON.parse(request.requestBody); + assert.notOk(json.token_type, 'Did not request tokens cookies'); + return new Response(200); + }); + await authenticator.authenticate({}, '', false).then(() => { + assert.ok(true, 'authentication succeeded'); + }); + }); + + test('it authenticates with the expected payload', async function (assert) { + assert.expect(1); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.authEndpoint, (schema, request) => { + const json = JSON.parse(request.requestBody); + assert.deepEqual(json, { + auth_method_id: '123', + token_type: 'cookie', + credentials: { + name: 'foo', + password: 'bar', + }, + }); + return new Response(200); + }); + const creds = { + identification: 'foo', + password: 'bar', + }; + const authMethodID = '123'; + await authenticator.authenticate(creds, authMethodID); + }); + + test('it rejects if the endpoint sends an error status code', async function (assert) { + assert.expect(1); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.authEndpoint, () => { + return new Response(400); + }); + await authenticator.authenticate({}).catch(() => { + assert.ok(true, 'authentication failed'); + }); + }); + + test('it deauthenticates on invalidation', async function (assert) { + assert.expect(1); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.deauthEndpoint, () => { + assert.ok(true, 'deauthentication occurred'); + return new Response(200); + }); + await authenticator.invalidate(); + }); + + test('it invalidation succeeds even if the deauthentication request fails', async function (assert) { + assert.expect(2); + const authenticator = this.owner.lookup('authenticator:password'); + this.server.post(authenticator.deauthEndpoint, () => { + assert.ok(true, 'deauthentication was requested'); + return new Response(500); + }); + await authenticator + .invalidate() + .then(() => assert.ok(true)) + .catch(() => assert.notOk(true, 'uh oh, this should not happen')); + }); +}); diff --git a/addons/auth/tests/unit/session-stores/cookie-test.js b/addons/auth/tests/unit/session-stores/cookie-test.js new file mode 100644 index 000000000..464ebab52 --- /dev/null +++ b/addons/auth/tests/unit/session-stores/cookie-test.js @@ -0,0 +1,92 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Session Store | cookie', function (hooks) { + setupTest(hooks); + + let sessionStore; + + hooks.beforeEach(function () { + sessionStore = this.owner.lookup('session-store:cookie'); + sessionStore.clear(); + }); + + hooks.afterEach(function () { + sessionStore.clear(); + }); + + test('it restores a session if both necessary cookies are present (session exists)', async function (assert) { + assert.expect(2); + sessionStore.cookies.write(sessionStore.cookieName, 'token-value', { + path: '/', + }); + sessionStore.cookies.write( + sessionStore.authenticatorCookieName, + 'authenticator-name', + { path: '/' } + ); + assert.ok(sessionStore.sessionExists); + const session = await sessionStore.restore(); + assert.deepEqual(session, { + authenticated: { + authenticator: 'authenticator-name', + }, + }); + }); + + test('it rejects restore if the session does not exist', async function (assert) { + assert.expect(2); + assert.notOk(sessionStore.sessionExists); + await sessionStore.restore().catch(() => {}); + assert.ok(true, 'Rejected'); + }); + + test('it cleans up cookies on restore rejection', async function (assert) { + assert.expect(4); + // For session cookie + sessionStore.cookies.write(sessionStore.cookieName, 'token-value', { + path: '/', + }); + assert.ok( + sessionStore.cookies.exists(sessionStore.cookieName), + 'cookie exists' + ); + await sessionStore.restore().catch(() => {}); + assert.notOk( + sessionStore.cookies.exists(sessionStore.cookieName), + 'cookie was cleared' + ); + // For authenticator name cookie + sessionStore.cookies.write( + sessionStore.authenticatorCookieName, + 'authenticator-name', + { path: '/' } + ); + assert.ok( + sessionStore.cookies.exists(sessionStore.authenticatorCookieName), + 'cookie exists' + ); + await sessionStore.restore().catch(() => {}); + assert.notOk( + sessionStore.cookies.exists(sessionStore.authenticatorCookieName), + 'cookie was cleared' + ); + }); + + test('it persists only an authenticator name cookie, since the server persists the session cookie', async function (assert) { + assert.expect(4); + assert.notOk(sessionStore.cookies.exists(sessionStore.cookieName)); + assert.notOk( + sessionStore.cookies.exists(sessionStore.authenticatorCookieName) + ); + sessionStore.persist({ authenticated: { authenticator: 'foobar' } }); + assert.notOk( + sessionStore.cookies.exists(sessionStore.cookieName), + 'session cookie was not set' + ); + assert.equal( + sessionStore.cookies.read(sessionStore.authenticatorCookieName), + 'foobar' + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index c7cf88da0..5304e5ace 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5335,6 +5335,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base-64@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" + integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs= + base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" @@ -5707,6 +5712,23 @@ broccoli-concat@^3.2.2, broccoli-concat@^3.7.1, broccoli-concat@^3.7.4, broccoli lodash.uniq "^4.2.0" walk-sync "^0.3.2" +broccoli-concat@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/broccoli-concat/-/broccoli-concat-4.2.4.tgz#78e359ddc540b999d815355163bf3cfb6bd67322" + integrity sha512-NgdBIE57r+U/AslBohQr0mCS7PopIWL8dihMI1CzqffQkisAgqWMuddjYmizqRBQlml7crBFaBeUnPDHhf4/RQ== + dependencies: + broccoli-debug "^0.6.5" + broccoli-kitchen-sink-helpers "^0.3.1" + broccoli-plugin "^4.0.2" + ensure-posix-path "^1.0.2" + fast-sourcemap-concat "^2.1.0" + find-index "^1.1.0" + fs-extra "^8.1.0" + fs-tree-diff "^2.0.1" + lodash.merge "^4.6.2" + lodash.omit "^4.1.0" + lodash.uniq "^4.2.0" + broccoli-config-loader@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/broccoli-config-loader/-/broccoli-config-loader-1.0.1.tgz#d10aaf8ebc0cb45c1da5baa82720e1d88d28c80a" @@ -5744,7 +5766,7 @@ broccoli-file-creator@^1.1.1: broccoli-plugin "^1.1.0" mkdirp "^0.5.1" -broccoli-file-creator@^2.1.1: +broccoli-file-creator@^2.0.0, broccoli-file-creator@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/broccoli-file-creator/-/broccoli-file-creator-2.1.1.tgz#7351dd2496c762cfce7736ce9b49e3fce0c7b7db" integrity sha512-YpjOExWr92C5vhnK0kmD81kM7U09kdIRZk9w4ZDCDHuHXW+VE/x6AGEOQQW3loBQQ6Jk+k+TSm8dESy4uZsnjw== @@ -5816,7 +5838,7 @@ broccoli-funnel@^1.0.1: symlink-or-copy "^1.0.0" walk-sync "^0.3.1" -broccoli-funnel@^2.0.0, broccoli-funnel@^2.0.1, broccoli-funnel@^2.0.2, broccoli-funnel@~2.0.2: +"broccoli-funnel@^1.2.0 || ^2.0.0", broccoli-funnel@^2.0.0, broccoli-funnel@^2.0.1, broccoli-funnel@^2.0.2, broccoli-funnel@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-2.0.2.tgz#0edf629569bc10bd02cc525f74b9a38e71366a75" integrity sha512-/vDTqtv7ipjEZQOVqO4vGDVAOZyuYzQ/EgGoyewfOgh1M7IQAToBKZI0oAQPgMBeFPPlIbfMuAngk+ohPBuaHQ== @@ -5896,7 +5918,7 @@ broccoli-merge-trees@^2.0.0: broccoli-plugin "^1.3.0" merge-trees "^1.0.1" -broccoli-merge-trees@^3.0.0, broccoli-merge-trees@^3.0.1, broccoli-merge-trees@^3.0.2, broccoli-merge-trees@~3.0.2: +"broccoli-merge-trees@^2.0.0 || ^3.0.0", broccoli-merge-trees@^3.0.0, broccoli-merge-trees@^3.0.1, broccoli-merge-trees@^3.0.2, broccoli-merge-trees@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-3.0.2.tgz#f33b451994225522b5c9bcf27d59decfd8ba537d" integrity sha512-ZyPAwrOdlCddduFbsMyyFzJUrvW6b04pMvDiAQZrCwghlvgowJDY+EfoXn+eR1RRA5nmGHJ+B68T63VnpRiT1A== @@ -8329,7 +8351,7 @@ ember-cli-babel-plugin-helpers@^1.0.0, ember-cli-babel-plugin-helpers@^1.1.0: resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac" integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA== -ember-cli-babel@7: +ember-cli-babel@7, ember-cli-babel@^7.1.0: version "7.21.0" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.21.0.tgz#c79e888876aee87dfc3260aee7cb580b74264bbc" integrity sha512-jHVi9melAibo0DrAG3GAxid+29xEyjBoU53652B4qcu3Xp58feZGTH/JGXovH7TjvbeNn65zgNyoV3bk1onULw== @@ -8901,6 +8923,14 @@ ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-co ember-cli-version-checker "^2.1.1" semver "^5.4.1" +ember-cookies@^0.5.0, ember-cookies@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/ember-cookies/-/ember-cookies-0.5.2.tgz#06e33463f2f83254fefaf224cc944dec3fb9d3ba" + integrity sha512-nZ7oG97kBcO9UHjO95ryABpnVx62Bhxo7lIsBJNmWvFXleILm9DGueiAynzXxuYWWPuKIeeSbYakrS1869tNTw== + dependencies: + ember-cli-babel "^7.1.0" + ember-getowner-polyfill "^1.1.0 || ^2.0.0" + ember-copy@1.0.0, ember-copy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ember-copy/-/ember-copy-1.0.0.tgz#426554ba6cf65920f31d24d0a3ca2cb1be16e4aa" @@ -9031,6 +9061,25 @@ ember-fetch@^7.0.0: node-fetch "^2.6.0" whatwg-fetch "^3.0.0" +ember-fetch@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ember-fetch/-/ember-fetch-8.0.1.tgz#dc57f3c3ba464e4a8722785f6b44265c46b38020" + integrity sha512-J+knuBFQJlMQUuij2tziMiVcJ+Ss0lCV2XiCQO2cHmG2PUsfvLlTGZ04uUyOXPqrJISxY2W3Et43fQBg4jy7SA== + dependencies: + abortcontroller-polyfill "^1.4.0" + broccoli-concat "^4.2.4" + broccoli-debug "^0.6.5" + broccoli-merge-trees "^4.2.0" + broccoli-rollup "^2.1.1" + broccoli-stew "^3.0.0" + broccoli-templater "^2.0.1" + calculate-cache-key-for-tree "^2.0.0" + caniuse-api "^3.0.0" + ember-cli-babel "^7.13.2" + ember-cli-typescript "^3.1.3" + node-fetch "^2.6.0" + whatwg-fetch "^3.0.0" + ember-get-config@0.2.4, ember-get-config@^0.2.2, ember-get-config@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/ember-get-config/-/ember-get-config-0.2.4.tgz#118492a2a03d73e46004ed777928942021fe1ecd" @@ -9039,7 +9088,7 @@ ember-get-config@0.2.4, ember-get-config@^0.2.2, ember-get-config@^0.2.4: broccoli-file-creator "^1.1.1" ember-cli-babel "^6.3.0" -ember-getowner-polyfill@^2.0.0: +"ember-getowner-polyfill@^1.1.0 || ^2.0.0", ember-getowner-polyfill@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ember-getowner-polyfill/-/ember-getowner-polyfill-2.2.0.tgz#38e7dccbcac69d5ec694000329ec0b2be651d2b2" integrity sha512-rwGMJgbGzxIAiWYjdpAh04Abvt0s3HuS/VjHzUFhVyVg2pzAuz45B9AzOxYXzkp88vFC7FPaiA4kE8NxNk4A4Q== @@ -9247,6 +9296,20 @@ ember-router-generator@^2.0.0: "@babel/traverse" "^7.4.5" recast "^0.18.1" +ember-simple-auth@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ember-simple-auth/-/ember-simple-auth-3.0.0.tgz#d994f512cda1a8a5a6ef5a853fac60c0c0d6acd6" + integrity sha512-JaEw5W/Z9+HIH9I2BmE/JD4KofH4rp79Y51zJpt3UQI1OwWFOzFa+ulN0rK/DVLpGOz7uL7hBIE/rtEcEN1eqA== + dependencies: + base-64 "^0.1.0" + broccoli-file-creator "^2.0.0" + broccoli-funnel "^1.2.0 || ^2.0.0" + broccoli-merge-trees "^2.0.0 || ^3.0.0" + ember-cli-babel "^7.12.0" + ember-cli-is-package-missing "^1.0.0" + ember-cookies "^0.5.0" + silent-error "^1.0.0" + ember-source-channel-url@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/ember-source-channel-url/-/ember-source-channel-url-1.2.0.tgz#77eb9d0889e5f5370e6c70fcb2696c63ff4a34a1" @@ -10209,6 +10272,20 @@ fast-sourcemap-concat@^1.4.0: source-map-url "^0.3.0" sourcemap-validator "^1.1.0" +fast-sourcemap-concat@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-sourcemap-concat/-/fast-sourcemap-concat-2.1.0.tgz#12dd36bfc38c804093e4bd1de61dd6216f574211" + integrity sha512-L9uADEnnHOeF4U5Kc3gzEs3oFpNCFkiTJXvT+nKmR0zcFqHZJJbszWT7dv4t9558FJRGpCj8UxUpTgz2zwiIZA== + dependencies: + chalk "^2.0.0" + fs-extra "^5.0.0" + heimdalljs-logger "^0.1.9" + memory-streams "^0.1.3" + mkdirp "^0.5.0" + source-map "^0.4.2" + source-map-url "^0.3.0" + sourcemap-validator "^1.1.0" + fastq@^1.6.0: version "1.8.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481"