From 4e2d6faa218545d2a146a4b4ab365c963416371d Mon Sep 17 00:00:00 2001 From: Vincent Voyer Date: Mon, 9 Mar 2020 15:51:14 +0100 Subject: [PATCH] feat(lib): new major version (#7) * feat(lib): new major version complete rewrite using iron-store changed the API to ease usage on Next.js BREAKING CHANGES * fix linting * update readme * update readme and pkg description --- .prettierignore | 1 + README.md | 151 ++++++++++++----------- lib/index.js | 114 +++++++++--------- lib/index.test.js | 298 +++++++++++++++++++++++++--------------------- package.json | 18 ++- yarn.lock | 13 +- 6 files changed, 316 insertions(+), 279 deletions(-) diff --git a/.prettierignore b/.prettierignore index dcacf9e6..5b0949c3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ +/.history/ /.yarn/ /.vscode/ /dist/ diff --git a/README.md b/README.md index 7d055a5d..27b5f304 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,121 @@ -# iron-session [![GitHub license](https://img.shields.io/github/license/vvo/iron-session?style=flat)](https://github.com/vvo/iron-session/blob/master/LICENSE) ![Tests](https://github.com/vvo/iron-session/workflows/Tests/badge.svg) [![codecov](https://codecov.io/gh/vvo/iron-session/branch/master/graph/badge.svg)](https://codecov.io/gh/vvo/iron-session) ![npm](https://img.shields.io/npm/v/iron-session) +# next-iron-session [![GitHub license](https://img.shields.io/github/license/vvo/next-iron-session?style=flat)](https://github.com/vvo/next-iron-session/blob/master/LICENSE) ![Tests](https://github.com/vvo/next-iron-session/workflows/Tests/badge.svg) [![codecov](https://codecov.io/gh/vvo/next-iron-session/branch/master/graph/badge.svg)](https://codecov.io/gh/vvo/next-iron-session) ![npm](https://img.shields.io/npm/v/next-iron-session) -**This JavaScript backend utility** allows you to create a session to then be stored in browser cookies via a signed and encrypted token value. This provides client sessions that are ⚒️ iron-strong. +_🛠 Next.js stateless session utility using signed and encrypted cookies to store data_ -The token stored on the client contains the session data, not your server, making it a "stateless" session from the server point of view. The token is signed and encrypted using [@hapi/iron](https://github.com/hapijs/iron). +--- -**⚡️ Flash session data is supported**. It means you can store some data which will be deleted when read. This is useful for temporary tokens, redirects or notices on your UI. +**This [Next.js](https://nextjs.org/) backend utility** allows you to create a session to then be stored in browser cookies via a signed and encrypted seal. This provides client sessions that are ⚒️ iron-strong. -**By default the cookie has an ⏰ expiration time of 15 days**, set via [`maxAge`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Directives). After that, even if someone tries to reuse the cookie, @hapi/iron will not accept the underlying token. Because the expiration is also part of the token value. See https://hapi.dev/family/iron for more information on @hapi/iron mechanisms. +The seal stored on the client contains the session data, not your server, making it a "stateless" session from the server point of view. This is a different take than [next-session](https://github.com/hoangvvo/next-session/) where the cookie contains a session ID to then be used to identity data on the server-side. -**Why use pure 🍪 cookies for sessions?** This makes your sessions stateless: you do not have to store session data on your server. This is particularly useful in serverless architectures. Still, there are some drawbacks to this approach: +The seal is signed and encrypted using [@hapi/iron](https://github.com/hapijs/iron), [iron-store](https://github.com/vvo/iron-store/) is used behind the scenes. -- you cannot invalidate a cookie when needed because there's no state stored on the server-side about the tokens. We consider that the way the cookie is stored reduces the possibility for this eventuality to happen. -- application not supporting cookies won't work, this could be solved in the future by exposing the underlying token instead of signed and encrypted cookies. Open an issue if you're interested. -- on most browsers, you're limited to 4,096 bytes per cookie. To give you an idea, an `iron-session` containing `{user: {id: 230, admin: true}}` is 358 bytes signed and encrypted: still plenty of available cookie space in here. +**⚡️ Flash session data is supported**. It means you can store some data which will be deleted when read. This is useful for temporary data, redirects or notices on your UI. -Now that you know the drawbacks, you can decide if they are an issue for your application or not. - -**🤓 References:** - -- https://owasp.org/www-project-cheat-sheets/cheatsheets/Session_Management_Cheat_Sheet.html#cookies -- https://owasp.org/www-project-cheat-sheets/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#encryption-based-token-pattern - -## How is this different from [JWT](https://jwt.io/)? +**By default the cookie has an ⏰ expiration time of 15 days**, set via [`maxAge`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Directives). After that, even if someone tries to reuse the cookie, `next-iron-session` will not accept the underlying seal because the expiration is part of the seal value. See https://hapi.dev/family/iron for more information on @hapi/iron mechanisms. -Not so much: - -- JWT is a standard, it stores metadata in the JWT token themselves to ensure communication between different systems is flawless. -- JWT tokens are not encrypted, the payload is visible by customers if they manage to inspect the token. You would have to use [JWE](https://tools.ietf.org/html/rfc7516) to achieve the same. -- @hapi/iron mechanism is not a standard, it's a way to sign and encrypt data into tokens - -Depending on your own needs and preferences, `iron-session-cookie` may or may not fit you. - -## Instalation +## Installation ```bash -npm add iron-session +npm add next-iron-session ``` ## Usage -The examples are using a user login flow: login, verify, log out. But you can use `iron-session` for any other session need. +The password is a private key you must pass at runtime, it has to be at least 32 characters long. Use https://1password.com/password-generator/ to generate strong passwords. -The password is a private key you must pass at runtime, it has to be at least 32 characters long. https://1password.com/password-generator/ is a good way to generate a strong password. +Store passwords in secret environment variables on your platform. -### When the user logs in +**login.js**: ```js -import { createSession } from "iron-session"; -export default async (req, res) => { - // when user successfully logs in using email/password, oauth, ... then we create a session - // const user = ... +import withIronSession from "iron-session"; - const session = await createSession({ - password: process.env.SECRET_SESSION_PASSWORD - }); - - session.set({ name: "user", value: { id: 230, admin: true } }); - session.set({ name: "message", value: "Login success", flash: true }); - - res.writeHead(200, { - "set-cookie": await session.serializeCookie() +async function handler(req, res, session) { + session.set("user", { + id: 230, + admin: true }); + await session.save(); + res.send("Logged in"); +} - res.end("ok"); -}; +export default withIronSession(handler, { + password: "complex_password_at_least_32_characters_long" +}); ``` -`serializeCookie` accepts all the options from https://github.com/jshttp/cookie#cookieserializename-value-options, merged with `iron-session` defaults. The defaults are: +**user.js**: ```js -{ - httpOnly: true, - secure: true, - sameSite: "lax", - maxAge: (ttl === 0 ? 2147483647 : ttl) - 60, // For Iron, ttl 0 means it will never expire. For browser cookies, maxAge 0 means it will expire immediately. WhilCookie must expire before the seal, otherwise you could have expired seals stored in a cookie +import withIronSession from "iron-session"; + +function handler(req, res, session) { + const user = session.get("user"); + res.send({ user }); } + +export default withIronSession(handler, { + password: "complex_password_at_least_32_characters_long" +}); ``` -### Checking if the user is logged in +**logout.js**: ```js -import { getSession, parseCookie } from "iron-session"; -export default async (req, res) => { - const session = await getSession({ - password: process.env.SECRET_SESSION_PASSWORD, - sealed: parseCookie({ cookie: req.getHeader("cookie") }) - }); +import withIronSession from "iron-session"; - const user = session.get({ name: "user" }); - const flashMessage = session.get({ name: "message" }); +function handler(req, res, session) { + session.destroy(); + res.send("Logged out"); +} - res.end("ok"); -}; +export default withIronSession(handler, { + password: "complex_password_at_least_32_characters_long" +}); ``` -### When the user logs out +## API -```js -import { deleteCookie } from "iron-session"; -export default async (req, res) => { - res.writeHead(200, { - "set-cookie": deleteCookie() - }); +### withIronSession(handler, {password, ttl, cookieName, cookieOptions}) - res.end("ok"); -}; -``` +### session.set + +### session.get + +### session.setFlash + +### session.destroy + +## FAQ + +### Why use pure 🍪 cookies for sessions? + +This makes your sessions stateless: you do not have to store session data on your server. This is particularly useful in serverless architectures. Still, there are some drawbacks to this approach: + +- you cannot invalidate a seal when needed because there's no state stored on the server-side about them. We consider that the way the cookie is stored reduces the possibility for this eventuality to happen. +- application not supporting cookies won't work, but you can use [iron-store](https://github.com/vvo/iron-store/) to implement something similar. In the future we could allow `next-iron-session` to accept [basic auth](https://tools.ietf.org/html/rfc7617) or bearer token methods too. Open an issue if you're interested. +- on most browsers, you're limited to 4,096 bytes per cookie. To give you an idea, a `next-iron-session` cookie containing `{user: {id: 230, admin: true}}` is 358 bytes signed and encrypted: still plenty of available cookie space in here. + +Now that you know the drawbacks, you can decide if they are an issue for your application or not. + +### How is this different from [JWT](https://jwt.io/)? + +Not so much: + +- JWT is a standard, it stores metadata in the JWT seal themselves to ensure communication between different systems is flawless. +- JWT seals are not encrypted, the payload is visible by customers if they manage to inspect the seal. You would have to use [JWE](https://tools.ietf.org/html/rfc7516) to achieve the same. +- @hapi/iron mechanism is not a standard, it's a way to sign and encrypt data into seals + +Depending on your own needs and preferences, `next-iron-session-cookie` may or may not fit you. ## Project status This is a recent library I authored because I needed it. While @hapi/iron is battle-tested and [used in production on a lot of websites](https://hapi.dev/), this library is not. Please use it at your own risk. If you find bugs or have API ideas, create an issue. + +## 🤓 References + +- https://owasp.org/www-project-cheat-sheets/cheatsheets/Session_Management_Cheat_Sheet.html#cookies +- https://owasp.org/www-project-cheat-sheets/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#encryption-based-seal-pattern diff --git a/lib/index.js b/lib/index.js index bd97716e..40b923c3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,69 +1,67 @@ -import Iron from "@hapi/iron"; +import ironStore from "iron-store"; import cookie from "cookie"; -import clone from "clone"; -const defaultTtl = 15 * 24 * 3600; -const cookieName = "__ironSession"; +// TODO: warn on no session usage +// TODO: warn when session saved after end +// TODO: warn when session not saved after set and req ended -export async function createSession({ password, ttl = defaultTtl }) { - return getSession({ password, ttl }); +// default time allowed to check for iron seal validity when ttl passed +// see https://hapi.dev/family/iron/api/?v=6.0.0#options +const timestampSkewSec = 60; + +function throwOnNoPassword() { + throw new Error("next-iron-sesion: Missing parameter `password`"); } -export async function getSession({ sealed, password, ttl = defaultTtl }) { - const options = { ...Iron.defaults, ttl }; - const store = - sealed !== undefined - ? await Iron.unseal(sealed, password, options) - : { persistent: {}, flash: {} }; +function computeCookieMaxAge(ttl) { + return (ttl === 0 ? 2147483647 : ttl) - timestampSkewSec; +} - return { - set({ name, value, flash = false }) { - if (flash === true) { - store.flash[name] = clone(value); - } else { - store.persistent[name] = clone(value); - } - }, - get({ name = undefined } = {}) { - if (name === undefined) { - const flash = store.flash; - store.flash = {}; - return clone({ - ...flash, - ...store.persistent - }); - } +const defaultCookieOptions = { + httpOnly: true, + secure: true, + sameSite: "lax" +}; - if (store.flash[name] !== undefined) { - const value = store.flash[name]; - delete store.flash[name]; - return value; // no need to clone, we removed the reference from the flash store - } else { - return clone(store.persistent[name]); - } - }, - async serializeCookie(cookieOptions = {}) { - return cookie.serialize( - cookieName, - await Iron.seal(store, password, options), - { - httpOnly: true, - secure: true, - sameSite: "lax", - maxAge: (ttl === 0 ? 2147483647 : ttl) - 60, // For Iron, ttl 0 means it will never expire. For browser cookies, maxAge 0 means it will expire immediately. WhilCookie must expire before the seal, otherwise you could have expired seals stored in a cookie - ...cookieOptions - } - ); - } +export default function withIronSession( + withIronSessionWrapperHandler, + { + ttl = 15 * 24 * 3600, + cookieName = "__ironSession", + password = throwOnNoPassword(), + cookieOptions: userCookieOptions = {} + } = {} +) { + const cookieOptions = { + ...defaultCookieOptions, + ...userCookieOptions, + maxAge: userCookieOptions.maxAge || computeCookieMaxAge(ttl) }; -} -export function parseCookie({ cookie: cookieValue }) { - return cookie.parse(cookieValue)[cookieName]; -} + return async function withIronSessionHandler(req, res) { + const store = await ironStore({ + sealed: req.cookies[cookieName], + password, + ttl + }); + + const session = { + set: store.set, + get: store.get, + setFlash: store.setFlash, + async save() { + const seal = await store.seal(); + const cookieValue = cookie.serialize(cookieName, seal, cookieOptions); + res.setHeader("set-cookie", [cookieValue]); + }, + destroy() { + const cookieValue = cookie.serialize(cookieName, "", { + maxAge: 0 + }); + res.setHeader("set-cookie", [cookieValue]); + } + }; -export function deleteCookie() { - return cookie.serialize(cookieName, "", { - maxAge: 0 - }); + return withIronSessionWrapperHandler(req, res, session); + }; } diff --git a/lib/index.test.js b/lib/index.test.js index e981af22..0b15168e 100644 --- a/lib/index.test.js +++ b/lib/index.test.js @@ -1,160 +1,192 @@ -import { - createSession, - getSession, - parseCookie, - deleteCookie -} from "./index.js"; -import Iron from "@hapi/iron"; -import { advanceTo, clear } from "jest-date-mock"; +import withIronSession from "./index.js"; const password = "Gbm49ATjnqnkCCCdhV4uDBhbfnPqsCW0"; -test("it creates sessions", async () => { - const session = await createSession({ password }); - session.set({ name: "userId", value: 12 }); - expect(session.get({ name: "userId" })).toBe(12); - const cookie = await session.serializeCookie(); - expect(typeof cookie).toBe("string"); - expect(cookie.length).toBe(346); // we can't test the actual value are there's a good amount of random crypto in it +test("ironStore(handler) without a password", () => { + return new Promise(done => { + const handler = () => {}; + expect(() => { + withIronSession(handler); + }).toThrowErrorMatchingInlineSnapshot( + `"next-iron-sesion: Missing parameter \`password\`"` + ); + done(); + }); }); -test("it reads sessions", async () => { - // this was created in console with a ttl of 0 (never expires) - const firstSession = await createSession({ password }); - firstSession.set({ name: "user", value: { id: 230, admin: true } }); - const cookie = await firstSession.serializeCookie(); - const secondSession = await getSession({ - sealed: parseCookie({ cookie }), - password +test("ironStore(handler, {password})", () => { + return new Promise(done => { + const handler = (req, res, session) => { + expect(session).toMatchInlineSnapshot(` + Object { + "destroy": [Function], + "get": [Function], + "save": [Function], + "set": [Function], + "setFlash": [Function], + } + `); + done(); + }; + const wrappedHandler = withIronSession(handler, { password }); + wrappedHandler( + { + cookies: {} + }, + {} + ); }); - expect(secondSession.get()).toMatchInlineSnapshot(` - Object { - "user": Object { - "admin": true, - "id": 230, +}); + +test("session.set", () => { + return new Promise(done => { + const handler = (req, res, session) => { + expect(session.set("user", { id: 20 })).toMatchInlineSnapshot(` + Object { + "id": 20, + } + `); + done(); + }; + const wrappedHandler = withIronSession(handler, { password }); + wrappedHandler( + { + cookies: {} }, - } - `); + {} + ); + }); }); -test("it throws when ttl reached (with a 60s difference allowance, set by Iron)", async () => { - advanceTo(100); - const sealed = await Iron.seal( - { persistent: { userId: 400, admin: false } }, - password, - { - ...Iron.defaults, - ttl: 120 * 1000 - } - ); - advanceTo(140 * 1000); // still within the 120 + 60 seconds allowance - expect((await getSession({ sealed, password })).get()).toMatchInlineSnapshot(` - Object { - "admin": false, - "userId": 400, - } - `); - advanceTo(190 * 1000); // above the 120 + 60 seconds allowance - await expect( - getSession({ sealed, password }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Expired seal"`); - clear(); +test("session.setFlash", () => { + return new Promise(done => { + const handler = (req, res, session) => { + session.setFlash("state", "dfsafsalfk21lkf12lkf21"); + expect(session.get("state")).toMatchInlineSnapshot( + `"dfsafsalfk21lkf12lkf21"` + ); + expect(session.get("state")).toMatchInlineSnapshot(`undefined`); + done(); + }; + const wrappedHandler = withIronSession(handler, { password }); + wrappedHandler( + { + cookies: {} + }, + {} + ); + }); }); -test("cookie headers are well set", async () => { - const session = await createSession({ password }); - session.set({ name: "userId", value: 900 }); - const cookie = await session.serializeCookie(); - const cookieValues = cookie.split(";"); - cookieValues.shift(); // first element is the token +test("session.save creates a seal and stores it in a cookie", () => { + return new Promise(done => { + const handler = async (req, res, session) => { + await session.save(); + const headerName = res.setHeader.mock.calls[0][0]; + expect(headerName).toMatchInlineSnapshot(`"set-cookie"`); - expect(cookieValues.join(";")).toMatchInlineSnapshot( - `" Max-Age=1295940; HttpOnly; Secure; SameSite=Lax"` - ); -}); + const headerValue = res.setHeader.mock.calls[0][1]; + expect(Array.isArray(headerValue)).toBe(true); + expect(headerValue).toHaveLength(1); -test("Data is cloned on set", async () => { - const session = await createSession({ password }); - const user = { id: 1200, admin: true }; - session.set({ name: "user", value: user }); - expect(session.get()).toMatchInlineSnapshot(` -Object { - "user": Object { - "admin": true, - "id": 1200, - }, -} -`); - user.id = 2200; - expect(session.get()).toMatchInlineSnapshot(` -Object { - "user": Object { - "admin": true, - "id": 1200, - }, -} -`); + const cookie = headerValue[0]; + const seal = cookie.split(";")[0].split("=")[1]; + expect(seal).toHaveLength(262); + + const cookieParams = cookie + .split(";") + .slice(1) + .join(";"); + expect(cookieParams).toMatchInlineSnapshot( + `" Max-Age=1295940; HttpOnly; Secure; SameSite=Lax"` + ); + done(); + }; + const wrappedHandler = withIronSession(handler, { password }); + wrappedHandler( + { + cookies: {} + }, + { + setHeader: jest.fn() + } + ); + }); }); -test("Data is cloned on get", async () => { - const session = await createSession({ password }); - const user = { id: 1700, admin: true }; - session.set({ name: "user", value: user }); - const sessionUser = session.get({ name: "user" }); - sessionUser.id = 3400; - expect(session.get()).toMatchInlineSnapshot(` +test("ironStore(handler, {password}) with existing session", async () => { + return new Promise(done => { + const handler = (req, res, session) => { + expect(session.get()).toMatchInlineSnapshot(` Object { "user": Object { - "admin": true, - "id": 1700, + "id": 3, }, } `); -}); - -test("Flash data gets deleted when read", async () => { - const firstSession = await createSession({ password }); - firstSession.set({ name: "state", value: "yes", flash: true }); - const firstCookie = await firstSession.serializeCookie(); - const secondSession = await getSession({ - sealed: parseCookie({ cookie: firstCookie }), - password - }); - expect(secondSession.get({ name: "state" })).toMatchInlineSnapshot(`"yes"`); - const secondCookie = await secondSession.serializeCookie(); - const thirdSession = await getSession({ - sealed: parseCookie({ cookie: secondCookie }), - password + done(); + }; + const wrappedHandler = withIronSession(handler, { password }); + wrappedHandler( + { + cookies: { + __ironSession: + "Fe26.2**4e769b9b7b921621ed5658cfc0d7d8e267dc8ee93663c2803c257b31111394e3*jRXOJHmt_BDG9nNTXcVRXQ*UHpK9GYp7SXTiEsxTzTUq_tQD_-ZUp7PguEXy-bRFuBE4fW74-9wm9UtlWO2rlwB**d504d6d197d183efec0ae6d3c2378c43048c8752d6c3c591c92289ed01142b3c*3NG2fCo8A53CXPU8rEAMnDB7X9UkwzTaHieumPBqyTw" + } + }, + {} + ); }); - expect(thirdSession.get({ name: "state" })).toMatchInlineSnapshot( - `undefined` - ); }); -test("serializeCookie accepts options", async () => { - const session = await createSession({ password }); - session.set({ name: "userId", value: 4320 }); - const cookie = await session.serializeCookie({ secure: false }); - const cookieValues = cookie.split(";"); - cookieValues.shift(); // first element is the token - - expect(cookieValues.join(";")).toMatchInlineSnapshot( - `" Max-Age=1295940; HttpOnly; SameSite=Lax"` - ); +test("When ttl is 0, maxAge have a specific value", () => { + return new Promise(done => { + const handler = async (req, res, session) => { + await session.save(); + const headerValue = res.setHeader.mock.calls[0][1]; + const cookie = headerValue[0]; + const maxAgeParam = cookie.split(";")[1]; + expect(maxAgeParam).toMatchInlineSnapshot(`" Max-Age=2147483587"`); + done(); + }; + const wrappedHandler = withIronSession(handler, { password, ttl: 0 }); + wrappedHandler( + { + cookies: {} + }, + { + setHeader: jest.fn() + } + ); + }); }); -test("deleteCookie creates a cookie to expire immediately", () => { - expect(deleteCookie()).toMatchInlineSnapshot(`"__ironSession=; Max-Age=0"`); +test("session.destroy", () => { + return new Promise(done => { + const handler = async (req, res, session) => { + session.destroy(); + expect(res.setHeader.mock.calls[0]).toMatchInlineSnapshot(` +Array [ + "set-cookie", + Array [ + "__ironSession=; Max-Age=0", + ], +] +`); + done(); + }; + const wrappedHandler = withIronSession(handler, { password, ttl: 0 }); + wrappedHandler( + { + cookies: { coucou: true } + }, + { + setHeader: jest.fn() + } + ); + }); }); -test("ttl = 0 sets a maximum maxAge directive (see https://stackoverflow.com/a/22479460/147079)", async () => { - const session = await createSession({ password, ttl: 0 }); - session.set({ name: "userId", value: 4320 }); - const cookie = await session.serializeCookie({ secure: false }); - const cookieValues = cookie.split(";"); - cookieValues.shift(); // first element is the token - - expect(cookieValues.join(";")).toMatchInlineSnapshot( - `" Max-Age=2147483587; HttpOnly; SameSite=Lax"` - ); -}); +// Maybe TODO: warn on no session usage +// Maybe TODO: warn when session saved after end +// Maybe TODO: warn when session not saved after set and req ended diff --git a/package.json b/package.json index d1326ba4..af43e1b6 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "iron-session", + "name": "next-iron-session", "version": "0.0.0-development", "private": false, - "description": "Backend agnostic session utility based on @hapi/iron with signed and encrypted cookie serialization ability", + "description": "Next.js stateless session utility using signed and encrypted cookies to store data", "repository": { "type": "git", - "url": "https://github.com/vvo/iron-session.git" + "url": "https://github.com/vvo/next-iron-session.git" }, "license": "MIT", "author": "Vincent Voyer ", @@ -37,6 +37,7 @@ }, "eslintConfig": { "env": { + "es6": true, "jest": true, "node": true }, @@ -52,15 +53,11 @@ "plugin:jest/recommended" ] }, - "jest": { - "setupFiles": [ - "jest-date-mock" - ] - }, + "jest": {}, "dependencies": { - "@hapi/iron": "^6.0.0", "clone": "^2.1.2", - "cookie": "^0.4.0" + "cookie": "^0.4.0", + "iron-store": "^1.0.0" }, "devDependencies": { "@babel/cli": "7.8.4", @@ -71,7 +68,6 @@ "eslint": "6.8.0", "eslint-plugin-jest": "23.8.2", "jest": "25.1.0", - "jest-date-mock": "1.0.8", "prettier": "1.19.1", "prettier-plugin-packagejson": "2.0.10", "semantic-release": "17.0.4" diff --git a/yarn.lock b/yarn.lock index 364e66fd..dcd60029 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3803,6 +3803,14 @@ ip@1.1.5: resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= +iron-store@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/iron-store/-/iron-store-1.0.0.tgz#0fe5736d5bc376f7c1dc3f3cf64526db6f95ae77" + integrity sha512-bwv/lD+zQYDzrGHpXP5ajm4Ge9Vgk+DE3I9WGHnmUic3X1Uk5MwtkhJMIw2fNiUddSS3R5AgTkMhvFYsamN+TQ== + dependencies: + "@hapi/iron" "^6.0.0" + clone "^2.1.2" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -4215,11 +4223,6 @@ jest-config@^25.1.0: pretty-format "^25.1.0" realpath-native "^1.1.0" -jest-date-mock@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/jest-date-mock/-/jest-date-mock-1.0.8.tgz#13468c0352c5a3614c6b356dbc6b88eb37d9e0b3" - integrity sha512-0Lyp+z9xvuNmLbK+5N6FOhSiBeux05Lp5bbveFBmYo40Aggl2wwxFoIrZ+rOWC8nDNcLeBoDd2miQdEDSf3iQw== - jest-diff@^25.1.0: version "25.1.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.1.0.tgz#58b827e63edea1bc80c1de952b80cec9ac50e1ad"