sesame function

This commit is contained in:
2020-01-18 15:40:27 +01:00
parent dec859233b
commit 217df18585
6 changed files with 78 additions and 17 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "rpclibrary", "name": "rpclibrary",
"version": "1.4.7", "version": "1.5.0",
"description": "rpclibrary is a websocket on steroids!", "description": "rpclibrary is a websocket on steroids!",
"main": "./js/Index.js", "main": "./js/Index.js",
"repository": { "repository": {
+11 -6
View File
@@ -20,6 +20,7 @@ export class RPCServer<
private closeHandler:T.CloseHandler private closeHandler:T.CloseHandler
private errorHandler: T.ErrorHandler private errorHandler: T.ErrorHandler
private connectionHandler: T.ConnectionHandler private connectionHandler: T.ConnectionHandler
private sesame? : T.SesameFunction
/** /**
* @throws On RPC with no name * @throws On RPC with no name
@@ -30,11 +31,16 @@ export class RPCServer<
constructor( constructor(
private port:number, private port:number,
private exporters: I.RPCExporter<T.RPCInterface<InterfaceT>, keyof InterfaceT, SubResType>[] = [], private exporters: I.RPCExporter<T.RPCInterface<InterfaceT>, keyof InterfaceT, SubResType>[] = [],
private conf: T.ServerConf = {} conf: T.ServerConf = {}
){ ){
if(!conf.visibility) this.visibility = "127.0.0.1" 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) => { this.errorHandler = (socket:I.Socket) => (error:any) => {
if(conf.errorHandler) conf.errorHandler(socket, error) if(conf.errorHandler) conf.errorHandler(socket, error)
socket.destroy(); socket.destroy();
@@ -42,12 +48,12 @@ export class RPCServer<
} }
this.closeHandler = (socket:I.Socket) => { 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) else conf.closeHandler(socket)
} }
this.connectionHandler = (socket:I.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) else conf.connectionHandler(socket)
} }
@@ -74,15 +80,14 @@ export class RPCServer<
}) })
this.ws.listen(this.port, this.visibility) this.ws.listen(this.port, this.visibility)
}catch(e){ }catch(e){
//@ts-ignore this.errorHandler(this.io, e)
this.errorHandler(undefined)("Unable to connect to socket")
} }
} }
protected initRPCs(socket:I.Socket){ protected initRPCs(socket:I.Socket){
socket.hook('info', () => rpcInfos) socket.hook('info', () => rpcInfos)
const rpcInfos:T.ExtendedRpcInfo[] = [ 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))
] ]
} }
+1 -1
View File
@@ -134,7 +134,7 @@ export class RPCSocket implements I.Socket{
const argParams = fnArgs.map(stripAfterEquals).join(",") const argParams = fnArgs.map(stripAfterEquals).join(",")
if(!sesame) if(!sesame)
return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' ) 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+')} )()' ) return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", "'+sesame+'", '+argParams+')} )()' )
} }
+4 -2
View File
@@ -7,8 +7,10 @@ export type Visibility = "127.0.0.1" | "0.0.0.0"
export type ConnectionHandler = (socket:I.Socket) => void export type ConnectionHandler = (socket:I.Socket) => void
export type ErrorHandler = (socket:I.Socket, error:any) => void export type ErrorHandler = (socket:I.Socket, error:any) => void
export type CloseHandler = (socket:I.Socket) => void export type CloseHandler = (socket:I.Socket) => void
export type SesameFunction = (sesame : string) => boolean
export type SesameConf = { export type SesameConf = {
sesame?: string sesame?: string | SesameFunction
} }
export type ServerConf = { export type ServerConf = {
connectionHandler?: ConnectionHandler connectionHandler?: ConnectionHandler
@@ -65,7 +67,7 @@ export type BaseInfo = {
export type HookInfo<SubresT = {}> = BaseInfo & { export type HookInfo<SubresT = {}> = BaseInfo & {
type: 'Hook', type: 'Hook',
generator: (socket?:I.Socket) => (...args:any) => SubresT generator: (socket?:I.Socket) => (...args:any[]) => SubresT
} }
export type CallInfo = BaseInfo & { export type CallInfo = BaseInfo & {
+18 -7
View File
@@ -10,7 +10,8 @@ import { SubscriptionResponse } from "./Types";
* @param owner The owning RPC group's name * @param owner The owning RPC group's name
* @throws Error on RPC without name property * @throws Error on RPC without name property
*/ */
export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string, sesame?:string):T.RpcInfo => { export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string, sesame?:T.SesameFunction):T.RpcInfo => {
switch (typeof rpc){ switch (typeof rpc){
case "object": case "object":
if(rpc['call']){ if(rpc['call']){
@@ -19,7 +20,7 @@ export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner
argNames: extractArgs(rpc['call']), argNames: extractArgs(rpc['call']),
type: "Call", type: "Call",
name: rpc.name, 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{ }else{
const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, sesame) const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, sesame)
@@ -44,7 +45,7 @@ RPC did not provide a name.
argNames: extractArgs(rpc), argNames: extractArgs(rpc),
type: "Call", type: "Call",
name: rpc.name, name: rpc.name,
call: async(...args) => (<Function>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) throw new Error("Bad socketIORPC type "+ typeof rpc)
@@ -56,7 +57,7 @@ RPC did not provide a name.
* @param exporter The exporter * @param exporter The exporter
* @param makeUnique @default true Attach a suffix to RPC names * @param makeUnique @default true Attach a suffix to RPC names
*/ */
export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, sesame?:string, makeUnique = true):T.ExtendedRpcInfo[]{ export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
const owner = exporter.name const owner = exporter.name
const RPCs = [...exporter.exportRPCs()] const RPCs = [...exporter.exportRPCs()]
@@ -85,15 +86,15 @@ export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter
* @param rpc The RPC to transform * @param rpc The RPC to transform
* @returns A {@link HookFunction} * @returns A {@link HookFunction}
*/ */
const hookGenerator = (rpc:T.HookRPC<any, any, any>, sesame?:string): T.HookInfo['generator'] => { const hookGenerator = (rpc:T.HookRPC<any, any, any>, sesame?:T.SesameFunction): T.HookInfo['generator'] => {
const argsArr = extractArgs(rpc.hook) const argsArr = extractArgs(rpc.hook)
if(sesame){ if(sesame){
const _sesame = argsArr.shift() const _sesame = argsArr.shift()
if(sesame !== _sesame){ if(!sesame(_sesame!)){
throw new Error('Bad sesame') throw new Error('Bad sesame')
} }
} }
argsArr.pop() argsArr.pop() //remove 'callback' from the end
const args = argsArr.join(',') const args = argsArr.join(',')
return eval(`(socket) => async (`+args+`) => { return eval(`(socket) => async (`+args+`) => {
@@ -131,4 +132,14 @@ export function makeSubResponse<T extends {} = {}>(extension:T):SubscriptionResp
uuid: uuidv4(), uuid: uuidv4(),
...extension ...extension
} }
}
export function makeSesameFunction (sesame : T.SesameFunction | string) : T.SesameFunction {
if(typeof sesame === 'function'){
return sesame
}
return (testSesame : string) => {
return testSesame === sesame
}
} }
+43
View File
@@ -223,4 +223,47 @@ describe('It should do unhook', () => {
done(new Error("Results did not match: "+[r1,r2,r3,r4])) done(new Error("Results did not match: "+[r1,r2,r3,r4]))
}) })
}) })
})
type SesameTestIfc = { test: { checkCandy: ()=>Promise<string>} }
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<string> { return candy },
]}
],{
sesame: (_sesame) => _sesame === 'sesame!'
})
const sock = new RPCSocket(20004, "localhost")
client = await sock.connect<SesameTestIfc>('sesame!')
})
after(() => {
client.destroy()
server.destroy()
})
it('should not work without sesame', (done) => {
const sock = new RPCSocket(20004, "localhost")
sock.connect<SesameTestIfc>( /* 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())
})
}) })