@@ -22,6 +22,10 @@ const ZORK_PORT = 9000;
2222// Key under which our contacts are saved in storage.
2323const STORAGE_KEY = 'cloud-social-contacts' ;
2424
25+ // Admin users can issue invites.
26+ const ADMIN_USERNAME = 'giver' ;
27+ const REGULAR_USERNAME = 'getter' ;
28+
2529// Credentials for accessing a cloud instance.
2630// The serialised, base64 form is distributed amongst users.
2731// TODO: add (private) keys, for key-based auth
@@ -335,9 +339,22 @@ export class CloudSocialProvider {
335339 // social2
336340 ////
337341
338- public inviteUser = ( inviteCode : string ) : Promise < void > => {
339- return Promise . reject (
340- new Error ( 'inviteUser unimplemented' ) ) ;
342+ // Returns a new invite code for the specified server.
343+ // Rejects if the user is not logged in as an admin or there
344+ // is any error executing the command.
345+ // TODO: typings for invite codes
346+ public inviteUser = ( clientId : string ) : Promise < Object > => {
347+ log . debug ( 'inviteUser' ) ;
348+ if ( ! ( clientId in this . clients_ ) ) {
349+ return Promise . reject ( new Error ( 'unknown client ' + clientId ) ) ;
350+ }
351+ if ( this . savedContacts_ [ clientId ] . invite . user !== ADMIN_USERNAME ) {
352+ return Promise . reject ( new Error ( 'user is logged in as non-admin user ' +
353+ this . savedContacts_ [ clientId ] . invite . user ) ) ;
354+ }
355+ return this . clients_ [ clientId ] . then ( ( connection : Connection ) => {
356+ return connection . issueInvite ( ) ;
357+ } ) ;
341358 }
342359
343360 // Parses an invite code, received from uProxy in JSON format.
@@ -526,11 +543,24 @@ class Connection {
526543
527544 // Fetches the server's description, i.e. /banner.
528545 public getBanner = ( ) : Promise < string > => {
546+ return this . exec_ ( 'cat /banner' ) ;
547+ }
548+
549+ // Returns a base64-decoded, deserialised invite code.
550+ public issueInvite = ( ) : Promise < Object > => {
551+ return this . exec_ ( 'sudo /issue_invite.sh' ) . then ( ( inviteCode : string ) => {
552+ return JSON . parse ( new Buffer ( inviteCode , 'base64' ) . toString ( ) ) ;
553+ } ) ;
554+ }
555+
556+ // Executes a command, fulfilling with the command's stdout
557+ // or rejecting if output is received on stderr.
558+ private exec_ = ( command :string ) : Promise < string > => {
529559 if ( this . state_ !== ConnectionState . ESTABLISHED ) {
530- return Promise . reject ( new Error ( 'can only fetch banner in ESTABLISHED state' ) ) ;
560+ return Promise . reject ( new Error ( 'can only execute commands in ESTABLISHED state' ) ) ;
531561 }
532562 return new Promise < string > ( ( F , R ) => {
533- this . client_ . exec ( 'cat /banner' , ( e : Error , stream : ssh2 . Channel ) => {
563+ this . client_ . exec ( command , ( e : Error , stream : ssh2 . Channel ) => {
534564 if ( e ) {
535565 R ( e ) ;
536566 return ;
@@ -540,7 +570,7 @@ class Connection {
540570 } ) . stderr . on ( 'data' , function ( data : Buffer ) {
541571 R ( new Error ( data . toString ( ) ) ) ;
542572 } ) . on ( 'close' , function ( code : any , signal : any ) {
543- log . debug ( 'banner stream closed' ) ;
573+ log . debug ( 'exec stream closed' ) ;
544574 } ) ;
545575 } ) ;
546576 } ) ;
0 commit comments