@ -15,10 +15,11 @@ define([
'/bower_components/chainpad-netflux/chainpad-netflux.js' ,
'/bower_components/chainpad-netflux/chainpad-netflux.js' ,
'/bower_components/chainpad/chainpad.dist.js' ,
'/bower_components/chainpad/chainpad.dist.js' ,
'/bower_components/nthen/index.js' ,
'/bower_components/nthen/index.js' ,
'/bower_components/saferphore/index.js' ,
'/bower_components/tweetnacl/nacl-fast.min.js' ,
'/bower_components/tweetnacl/nacl-fast.min.js' ,
] , function ( Util , Hash , Constants , Realtime ,
] , function ( Util , Hash , Constants , Realtime ,
ProxyManager , UserObject , SF , Roster , Messaging ,
ProxyManager , UserObject , SF , Roster , Messaging ,
Listmap , Crypto , CpNetflux , ChainPad , nThen ) {
Listmap , Crypto , CpNetflux , ChainPad , nThen , Saferphore ) {
var Team = { } ;
var Team = { } ;
var Nacl = window . nacl ;
var Nacl = window . nacl ;
@ -73,8 +74,8 @@ define([
var closeTeam = function ( ctx , teamId ) {
var closeTeam = function ( ctx , teamId ) {
var team = ctx . teams [ teamId ] ;
var team = ctx . teams [ teamId ] ;
if ( ! team ) { return ; }
if ( ! team ) { return ; }
team . listmap . stop ( ) ;
try { team . listmap . stop ( ) ; } catch ( e ) { }
team . roster . stop ( ) ;
try { team . roster . stop ( ) ; } catch ( e ) { }
team . proxy = { } ;
team . proxy = { } ;
delete ctx . teams [ teamId ] ;
delete ctx . teams [ teamId ] ;
delete ctx . store . proxy . teams [ teamId ] ;
delete ctx . store . proxy . teams [ teamId ] ;
@ -99,18 +100,6 @@ define([
if ( membersChannel ) { list . push ( membersChannel ) ; }
if ( membersChannel ) { list . push ( membersChannel ) ; }
if ( mailboxChannel ) { list . push ( mailboxChannel ) ; }
if ( mailboxChannel ) { list . push ( mailboxChannel ) ; }
// XXX Add the team mailbox
/ *
if ( store . proxy . mailboxes ) {
var mList = Object . keys ( store . proxy . mailboxes ) . map ( function ( m ) {
return store . proxy . mailboxes [ m ] . channel ;
} ) ;
list = list . concat ( mList ) ;
}
* /
list . sort ( ) ;
list . sort ( ) ;
return list ;
return list ;
} ;
} ;
@ -185,7 +174,6 @@ define([
channel : secret . channel ,
channel : secret . channel ,
secret : secret ,
secret : secret ,
validateKey : secret . keys . validateKey
validateKey : secret . keys . validateKey
// XXX owners: team owner + all admins?
} ;
} ;
} ;
} ;
@ -290,7 +278,9 @@ define([
} ;
} ;
var openChannel = function ( ctx , teamData , id , cb ) {
var openChannel = function ( ctx , teamData , id , _cb ) {
var cb = Util . once ( _cb ) ;
var secret = Hash . getSecrets ( 'team' , teamData . hash , teamData . password ) ;
var secret = Hash . getSecrets ( 'team' , teamData . hash , teamData . password ) ;
var crypto = Crypto . createEncryptor ( secret . keys ) ;
var crypto = Crypto . createEncryptor ( secret . keys ) ;
@ -298,7 +288,34 @@ define([
var roster ;
var roster ;
var lm ;
var lm ;
// Roster keys
var myKeys = {
curvePublic : ctx . store . proxy . curvePublic ,
curvePrivate : ctx . store . proxy . curvePrivate
} ;
var rosterData = keys . roster || { } ;
var rosterKeys = rosterData . edit ? Crypto . Team . deriveMemberKeys ( rosterData . edit , myKeys )
: Crypto . Team . deriveGuestKeys ( rosterData . view || '' ) ;
nThen ( function ( waitFor ) {
nThen ( function ( waitFor ) {
ctx . store . anon _rpc . send ( "IS_NEW_CHANNEL" , secret . channel , waitFor ( function ( e , response ) {
if ( response && response . length && typeof ( response [ 0 ] ) === 'boolean' && response [ 0 ] ) {
// Channel is empty: remove this team
delete ctx . store . proxy . teams [ id ] ;
waitFor . abort ( ) ;
cb ( { error : 'ENOENT' } ) ;
}
} ) ) ;
ctx . store . anon _rpc . send ( "IS_NEW_CHANNEL" , rosterKeys . channel , waitFor ( function ( e , response ) {
if ( response && response . length && typeof ( response [ 0 ] ) === 'boolean' && response [ 0 ] ) {
// Channel is empty: remove this team
delete ctx . store . proxy . teams [ id ] ;
waitFor . abort ( ) ;
cb ( { error : 'ENOENT' } ) ;
}
} ) ) ;
} ) . nThen ( function ( waitFor ) {
// Load the proxy
// Load the proxy
var cfg = {
var cfg = {
data : { } ,
data : { } ,
@ -313,17 +330,25 @@ define([
userName : 'team' ,
userName : 'team' ,
classic : true
classic : true
} ;
} ;
cfg . onMetadataUpdate = function ( ) {
var team = ctx . teams [ id ] ;
if ( ! team ) { return ; }
ctx . emit ( 'ROSTER_CHANGE' , id , team . clients ) ;
} ;
lm = Listmap . create ( cfg ) ;
lm = Listmap . create ( cfg ) ;
lm . proxy . on ( 'ready' , waitFor ( ) ) ;
lm . proxy . on ( 'ready' , waitFor ( ) ) ;
lm . proxy . on ( 'error' , function ( info ) {
if ( info && typeof ( info . loaded ) !== "undefined" && ! info . loaded ) {
cb ( { error : 'ECONNECT' } ) ;
}
if ( info && info . error ) {
if ( info . error === "EDELETED" ) {
closeTeam ( ctx , id ) ;
}
}
} ) ;
// Load the roster
// Load the roster
var myKeys = {
curvePublic : ctx . store . proxy . curvePublic ,
curvePrivate : ctx . store . proxy . curvePrivate
} ;
var rosterData = keys . roster || { } ;
var rosterKeys = rosterData . edit ? Crypto . Team . deriveMemberKeys ( rosterData . edit , myKeys )
: Crypto . Team . deriveGuestKeys ( rosterData . view || '' ) ;
Roster . create ( {
Roster . create ( {
network : ctx . store . network ,
network : ctx . store . network ,
channel : rosterKeys . channel ,
channel : rosterKeys . channel ,
@ -462,10 +487,15 @@ define([
}
}
} ) ) ;
} ) ) ;
} ) . nThen ( function ( ) {
} ) . nThen ( function ( ) {
var id = Util . createRandomInteger ( ) ;
config . onMetadataUpdate = function ( ) {
var team = ctx . teams [ id ] ;
if ( ! team ) { return ; }
ctx . emit ( 'ROSTER_CHANGE' , id , team . clients ) ;
} ;
var lm = Listmap . create ( config ) ;
var lm = Listmap . create ( config ) ;
var proxy = lm . proxy ;
var proxy = lm . proxy ;
proxy . on ( 'ready' , function ( ) {
proxy . on ( 'ready' , function ( ) {
var id = Util . createRandomInteger ( ) ;
// Store keys in our drive
// Store keys in our drive
var keys = {
var keys = {
drive : {
drive : {
@ -505,10 +535,91 @@ define([
if ( info && typeof ( info . loaded ) !== "undefined" && ! info . loaded ) {
if ( info && typeof ( info . loaded ) !== "undefined" && ! info . loaded ) {
cb ( { error : 'ECONNECT' } ) ;
cb ( { error : 'ECONNECT' } ) ;
}
}
if ( info && info . error ) {
if ( info . error === "EDELETED" ) {
closeTeam ( ctx , id ) ;
}
}
} ) ;
} ) ;
} ) ;
} ) ;
} ;
} ;
var deleteTeam = function ( ctx , data , cId , cb ) {
var teamId = data . teamId ;
if ( ! teamId ) { return void cb ( { error : 'EINVAL' } ) ; }
var team = ctx . teams [ teamId ] ;
var teamData = Util . find ( ctx , [ 'store' , 'proxy' , 'teams' , teamId ] ) ;
if ( ! team || ! teamData ) { return void cb ( { error : 'ENOENT' } ) ; }
var state = team . roster . getState ( ) ;
var curvePublic = Util . find ( ctx , [ 'store' , 'proxy' , 'curvePublic' ] ) ;
var me = state . members [ curvePublic ] ;
if ( ! me || me . role !== "OWNER" ) { return cb ( { error : "EFORBIDDEN" } ) ; }
var edPublic = Util . find ( ctx , [ 'store' , 'proxy' , 'edPublic' ] ) ;
nThen ( function ( waitFor ) {
ctx . Store . anonRpcMsg ( null , {
msg : 'GET_METADATA' ,
data : teamData . channel
} , waitFor ( function ( obj ) {
// If we can't get owners, abort
if ( obj && obj . error ) {
waitFor . abort ( ) ;
return cb ( { error : obj . error } ) ;
}
// Check if we're an owner of the team drive
var metadata = obj [ 0 ] ;
if ( metadata && Array . isArray ( metadata . owners ) &&
metadata . owners . indexOf ( edPublic ) !== - 1 ) { return ; }
// If w'e're not an owner, abort
waitFor . abort ( ) ;
cb ( { error : 'EFORBIDDEN' } ) ;
} ) ) ;
} ) . nThen ( function ( waitFor ) {
team . proxy . delete = true ;
// Delete the owned pads
var ownedPads = team . manager . getChannelsList ( 'owned' ) ;
var sem = Saferphore . create ( 10 ) ;
ownedPads . forEach ( function ( c ) {
var w = waitFor ( ) ;
sem . take ( function ( give ) {
team . rpc . removeOwnedChannel ( c , give ( function ( err ) {
if ( err ) { console . error ( err ) ; }
w ( ) ;
} ) ) ;
} ) ;
} ) ;
} ) . nThen ( function ( waitFor ) {
// Delete the pins log
team . rpc . removePins ( waitFor ( function ( err ) {
if ( err ) { console . error ( err ) ; }
console . error ( err ) ;
} ) ) ;
// Delete the roster
var rosterChan = Util . find ( teamData , [ 'keys' , 'roster' , 'channel' ] ) ;
ctx . store . rpc . removeOwnedChannel ( rosterChan , waitFor ( function ( err ) {
if ( err ) { console . error ( err ) ; }
console . error ( err ) ;
} ) ) ;
// Delete the chat
var chatChan = Util . find ( teamData , [ 'keys' , 'chat' , 'channel' ] ) ;
/ *
ctx . store . rpc . removeOwnedChannel ( chatChan , waitFor ( function ( err ) {
if ( err ) { console . error ( err ) ; }
console . error ( err ) ;
} ) ) ;
* / / / XXX
// Delete the team drive
ctx . store . rpc . removeOwnedChannel ( teamData . channel , waitFor ( function ( err ) {
if ( err ) { console . error ( err ) ; }
console . error ( err ) ;
} ) ) ;
} ) . nThen ( function ( ) {
cb ( ) ;
closeTeam ( ctx , teamId ) ;
} ) ;
} ;
var joinTeam = function ( ctx , data , cId , cb ) {
var joinTeam = function ( ctx , data , cId , cb ) {
var team = data . team ;
var team = data . team ;
if ( ! team . hash || ! team . channel || ! team . password
if ( ! team . hash || ! team . channel || ! team . password
@ -540,12 +651,43 @@ define([
var getTeamRoster = function ( ctx , data , cId , cb ) {
var getTeamRoster = function ( ctx , data , cId , cb ) {
var teamId = data . teamId ;
var teamId = data . teamId ;
if ( ! teamId ) { return void cb ( { error : 'EINVAL' } ) ; }
if ( ! teamId ) { return void cb ( { error : 'EINVAL' } ) ; }
var teamData = Util . find ( ctx , [ 'store' , 'proxy' , 'teams' , teamId ] ) ;
if ( ! teamData ) { return void cb ( { error : 'ENOENT' } ) ; }
var team = ctx . teams [ teamId ] ;
var team = ctx . teams [ teamId ] ;
if ( ! team ) { return void cb ( { error : 'ENOENT' } ) ; }
if ( ! team ) { return void cb ( { error : 'ENOENT' } ) ; }
if ( ! team . roster ) { return void cb ( { error : 'NO_ROSTER' } ) ; }
if ( ! team . roster ) { return void cb ( { error : 'NO_ROSTER' } ) ; }
var state = team . roster . getState ( ) || { } ;
var state = team . roster . getState ( ) || { } ;
var members = state . members || { } ;
var members = state . members || { } ;
// Get pending owners
var md = team . listmap . metadata || { } ;
if ( Array . isArray ( md . pending _owners ) ) {
// Get the members associated to the pending_owners' edPublic and mark them as such
md . pending _owners . forEach ( function ( ed ) {
var member ;
Object . keys ( members ) . some ( function ( curve ) {
if ( members [ curve ] . edPublic === ed ) {
member = members [ curve ] ;
return true ;
}
} ) ;
if ( ( ! member || member . role !== 'OWNER' ) && teamData . owner ) {
var removeOwnership = function ( chan ) {
ctx . Store . setPadMetadata ( null , {
channel : chan ,
command : 'RM_PENDING_OWNERS' ,
value : [ ed ] ,
} , function ( ) { } ) ;
} ;
removeOwnership ( teamData . channel ) ;
removeOwnership ( Util . find ( teamData , [ 'keys' , 'roster' , 'channel' ] ) ) ;
removeOwnership ( Util . find ( teamData , [ 'keys' , 'chat' , 'channel' ] ) ) ;
return ;
}
member . pendingOwner = true ;
} ) ;
}
// Add online status (using messenger data)
// Add online status (using messenger data)
var chatData = team . getChatData ( ) ;
var chatData = team . getChatData ( ) ;
var online = ctx . store . messenger . getOnlineList ( chatData . channel ) || [ ] ;
var online = ctx . store . messenger . getOnlineList ( chatData . channel ) || [ ] ;
@ -584,6 +726,159 @@ define([
} ) ;
} ) ;
} ;
} ;
var offerOwnership = function ( ctx , data , cId , _cb ) {
var cb = Util . once ( _cb ) ;
var teamId = data . teamId ;
if ( ! teamId ) { return void cb ( { error : 'EINVAL' } ) ; }
var teamData = Util . find ( ctx , [ 'store' , 'proxy' , 'teams' , teamId ] ) ;
if ( ! teamData ) { return void cb ( { error : 'ENOENT' } ) ; }
var team = ctx . teams [ teamId ] ;
if ( ! team ) { return void cb ( { error : 'ENOENT' } ) ; }
if ( ! team . roster ) { return void cb ( { error : 'NO_ROSTER' } ) ; }
if ( ! data . curvePublic ) { return void cb ( { error : 'MISSING_DATA' } ) ; }
var state = team . roster . getState ( ) ;
var user = state . members [ data . curvePublic ] ;
nThen ( function ( waitFor ) {
// Offer ownership to a friend
var onError = function ( res ) {
var err = res && res . error ;
if ( err ) {
waitFor . abort ( ) ;
return void cb ( { error : err } ) ;
}
} ;
var addPendingOwner = function ( chan ) {
ctx . Store . setPadMetadata ( null , {
channel : chan ,
command : 'ADD_PENDING_OWNERS' ,
value : [ user . edPublic ] ,
} , waitFor ( onError ) ) ;
} ;
// Team proxy
addPendingOwner ( teamData . channel ) ;
// Team roster
addPendingOwner ( Util . find ( teamData , [ 'keys' , 'roster' , 'channel' ] ) ) ;
// Team chat
addPendingOwner ( Util . find ( teamData , [ 'keys' , 'chat' , 'channel' ] ) ) ;
} ) . nThen ( function ( waitFor ) {
var obj = { } ;
obj [ user . curvePublic ] = {
role : 'OWNER'
} ;
team . roster . describe ( obj , waitFor ( function ( err ) {
if ( err ) { console . error ( err ) ; }
} ) ) ;
} ) . nThen ( function ( waitFor ) {
// Send mailbox to offer ownership
var myData = Messaging . createData ( ctx . store . proxy , false ) ;
ctx . store . mailbox . sendTo ( "ADD_OWNER" , {
teamChannel : teamData . channel ,
chatChannel : Util . find ( teamData , [ 'keys' , 'chat' , 'channel' ] ) ,
rosterChannel : Util . find ( teamData , [ 'keys' , 'roster' , 'channel' ] ) ,
title : teamData . metadata . name ,
user : myData
} , {
channel : user . notifications ,
curvePublic : user . curvePublic
} , waitFor ( ) ) ;
} ) . nThen ( function ( ) {
cb ( ) ;
} ) ;
} ;
var revokeOwnership = function ( ctx , teamId , user , _cb ) {
var cb = Util . once ( _cb ) ;
if ( ! teamId ) { return void cb ( { error : 'EINVAL' } ) ; }
var teamData = Util . find ( ctx , [ 'store' , 'proxy' , 'teams' , teamId ] ) ;
if ( ! teamData ) { return void cb ( { error : 'ENOENT' } ) ; }
var team = ctx . teams [ teamId ] ;
if ( ! team ) { return void cb ( { error : 'ENOENT' } ) ; }
var md = team . listmap . metadata || { } ;
var isPendingOwner = ( md . pending _owners || [ ] ) . indexOf ( user . edPublic ) !== - 1 ;
nThen ( function ( waitFor ) {
var cmd = isPendingOwner ? 'RM_PENDING_OWNERS' : 'RM_OWNERS' ;
var onError = function ( res ) {
var err = res && res . error ;
if ( err ) {
waitFor . abort ( ) ;
return void cb ( err ) ;
}
} ;
var removeOwnership = function ( chan ) {
ctx . Store . setPadMetadata ( null , {
channel : chan ,
command : cmd ,
value : [ user . edPublic ] ,
} , waitFor ( onError ) ) ;
} ;
// Team proxy
removeOwnership ( teamData . channel ) ;
// Team roster
removeOwnership ( Util . find ( teamData , [ 'keys' , 'roster' , 'channel' ] ) ) ;
// Team chat
removeOwnership ( Util . find ( teamData , [ 'keys' , 'chat' , 'channel' ] ) ) ;
} ) . nThen ( function ( waitFor ) {
var obj = { } ;
obj [ user . curvePublic ] = {
role : 'ADMIN' ,
pendingOwner : false
} ;
team . roster . describe ( obj , waitFor ( function ( err ) {
if ( err ) { console . error ( err ) ; }
} ) ) ;
} ) . nThen ( function ( waitFor ) {
// Send mailbox to offer ownership
var myData = Messaging . createData ( ctx . store . proxy , false ) ;
ctx . store . mailbox . sendTo ( "RM_OWNER" , {
teamChannel : teamData . channel ,
title : teamData . metadata . name ,
pending : isPendingOwner ,
user : myData
} , {
channel : user . notifications ,
curvePublic : user . curvePublic
} , waitFor ( ) ) ;
} ) . nThen ( function ( ) {
cb ( ) ;
} ) ;
} ;
// We've received an offer to be an owner of the team.
// If we accept, we need to set the "owner" flag in our team data
// If we decline, we need to change our role back to "ADMIN"
var answerOwnership = function ( ctx , data , cId , cb ) {
var myTeams = ctx . store . proxy . teams ;
var teamId ;
Object . keys ( myTeams ) . forEach ( function ( id ) {
if ( myTeams [ id ] . channel === data . teamChannel ) {
teamId = id ;
return true ;
}
} ) ;
if ( ! teamId ) { return void cb ( { error : 'EINVAL' } ) ; }
var teamData = Util . find ( ctx , [ 'store' , 'proxy' , 'teams' , teamId ] ) ;
if ( ! teamData ) { return void cb ( { error : 'ENOENT' } ) ; }
var team = ctx . teams [ teamId ] ;
if ( ! team ) { return void cb ( { error : 'ENOENT' } ) ; }
if ( ! team . roster ) { return void cb ( { error : 'NO_ROSTER' } ) ; }
var obj = { } ;
// Accept
if ( data . answer ) {
teamData . owner = true ;
return ;
}
// Decline
obj [ ctx . store . proxy . curvePublic ] = {
role : 'ADMIN' ,
} ;
team . roster . describe ( obj , function ( err ) {
if ( err ) { return void cb ( { error : err } ) ; }
cb ( ) ;
} ) ;
} ;
var describeUser = function ( ctx , data , cId , cb ) {
var describeUser = function ( ctx , data , cId , cb ) {
var teamId = data . teamId ;
var teamId = data . teamId ;
if ( ! teamId ) { return void cb ( { error : 'EINVAL' } ) ; }
if ( ! teamId ) { return void cb ( { error : 'EINVAL' } ) ; }
@ -591,6 +886,19 @@ define([
if ( ! team ) { return void cb ( { error : 'ENOENT' } ) ; }
if ( ! team ) { return void cb ( { error : 'ENOENT' } ) ; }
if ( ! team . roster ) { return void cb ( { error : 'NO_ROSTER' } ) ; }
if ( ! team . roster ) { return void cb ( { error : 'NO_ROSTER' } ) ; }
if ( ! data . curvePublic || ! data . data ) { return void cb ( { error : 'MISSING_DATA' } ) ; }
if ( ! data . curvePublic || ! data . data ) { return void cb ( { error : 'MISSING_DATA' } ) ; }
var state = team . roster . getState ( ) ;
var user = state . members [ data . curvePublic ] ;
// It it is an ownership revocation, we have to set it in pad metadata first
if ( user . role === "OWNER" && data . data . role !== "OWNER" ) {
revokeOwnership ( ctx , teamId , user , function ( err ) {
if ( ! err ) { return ; }
console . error ( err ) ;
return void cb ( { error : err } ) ;
} ) ;
return ;
}
var obj = { } ;
var obj = { } ;
obj [ data . curvePublic ] = data . data ;
obj [ data . curvePublic ] = data . data ;
team . roster . describe ( obj , function ( err ) {
team . roster . describe ( obj , function ( err ) {
@ -825,6 +1133,12 @@ define([
if ( cmd === 'SET_TEAM_METADATA' ) {
if ( cmd === 'SET_TEAM_METADATA' ) {
return void setTeamMetadata ( ctx , data , clientId , cb ) ;
return void setTeamMetadata ( ctx , data , clientId , cb ) ;
}
}
if ( cmd === 'OFFER_OWNERSHIP' ) {
return void offerOwnership ( ctx , data , clientId , cb ) ;
}
if ( cmd === 'ANSWER_OWNERSHIP' ) {
return void answerOwnership ( ctx , data , clientId , cb ) ;
}
if ( cmd === 'DESCRIBE_USER' ) {
if ( cmd === 'DESCRIBE_USER' ) {
return void describeUser ( ctx , data , clientId , cb ) ;
return void describeUser ( ctx , data , clientId , cb ) ;
}
}
@ -840,6 +1154,9 @@ define([
if ( cmd === 'REMOVE_USER' ) {
if ( cmd === 'REMOVE_USER' ) {
return void removeUser ( ctx , data , clientId , cb ) ;
return void removeUser ( ctx , data , clientId , cb ) ;
}
}
if ( cmd === 'DELETE_TEAM' ) {
return void deleteTeam ( ctx , data , clientId , cb ) ;
}
if ( cmd === 'CREATE_TEAM' ) {
if ( cmd === 'CREATE_TEAM' ) {
return void createTeam ( ctx , data , clientId , cb ) ;
return void createTeam ( ctx , data , clientId , cb ) ;
}
}