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:
David Kebler 2019-08-28 09:02:12 -07:00
parent 3299e9125e
commit 8078831971
4 changed files with 85 additions and 24 deletions

View file

@ -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

View file

@ -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"
},

View file

@ -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

View file

@ -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 = {