diff --git a/.github/workflows/cypress_tests.yml b/.github/workflows/cypress_tests.yml index 52394586..56a85d3f 100644 --- a/.github/workflows/cypress_tests.yml +++ b/.github/workflows/cypress_tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 - name: Checkout uses: actions/checkout@v4 - name: Cypress run @@ -40,7 +40,8 @@ jobs: CYPRESS_NODE_ENV: test NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} NEXT_PUBLIC_ORCID_API_URL: ${{ secrets.NEXT_PUBLIC_ORCID_API_URL }} + NEXT_PUBLIC_JWT_PUBLIC_KEY: ${{ secrets.CYPRESS_JWT_PUBLIC_KEY_2025}} SITEMAPS_URL: ${{ secrets.SITEMAPS_URL }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - CYPRESS_USER_COOKIE: ${{ secrets.CYPRESS_USER_COOKIE }} + CYPRESS_userCookie: ${{ secrets.CYPRESS_USER_COOKIE_2025 }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/cypress.config.ts b/cypress.config.ts index 72876e20..fec6fd49 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -6,12 +6,12 @@ export default defineConfig({ projectId: 'yur1cf', retries: 2, e2e: { - setupNodeEvents(on, config) {}, + setupNodeEvents() {}, baseUrl: 'http://localhost:3000', specPattern: 'cypress/e2e/**/*.test.*', }, component: { - setupNodeEvents(on, config) { }, + setupNodeEvents() { }, specPattern: 'src/components/**/*.test.*', devServer: { bundler: 'webpack', diff --git a/cypress/e2e/session.test.ts b/cypress/e2e/session.test.ts new file mode 100644 index 00000000..821fde4a --- /dev/null +++ b/cypress/e2e/session.test.ts @@ -0,0 +1,38 @@ +describe('useSession behavior', () => { + beforeEach(() => { + cy.setCookie('_consent', 'true'); + }); + + it('shows logged in state with valid token', () => { + cy.then(() => { + let userCookie = Cypress.env('userCookie'); + if (typeof userCookie === 'object') { + userCookie = JSON.stringify(userCookie); + } + Cypress.log({ + name: 'diagnostics:userCookie', + message: [ + `type=${typeof userCookie}`, + `present=${Boolean(userCookie)}`, + `stringLength=${typeof userCookie === 'string' ? userCookie.length : 'n/a'}`, + `preview=${userCookie.substring(0, 50)}...`, + ], + }); + }); + + cy.setCookie('_datacite', String(Cypress.env('userCookie')), { log: false }); + cy.visit('/'); + cy.get('#sign-in').should('contain.text', 'DataCite Test User'); // Match your JWT payload name + }); + + it('shows logged out state without token', () => { + cy.visit('/'); + cy.get('#sign-in').should('contain.text', 'Sign In'); + }); + + it('shows logged out state with invalid token', () => { + cy.setCookie('_datacite', '{"authenticated":{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.invalid.signature"}}', { log: false }); + cy.visit('/'); + cy.get('#sign-in').should('contain.text', 'Sign In'); + }); +}); \ No newline at end of file diff --git a/package.json b/package.json index d196db46..d9680426 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "@testing-library/user-event": "^11.2.1", "@types/gtag.js": "^0.0.3", "@types/js-cookie": "^2.2.6", - "@types/jsonwebtoken": "^8.5.0", "@types/lodash": "^4.14.154", "@types/react-content-loader": "^4.0.0", "@types/testing-library__cypress": "^5.0.5", @@ -56,8 +55,8 @@ "html-react-parser": "^5.1.12", "iso-3166-1": "^2.1.1", "iso-639-1": "^3.1.3", + "jose": "^6.1.3", "js-yaml-loader": "^1.2.2", - "jsonwebtoken": "^8.5.1", "linkify-react": "^4.2.0", "linkifyjs": "^4.2.0", "maltipoo": "https://github.com/datacite/maltipoo#2.0.3", diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 5bcf5cb8..4d52113e 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -42,6 +42,7 @@ const sourceSans3 = Source_Sans_3({ }) export default async function RootLayout({ children }: PropsWithChildren) { + const authToken = await getAuthToken() return ( @@ -54,7 +55,7 @@ export default async function RootLayout({ children }: PropsWithChildren) { - +
{children}
diff --git a/src/components/Claim/Claim.tsx b/src/components/Claim/Claim.tsx index 188f1396..c5b4526c 100644 --- a/src/components/Claim/Claim.tsx +++ b/src/components/Claim/Claim.tsx @@ -5,7 +5,7 @@ import { useEffect } from 'react'; import { useMutation, ApolloCache } from '@apollo/client' import { faOrcid } from '@fortawesome/free-brands-svg-icons' -import { session, User } from 'src/utils/session' +import { useSession, User } from 'src/utils/session' import { Claim as ClaimType } from 'src/data/types' import Error from 'src/components/Error/Error' import ClaimStatus from 'src/components/ClaimStatus/ClaimStatus' @@ -80,7 +80,7 @@ export default function Claim({ doi_id }: Props) { } }) - const user = session() + const { user } = useSession() const claim: ClaimType = data?.work.claims[0] || { id: null, diff --git a/src/components/DiscoverWorksAlert/DiscoverWorksAlert.tsx b/src/components/DiscoverWorksAlert/DiscoverWorksAlert.tsx index 4dcde86f..f886c707 100644 --- a/src/components/DiscoverWorksAlert/DiscoverWorksAlert.tsx +++ b/src/components/DiscoverWorksAlert/DiscoverWorksAlert.tsx @@ -9,12 +9,12 @@ import DataCiteButton from 'src/components/DataCiteButton/DataCiteButton' import { faSearch } from '@fortawesome/free-solid-svg-icons' import { PROFILES_SETTINGS_URL } from 'src/data/constants' -import { session } from "src/utils/session"; +import { useSession } from "src/utils/session"; import styles from './DiscoverWorksAlert.module.scss' export default function DiscoverWorksAlert() { - const user = session() + const { user } = useSession() const [show, setShow] = useState(true); diff --git a/src/components/Header/ClientButtons.tsx b/src/components/Header/ClientButtons.tsx index f4b8ac32..fa151b2b 100644 --- a/src/components/Header/ClientButtons.tsx +++ b/src/components/Header/ClientButtons.tsx @@ -7,10 +7,10 @@ import { faAddressCard } from '@fortawesome/free-solid-svg-icons' import { faOrcid } from '@fortawesome/free-brands-svg-icons' import { ORCID_URL } from 'src/data/constants'; -import { session } from "src/utils/session"; +import { useSession } from "src/utils/session"; export function UserCommonsPageButton() { - const user = session() + const { user } = useSession() if (!user) throw new Error("User not signed in") const href = '/orcid.org/' + user.uid @@ -21,7 +21,7 @@ export function UserCommonsPageButton() { export function UserOrcidButton() { - const user = session() + const { user } = useSession() if (!user) throw new Error("User not signed in") const href = `${ORCID_URL}/${user.uid}` diff --git a/src/components/Header/Dropdown.tsx b/src/components/Header/Dropdown.tsx index 2abbb489..d0d643fa 100644 --- a/src/components/Header/Dropdown.tsx +++ b/src/components/Header/Dropdown.tsx @@ -1,11 +1,12 @@ 'use client' import React, { PropsWithChildren } from 'react' -import { session } from 'src/utils/session' +import { useSession } from 'src/utils/session' import NavDropdown from 'react-bootstrap/NavDropdown' export default function UserDropdown({ children }: PropsWithChildren) { - const user = session() + const { user, loading } = useSession() + if (loading) return null // Or return a loading skeleton if (!user) throw new Error("User not signed in") return diff --git a/src/components/Header/NavRight.tsx b/src/components/Header/NavRight.tsx index f269b22e..782ff46b 100644 --- a/src/components/Header/NavRight.tsx +++ b/src/components/Header/NavRight.tsx @@ -1,7 +1,7 @@ 'use client' import React from 'react'; -import { session } from 'src/utils/session'; +import { useSession } from 'src/utils/session'; interface Props { signedInContent: React.ReactNode @@ -9,7 +9,7 @@ interface Props { } export default function NavRight({ signedInContent, signedOutContent }: Props) { - const user = session() + const { user } = useSession() if (!user) return signedOutContent return signedInContent diff --git a/src/utils/apolloClient/apolloClient.ts b/src/utils/apolloClient/apolloClient.ts index f66be1c9..f2908d84 100644 --- a/src/utils/apolloClient/apolloClient.ts +++ b/src/utils/apolloClient/apolloClient.ts @@ -1,8 +1,9 @@ -import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'; +import { cookies } from 'next/headers'; import apolloClientBuilder from './builder' -export function getAuthToken() { - const sessionCookie = JSON.parse(((cookies() as unknown as UnsafeUnwrappedCookies).get('_datacite') as any)?.value || '{}') +export async function getAuthToken() { + const cookieStore = await cookies() + const sessionCookie = JSON.parse((cookieStore.get('_datacite') as any)?.value || '{}') return sessionCookie?.authenticated?.access_token } diff --git a/src/utils/apolloClient/builder.ts b/src/utils/apolloClient/builder.ts index e21a5946..c87083ee 100644 --- a/src/utils/apolloClient/builder.ts +++ b/src/utils/apolloClient/builder.ts @@ -8,16 +8,16 @@ import { DATACITE_API_URL } from 'src/data/constants' * this throws an error unless the token is returned from a function that is called * in the authLink setContext. I'm not sure why */ -export default function apolloClientBuilder(getToken: () => string) { +export default function apolloClientBuilder(getToken: () => Promise) { // needed for CORS, see https://www.apollographql.com/docs/react/networking/authentication/#cookie const httpLink = createHttpLink({ uri: DATACITE_API_URL + '/graphql', credentials: 'include' }) - const authLink = setContext((_, { headers }) => { + const authLink = setContext(async (_, { headers }) => { // return the headers to the context so httpLink can read them - const token = getToken() + const token = await getToken() return { headers: { diff --git a/src/utils/apolloClient/provider.tsx b/src/utils/apolloClient/provider.tsx index 309cf8a0..c6e32824 100644 --- a/src/utils/apolloClient/provider.tsx +++ b/src/utils/apolloClient/provider.tsx @@ -2,9 +2,9 @@ import React, { PropsWithChildren } from 'react' import apolloClientBuilder from 'src/utils/apolloClient/builder' import { ApolloNextAppProvider } from "@apollo/experimental-nextjs-app-support"; -export default function ApolloProvider({ token, children }: PropsWithChildren<{ token: string }>) { +export default function ApolloProvider({ token, children }: PropsWithChildren<{ token: string | null }>) { return ( - apolloClientBuilder(() => token)}> + apolloClientBuilder(async () => token)}> {children} ); diff --git a/src/utils/session.ts b/src/utils/session.ts index 2c4b7580..ae3f24b7 100644 --- a/src/utils/session.ts +++ b/src/utils/session.ts @@ -1,5 +1,6 @@ +import { useState, useEffect } from 'react' import { Cookies } from 'react-cookie-consent' -import JsonWebToken from 'jsonwebtoken' +import { jwtVerify, importSPKI } from 'jose' import { JWT_KEY } from 'src/data/constants' export type User = { @@ -7,26 +8,39 @@ export type User = { name: string } | null -export const session = () => { - // RSA public key - if (!JWT_KEY) return null +export const useSession = () => { + const [user, setUser] = useState(null) + const [loading, setLoading] = useState(true) - const sessionCookie = Cookies.getJSON('_datacite') - const token = sessionCookie?.authenticated?.access_token - if (!token) return null + useEffect(() => { + const fetchUser = async () => { + // RSA public key + if (!JWT_KEY) { + setLoading(false) + return + } - let user: any = null - function setUser(error: any, payload: any) { - if (error) { - console.log('JWT verification error: ' + error.message) - return - } + const sessionCookie = Cookies.getJSON('_datacite') + const token = sessionCookie?.authenticated?.access_token + if (!token) { + setLoading(false) + return + } - user = payload - } + try { + const publicKey = await importSPKI(JWT_KEY, 'RS256') + const { payload } = await jwtVerify(token, publicKey) + setUser(payload as User) + } catch (error: any) { + console.log('JWT verification error: ' + error.message) + setUser(null) + } finally { + setLoading(false) + } + } - // verify asymmetric token, using RSA with SHA-256 hash algorithm - JsonWebToken.verify(token, JWT_KEY, { algorithms: ['RS256'] }, setUser) + fetchUser() + }, []) - return user as User + return { user, loading } } diff --git a/yarn.lock b/yarn.lock index 4626bf6c..f8815590 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2047,13 +2047,6 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/jsonwebtoken@^8.5.0": - version "8.5.9" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz#2c064ecb0b3128d837d2764aa0b117b0ff6e4586" - integrity sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg== - dependencies: - "@types/node" "*" - "@types/lodash@^4.14.154": version "4.17.20" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93" @@ -3403,11 +3396,6 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== -buffer-equal-constant-time@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -4559,13 +4547,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - edge-runtime@2.5.9: version "2.5.9" resolved "https://registry.yarnpkg.com/edge-runtime/-/edge-runtime-2.5.9.tgz#9daeb329f0339b8377483f230789b3d68f45f1d9" @@ -6484,6 +6465,11 @@ joi@^17.3.0: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" +jose@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/jose/-/jose-6.1.3.tgz#8453d7be88af7bb7d64a0481d6a35a0145ba3ea5" + integrity sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -6633,22 +6619,6 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - jsprim@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" @@ -6669,23 +6639,6 @@ jsprim@^2.0.2: object.assign "^4.1.4" object.values "^1.1.6" -jwa@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" - integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== - dependencies: - buffer-equal-constant-time "^1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -6965,11 +6918,6 @@ lodash.identity@~2.4.1: resolved "https://registry.yarnpkg.com/lodash.identity/-/lodash.identity-2.4.1.tgz#6694cffa65fef931f7c31ce86c74597cf560f4f1" integrity sha512-VRYX+8XipeLjorag5bz3YBBRJ+5kj8hVBzfnaHgXPZAVTYowBdY5l0M5ZnOmlAMCOXBFabQtm7f5VqjMKEji0w== -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== - lodash.isarray@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-2.4.1.tgz#b52a326c1f62f6d7da73a31d5401df6ef44f0fa1" @@ -6977,26 +6925,11 @@ lodash.isarray@~2.4.1: dependencies: lodash._isnative "~2.4.1" -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== - lodash.isfunction@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-2.4.1.tgz#2cfd575c73e498ab57e319b77fa02adef13a94d1" integrity sha512-6XcAB3izeQxPOQQNAJbbdjXbvWEt2Pn9ezPrjr4CwoLwmqsLVbsiEXD19cmmt4mbzOCOCdHzOQiUivUOJLra7w== -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== - lodash.isobject@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-2.4.1.tgz#5a2e47fe69953f1ee631a7eba1fe64d2d06558f5" @@ -7004,16 +6937,6 @@ lodash.isobject@~2.4.1: dependencies: lodash._objecttypes "~2.4.1" -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== - lodash.keys@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-2.4.1.tgz#48dea46df8ff7632b10d706b8acb26591e2b3727" @@ -7038,7 +6961,7 @@ lodash.noop@~2.4.1: resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-2.4.1.tgz#4fb54f816652e5ae10e8f72f717a388c7326538a" integrity sha512-uNcV98/blRhInPUGQEnj9ekXXfG+q+rfoNSFZgl/eBfog9yBDW9gfUv2AHX/rAF7zZRlzWhbslGhbGQFZlCkZA== -lodash.once@^4.0.0, lodash.once@^4.1.1: +lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== @@ -8477,11 +8400,6 @@ semver@7.3.5: dependencies: lru-cache "^6.0.0" -semver@^5.6.0: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3, semver@^7.7.1, semver@^7.7.2: version "7.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"