0.2.19
register authenticator added to consumer and socket default consumer authenticator looks for token in environment or opts or sets to 'default' default socket authenticator looks for token validator method add and remove token methods default token validator method just checks sent token against list can register alt token validator improved authenticate function to handle fail reason and anonymous connect
This commit is contained in:
parent
3299e9125e
commit
8078831971
4 changed files with 85 additions and 24 deletions
|
@ -37,7 +37,9 @@ class Test extends Socket {
|
|||
// // ca: [ fs.readFileSync('client-cert.pem') ]
|
||||
// }
|
||||
|
||||
let options = {path:true, tokens:['test'], conPacket:{cmd:'onconnect', data:'this is a packet data sent consumer after handshake/authentification'}}
|
||||
let options = {path:true}
|
||||
// options.conPacket = {cmd:'onconnect', data:'this is a packet data sent consumer after handshake/authentification'}
|
||||
options.tokens = ['cheetos']
|
||||
let test = new Test(options)
|
||||
|
||||
async function processor (packet) {
|
||||
|
@ -64,7 +66,7 @@ test.registerPacketProcessor(processor)
|
|||
// console.log('using TLS')
|
||||
// }
|
||||
|
||||
|
||||
// test.addTokens('cheetos')
|
||||
await test.create()
|
||||
|
||||
let count = 0
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@uci/socket",
|
||||
"version": "0.2.18",
|
||||
"version": "0.2.19",
|
||||
"description": "JSON packet intra(named)/inter(TCP) host communication over socket",
|
||||
"main": "src",
|
||||
"scripts": {
|
||||
|
@ -10,9 +10,11 @@
|
|||
"testci": "istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -R spec --recursive && codecov || true",
|
||||
"s": "node -r esm examples/server",
|
||||
"devs": "UCI_ENV=dev ./node_modules/.bin/nodemon -r esm examples/server",
|
||||
"devs:anon": "UCI_ANON=true npm run devs",
|
||||
"devs:debug": "UCI_LOG_LEVEL=debug npm run devs",
|
||||
"client": "=node -r esm examples/client",
|
||||
"devc": "UCI_ENV=dev ./node_modules/.bin/nodemon -r esm examples/client",
|
||||
"devc:token": "UCI_CLIENT_TOKEN='cheetos' npm run devc",
|
||||
"devc:debug": "UCI_LOG_LEVEL=debug npm run devc",
|
||||
"c2": "node -r esm examples/client2"
|
||||
},
|
||||
|
|
|
@ -93,13 +93,14 @@ class SocketConsumer extends Socket {
|
|||
if (packet._handshake) {
|
||||
clearTimeout(initTimeout)
|
||||
this._connected = true
|
||||
let authPacket = this._authenticate()
|
||||
let authPacket = this._authenticate() || {}
|
||||
authPacket._authenticate = true
|
||||
authPacket.clientName = this.id
|
||||
let res = (await this._authenticateSend(authPacket)) || {}
|
||||
console.log('auth response', res)
|
||||
// if ((await this.send(authPacket)).authenticated) authenticated()
|
||||
if (!res.authenticated) reject('unable to authenticate')
|
||||
if (!res.authenticated) {
|
||||
this.emit('connection',{msg:`authentication failed: ${res.reason}`, id:this.id, opts:this.opts, authenticated:this._authenticated, connected:this._connected})
|
||||
reject('unable to authenticate')
|
||||
}
|
||||
else {
|
||||
this._authenticated = res.authenticated
|
||||
this.removeListener('error',initialErrorHandler)
|
||||
|
@ -153,9 +154,7 @@ class SocketConsumer extends Socket {
|
|||
if (err)
|
||||
resolve({error: 'unable to serialize packet for sending',packet: packet})
|
||||
await this.__write(res)
|
||||
console.log(packet._header)
|
||||
this.once(packet._header.id, async function(reply) {
|
||||
console.log('returning response', reply)
|
||||
let res = await this._packetProcess(reply)
|
||||
if (!res) { // if packetProcess was not promise
|
||||
res = reply
|
||||
|
@ -172,12 +171,19 @@ class SocketConsumer extends Socket {
|
|||
this._packetProcess = func
|
||||
}
|
||||
|
||||
// PRIVATE METHODS
|
||||
_authenticate () {
|
||||
return { token:'test'}
|
||||
// func should return an object the server expects
|
||||
registerAuthenticator (func) {
|
||||
this._authenticate = func
|
||||
}
|
||||
|
||||
async _authenticateSend (authPacket) {
|
||||
// PRIVATE METHODS
|
||||
|
||||
// default authentication using a simple token
|
||||
_authenticate () {
|
||||
return { token: process.env.UCI_CLIENT_TOKEN || this.token || 'default' }
|
||||
}
|
||||
|
||||
async _authenticateSend (authPacket={}) {
|
||||
return new Promise(async resolve => {
|
||||
setTimeout(() => {resolve({ error: 'no response from socket in 10sec' })}, 10000)
|
||||
let [err, res] = await btc(this.stream.serialize)(authPacket)
|
||||
|
@ -206,13 +212,13 @@ class SocketConsumer extends Socket {
|
|||
const handshake = async (packet) => {
|
||||
if (packet._handshake) {
|
||||
this._connected = true
|
||||
let authPacket = this._authenticate()
|
||||
let authPacket = this._authenticate() || {}
|
||||
authPacket._authenticate = true
|
||||
authPacket.clientName = this.id
|
||||
let res = (await this._authenticateSend(authPacket)) || {}
|
||||
if (!res.authenticated) {
|
||||
this.emit('connection',{msg:'authentification failed', id:this.id, opts:this.opts, authenticated:this._authenticated, connected:this._connected, res:res})
|
||||
|
||||
this.emit('connection',{msg:`authentication failed: ${res.reason}`, id:this.id, opts:this.opts, authenticated:this._authenticated, connected:this._connected})
|
||||
this.emit('error',{code:'authentification failed'})
|
||||
}
|
||||
else {
|
||||
this._authenticated = res.authenticated
|
||||
|
|
|
@ -57,7 +57,7 @@ export default function socketClass(Server) {
|
|||
if (path.dirname(opts.path) === '.') // relative path sent
|
||||
opts.path = path.join(DEFAULT_PIPE_DIR, opts.path)
|
||||
}
|
||||
this.allowAnonymous = process.env.UCI_ANON || opts.allowAnonymous || false
|
||||
this.allowAnonymous = (!opts.tokens || !!process.env.UCI_ANON || opts.allowAnonymous) ? true : false
|
||||
this.tokens = opts.tokens || []
|
||||
this.keepAlive = 'keepAlive' in opts ? opts.keepAlive : true
|
||||
this.pingInterval = opts.pingInterval === false ? opts.pingInterval : (opts.pingInterval * 1000 || 5000)
|
||||
|
@ -150,6 +150,41 @@ export default function socketClass(Server) {
|
|||
clearInterval(this._ping)
|
||||
}
|
||||
|
||||
addTokens(tokens) {
|
||||
if (typeof tokens ==='string'){
|
||||
tokens = tokens.split(',')
|
||||
}
|
||||
this.tokens = this.tokens.concat(tokens)
|
||||
if (this.tokens.length>0) this.allowAnonymous = false
|
||||
}
|
||||
|
||||
removeTokens(tokens) {
|
||||
if (typeof tokens ==='string'){
|
||||
if (tokens === 'all') {
|
||||
this.tokens = []
|
||||
this.allowAnonymous = true
|
||||
return
|
||||
}
|
||||
tokens = tokens.split(',')
|
||||
}
|
||||
this.tokens = this.tokens.filter(token => !tokens.includes(token))
|
||||
if (this.tokens.length===0) {
|
||||
log.warn({msg:'all tokens have been removed, switching to allow anonymous connections'})
|
||||
this.allowAnonymous = true
|
||||
}
|
||||
}
|
||||
|
||||
registerTokenValidator (func) {
|
||||
this.allowAnonymous = false
|
||||
this._validateToken = func
|
||||
}
|
||||
|
||||
registerAuthenticator (func) {
|
||||
this.allowAnonymous = false
|
||||
this._authenticate = func
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* push - pushes a supplied UCI object packet to all connected clients
|
||||
*
|
||||
|
@ -193,17 +228,26 @@ export default function socketClass(Server) {
|
|||
}
|
||||
|
||||
async function authenticate (client,packet) {
|
||||
log.info({msg:`authentication packet from client ${client.id}`, packet:packet})
|
||||
log.debug({msg:`authentication packet from client ${client.id}`, packet:packet})
|
||||
client.stream.removeAllListeners('message')
|
||||
if (!packet._authenticate) reject('first client packet was not authentication')
|
||||
else {
|
||||
let [err, res] = await btc(this._authenticate)(packet)
|
||||
client.authenticated = this.allowAnonymous ? 'anonymous' : (err || res)
|
||||
client.authenticated = this.allowAnonymous ? 'anonymous' : (err ? false : res)
|
||||
client.name = packet.clientName
|
||||
packet.authenticated = client.authenticated
|
||||
packet.reason = err || null
|
||||
log.debug({msg:'sending authorization result to client', packet:packet})
|
||||
await this._send(client,packet) // send either way
|
||||
if (err && !this.allowAnonymous) reject(client.authenticated)
|
||||
else resolve(client.authenticated)
|
||||
if (err && !this.allowAnonymous) {
|
||||
log.info({msg:'client authentication failed', client:client.name, client_id:client.id, reason:err})
|
||||
reject(packet.reason)
|
||||
}
|
||||
else {
|
||||
log.info({msg:'client authenticated successfuly', client:client.name, client_id:client.id})
|
||||
if (this.allowAnonymous) log.warn({msg:'client connected anonymously', client:client.name, client_id:client.id})
|
||||
resolve(client.authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.bind(this))
|
||||
|
@ -211,9 +255,16 @@ export default function socketClass(Server) {
|
|||
|
||||
// private methods
|
||||
|
||||
// default authenticator
|
||||
|
||||
// default validator
|
||||
_validateToken (token) {
|
||||
if (token) return this.tokens.includes(token)
|
||||
return false
|
||||
}
|
||||
|
||||
// default authenticator - reject value should be reason which is returned to client
|
||||
async _authenticate (packet) {
|
||||
if (this.tokens.indexOf(packet.token) === -1) return Promise.reject(false)
|
||||
if (!this._validateToken(packet.token)) return Promise.reject('invalid token')
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -271,7 +322,7 @@ export default function socketClass(Server) {
|
|||
// that's it. Connection is active
|
||||
|
||||
async function messageProcess(client, packet) {
|
||||
log.info({method:'_listen', line:179, packet: packet, client:client.name, msg:'incoming packet on socket side'})
|
||||
log.debug({method:'_listen', line:179, packet: packet, client:client.name, msg:'incoming packet on socket side'})
|
||||
let res = (await this._packetProcess(clone(packet))) || {}
|
||||
if (Object.keys(res).length === 0)
|
||||
res = {
|
||||
|
|
Loading…
Reference in a new issue