diff --git a/package.json b/package.json index c0760d4..fe4651a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rpclibrary", - "version": "1.4.7", + "version": "1.5.0", "description": "rpclibrary is a websocket on steroids!", "main": "./js/Index.js", "repository": { diff --git a/src/Backend.ts b/src/Backend.ts index 3d3716a..0c84158 100644 --- a/src/Backend.ts +++ b/src/Backend.ts @@ -20,6 +20,7 @@ export class RPCServer< private closeHandler:T.CloseHandler private errorHandler: T.ErrorHandler private connectionHandler: T.ConnectionHandler + private sesame? : T.SesameFunction /** * @throws On RPC with no name @@ -30,11 +31,16 @@ export class RPCServer< constructor( private port:number, private exporters: I.RPCExporter, keyof InterfaceT, SubResType>[] = [], - private conf: T.ServerConf = {} + conf: T.ServerConf = {} ){ if(!conf.visibility) this.visibility = "127.0.0.1" + if(conf.sesame){ + console.log("Setting Sesame") + this.sesame = U.makeSesameFunction(conf.sesame) + } + this.errorHandler = (socket:I.Socket) => (error:any) => { if(conf.errorHandler) conf.errorHandler(socket, error) socket.destroy(); @@ -42,12 +48,12 @@ export class RPCServer< } this.closeHandler = (socket:I.Socket) => { - if(!conf.closeHandler) console.log("Socket on port "+socket.port+" closing") + if(!conf.closeHandler) console.log("Connection on port "+socket.port+" closing") else conf.closeHandler(socket) } this.connectionHandler = (socket:I.Socket) => { - if(!conf.connectionHandler) console.log("New websocket connection on port "+socket.port) + if(!conf.connectionHandler) console.log("New connection on port "+socket.port) else conf.connectionHandler(socket) } @@ -74,15 +80,14 @@ export class RPCServer< }) this.ws.listen(this.port, this.visibility) }catch(e){ - //@ts-ignore - this.errorHandler(undefined)("Unable to connect to socket") + this.errorHandler(this.io, e) } } protected initRPCs(socket:I.Socket){ socket.hook('info', () => rpcInfos) const rpcInfos:T.ExtendedRpcInfo[] = [ - ...this.exporters.flatMap(exporter => U.rpcHooker(socket, exporter, this.conf.sesame)) + ...this.exporters.flatMap(exporter => U.rpcHooker(socket, exporter, this.sesame)) ] } diff --git a/src/Frontend.ts b/src/Frontend.ts index 1d242a2..aed48fe 100644 --- a/src/Frontend.ts +++ b/src/Frontend.ts @@ -134,7 +134,7 @@ export class RPCSocket implements I.Socket{ const argParams = fnArgs.map(stripAfterEquals).join(",") if(!sesame) return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' ) - else + else return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", "'+sesame+'", '+argParams+')} )()' ) } diff --git a/src/Types.ts b/src/Types.ts index bb4cd06..2b571be 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -7,8 +7,10 @@ export type Visibility = "127.0.0.1" | "0.0.0.0" export type ConnectionHandler = (socket:I.Socket) => void export type ErrorHandler = (socket:I.Socket, error:any) => void export type CloseHandler = (socket:I.Socket) => void +export type SesameFunction = (sesame : string) => boolean + export type SesameConf = { - sesame?: string + sesame?: string | SesameFunction } export type ServerConf = { connectionHandler?: ConnectionHandler @@ -65,7 +67,7 @@ export type BaseInfo = { export type HookInfo = BaseInfo & { type: 'Hook', - generator: (socket?:I.Socket) => (...args:any) => SubresT + generator: (socket?:I.Socket) => (...args:any[]) => SubresT } export type CallInfo = BaseInfo & { diff --git a/src/Utils.ts b/src/Utils.ts index 1bcf7cd..b479e49 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -10,7 +10,8 @@ import { SubscriptionResponse } from "./Types"; * @param owner The owning RPC group's name * @throws Error on RPC without name property */ -export const rpcToRpcinfo = (rpc : T.RPC, owner: string, sesame?:string):T.RpcInfo => { +export const rpcToRpcinfo = (rpc : T.RPC, owner: string, sesame?:T.SesameFunction):T.RpcInfo => { + switch (typeof rpc){ case "object": if(rpc['call']){ @@ -19,7 +20,7 @@ export const rpcToRpcinfo = (rpc : T.RPC, owner argNames: extractArgs(rpc['call']), type: "Call", name: rpc.name, - call: sesame?async (_sesame, ...args) => {if(sesame === _sesame) return await rpc['call'].apply({}, args)}:rpc['call'], + call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args)}:rpc['call'], // check & remove sesame } }else{ const generator = hookGenerator(>rpc, sesame) @@ -44,7 +45,7 @@ RPC did not provide a name. argNames: extractArgs(rpc), type: "Call", name: rpc.name, - call: async(...args) => (rpc).apply({}, args), + call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc.apply({}, args)}:rpc, // check & remove sesame } } throw new Error("Bad socketIORPC type "+ typeof rpc) @@ -56,7 +57,7 @@ RPC did not provide a name. * @param exporter The exporter * @param makeUnique @default true Attach a suffix to RPC names */ -export function rpcHooker(socket: I.Socket, exporter:I.RPCExporter, sesame?:string, makeUnique = true):T.ExtendedRpcInfo[]{ +export function rpcHooker(socket: I.Socket, exporter:I.RPCExporter, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{ const owner = exporter.name const RPCs = [...exporter.exportRPCs()] @@ -85,15 +86,15 @@ export function rpcHooker(socket: I.Socket, exporter:I.RPCExporter * @param rpc The RPC to transform * @returns A {@link HookFunction} */ -const hookGenerator = (rpc:T.HookRPC, sesame?:string): T.HookInfo['generator'] => { +const hookGenerator = (rpc:T.HookRPC, sesame?:T.SesameFunction): T.HookInfo['generator'] => { const argsArr = extractArgs(rpc.hook) if(sesame){ const _sesame = argsArr.shift() - if(sesame !== _sesame){ + if(!sesame(_sesame!)){ throw new Error('Bad sesame') } } - argsArr.pop() + argsArr.pop() //remove 'callback' from the end const args = argsArr.join(',') return eval(`(socket) => async (`+args+`) => { @@ -131,4 +132,14 @@ export function makeSubResponse(extension:T):SubscriptionResp uuid: uuidv4(), ...extension } +} + +export function makeSesameFunction (sesame : T.SesameFunction | string) : T.SesameFunction { + if(typeof sesame === 'function'){ + return sesame + } + + return (testSesame : string) => { + return testSesame === sesame + } } \ No newline at end of file diff --git a/test/Test.ts b/test/Test.ts index b20429a..5a91a09 100644 --- a/test/Test.ts +++ b/test/Test.ts @@ -223,4 +223,47 @@ describe('It should do unhook', () => { done(new Error("Results did not match: "+[r1,r2,r3,r4])) }) }) +}) + +type SesameTestIfc = { test: { checkCandy: ()=>Promise} } + +describe('Sesame should unlock the socket', () => { + let candy = "OK" + let client: RPCSocket & SesameTestIfc + let server: RPCServer<{topic: string}, SesameTestIfc> + + before(async() => { + server = new RPCServer<{ topic: string }, SesameTestIfc>(20004, [{ + name: "test", + exportRPCs: () => [ + async function checkCandy():Promise { return candy }, + ]} + ],{ + sesame: (_sesame) => _sesame === 'sesame!' + }) + const sock = new RPCSocket(20004, "localhost") + client = await sock.connect('sesame!') + }) + + after(() => { + client.destroy() + server.destroy() + }) + + it('should not work without sesame', (done) => { + const sock = new RPCSocket(20004, "localhost") + sock.connect( /* no sesame */).then(async (c) => { + c.test.checkCandy().then(d => { + if(d === candy) + done("should not be able to get candy") + done() + }).finally(() => { + sock.destroy() + }) + }) + }) + + it('should work with sesame', (done) => { + client.test.checkCandy().then(c => done()) + }) }) \ No newline at end of file