From 52431a6beef4f8d94c4c0d7c2c3c3023c0020e4d Mon Sep 17 00:00:00 2001 From: Aaron Harper Date: Fri, 19 Apr 2024 09:24:37 -0400 Subject: [PATCH] Add signing key rotation support (#541) --- .changeset/slimy-pears-tan.md | 5 + packages/inngest/package.json | 1 + packages/inngest/src/api/api.ts | 33 +++- packages/inngest/src/components/Inngest.ts | 3 + .../src/components/InngestCommHandler.ts | 108 ++++++++++++-- packages/inngest/src/helpers/consts.ts | 1 + packages/inngest/src/helpers/env.ts | 2 +- packages/inngest/src/helpers/net.test.ts | 65 ++++++++ packages/inngest/src/helpers/net.ts | 37 +++++ packages/inngest/src/test/helpers.ts | 141 +++++++++++++++--- packages/inngest/src/types.ts | 42 +++--- pnpm-lock.yaml | 39 ++++- 12 files changed, 409 insertions(+), 68 deletions(-) create mode 100644 .changeset/slimy-pears-tan.md create mode 100644 packages/inngest/src/helpers/net.test.ts create mode 100644 packages/inngest/src/helpers/net.ts diff --git a/.changeset/slimy-pears-tan.md b/.changeset/slimy-pears-tan.md new file mode 100644 index 000000000..cafc3ca1b --- /dev/null +++ b/.changeset/slimy-pears-tan.md @@ -0,0 +1,5 @@ +--- +"inngest": minor +--- + +Add signing key rotation support diff --git a/packages/inngest/package.json b/packages/inngest/package.json index 465cee4cd..63026bad8 100644 --- a/packages/inngest/package.json +++ b/packages/inngest/package.json @@ -212,6 +212,7 @@ "glob": "^10.3.10", "inquirer": "^9.2.10", "jest": "^29.3.1", + "jest-fetch-mock": "^3.0.3", "koa": "^2.14.2", "minimist": "^1.2.8", "next": "^13.5.4", diff --git a/packages/inngest/src/api/api.ts b/packages/inngest/src/api/api.ts index de8e3b3b7..f0c6ff2f7 100644 --- a/packages/inngest/src/api/api.ts +++ b/packages/inngest/src/api/api.ts @@ -2,6 +2,7 @@ import { type fetch } from "cross-fetch"; import { type ExecutionVersion } from "../components/execution/InngestExecution"; import { getFetch } from "../helpers/env"; import { getErrorMessage } from "../helpers/errors"; +import { fetchWithAuthFallback } from "../helpers/net"; import { hashSigningKey } from "../helpers/strings"; import { err, ok, type Result } from "../types"; import { @@ -18,21 +19,25 @@ type FetchT = typeof fetch; interface InngestApiConstructorOpts { baseUrl?: string; signingKey: string; + signingKeyFallback: string | undefined; fetch?: FetchT; } export class InngestApi { public readonly baseUrl: string; private signingKey: string; + private signingKeyFallback: string | undefined; private readonly fetch: FetchT; constructor({ baseUrl = "https://api.inngest.com", signingKey, + signingKeyFallback, fetch, }: InngestApiConstructorOpts) { this.baseUrl = baseUrl; this.signingKey = signingKey; + this.signingKeyFallback = signingKeyFallback; this.fetch = getFetch(fetch); } @@ -40,6 +45,14 @@ export class InngestApi { return hashSigningKey(this.signingKey); } + private get hashedFallbackKey(): string | undefined { + if (!this.signingKeyFallback) { + return; + } + + return hashSigningKey(this.signingKeyFallback); + } + // set the signing key in case it was not instantiated previously setSigningKey(key: string | undefined) { if (typeof key === "string" && this.signingKey === "") { @@ -47,14 +60,23 @@ export class InngestApi { } } + setSigningKeyFallback(key: string | undefined) { + if (typeof key === "string" && !this.signingKeyFallback) { + this.signingKeyFallback = key; + } + } + async getRunSteps( runId: string, version: ExecutionVersion ): Promise> { const url = new URL(`/v0/runs/${runId}/actions`, this.baseUrl); - return this.fetch(url, { - headers: { Authorization: `Bearer ${this.hashedKey}` }, + return fetchWithAuthFallback({ + authToken: this.hashedKey, + authTokenFallback: this.hashedFallbackKey, + fetch: this.fetch, + url, }) .then(async (resp) => { const data: unknown = await resp.json(); @@ -78,8 +100,11 @@ export class InngestApi { ): Promise> { const url = new URL(`/v0/runs/${runId}/batch`, this.baseUrl); - return this.fetch(url, { - headers: { Authorization: `Bearer ${this.hashedKey}` }, + return fetchWithAuthFallback({ + authToken: this.hashedKey, + authTokenFallback: this.hashedFallbackKey, + fetch: this.fetch, + url, }) .then(async (resp) => { const data: unknown = await resp.json(); diff --git a/packages/inngest/src/components/Inngest.ts b/packages/inngest/src/components/Inngest.ts index 10f8470f3..ded418d1c 100644 --- a/packages/inngest/src/components/Inngest.ts +++ b/packages/inngest/src/components/Inngest.ts @@ -208,6 +208,7 @@ export class Inngest { this.inngestApi = new InngestApi({ baseUrl: this.apiBaseUrl || defaultInngestApiBaseUrl, signingKey: processEnv(envKeys.InngestSigningKey) || "", + signingKeyFallback: processEnv(envKeys.InngestSigningKeyFallback), fetch: this.fetch, }); @@ -459,6 +460,8 @@ export class Inngest { } } + // We don't need to do fallback auth here because this uses event keys and + // not signing keys const response = await this.fetch(url, { method: "POST", body: stringify(payloads), diff --git a/packages/inngest/src/components/InngestCommHandler.ts b/packages/inngest/src/components/InngestCommHandler.ts index 8a83892f1..5d6d1f523 100644 --- a/packages/inngest/src/components/InngestCommHandler.ts +++ b/packages/inngest/src/components/InngestCommHandler.ts @@ -29,6 +29,7 @@ import { undefinedToNull, type FnData, } from "../helpers/functions"; +import { fetchWithAuthFallback } from "../helpers/net"; import { runAsPromise } from "../helpers/promises"; import { createStream } from "../helpers/stream"; import { hashSigningKey, stringify } from "../helpers/strings"; @@ -37,11 +38,12 @@ import { logLevels, type EventPayload, type FunctionConfig, - type IntrospectRequest, + type InsecureIntrospection, type LogLevel, type OutgoingOp, type RegisterOptions, type RegisterRequest, + type SecureIntrospection, type SupportedFrameworkName, } from "../types"; import { version } from "../version"; @@ -246,6 +248,12 @@ export class InngestCommHandler< */ protected signingKey: string | undefined; + /** + * The same as signingKey, except used as a fallback when auth fails using the + * primary signing key. + */ + protected signingKeyFallback: string | undefined; + /** * A property that can be set to indicate whether we believe we are in * production mode. @@ -394,6 +402,7 @@ export class InngestCommHandler< ); this.signingKey = options.signingKey; + this.signingKeyFallback = options.signingKeyFallback; this.serveHost = options.serveHost || this.env[envKeys.InngestServeHost]; this.servePath = options.servePath || this.env[envKeys.InngestServePath]; @@ -442,6 +451,10 @@ export class InngestCommHandler< return hashSigningKey(this.signingKey); } + private get hashedSigningKeyFallback(): string { + return hashSigningKey(this.signingKeyFallback); + } + /** * `createHandler` should be used to return a type-equivalent version of the * `handler` specified during instantiation. @@ -830,14 +843,39 @@ export class InngestCommHandler< deployId: null, }); - const introspection: IntrospectRequest = { - message: "Inngest endpoint configured correctly.", - hasEventKey: this.client["eventKeySet"](), - hasSigningKey: Boolean(this.signingKey), - functionsFound: registerBody.functions.length, - mode: this._mode, + const signature = await actions.headers( + "checking signature for run request", + headerKeys.Signature + ); + + let introspection: InsecureIntrospection | SecureIntrospection = { + extra: { + is_mode_explicit: this._mode.isExplicit, + message: "Inngest endpoint configured correctly.", + }, + has_event_key: this.client["eventKeySet"](), + has_signing_key: Boolean(this.signingKey), + function_count: registerBody.functions.length, + mode: this._mode.type, }; + // Only allow secure introspection in Cloud mode, since Dev mode skips + // signature validation + if (this._mode.type === "cloud") { + try { + this.validateSignature(signature ?? undefined, ""); + + introspection = { + ...introspection, + signing_key_fallback_hash: this.hashedSigningKeyFallback, + signing_key_hash: this.hashedSigningKey, + }; + } catch { + // Swallow signature validation error since we'll just return the + // insecure introspection + } + } + return { status: 200, body: stringify(introspection), @@ -1112,14 +1150,17 @@ export class InngestCommHandler< } try { - res = await this.fetch(registerURL.href, { - method: "POST", - body: stringify(body), - headers: { - ...getHeaders(), - Authorization: `Bearer ${this.hashedSigningKey}`, + res = await fetchWithAuthFallback({ + authToken: this.hashedSigningKey, + authTokenFallback: this.hashedSigningKeyFallback, + fetch: this.fetch, + url: registerURL.href, + options: { + method: "POST", + body: stringify(body), + headers: getHeaders(), + redirect: "follow", }, - redirect: "follow", }); } catch (err: unknown) { this.log("error", err); @@ -1177,6 +1218,16 @@ export class InngestCommHandler< this.client["inngestApi"].setSigningKey(this.signingKey); } + if (this.env[envKeys.InngestSigningKeyFallback]) { + if (!this.signingKeyFallback) { + this.signingKeyFallback = String( + this.env[envKeys.InngestSigningKeyFallback] + ); + } + + this.client["inngestApi"].setSigningKeyFallback(this.signingKeyFallback); + } + if (!this.client["eventKeySet"]() && this.env[envKeys.InngestEventKey]) { this.client.setEventKey(String(this.env[envKeys.InngestEventKey])); } @@ -1217,6 +1268,7 @@ export class InngestCommHandler< body, allowExpiredSignatures: this.allowExpiredSignatures, signingKey: this.signingKey, + signingKeyFallback: this.signingKeyFallback, }); } @@ -1282,7 +1334,7 @@ class RequestSignature { return delta > 1000 * 60 * 5; } - public verifySignature({ + #verifySignature({ body, signingKey, allowExpiredSignatures, @@ -1314,6 +1366,32 @@ class RequestSignature { throw new Error("Invalid signature"); } } + + public verifySignature({ + body, + signingKey, + signingKeyFallback, + allowExpiredSignatures, + }: { + body: unknown; + signingKey: string; + signingKeyFallback: string | undefined; + allowExpiredSignatures: boolean; + }): void { + try { + this.#verifySignature({ body, signingKey, allowExpiredSignatures }); + } catch (err) { + if (!signingKeyFallback) { + throw err; + } + + this.#verifySignature({ + body, + signingKey: signingKeyFallback, + allowExpiredSignatures, + }); + } + } } /** diff --git a/packages/inngest/src/helpers/consts.ts b/packages/inngest/src/helpers/consts.ts index 6616bd233..c26b29412 100644 --- a/packages/inngest/src/helpers/consts.ts +++ b/packages/inngest/src/helpers/consts.ts @@ -17,6 +17,7 @@ export enum queryKeys { export enum envKeys { InngestSigningKey = "INNGEST_SIGNING_KEY", + InngestSigningKeyFallback = "INNGEST_SIGNING_KEY_FALLBACK", InngestEventKey = "INNGEST_EVENT_KEY", /** diff --git a/packages/inngest/src/helpers/env.ts b/packages/inngest/src/helpers/env.ts index 5ae118a48..9239e26f6 100644 --- a/packages/inngest/src/helpers/env.ts +++ b/packages/inngest/src/helpers/env.ts @@ -124,7 +124,7 @@ export interface ModeOptions { } export class Mode { - private readonly type: "cloud" | "dev"; + public readonly type: "cloud" | "dev"; /** * Whether the mode was explicitly set, or inferred from other sources. diff --git a/packages/inngest/src/helpers/net.test.ts b/packages/inngest/src/helpers/net.test.ts new file mode 100644 index 000000000..979912664 --- /dev/null +++ b/packages/inngest/src/helpers/net.test.ts @@ -0,0 +1,65 @@ +import fetchMock from "jest-fetch-mock"; +import { fetchWithAuthFallback } from "./net"; + +describe("fetchWithAuthFallback", () => { + beforeEach(() => { + fetchMock.resetMocks(); + }); + + it("should make a fetch request with the provided auth token", async () => { + fetchMock.mockResponseOnce(JSON.stringify({ data: "12345" })); + + const response = await fetchWithAuthFallback({ + authToken: "testToken", + fetch: fetchMock as typeof fetch, + url: "https://example.com", + }); + + expect(fetchMock).toHaveBeenCalledWith("https://example.com", { + headers: { + Authorization: "Bearer testToken", + }, + }); + expect(response.status).toEqual(200); + }); + + it("should retry with the fallback token if the first request fails with 401", async () => { + fetchMock.mockResponses( + [JSON.stringify({}), { status: 401 }], + [JSON.stringify({ data: "12345" }), { status: 200 }] + ); + + const response = await fetchWithAuthFallback({ + authToken: "testToken", + authTokenFallback: "fallbackToken", + fetch: fetchMock as typeof fetch, + url: "https://example.com", + }); + + expect(fetchMock).toHaveBeenNthCalledWith(1, "https://example.com", { + headers: { + Authorization: "Bearer testToken", + }, + }); + expect(fetchMock).toHaveBeenNthCalledWith(2, "https://example.com", { + headers: { + Authorization: "Bearer fallbackToken", + }, + }); + expect(response.status).toEqual(200); + }); + + it("should not retry with the fallback token if the first request fails with a non-401/403 status", async () => { + fetchMock.mockResponseOnce(JSON.stringify({}), { status: 500 }); + + const response = await fetchWithAuthFallback({ + authToken: "testToken", + authTokenFallback: "fallbackToken", + fetch: fetchMock as typeof fetch, + url: "https://example.com", + }); + + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(response.status).toEqual(500); + }); +}); diff --git a/packages/inngest/src/helpers/net.ts b/packages/inngest/src/helpers/net.ts new file mode 100644 index 000000000..57344c15b --- /dev/null +++ b/packages/inngest/src/helpers/net.ts @@ -0,0 +1,37 @@ +/** + * Send an HTTP request with the given signing key. If the response is a 401 or + * 403, then try again with the fallback signing key + */ +export async function fetchWithAuthFallback({ + authToken, + authTokenFallback, + fetch, + options, + url, +}: { + authToken?: string; + authTokenFallback?: string; + fetch: TFetch; + options?: Parameters[1]; + url: URL | string; +}): Promise { + let res = await fetch(url, { + ...options, + headers: { + ...options?.headers, + Authorization: `Bearer ${authToken}`, + }, + }); + + if ([401, 403].includes(res.status) && authTokenFallback) { + res = await fetch(url, { + ...options, + headers: { + ...options?.headers, + Authorization: `Bearer ${authTokenFallback}`, + }, + }); + } + + return res; +} diff --git a/packages/inngest/src/test/helpers.ts b/packages/inngest/src/test/helpers.ts index 5a4116c8c..051cccc37 100644 --- a/packages/inngest/src/test/helpers.ts +++ b/packages/inngest/src/test/helpers.ts @@ -329,7 +329,12 @@ export const testFramework = ( describe("GET", () => { test("shows introspection data", async () => { const ret = await run( - [{ client: createClient({ id: "test" }), functions: [] }], + [ + { + client: createClient({ id: "test", isDev: true }), + functions: [], + }, + ], [ { method: "GET", @@ -352,10 +357,13 @@ export const testFramework = ( }); expect(body).toMatchObject({ - message: "Inngest endpoint configured correctly.", - functionsFound: 0, - hasEventKey: false, - hasSigningKey: false, + function_count: 0, + has_event_key: false, + has_signing_key: false, + mode: "dev", + extra: expect.objectContaining({ + is_mode_explicit: true, + }), }); }); @@ -369,10 +377,7 @@ export const testFramework = ( const body = JSON.parse(ret.body); expect(body).toMatchObject({ - message: "Inngest endpoint configured correctly.", - functionsFound: 0, - hasEventKey: true, - hasSigningKey: false, + has_event_key: true, }); }); @@ -386,10 +391,7 @@ export const testFramework = ( const body = JSON.parse(ret.body); expect(body).toMatchObject({ - message: "Inngest endpoint configured correctly.", - functionsFound: 0, - hasEventKey: false, - hasSigningKey: true, + has_signing_key: true, }); }); }); @@ -869,6 +871,111 @@ export const testFramework = ( body: JSON.stringify("fn"), }); }); + + describe("key rotation", () => { + test("should validate a signature with a fallback key successfully", async () => { + const event = { + data: {}, + id: "", + name: "inngest/scheduled.timer", + ts: 1674082830001, + user: {}, + v: "1", + }; + + const body = { + ctx: { + fn_id: "local-testing-local-cron", + run_id: "01GQ3HTEZ01M7R8Z9PR1DMHDN1", + step_id: "step", + }, + event, + events: [event], + steps: {}, + use_api: false, + }; + const ret = await run( + [ + { + client: inngest, + functions: [fn], + signingKey: "fake", + signingKeyFallback: + "signkey-test-f00f3005a3666b359a79c2bc3380ce2715e62727ac461ae1a2618f8766029c9f", + __testingAllowExpiredSignatures: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + ], + [ + { + method: "POST", + headers: { + [headerKeys.Signature]: + "t=1687306735&s=70312c7815f611a4aa0b6f985910a85a6c232c845838d7f49f1d05fd8b2b0779", + }, + url: "/api/inngest?fnId=test-test&stepId=step", + body, + }, + ], + env + ); + expect(ret).toMatchObject({ + status: 200, + body: JSON.stringify("fn"), + }); + }); + + test("should fail if validation fails with both keys", async () => { + const event = { + data: {}, + id: "", + name: "inngest/scheduled.timer", + ts: 1674082830001, + user: {}, + v: "1", + }; + + const body = { + ctx: { + fn_id: "local-testing-local-cron", + run_id: "01GQ3HTEZ01M7R8Z9PR1DMHDN1", + step_id: "step", + }, + event, + events: [event], + steps: {}, + use_api: false, + }; + const ret = await run( + [ + { + client: inngest, + functions: [fn], + signingKey: "fake", + signingKeyFallback: "another-fake", + __testingAllowExpiredSignatures: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + ], + [ + { + method: "POST", + headers: { + [headerKeys.Signature]: + "t=1687306735&s=70312c7815f611a4aa0b6f985910a85a6c232c845838d7f49f1d05fd8b2b0779", + }, + url: "/api/inngest?fnId=test-test&stepId=step", + body, + }, + ], + env + ); + expect(ret).toMatchObject({ + status: 500, + body: expect.stringContaining("Invalid signature"), + }); + }); + }); }); describe("malformed payloads", () => { @@ -1222,10 +1329,10 @@ export const checkIntrospection = ({ name, triggers }: CheckIntrospection) => { const { success } = z .object({ - message: z.string(), - hasSigningKey: z.boolean(), - hasEventKey: z.boolean(), - functionsFound: z.number(), + has_signing_key: z.boolean(), + has_event_key: z.boolean(), + function_count: z.number(), + mode: z.string(), }) .safeParse(await res.json()); diff --git a/packages/inngest/src/types.ts b/packages/inngest/src/types.ts index d9715e239..20d8db3e8 100644 --- a/packages/inngest/src/types.ts +++ b/packages/inngest/src/types.ts @@ -13,7 +13,6 @@ import { } from "./components/InngestMiddleware"; import { type createStepTools } from "./components/InngestStepTools"; import { type internalEvents } from "./helpers/consts"; -import { type Mode } from "./helpers/env"; import { type AsTuple, type IsEqual, @@ -734,6 +733,12 @@ export interface RegisterOptions { */ signingKey?: string; + /** + * The same as signingKey, except used as a fallback when auth fails using the + * primary signing key. + */ + signingKeyFallback?: string; + /** * The URL used to register functions with Inngest. * Defaults to https://api.inngest.com/fn/register @@ -980,29 +985,20 @@ export interface RegisterRequest { * * @internal */ -export interface IntrospectRequest { - message: string; - - /** - * Represents whether a signing key could be found when running this handler. - */ - hasSigningKey: boolean; - - /** - * Represents whether an event key could be found when running this handler. - */ - hasEventKey: boolean; - - /** - * The number of Inngest functions found at this handler. - */ - functionsFound: number; +export interface InsecureIntrospection { + extra: { + is_mode_explicit: boolean; + message: string; + }; + function_count: number; + has_event_key: boolean; + has_signing_key: boolean; + mode: "cloud" | "dev"; +} - /** - * The mode that this handler is running in and whether it has been inferred - * or explicitly set. - */ - mode: Mode; +export interface SecureIntrospection extends InsecureIntrospection { + signing_key_fallback_hash: string | null; + signing_key_hash: string | null; } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a01e20ce..52ec36d1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,7 +44,7 @@ importers: version: 29.5.0(@types/node@18.16.16) ts-jest: specifier: ^29.1.0 - version: 29.1.0(@babel/core@7.21.3)(jest@29.5.0)(typescript@5.4.2) + version: 29.1.0(@babel/core@7.23.6)(jest@29.5.0)(typescript@5.4.2) packages/eslint-plugin-internal: dependencies: @@ -187,6 +187,9 @@ importers: jest: specifier: ^29.3.1 version: 29.5.0(@types/node@18.16.16) + jest-fetch-mock: + specifier: ^3.0.3 + version: 3.0.3 koa: specifier: ^2.14.2 version: 2.14.2 @@ -195,7 +198,7 @@ importers: version: 1.2.8 next: specifier: ^13.5.4 - version: 13.5.4(@babel/core@7.23.6)(react-dom@18.2.0)(react@18.2.0) + version: 13.5.4(@babel/core@7.21.3)(react-dom@18.2.0)(react@18.2.0) nock: specifier: ^13.2.9 version: 13.2.9 @@ -213,7 +216,7 @@ importers: version: 0.3.4 ts-jest: specifier: ^29.1.0 - version: 29.1.0(@babel/core@7.23.6)(jest@29.5.0)(typescript@5.4.2) + version: 29.1.0(@babel/core@7.21.3)(jest@29.5.0)(typescript@5.4.2) tsx: specifier: ^3.12.7 version: 3.12.7 @@ -4188,6 +4191,14 @@ packages: cross-spawn: 7.0.3 dev: true + /cross-fetch@3.1.8: + resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: true + /cross-fetch@4.0.0: resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} dependencies: @@ -6720,6 +6731,15 @@ packages: jest-util: 29.5.0 dev: true + /jest-fetch-mock@3.0.3: + resolution: {integrity: sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==} + dependencies: + cross-fetch: 3.1.8 + promise-polyfill: 8.3.0 + transitivePeerDependencies: + - encoding + dev: true + /jest-get-type@27.5.1: resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -7885,7 +7905,7 @@ packages: engines: {node: '>= 0.6'} dev: true - /next@13.5.4(@babel/core@7.23.6)(react-dom@18.2.0)(react@18.2.0): + /next@13.5.4(@babel/core@7.21.3)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-+93un5S779gho8y9ASQhb/bTkQF17FNQOtXLKAj3lsNgltEcF0C5PMLLncDmH+8X1EnJH1kbqAERa29nRXqhjA==} engines: {node: '>=16.14.0'} hasBin: true @@ -7907,7 +7927,7 @@ packages: postcss: 8.4.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.1(@babel/core@7.23.6)(react@18.2.0) + styled-jsx: 5.1.1(@babel/core@7.21.3)(react@18.2.0) watchpack: 2.4.0 optionalDependencies: '@next/swc-darwin-arm64': 13.5.4 @@ -7983,7 +8003,6 @@ packages: optional: true dependencies: whatwg-url: 5.0.0 - dev: false /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -8558,6 +8577,10 @@ packages: engines: {node: '>= 0.6.0'} dev: true + /promise-polyfill@8.3.0: + resolution: {integrity: sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==} + dev: true + /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -9597,7 +9620,7 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - /styled-jsx@5.1.1(@babel/core@7.23.6)(react@18.2.0): + /styled-jsx@5.1.1(@babel/core@7.21.3)(react@18.2.0): resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} peerDependencies: @@ -9610,7 +9633,7 @@ packages: babel-plugin-macros: optional: true dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.21.3 client-only: 0.0.1 react: 18.2.0 dev: true