chore: port auth addon from demo branch

This commit is contained in:
Randall Morey committed 2020-07-24 11:15:40 -04:00
1 parent 26388b8527
commit 4ef58a0d6d
14 files changed
+558 -5

No files matched your search

View File
Whitespace-only changes.
@@ -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);
}
}
+134
View File
@@ -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 });
}
}
@@ -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 {}
+4
View File
@@ -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",
@@ -0,0 +1,6 @@
import PasswordAuthenticator from 'auth/authenticators/password';
export default class ApplicationPasswordAuthenticator extends PasswordAuthenticator {
authEndpoint = '/authenticate';
deauthEndpoint = '/deauthenticate';
}
@@ -0,0 +1,6 @@
import CookieSessionStore from 'auth/session-stores/cookie';
export default class ApplicationCookieSessionStore extends CookieSessionStore {
cookiePath = '/';
cookieName = 'my-cookie';
}
@@ -0,0 +1,3 @@
import LocalStorageSessionStore from 'auth/session-stores/local-storage';
export default class ApplicationLocalStorageSessionStore extends LocalStorageSessionStore {}
+22
View File
@@ -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
*/
}
@@ -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);
}
@@ -0,0 +1,3 @@
import { JSONAPISerializer } from 'ember-cli-mirage';
export default JSONAPISerializer.extend({});
@@ -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'));
});
});
@@ -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'
);
});
});
+82 -5
View File
@@ -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"