From b5da3111335f5182e0bea265d010a053bf5358e5 Mon Sep 17 00:00:00 2001 From: David Walker Date: Sun, 8 Feb 2026 08:39:42 -0800 Subject: [PATCH] REFACTOR: Consolidate checks under getFailureResult() (#2476) There was duplicated code, and more importantly, were were handling certain things subtly differently in exec() and scp() as a result. This notably causes a behavior change in exec() and scp() where failure to authenticate now returns failure instead of throwing, which I believe is the proper response. This also makes it easier to see in the code exactly which functions require what (auth, session, etc.) --- src/DarkNet/effects/offlineServerHandling.ts | 82 +++++++++----------- src/NetscriptFunctions.ts | 37 +++++---- test/jest/Netscript/Darknet.test.ts | 24 ++---- 3 files changed, 62 insertions(+), 81 deletions(-) diff --git a/src/DarkNet/effects/offlineServerHandling.ts b/src/DarkNet/effects/offlineServerHandling.ts index ed8086805..9194956ca 100644 --- a/src/DarkNet/effects/offlineServerHandling.ts +++ b/src/DarkNet/effects/offlineServerHandling.ts @@ -19,6 +19,8 @@ type FailureResultOptions = { requireSession?: boolean; requireDirectConnection?: boolean; preventUseOnStationaryServers?: boolean; + allowNonDarknet?: boolean; + backdoorBypasses?: boolean; }; export const logger = (ctx: NetscriptContext) => (message: string) => helpers.log(ctx, () => message); @@ -39,13 +41,13 @@ export function getFailureResult( ): | { success: true; code: DarknetResponseCode; message: string; server: DarknetServer } | { success: false; code: DarknetResponseCode; message: string } { - expectDarknetAccess(ctx); const currentServer = ctx.workerScript.getServer(); const targetServer = GetServer(host); - // If the target server does not exist if (!targetServer) { if (DarknetState.offlineServers.includes(host)) { - // If the server is offline, return a dummy object with isOnline = false. + // Because servers going offline is timing-sensitive, it is outside of + // player's control. So we don't want to throw for "server does not exist" in this case, + // despite throwing being the usual doctrine. logger(ctx)(`Server ${host} is offline.`); return { success: false, @@ -53,20 +55,42 @@ export function getFailureResult( message: GenericResponseMessage.ServiceUnavailable, }; } else { - // Throw, otherwise. throw errorMessage(ctx, `Server ${host} does not exist.`); } } + const success = { + success: true, + code: ResponseCodeEnum.Success, + message: GenericResponseMessage.Success, + server: targetServer as DarknetServer, + } as const; if (!(targetServer instanceof DarknetServer)) { + if (options.allowNonDarknet) { + // The return is off-shape here: server is of type DarknetServer, but + // we've explicitly validated that it's only a BaseServer. Callers + // using allowNonDarknet should call GetServer on their own for proper + // type-safety, instead of using the server field. + return success; + } const result = `${targetServer.hostname} is not a darknet server.`; throw errorMessage(ctx, result); } + // This is down here because we don't require darknet access for using + // allowNonDarknet APIs on non-darknet servers. + expectDarknetAccess(ctx); if (options.preventUseOnStationaryServers && targetServer.isStationary) { const result = `${targetServer.hostname} is not a valid target: it is a stationary server.`; throw errorMessage(ctx, result); } - if (options.requireDirectConnection && !isDirectConnected(currentServer, targetServer)) { - const result = `${targetServer.hostname} is not connected to the current server ${currentServer.hostname}. It may have moved.`; + if ( + options.requireDirectConnection && + !isDirectConnected(currentServer, targetServer) && + !(options.backdoorBypasses && targetServer.backdoorInstalled) + ) { + let result = `${targetServer.hostname} is not connected to the current server ${currentServer.hostname}. It may have moved.`; + if (options.backdoorBypasses) { + result += " You can also use a backdoor or stasis link on the target to allow remote access."; + } logger(ctx)(result); return { success: false, @@ -74,7 +98,11 @@ export function getFailureResult( message: GenericResponseMessage.DirectConnectionRequired, }; } - if ((options.requireSession || options.requireAdminRights) && !targetServer.hasAdminRights) { + if (ctx.workerScript.hostname === targetServer.hostname || targetServer.hostname === SpecialServers.DarkWeb) { + // We always are authed to ourselves and DarkWeb. Early-out past the last checks. + return success; + } + if (options.requireAdminRights && !targetServer.hasAdminRights) { const result = `${targetServer.hostname} requires root access. Use ns.dnet.authenticate() to gain access.`; logger(ctx)(result); return { @@ -97,12 +125,7 @@ export function getFailureResult( }; } - return { - success: true, - code: ResponseCodeEnum.Success, - message: GenericResponseMessage.Success, - server: targetServer, - }; + return success; } export const isDirectConnected = (currentServer: BaseServer, targetServer: DarknetServer): boolean => @@ -128,39 +151,6 @@ export function expectRunningOnDarknetServer(ctx: NetscriptContext): DarknetServ return server; } -export function expectAuthenticated(ctx: NetscriptContext, server: DarknetServer) { - /** - * Some non-dnet APIs (e.g., scp, exec) requires a session. We make darkweb an exception, so the player can interact - * with it without buying DarkscapeNavigator.exe. - */ - if (ctx.workerScript.hostname === server.hostname || server.hostname === SpecialServers.DarkWeb) { - return; - } - if (!server.hasAdminRights) { - throw errorMessage( - ctx, - `[${ctx.function}] Server ${server.hostname} is password-protected. Use ns.dnet.authenticate() to gain access before running ${ctx.function}.`, - ); - } - if (!isAuthenticated(server, ctx.workerScript.pid)) { - throw errorMessage( - ctx, - `[${ctx.function}] Server ${server.hostname} requires a session to be targeted with ${ctx.function}. Use ns.dnet.connectToSession() first to authenticate with that server.`, - ); - } -} - -/** - * This function checks if the target server has a session and a direct connection (serversOnNetwork, stasis link, - * backdoor) to the running script's server. - */ -export function hasExecConnection(ctx: NetscriptContext, targetServer: DarknetServer) { - expectAuthenticated(ctx, targetServer); - const directConnected = isDirectConnected(ctx.workerScript.getServer(), targetServer); - const backdoored = targetServer.backdoorInstalled; - return directConnected || backdoored; -} - export function getTimeoutChance() { const backdooredDarknetServerCount = getBackdooredDarkwebServers().length - 2; return Math.max(Math.min(backdooredDarknetServerCount * 0.03, 0.5), 0); diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index a9f69aa6e..821a2c9ae 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -110,7 +110,7 @@ import { compile } from "./NetscriptJSEvaluator"; import { Script } from "./Script/Script"; import { NetscriptFormat } from "./NetscriptFunctions/Format"; import { DarknetState } from "./DarkNet/models/DarknetState"; -import { expectAuthenticated, hasExecConnection } from "./DarkNet/effects/offlineServerHandling"; +import { getFailureResult } from "./DarkNet/effects/offlineServerHandling"; import { DarknetServer } from "./Server/DarknetServer"; import { FragmentTypeEnum } from "./CotMG/FragmentType"; import { exampleDarknetServerData, ResponseCodeEnum } from "./DarkNet/Enums"; @@ -653,20 +653,18 @@ export const ns: InternalAPI = { const host = helpers.string(ctx, "host", _host); const runOpts = helpers.runOptions(ctx, _thread_or_opt); const args = helpers.scriptArgs(ctx, _args); - if (DarknetState.offlineServers.includes(host)) { - helpers.log(ctx, () => `Script execution failed, because ${host} is offline.`); + if ( + !getFailureResult(ctx, host, { + allowNonDarknet: true, + requireAdminRights: true, + requireSession: true, + requireDirectConnection: true, + backdoorBypasses: true, + }).success + ) { return 0; } const server = helpers.getServer(ctx, host); - if (server instanceof DarknetServer && !hasExecConnection(ctx, server)) { - const currentHostname = ctx.workerScript.getServer().hostname; - helpers.log( - ctx, - () => - `The current server ${currentHostname} is not connected to ${host}. exec() to a password-protected server requires a direct connection, a stasis link, or a backdoor. Use exec() from an adjacent server, or set a stasis link on the target server.`, - ); - return 0; - } return runScriptFromScript("exec", server, path, args, ctx.workerScript, runOpts); }, spawn: @@ -785,19 +783,20 @@ export const ns: InternalAPI = { scp: (ctx) => (_files, _destination, _source) => { const destination = helpers.string(ctx, "destination", _destination); const source = helpers.string(ctx, "source", _source ?? ctx.workerScript.hostname); - if (DarknetState.offlineServers.includes(destination)) { - helpers.log(ctx, () => `scp failed, because ${destination} is offline.`); + if ( + !getFailureResult(ctx, destination, { + allowNonDarknet: true, + requireAdminRights: true, + requireSession: true, + }).success + ) { return false; } - if (DarknetState.offlineServers.includes(source)) { - helpers.log(ctx, () => `scp failed, because ${source} is offline.`); + if (!getFailureResult(ctx, source, { allowNonDarknet: true }).success) { return false; } const sourceServer = helpers.getServer(ctx, source); const destServer = helpers.getServer(ctx, destination); - if (destServer instanceof DarknetServer) { - expectAuthenticated(ctx, destServer); - } const files = Array.isArray(_files) ? _files : [_files]; const lits: FilePath[] = []; const contentFiles: ContentFilePath[] = []; diff --git a/test/jest/Netscript/Darknet.test.ts b/test/jest/Netscript/Darknet.test.ts index f1f98dacc..e9f1678fa 100644 --- a/test/jest/Netscript/Darknet.test.ts +++ b/test/jest/Netscript/Darknet.test.ts @@ -273,21 +273,17 @@ describe("home", () => { // Cannot scp before authenticating expect(dnetServer.hasAdminRights).toStrictEqual(false); - expect(() => { - ns.scp(scriptPath, dnetServerHostname, SpecialServers.Home); - }).toThrow(`Server ${dnetServerHostname} is password-protected`); + expect(ns.scp(scriptPath, dnetServerHostname, SpecialServers.Home)).toStrictEqual(false); expect(dnetServer.scripts.size).toStrictEqual(0); - // Cannot exec before authenticating - expect(() => { - ns.exec(scriptPath, dnetServerHostname); - }).toThrow(`Server ${dnetServerHostname} is password-protected`); + // Cannot exec from home because there is no direct connection + expect(ns.exec(scriptPath, dnetServerHostname)).toStrictEqual(0); - const { ws, ns: nsDarkWeb } = getWorkerScriptAndNS(SpecialServers.DarkWeb); + const { ws: wsDarkWeb, ns: nsDarkWeb } = getWorkerScriptAndNS(SpecialServers.DarkWeb); // Authenticate from darkweb expect((await nsDarkWeb.dnet.authenticate(dnetServerHostname, dnetServer.password)).success).toStrictEqual(true); expect(dnetServer.hasAdminRights).toStrictEqual(true); // Check session created after successfully calling authenticate API - expect(getServerState(dnetServerHostname).authenticatedPIDs.includes(ws.pid)).toStrictEqual(true); + expect(getServerState(dnetServerHostname).authenticatedPIDs.includes(wsDarkWeb.pid)).toStrictEqual(true); // Write the test script to darkweb nsDarkWeb.write(scriptPath, scriptContent); // scp from darkweb @@ -300,14 +296,10 @@ describe("home", () => { dnetServer.scripts.clear(); // Cannot scp from home without a session - expect(() => { - ns.scp(scriptPath, dnetServerHostname, SpecialServers.Home); - }).toThrow(`Server ${dnetServerHostname} requires a session`); + expect(ns.scp(scriptPath, dnetServerHostname, SpecialServers.Home)).toStrictEqual(false); expect(dnetServer.scripts.size).toStrictEqual(0); - // Cannot exec from home without a session - expect(() => { - ns.exec(scriptPath, dnetServerHostname); - }).toThrow(`Server ${dnetServerHostname} requires a session`); + // Cannot exec from home because there is no direct connection + expect(ns.exec(scriptPath, dnetServerHostname)).toStrictEqual(0); // Create a session from home to dnet server expect(ns.dnet.connectToSession(dnetServerHostname, dnetServer.password).success).toStrictEqual(true);