diff --git a/packages/ember-simple-auth/package.json b/packages/ember-simple-auth/package.json index e469765c4..c4e01b3d3 100644 --- a/packages/ember-simple-auth/package.json +++ b/packages/ember-simple-auth/package.json @@ -33,7 +33,7 @@ "@embroider/addon-shim": "^1.0.0", "@embroider/macros": "^1.0.0", "ember-cli-is-package-missing": "^1.0.0", - "ember-cookies": "^1.0.0", + "ember-cookies": "^1.2.0", "silent-error": "^1.0.0" }, "devDependencies": { diff --git a/packages/ember-simple-auth/src/session-stores/adaptive.js b/packages/ember-simple-auth/src/session-stores/adaptive.js index 25bf4bdf8..e7737a297 100644 --- a/packages/ember-simple-auth/src/session-stores/adaptive.js +++ b/packages/ember-simple-auth/src/session-stores/adaptive.js @@ -84,6 +84,24 @@ export default Base.extend({ _sameSite: null, sameSite: proxyToInternalStore(), + /** + Allows servers to assert that a cookie should opt in to partitioned storage, + i.e. use a separate cookie per top level site if the cookie is used in a + third party context + + Available options: + - null + - true + + @memberof AdaptiveStore + @property partitioned + @type Boolean + @default null + @public + */ + _partitioned: null, + partitioned: proxyToInternalStore(), + /** The name of the cookie to use if `localStorage` is not available. @@ -158,7 +176,8 @@ export default Base.extend({ 'cookieName', 'cookieExpirationTime', 'cookiePath', - 'sameSite' + 'sameSite', + 'partitioned' ); cookieStorage.setProperties(options); diff --git a/packages/ember-simple-auth/src/session-stores/cookie.js b/packages/ember-simple-auth/src/session-stores/cookie.js index a51353a3b..997a7c4c6 100644 --- a/packages/ember-simple-auth/src/session-stores/cookie.js +++ b/packages/ember-simple-auth/src/session-stores/cookie.js @@ -160,6 +160,24 @@ export default BaseStore.extend({ } }), + /** + Allows servers to assert that a cookie should opt in to partitioned storage, + i.e. use a separate cookie per top level site if the cookie is used in a + third party context + + Available options: + - null + - true + + @memberof CookieStore + @property partitioned + @type Boolean + @default null + @public + */ + _partitioned: null, + partitioned: persistingProperty(), + _cookies: service('cookies'), _secureCookies() { @@ -272,6 +290,7 @@ export default BaseStore.extend({ path: this.get('cookiePath'), secure: this._secureCookies(), sameSite: this.get('sameSite'), + partitioned: this.get('partitioned'), }; if (this._oldCookieName) { A([this._oldCookieName, `${this._oldCookieName}-expiration_time`]).forEach(oldCookie => { diff --git a/packages/test-esa/package.json b/packages/test-esa/package.json index d7f5cb270..0779ec96b 100644 --- a/packages/test-esa/package.json +++ b/packages/test-esa/package.json @@ -51,7 +51,7 @@ "ember-maybe-import-regenerator": "1.0.0", "ember-qunit": "7.0.0", "ember-resolver": "11.0.1", - "ember-simple-auth": "6.1.0", + "ember-simple-auth": "workspace:*", "ember-source": "5.12.0", "ember-source-channel-url": "3.0.0", "ember-try": "3.0.0", diff --git a/packages/test-esa/tests/unit/session-stores/shared/cookie-store-behavior.js b/packages/test-esa/tests/unit/session-stores/shared/cookie-store-behavior.js index fac3b9d38..7139f3903 100644 --- a/packages/test-esa/tests/unit/session-stores/shared/cookie-store-behavior.js +++ b/packages/test-esa/tests/unit/session-stores/shared/cookie-store-behavior.js @@ -46,13 +46,14 @@ export default function (options) { let cookieService = store.get('_cookies'); await store.persist({ key: 'value' }); - assert.ok( + assert.true( cookieService.write.calledWith('test:session', JSON.stringify({ key: 'value' }), { domain: null, expires: null, path: '/', sameSite: null, secure: false, + partitioned: null, }) ); }); @@ -65,13 +66,14 @@ export default function (options) { }); await store.persist({ key: 'value' }); - assert.ok( + assert.true( cookieService.write.calledWith('session-cookie-domain', JSON.stringify({ key: 'value' }), { domain: 'example.com', expires: null, path: '/', sameSite: null, secure: false, + partitioned: null, }) ); }); @@ -85,13 +87,14 @@ export default function (options) { let cookieService = store.get('_cookies'); await store.persist({ key: 'value' }); - assert.ok( + assert.true( cookieService.write.calledWith('session-cookie-domain', JSON.stringify({ key: 'value' }), { domain: 'example.com', expires: null, path: '/hello-world', sameSite: null, secure: false, + partitioned: null, }) ); }); @@ -104,17 +107,42 @@ export default function (options) { }); let cookieService = store.get('_cookies'); await store.persist({ key: 'value' }); - assert.ok( + assert.true( cookieService.write.calledWith('session-cookie-domain', JSON.stringify({ key: 'value' }), { domain: 'example.com', expires: null, path: '/', sameSite: 'Strict', secure: false, + partitioned: null, }) ); }); + test('respects the configured partitioned', async function (assert) { + run(() => { + store.set('cookieName', 'session-cookie-partitioned'); + store.set('cookieDomain', 'example.com'); + store.set('partitioned', true); + }); + let cookieService = store.get('_cookies'); + await store.persist({ key: 'value' }); + assert.true( + cookieService.write.calledWith( + 'session-cookie-partitioned', + JSON.stringify({ key: 'value' }), + { + domain: 'example.com', + expires: null, + path: '/', + sameSite: null, + secure: false, + partitioned: true, + } + ) + ); + }); + test('sends a warning when `cookieExpirationTime` is less than 90 seconds', async function (assert) { assert.expect(2); run(() => { @@ -155,7 +183,7 @@ export default function (options) { }); test('stores the expiration time in a cookie named "test-session-expiration_time"', function (assert) { - assert.ok( + assert.true( cookieService.write.calledWith( 'test-session-expiration_time', 60, @@ -212,7 +240,7 @@ export default function (options) { await new Promise(resolve => { next(() => { next(() => { - assert.ok(triggered); + assert.true(triggered); resolve(); }); }); @@ -264,11 +292,11 @@ export default function (options) { }); await store.persist({ key: 'value' }); - assert.ok(cookieService.clear.calledWith('session-foo')); + assert.true(cookieService.clear.calledWith('session-foo')); - assert.ok(cookieService.clear.calledWith('session-foo-expiration_time')); + assert.true(cookieService.clear.calledWith('session-foo-expiration_time')); - assert.ok( + assert.true( cookieService.write.calledWith( 'session-bar', JSON.stringify({ key: 'value' }), @@ -283,7 +311,7 @@ export default function (options) { ) ); - assert.ok( + assert.true( cookieService.write.calledWith( 'session-bar-expiration_time', 1000, @@ -307,11 +335,11 @@ export default function (options) { }); await store.persist({ key: 'value' }); - assert.ok(cookieService.clear.calledWith(defaultName)); + assert.true(cookieService.clear.calledWith(defaultName)); - assert.ok(cookieService.clear.calledWith(`${defaultName}-expiration_time`)); + assert.true(cookieService.clear.calledWith(`${defaultName}-expiration_time`)); - assert.ok( + assert.true( cookieService.write.calledWith( 'session-bar', JSON.stringify({ key: 'value' }), @@ -336,11 +364,11 @@ export default function (options) { }); await store.persist({ key: 'value' }); - assert.ok(cookieService.clear.calledWith(defaultName)); + assert.true(cookieService.clear.calledWith(defaultName)); - assert.ok(cookieService.clear.calledWith(`${defaultName}-expiration_time`)); + assert.true(cookieService.clear.calledWith(`${defaultName}-expiration_time`)); - assert.ok( + assert.true( cookieService.write.calledWith( 'session-bar', JSON.stringify({ key: 'value' }), @@ -364,7 +392,7 @@ export default function (options) { await new Promise(resolve => { next(() => { - assert.ok(cookieService.clear.calledWith('session-foo-expiration_time')); + assert.true(cookieService.clear.calledWith('session-foo-expiration_time')); resolve(); }); }); @@ -379,7 +407,7 @@ export default function (options) { await new Promise(resolve => { next(() => { - assert.ok(cookieSpy.calledOnce); + assert.true(cookieSpy.calledOnce); resolve(); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 199dd9ef1..992448baa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -232,8 +232,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 ember-cookies: - specifier: ^1.0.0 - version: 1.1.2 + specifier: ^1.2.0 + version: 1.2.0 silent-error: specifier: ^1.0.0 version: 1.1.1 @@ -4649,6 +4649,10 @@ packages: resolution: {integrity: sha512-6GaN0eEDZT9SEUSZBxWzZMlvxjcGKXFTJNjv30LVXTTOxozE5IBmIxiDAEq0udi0UpWUGHLYQBgnANn4jdll7w==} engines: {node: '>= 16.*'} + ember-cookies@1.2.0: + resolution: {integrity: sha512-ZbJbXq7dhjqJfOG/BBVlqO1o4Y6SWENrhiEgI7vokwVeuqHrvfaaFWV1emZUrsvoCAbSq/TiEdncO1EdZcI7Fg==} + engines: {node: '>= 16.*'} + ember-data@5.3.9: resolution: {integrity: sha512-fUhvmq3piYapfSFlpFpuQrkGn9SPRzPNj9xfHtFhyUq7UrPSXvjbhsihg+vw46VLxNqlTUwVtU3kLjGuJU6O9Q==} engines: {node: '>= 18.20.4'} @@ -15876,6 +15880,12 @@ snapshots: transitivePeerDependencies: - supports-color + ember-cookies@1.2.0: + dependencies: + '@embroider/addon-shim': 1.8.9 + transitivePeerDependencies: + - supports-color + ember-data@5.3.9(@ember/string@3.1.1)(@ember/test-helpers@3.3.1(@babel/core@7.25.2)(ember-source@5.12.0(@glimmer/component@1.1.2(@babel/core@7.25.2))(rsvp@4.8.5)(webpack@5.96.1))(webpack@5.96.1))(@ember/test-waiters@3.1.0)(ember-source@5.12.0(@glimmer/component@1.1.2(@babel/core@7.25.2))(rsvp@4.8.5)(webpack@5.96.1))(qunit@2.22.0): dependencies: '@ember-data/adapter': 5.3.9(@ember-data/legacy-compat@5.3.9(yck2zkosf4wlsfltzltucskzsu))(@ember-data/request-utils@5.3.9(@ember/string@3.1.1)(@warp-drive/core-types@0.0.0-beta.12))(@ember-data/store@5.3.9(@ember-data/request-utils@5.3.9(@ember/string@3.1.1)(@warp-drive/core-types@0.0.0-beta.12))(@ember-data/request@5.3.9(@warp-drive/core-types@0.0.0-beta.12))(@ember-data/tracking@5.3.9(@warp-drive/core-types@0.0.0-beta.12)(ember-source@5.12.0(@glimmer/component@1.1.2(@babel/core@7.25.2))(rsvp@4.8.5)(webpack@5.96.1)))(@warp-drive/core-types@0.0.0-beta.12))(@warp-drive/core-types@0.0.0-beta.12)