diff --git a/src/@types/commands.ts b/src/@types/commands.ts index b17985d86..2609493b3 100644 --- a/src/@types/commands.ts +++ b/src/@types/commands.ts @@ -55,6 +55,11 @@ export interface ValidateDDOCommand extends Command { export interface StatusCommand extends Command {} export interface EchoCommand extends Command {} +export interface StopNodeCommand extends Command { + expiryTimestamp: number + signature: string +} + export interface QueryCommand extends Command { query: Record } diff --git a/src/components/core/adminOperations.ts b/src/components/core/adminOperations.ts new file mode 100644 index 000000000..490bb54be --- /dev/null +++ b/src/components/core/adminOperations.ts @@ -0,0 +1,51 @@ +import { Handler } from './handler.js' +import { P2PCommandResponse } from '../../@types/OceanNode.js' +import { StopNodeCommand } from '../../@types/commands.js' +import { CORE_LOGGER } from '../../utils/logging/common.js' +import { ReadableString } from '../P2P/handleProtocolCommands.js' +import { + ValidateParams, + validateCommandParameters, + buildInvalidRequestMessage, + buildInvalidParametersResponse +} from '../httpRoutes/validateCommands.js' +import { validateSignature } from '../../utils/auth.js' + +export class StopNodeHandler extends Handler { + validate(command: StopNodeCommand): ValidateParams { + const commandValidation = validateCommandParameters(command, [ + 'expiryTimestamp', + 'signature' + ]) + if (!commandValidation.valid) { + const errorMsg = `Command validation failed: ${JSON.stringify(commandValidation)}` + CORE_LOGGER.logMessage(errorMsg) + return buildInvalidRequestMessage(errorMsg) + } + if (!validateSignature(command.expiryTimestamp, command.signature)) { + const errorMsg = 'Expired authentication or invalid signature' + CORE_LOGGER.logMessage(errorMsg) + return buildInvalidRequestMessage(errorMsg) + } + return commandValidation + } + + handle(task: StopNodeCommand): Promise { + const validation = this.validate(task) + if (!validation.valid) { + return new Promise((resolve, reject) => { + resolve(buildInvalidParametersResponse(validation)) + }) + } + CORE_LOGGER.logMessage(`Stopping node execution...`) + setTimeout(() => { + process.exit() + }, 2000) + return new Promise((resolve, reject) => { + resolve({ + status: { httpStatus: 200 }, + stream: new ReadableString('EXIT OK') + }) + }) + } +} diff --git a/src/components/core/coreHandlersRegistry.ts b/src/components/core/coreHandlersRegistry.ts index 3171264d8..99af1a537 100644 --- a/src/components/core/coreHandlersRegistry.ts +++ b/src/components/core/coreHandlersRegistry.ts @@ -26,6 +26,7 @@ import { ComputeGetResultHandler, ComputeInitializeHandler } from './compute/index.js' +import { StopNodeHandler } from './adminOperations.js' export type HandlerRegistry = { handlerName: string // name of the handler @@ -98,6 +99,7 @@ export class CoreHandlersRegistry { PROTOCOL_COMMANDS.COMPUTE_INITIALIZE, new ComputeInitializeHandler(node) ) + this.registerCoreHandler(PROTOCOL_COMMANDS.STOP_NODE, new StopNodeHandler(node)) } public static getInstance(node: OceanNode): CoreHandlersRegistry { diff --git a/src/components/core/utils/statusHandler.ts b/src/components/core/utils/statusHandler.ts index c03916189..0c937a951 100644 --- a/src/components/core/utils/statusHandler.ts +++ b/src/components/core/utils/statusHandler.ts @@ -43,7 +43,6 @@ export async function status( ) } } - const status: OceanNodeStatus = { id: undefined, publicKey: undefined, diff --git a/src/components/httpRoutes/commands.ts b/src/components/httpRoutes/commands.ts index 127ddd107..d28836b02 100644 --- a/src/components/httpRoutes/commands.ts +++ b/src/components/httpRoutes/commands.ts @@ -14,9 +14,6 @@ broadcastCommandRoute.post( '/broadcastCommand', express.json(), async (req: Request, res: Response): Promise => { - // for now we can use the same validation function, - // but later we might need to have separate validation functions - // if we many different commands of each type const validate = validateCommandParameters(req.body, []) if (!validate.valid) { res.status(validate.status).send(validate.reason) diff --git a/src/components/httpRoutes/validateCommands.ts b/src/components/httpRoutes/validateCommands.ts index c9016afad..2a26f6016 100644 --- a/src/components/httpRoutes/validateCommands.ts +++ b/src/components/httpRoutes/validateCommands.ts @@ -1,6 +1,6 @@ +import { SUPPORTED_PROTOCOL_COMMANDS } from '../../utils/constants.js' import { P2PCommandResponse } from '../../@types/OceanNode.js' import { Command } from '../../@types/commands.js' -import { SUPPORTED_PROTOCOL_COMMANDS } from '../../utils/constants.js' import { CORE_LOGGER } from '../../utils/logging/common.js' export type ValidateParams = { @@ -15,6 +15,7 @@ export function validateBroadcastParameters(requestBody: any): ValidateParams { // if we many different commands of each type return validateCommandParameters(requestBody, []) } +// add others when we add suppor // request level validation, just check if we have a "command" field and its a supported one // each command handler is responsible for the reamining validatio of the command fields diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 6a2c70b66..4b91d23e9 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,29 +1,17 @@ import { sha256, toUtf8Bytes, verifyMessage } from 'ethers' import { HTTP_LOGGER } from './logging/common.js' -import { PROTOCOL_COMMANDS } from './constants.js' -import { StatusHandler } from '../components/core/statusHandler.js' -import { streamToObject } from './util.js' -import { Readable } from 'stream' -import { OceanNode } from '../OceanNode.js' +import { getAllowedAdmins } from './index.js' -export async function validateSignature( - expiryTimestamp: number, - signature: string, - oceanNode: OceanNode -): Promise { +export function validateSignature(expiryTimestamp: number, signature: string): boolean { try { const message = sha256(toUtf8Bytes(expiryTimestamp.toString())) const signerAddress = verifyMessage(message, signature).toLowerCase() HTTP_LOGGER.logMessage(`Resolved signer address: ${signerAddress}`) - const statusCommand = { - command: PROTOCOL_COMMANDS.STATUS - } - const response = await new StatusHandler(oceanNode).handle(statusCommand) - const status = await streamToObject(response.stream as Readable) + const allowedAdmins = getAllowedAdmins() const currentTimestamp = new Date().getTime() - for (const address of status.allowedAdmins) { - if (address.lowercase() === signerAddress && currentTimestamp < expiryTimestamp) { + for (const address of allowedAdmins) { + if (address.toLowerCase() === signerAddress && currentTimestamp < expiryTimestamp) { return true } } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index c1e65acef..4749e1163 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -22,7 +22,8 @@ export const PROTOCOL_COMMANDS = { COMPUTE_STOP: 'stopCompute', COMPUTE_GET_STATUS: 'getComputeStatus', COMPUTE_GET_RESULT: 'getComputeResult', - COMPUTE_INITIALIZE: 'initializeCompute' + COMPUTE_INITIALIZE: 'initializeCompute', + STOP_NODE: 'stopNode' } // more visible, keep then close to make sure we always update both export const SUPPORTED_PROTOCOL_COMMANDS: string[] = [ @@ -45,7 +46,8 @@ export const SUPPORTED_PROTOCOL_COMMANDS: string[] = [ PROTOCOL_COMMANDS.COMPUTE_STOP, PROTOCOL_COMMANDS.COMPUTE_GET_STATUS, PROTOCOL_COMMANDS.COMPUTE_GET_RESULT, - PROTOCOL_COMMANDS.COMPUTE_INITIALIZE + PROTOCOL_COMMANDS.COMPUTE_INITIALIZE, + PROTOCOL_COMMANDS.STOP_NODE ] export const MetadataStates = {