implemented auth utilities for yjs

This commit is contained in:
Kevin Jahns 2016-10-24 11:57:59 +02:00
parent 666ab8285c
commit 0521fac8d8
2 changed files with 143 additions and 98 deletions

View File

@ -1,6 +1,9 @@
/* @flow */ /* @flow */
'use strict' 'use strict'
function canRead (auth) { return auth === 'read' || auth === 'write' }
function canWrite (auth) { return auth === 'write' }
module.exports = function (Y/* :any */) { module.exports = function (Y/* :any */) {
class AbstractConnector { class AbstractConnector {
/* :: /* ::
@ -54,6 +57,8 @@ module.exports = function (Y/* :any */) {
this.syncStep2 = Promise.resolve() this.syncStep2 = Promise.resolve()
this.broadcastOpBuffer = [] this.broadcastOpBuffer = []
this.protocolVersion = 11 this.protocolVersion = 11
this.authInfo = opts.auth || null
this.checkAuth = opts.checkAuth || function () { return Promise.resolve('write') } // default is everyone has write access
} }
reconnect () { reconnect () {
} }
@ -87,6 +92,9 @@ module.exports = function (Y/* :any */) {
onUserEvent (f) { onUserEvent (f) {
this.userEventListeners.push(f) this.userEventListeners.push(f)
} }
removeUserEventListener (f) {
this.userEventListeners = this.userEventListeners.filter(g => { f !== g })
}
userLeft (user) { userLeft (user) {
if (this.connections[user] != null) { if (this.connections[user] != null) {
delete this.connections[user] delete this.connections[user]
@ -163,7 +171,8 @@ module.exports = function (Y/* :any */) {
type: 'sync step 1', type: 'sync step 1',
stateSet: stateSet, stateSet: stateSet,
deleteSet: deleteSet, deleteSet: deleteSet,
protocolVersion: conn.protocolVersion protocolVersion: conn.protocolVersion,
auth: conn.authInfo
}) })
}) })
} else { } else {
@ -217,7 +226,7 @@ module.exports = function (Y/* :any */) {
*/ */
receiveMessage (sender/* :UserId */, message/* :Message */) { receiveMessage (sender/* :UserId */, message/* :Message */) {
if (sender === this.userId) { if (sender === this.userId) {
return return Promise.resolve()
} }
if (this.debug) { if (this.debug) {
console.log(`receive ${sender} -> ${this.userId}: ${message.type}`, JSON.parse(JSON.stringify(message))) // eslint-disable-line console.log(`receive ${sender} -> ${this.userId}: ${message.type}`, JSON.parse(JSON.stringify(message))) // eslint-disable-line
@ -232,14 +241,36 @@ module.exports = function (Y/* :any */) {
type: 'sync stop', type: 'sync stop',
protocolVersion: this.protocolVersion protocolVersion: this.protocolVersion
}) })
return return Promise.reject('Incompatible protocol version')
} }
if (message.type === 'sync step 1') { if (message.auth != null && this.connections[sender] != null) {
// authenticate using auth in message
var auth = this.checkAuth(message.auth, this.y)
this.connections[sender].auth = auth
auth.then(auth => {
for (var f of this.userEventListeners) {
f({
action: 'userAuthenticated',
user: sender,
auth: auth
})
}
})
} else if (this.connections[sender] != null && this.connections[sender].auth == null) {
// authenticate without otherwise
this.connections[sender].auth = this.checkAuth(null, this.y)
}
if (this.connections[sender] != null && this.connections[sender].auth != null) {
return this.connections[sender].auth.then((auth) => {
if (message.type === 'sync step 1' && canRead(auth)) {
let conn = this let conn = this
let m = message let m = message
this.y.db.requestTransaction(function *() { this.y.db.requestTransaction(function *() {
var currentStateSet = yield* this.getStateSet() var currentStateSet = yield* this.getStateSet()
if (canWrite(auth)) {
yield* this.applyDeleteSet(m.deleteSet) yield* this.applyDeleteSet(m.deleteSet)
}
var ds = yield* this.getDeleteSet() var ds = yield* this.getDeleteSet()
var ops = yield* this.getOperations(m.stateSet) var ops = yield* this.getOperations(m.stateSet)
@ -248,7 +279,8 @@ module.exports = function (Y/* :any */) {
os: ops, os: ops,
stateSet: currentStateSet, stateSet: currentStateSet,
deleteSet: ds, deleteSet: ds,
protocolVersion: this.protocolVersion protocolVersion: this.protocolVersion,
auth: this.authInfo
}) })
if (this.forwardToSyncingClients) { if (this.forwardToSyncingClients) {
conn.syncingClients.push(sender) conn.syncingClients.push(sender)
@ -267,7 +299,7 @@ module.exports = function (Y/* :any */) {
} }
conn._setSyncedWith(sender) conn._setSyncedWith(sender)
}) })
} else if (message.type === 'sync step 2') { } else if (message.type === 'sync step 2' && canWrite(auth)) {
let conn = this let conn = this
var broadcastHB = !this.broadcastedHB var broadcastHB = !this.broadcastedHB
this.broadcastedHB = true this.broadcastedHB = true
@ -302,7 +334,7 @@ module.exports = function (Y/* :any */) {
this.syncStep2.then(function () { this.syncStep2.then(function () {
self._setSyncedWith(sender) self._setSyncedWith(sender)
}) })
} else if (message.type === 'update') { } else if (message.type === 'update' && canWrite(auth)) {
if (this.forwardToSyncingClients) { if (this.forwardToSyncingClients) {
for (var client of this.syncingClients) { for (var client of this.syncingClients) {
this.send(client, message) this.send(client, message)
@ -318,6 +350,10 @@ module.exports = function (Y/* :any */) {
} }
this.y.db.apply(message.ops) this.y.db.apply(message.ops)
} }
})
} else {
return Promise.reject('Unable to deliver message')
}
} }
_setSyncedWith (user) { _setSyncedWith (user) {
var conn = this.connections[user] var conn = this.connections[user]

View File

@ -24,11 +24,21 @@ module.exports = function (Y) {
} }
}, },
whenTransactionsFinished: function () { whenTransactionsFinished: function () {
var self = this
return new Promise (function (resolve, reject) {
// The connector first has to send the messages to the db.
// Wait for the checkAuth-function to resolve
// The test lib only has a simple checkAuth function: `() => Promise.resolve()`
// Just add a function to the event-queue, in order to wait for the event.
// TODO: this may be buggy in test applications (but it isn't be for real-life apps)
setTimeout(function () {
var ps = [] var ps = []
for (var name in this.users) { for (var name in self.users) {
ps.push(this.users[name].y.db.whenTransactionsFinished()) ps.push(self.users[name].y.db.whenTransactionsFinished())
} }
return Promise.all(ps) Promise.all(ps).then(resolve, reject)
}, 0)
})
}, },
flushOne: function flushOne () { flushOne: function flushOne () {
var bufs = [] var bufs = []
@ -54,8 +64,9 @@ module.exports = function (Y) {
delete buff[sender] delete buff[sender]
} }
var user = globalRoom.users[userId] var user = globalRoom.users[userId]
user.receiveMessage(m[0], m[1]) return user.receiveMessage(m[0], m[1]).then(function () {
return user.y.db.whenTransactionsFinished() return user.y.db.whenTransactionsFinished()
}, function () {})
} else { } else {
return false return false
} }
@ -72,7 +83,6 @@ module.exports = function (Y) {
} }
globalRoom.whenTransactionsFinished().then(nextFlush) globalRoom.whenTransactionsFinished().then(nextFlush)
} else { } else {
setTimeout(function () {
var c = globalRoom.flushOne() var c = globalRoom.flushOne()
if (c) { if (c) {
c.then(function () { c.then(function () {
@ -81,7 +91,6 @@ module.exports = function (Y) {
} else { } else {
resolve() resolve()
} }
}, 0)
} }
} }
globalRoom.whenTransactionsFinished().then(nextFlush) globalRoom.whenTransactionsFinished().then(nextFlush)
@ -107,7 +116,7 @@ module.exports = function (Y) {
this.syncingClientDuration = 0 this.syncingClientDuration = 0
} }
receiveMessage (sender, m) { receiveMessage (sender, m) {
super.receiveMessage(sender, JSON.parse(JSON.stringify(m))) return super.receiveMessage(sender, JSON.parse(JSON.stringify(m)))
} }
send (userId, message) { send (userId, message) {
var buffer = globalRoom.buffers[userId] var buffer = globalRoom.buffers[userId]
@ -154,7 +163,7 @@ module.exports = function (Y) {
if (buff[sender].length === 0) { if (buff[sender].length === 0) {
delete buff[sender] delete buff[sender]
} }
this.receiveMessage(m[0], m[1]) yield this.receiveMessage(m[0], m[1])
} }
yield self.whenTransactionsFinished() yield self.whenTransactionsFinished()
}) })