From 1572e5ac1314f51f6396c7aa056d7b6254e498ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20S=C3=A1nchez?= Date: Wed, 22 Jan 2025 18:23:27 +0100 Subject: [PATCH] External extensions tweaks (#203) --- .changeset/mighty-singers-burn.md | 5 ++ src/curated-extensions.ts | 31 ---------- src/utils/external-extensions.ts | 72 ++++++++++++++++++++++- src/utils/parse-arguments-into-options.ts | 57 ++---------------- 4 files changed, 81 insertions(+), 84 deletions(-) create mode 100644 .changeset/mighty-singers-burn.md delete mode 100644 src/curated-extensions.ts diff --git a/.changeset/mighty-singers-burn.md b/.changeset/mighty-singers-burn.md new file mode 100644 index 000000000..98dfba702 --- /dev/null +++ b/.changeset/mighty-singers-burn.md @@ -0,0 +1,5 @@ +--- +"create-eth": patch +--- + +Add trusted GitHub organizations for extensions diff --git a/src/curated-extensions.ts b/src/curated-extensions.ts deleted file mode 100644 index 98e500498..000000000 --- a/src/curated-extensions.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ExternalExtension } from "./types"; -import curatedExtension from "./extensions.json"; - -type ExtensionJSON = { - extensionFlagValue: string; - repository: string; - branch?: string; - // fields usefull for scaffoldeth.io - description: string; - version?: string; // if not present we default to latest - name?: string; // human redable name, if not present we default to branch or extensionFlagValue on UI -}; - -const extensions: ExtensionJSON[] = curatedExtension; - -const CURATED_EXTENSIONS = extensions.reduce>((acc, ext) => { - if (!ext.repository) { - throw new Error(`Extension must have 'repository': ${JSON.stringify(ext)}`); - } - if (!ext.extensionFlagValue) { - throw new Error(`Extension must have 'extensionFlagValue': ${JSON.stringify(ext)}`); - } - - acc[ext.extensionFlagValue] = { - repository: ext.repository, - branch: ext.branch, - }; - return acc; -}, {}); - -export { CURATED_EXTENSIONS }; diff --git a/src/utils/external-extensions.ts b/src/utils/external-extensions.ts index 1f464fd25..d8fa23bf5 100644 --- a/src/utils/external-extensions.ts +++ b/src/utils/external-extensions.ts @@ -1,10 +1,39 @@ import fs from "fs"; import path from "path"; +import * as https from "https"; import { fileURLToPath } from "url"; import { ExternalExtension, RawOptions, SolidityFramework } from "../types"; -import { CURATED_EXTENSIONS } from "../curated-extensions"; +import curatedExtension from "../extensions.json"; import { SOLIDITY_FRAMEWORKS } from "./consts"; +type ExtensionJSON = { + extensionFlagValue: string; + repository: string; + branch?: string; + // fields usefull for scaffoldeth.io + description: string; + version?: string; // if not present we default to latest + name?: string; // human redable name, if not present we default to branch or extensionFlagValue on UI +}; + +const TRUSTED_GITHUB_ORGANIZATIONS = ["scaffold-eth", "buidlguidl"]; + +const extensions: ExtensionJSON[] = curatedExtension; +const CURATED_EXTENSIONS = extensions.reduce>((acc, ext) => { + if (!ext.repository) { + throw new Error(`Extension must have 'repository': ${JSON.stringify(ext)}`); + } + if (!ext.extensionFlagValue) { + throw new Error(`Extension must have 'extensionFlagValue': ${JSON.stringify(ext)}`); + } + + acc[ext.extensionFlagValue] = { + repository: ext.repository, + branch: ext.branch, + }; + return acc; +}, {}); + function deconstructGithubUrl(url: string) { const urlParts = url.split("/"); const ownerName = urlParts[3]; @@ -14,6 +43,47 @@ function deconstructGithubUrl(url: string) { return { ownerName, repoName, branch }; } +export const validateExternalExtension = async ( + extensionName: string, + dev: boolean, +): Promise<{ repository: string; branch?: string; isTrusted: boolean } | string> => { + if (dev) { + // Check externalExtensions/${extensionName} exists + try { + const currentFileUrl = import.meta.url; + const externalExtensionsDirectory = path.resolve( + decodeURI(fileURLToPath(currentFileUrl)), + "../../externalExtensions", + ); + await fs.promises.access(`${externalExtensionsDirectory}/${extensionName}`); + } catch { + throw new Error(`Extension not found in "externalExtensions/${extensionName}"`); + } + + return extensionName; + } + + const { githubUrl, githubBranchUrl, branch, owner } = getDataFromExternalExtensionArgument(extensionName); + const isTrusted = TRUSTED_GITHUB_ORGANIZATIONS.includes(owner.toLowerCase()) || !!CURATED_EXTENSIONS[extensionName]; + + // Check if repository exists + await new Promise((resolve, reject) => { + https + .get(githubBranchUrl, res => { + if (res.statusCode !== 200) { + reject(new Error(`Extension not found: ${githubUrl}`)); + } else { + resolve(null); + } + }) + .on("error", err => { + reject(err); + }); + }); + + return { repository: githubUrl, branch, isTrusted }; +}; + // Gets the data from the argument passed to the `--extension` option. export const getDataFromExternalExtensionArgument = (externalExtension: string) => { if (CURATED_EXTENSIONS[externalExtension]) { diff --git a/src/utils/parse-arguments-into-options.ts b/src/utils/parse-arguments-into-options.ts index 0b92b3518..ed23ffeb8 100644 --- a/src/utils/parse-arguments-into-options.ts +++ b/src/utils/parse-arguments-into-options.ts @@ -1,59 +1,11 @@ -import type { Args, ExternalExtension, SolidityFramework, RawOptions, SolidityFrameworkChoices } from "../types"; +import type { Args, SolidityFramework, RawOptions, SolidityFrameworkChoices } from "../types"; import arg from "arg"; -import * as https from "https"; -import { - getDataFromExternalExtensionArgument, - getSolidityFrameworkDirsFromExternalExtension, -} from "./external-extensions"; +import { getSolidityFrameworkDirsFromExternalExtension, validateExternalExtension } from "./external-extensions"; import chalk from "chalk"; -import { CURATED_EXTENSIONS } from "../curated-extensions"; import { SOLIDITY_FRAMEWORKS } from "./consts"; import { validateFoundryUp } from "./system-validation"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; import { validateNpmName } from "./validate-name"; -const validateExternalExtension = async ( - extensionName: string, - dev: boolean, -): Promise<{ repository: string; branch?: string } | string> => { - if (dev) { - // Check externalExtensions/${extensionName} exists - try { - const currentFileUrl = import.meta.url; - const externalExtensionsDirectory = path.resolve( - decodeURI(fileURLToPath(currentFileUrl)), - "../../externalExtensions", - ); - await fs.promises.access(`${externalExtensionsDirectory}/${extensionName}`); - } catch { - throw new Error(`Extension not found in "externalExtensions/${extensionName}"`); - } - - return extensionName; - } - - const { githubUrl, githubBranchUrl, branch } = getDataFromExternalExtensionArgument(extensionName); - - // Check if repository exists - await new Promise((resolve, reject) => { - https - .get(githubBranchUrl, res => { - if (res.statusCode !== 200) { - reject(new Error(`Extension not found: ${githubUrl}`)); - } else { - resolve(null); - } - }) - .on("error", err => { - reject(err); - }); - }); - - return { repository: githubUrl, branch }; -}; - // TODO update smartContractFramework code with general extensions export async function parseArgumentsIntoOptions( rawArgs: Args, @@ -92,11 +44,12 @@ export async function parseArgumentsIntoOptions( // ToDo. Allow multiple const extension = extensionName ? await validateExternalExtension(extensionName, dev) : null; - if (!dev && extension && !CURATED_EXTENSIONS[args["--extension"] as string]) { + // if dev mode, extension would be a string + if (extension && typeof extension === "object" && !extension.isTrusted) { console.log( chalk.yellow( ` You are using a third-party extension. Make sure you trust the source of ${chalk.yellow.bold( - (extension as ExternalExtension).repository, + extension.repository, )}\n`, ), );