Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ prog
.command("open [json]")
.describe(i18n.getTokenSync("cli.commands.open.desc"))
.option("-p, --port", i18n.getTokenSync("cli.commands.open.option_port"), process.env.PORT)
.option("-f, --fresh-start", i18n.getTokenSync("cli.commands.open.option_fresh_start"), process.env.PORT)
.action(commands.http.start);

prog
Expand Down
3 changes: 2 additions & 1 deletion docs/cli/open.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ $ nsecure open [json]

> [!NOTE]
> If the `[json]` property is omitted, the command will default to searching for a `nsecure-result.json` file in the current working directory.
> If no `nsecure-result.json` file is found, it will behave as same as with `--fresh-start` option.

## ⚙️ Available Options

| Name | Shortcut | Default Value | Description |
|---|---|---|---|
| `--port` | `-p` | `process.env.PORT` | Specify the port on which the HTTP server should run. |

| `--fresh-start` | `-f` | `false` | Open the UI with no initial package. Also, the app will use a dedicated cache. |
3 changes: 2 additions & 1 deletion i18n/english.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const cli = {
},
open: {
desc: "Run an HTTP Server with a given nsecure JSON file",
option_port: "Define the running port"
option_port: "Define the running port",
option_fresh_start: "Launch the server from scratch, ignoring any existing payload file"
},
verify: {
desc: "Run a complete advanced analysis for a given npm package",
Expand Down
3 changes: 2 additions & 1 deletion i18n/french.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const cli = {
},
open: {
desc: "Démarre un serveur HTTP avec un fichier .json nsecure donné",
option_port: "Port à utiliser"
option_port: "Port à utiliser",
option_fresh_start: "Lance le serveur à partir de zéro, en ignorant tout fichier de payload existant"
},
verify: {
desc: "Démarre une analyse AST avancée pour un package npm donné",
Expand Down
18 changes: 18 additions & 0 deletions public/components/navigation/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,22 @@ export class ViewNavigation {
this.setNewActiveMenu(selectedNav);
}
}

hideMenu(menuName) {
const menu = this.menus.get(menuName);
if (!menu) {
return;
}

menu.classList.add("hidden");
}

showMenu(menuName) {
const menu = this.menus.get(menuName);
if (!menu) {
return;
}

menu.classList.remove("hidden");
}
}
4 changes: 4 additions & 0 deletions public/components/searchbar/searchbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ div.search-result-pannel .package+.package {
box-shadow: 2px 1px 10px #26107f7a;
}

#search-nav:has(#searchbar[style*="display: none;"]) {
display: none;
}

#search-nav .search-result-pannel .package {
height: 30px;
color: rgb(229, 229, 229);
Expand Down
4 changes: 4 additions & 0 deletions public/core/search-nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ function initPackagesNavigation(data) {
classList: ["packages"]
});

if (packages.length === 0) {
return fragment;
}

for (const pkg of packages) {
const { name, version, local } = parseNpmSpec(pkg);

Expand Down
13 changes: 13 additions & 0 deletions public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ async function init(options = {}) {
});
await secureDataSet.init();

if (secureDataSet.data === null) {
window.navigation.hideMenu("network--view");
window.navigation.hideMenu("home--view");
window.navigation.setNavByName("search--view");

searchview ??= new SearchView(null, null);

return;
}

window.navigation.showMenu("network--view");
window.navigation.showMenu("home--view");

window.vulnerabilityStrategy = secureDataSet.data.vulnerabilityStrategy;

// Initialize vis Network
Expand Down
15 changes: 13 additions & 2 deletions src/commands/http.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Import Node.js Dependencies
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";

// Import Third-party Dependencies
import * as SemVer from "semver";
Expand All @@ -9,6 +10,7 @@ import * as i18n from "@nodesecure/i18n";

// Import Internal Dependencies
import { buildServer } from "../http-server/index.js";
import { appCache } from "../http-server/cache.js";

// CONSTANTS
const kRequiredScannerRange = ">=5.1.0";
Expand All @@ -18,6 +20,7 @@ export async function start(
options = {}
) {
const port = Number(options.port);
const freshStart = Boolean(options.f);
const fileExtension = path.extname(payloadFileBasename);
if (fileExtension !== ".json" && fileExtension !== "") {
throw new Error("You must provide a JSON file (scanner payload) to open");
Expand All @@ -27,10 +30,18 @@ export async function start(
process.cwd(),
fileExtension === "" ? `${payloadFileBasename}.json` : payloadFileBasename
);
assertScannerVersion(dataFilePath);
const dataFilePathExists = fs.existsSync(dataFilePath);
const runFromPayload = dataFilePathExists && freshStart === false;
if (runFromPayload) {
assertScannerVersion(dataFilePath);
}
else {
appCache.prefix = crypto.randomBytes(4).toString("hex");
}

const httpServer = buildServer(dataFilePath, {
port: Number.isNaN(port) ? 0 : port
port: Number.isNaN(port) ? 0 : port,
runFromPayload
});

for (const eventName of ["SIGINT", "SIGTERM"]) {
Expand Down
28 changes: 23 additions & 5 deletions src/http-server/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export const CACHE_PATH = path.join(os.tmpdir(), "nsecure-cli");
export const DEFAULT_PAYLOAD_PATH = path.join(process.cwd(), "nsecure-result.json");

class _AppCache {
prefix = "";
startFromZero = false;

constructor() {
fs.mkdirSync(kPayloadsPath, { recursive: true });
}
Expand Down Expand Up @@ -58,12 +61,12 @@ class _AppCache {
}

async updatePayloadsList(payloadsList) {
await cacache.put(CACHE_PATH, kPayloadsCache, JSON.stringify(payloadsList));
await cacache.put(CACHE_PATH, `${this.prefix}${kPayloadsCache}`, JSON.stringify(payloadsList));
}

async payloadsList() {
try {
const { data } = await cacache.get(CACHE_PATH, kPayloadsCache);
const { data } = await cacache.get(CACHE_PATH, `${this.prefix}${kPayloadsCache}`);

return JSON.parse(data.toString());
}
Expand All @@ -75,6 +78,21 @@ class _AppCache {
}

async #initDefaultPayloadsList() {
if (this.startFromZero) {
const payloadsList = {
lru: [],
current: null,
older: [],
lastUsed: {},
root: null
};

logger.info(`[cache|init](startFromZero)`);
await cacache.put(CACHE_PATH, `${this.prefix}${kPayloadsCache}`, JSON.stringify(payloadsList));

return;
}

const payload = JSON.parse(fs.readFileSync(DEFAULT_PAYLOAD_PATH, "utf-8"));
const version = Object.keys(payload.dependencies[payload.rootDependencyName].versions)[0];
const formatted = `${payload.rootDependencyName}@${version}`;
Expand All @@ -89,7 +107,7 @@ class _AppCache {
};

logger.info(`[cache|init](dep: ${formatted}|version: ${version}|rootDependencyName: ${payload.rootDependencyName})`);
await cacache.put(CACHE_PATH, kPayloadsCache, JSON.stringify(payloadsList));
await cacache.put(CACHE_PATH, `${this.prefix}${kPayloadsCache}`, JSON.stringify(payloadsList));
this.updatePayload(formatted, payload);
}

Expand All @@ -98,7 +116,7 @@ class _AppCache {

try {
// prevent re-initialization of the cache
await cacache.get(CACHE_PATH, kPayloadsCache);
await cacache.get(CACHE_PATH, `${this.prefix}${kPayloadsCache}`);

return;
}
Expand All @@ -116,7 +134,7 @@ class _AppCache {
logger.info(`[cache|init](packagesInFolder: ${packagesInFolder})`);
}

await cacache.put(CACHE_PATH, kPayloadsCache, JSON.stringify({
await cacache.put(CACHE_PATH, `${this.prefix}${kPayloadsCache}`, JSON.stringify({
older: packagesInFolder,
current: null,
lru: []
Expand Down
7 changes: 7 additions & 0 deletions src/http-server/endpoints/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import { logger } from "../logger.js";
const kDefaultPayloadPath = path.join(process.cwd(), "nsecure-result.json");

export async function get(_req, res) {
if (appCache.startFromZero) {
logger.info("[data|get](no content)");
send(res, 204);

return;
}

try {
const { current, lru } = await appCache.payloadsList();
logger.info(`[data|get](current: ${current})`);
Expand Down
13 changes: 10 additions & 3 deletions src/http-server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,24 @@ import * as report from "./endpoints/report.js";
import * as middleware from "./middleware.js";
import * as wsHandlers from "./websocket/index.js";
import { logger } from "./logger.js";
import { appCache } from "./cache.js";

export function buildServer(dataFilePath, options = {}) {
const httpConfigPort = typeof options.port === "number" ? options.port : 0;
const openLink = typeof options.openLink === "boolean" ? options.openLink : true;
const enableWS = options.enableWS ?? process.env.NODE_ENV !== "test";

fs.accessSync(dataFilePath, fs.constants.R_OK | fs.constants.W_OK);
const runFromPayload = options.runFromPayload ?? true;

const httpServer = polka();

httpServer.use(middleware.buildContextMiddleware(dataFilePath));
if (runFromPayload) {
fs.accessSync(dataFilePath, fs.constants.R_OK | fs.constants.W_OK);
httpServer.use(middleware.buildContextMiddleware(dataFilePath));
}
else {
appCache.startFromZero = true;
}

httpServer.use(middleware.addStaticFiles);
httpServer.get("/", root.get);

Expand Down
12 changes: 12 additions & 0 deletions src/http-server/websocket/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ export async function search(ws, pkg) {
await appCache.updatePayloadsList(updatedList);
ws.send(JSON.stringify(cache));

if (appCache.startFromZero) {
ws.send(JSON.stringify({
status: "RELOAD",
...updatedList
}));
appCache.startFromZero = false;
}

return;
}

Expand All @@ -41,6 +49,8 @@ export async function search(ws, pkg) {
...updatedList
}));

appCache.startFromZero = false;

return;
}

Expand Down Expand Up @@ -76,6 +86,8 @@ export async function search(ws, pkg) {
...updatedList
}));

appCache.startFromZero = false;

logger.info(`[ws|search](data sent to client|cache: updated)`);
}
}
7 changes: 6 additions & 1 deletion workspaces/vis-network/src/dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,14 @@ export default class NodeSecureDataSet extends EventTarget {
}

this.FLAGS = FLAGS;
this.warnings = data.warnings;
this.data = data;

if (data === null) {
return;
}

this.warnings = data.warnings;

const dataEntries = Object.entries(data.dependencies);
this.dependenciesCount = dataEntries.length;

Expand Down
4 changes: 4 additions & 0 deletions workspaces/vis-network/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export async function getJSON(path, customHeaders = Object.create(null)) {
};
}

if (raw.status === 204) {
return null;
}

return raw.json();
}

Expand Down
Loading