Browse Source

powerbox identity requests

David Renshaw 8 years ago
parent
commit
1ad44de8c6
3 changed files with 153 additions and 4 deletions
  1. 6 4
      client/components/sidebar/sidebar.jade
  2. 3 0
      client/components/sidebar/sidebar.js
  3. 144 0
      sandstorm.js

+ 6 - 4
client/components/sidebar/sidebar.jade

@@ -30,10 +30,12 @@ template(name="membersWidget")
     .board-widget-content
       each currentBoard.activeMembers
         +userAvatar(userId=this.userId showStatus=true)
-      unless isSandstorm
-        if currentUser.isBoardAdmin
-          a.member.add-member.js-manage-board-members
-            i.fa.fa-plus
+      if isSandstorm
+        a.member.add-member.sandstorm-powerbox-request-identity
+          i.fa.fa-plus
+      else if currentUser.isBoardAdmin
+        a.member.add-member.js-manage-board-members
+          i.fa.fa-plus
       .clearfix
   if isInvited
     hr

+ 3 - 0
client/components/sidebar/sidebar.js

@@ -163,6 +163,9 @@ Template.membersWidget.helpers({
 Template.membersWidget.events({
   'click .js-member': Popup.open('member'),
   'click .js-manage-board-members': Popup.open('addMember'),
+  'click .sandstorm-powerbox-request-identity'() {
+    window.sandstormRequestIdentity();
+  },
   'click .js-member-invite-accept'() {
     const boardId = Session.get('currentBoard');
     Meteor.user().removeInvite(boardId);

+ 144 - 0
sandstorm.js

@@ -21,6 +21,79 @@ const sandstormBoard = {
 };
 
 if (isSandstorm && Meteor.isServer) {
+  const Capnp = require('capnp');
+  const Powerbox = Capnp.importSystem('sandstorm/powerbox.capnp');
+  const Identity = Capnp.importSystem('sandstorm/identity.capnp');
+  const SandstormHttpBridge =
+    Capnp.importSystem('sandstorm/sandstorm-http-bridge.capnp').SandstormHttpBridge;
+
+  let httpBridge = null;
+
+  function getHttpBridge() {
+    if (!httpBridge) {
+      const capnpConnection = Capnp.connect('unix:/tmp/sandstorm-api');
+      httpBridge = capnpConnection.restore(null, SandstormHttpBridge);
+    }
+    return httpBridge;
+  }
+
+  Meteor.methods({
+    sandstormClaimIdentityRequest(token, descriptor) {
+      check(token, String);
+      check(descriptor, String);
+
+      const parsedDescriptor = Capnp.parse(
+        Powerbox.PowerboxDescriptor,
+        new Buffer(descriptor, 'base64'),
+        { packed: true });
+
+      const tag = Capnp.parse(Identity.Identity.PowerboxTag, parsedDescriptor.tags[0].value);
+      const permissions = [];
+      if (tag.permissions[1]) {
+        permissions.push('configure');
+      }
+
+      if (tag.permissions[0]) {
+        permissions.push('participate');
+      }
+
+      const sessionId = this.connection.sandstormSessionId();
+      const httpBridge = getHttpBridge();
+      const session = httpBridge.getSessionContext(sessionId).context;
+      const api = httpBridge.getSandstormApi(sessionId).api;
+
+      Meteor.wrapAsync((done) => {
+        session.claimRequest(token).then((response) => {
+          const identity = response.cap.castAs(Identity.Identity);
+          const promises = [api.getIdentityId(identity), identity.getProfile()];
+          return Promise.all(promises).then((responses) => {
+            const identityId = responses[0].id.toString('hex').slice(0, 32);
+            const profile = responses[1].profile;
+            return profile.picture.getUrl().then((response) => {
+              const sandstormInfo = {
+                id: identityId,
+                name: profile.displayName.defaultText,
+                permissions,
+                picture: `${response.protocol}://${response.hostPath}`,
+                preferredHandle: profile.preferredHandle,
+                pronouns: profile.pronouns,
+              };
+
+              const login = Accounts.updateOrCreateUserFromExternalService(
+                'sandstorm', sandstormInfo,
+                { profile: { name: sandstormInfo.name, fullname: sandstormInfo.name } });
+
+              updateUserPermissions(login.userId, permissions);
+              done();
+            });
+          });
+        }).catch((e) => {
+          done(e, null);
+        });
+      })();
+    },
+  });
+
   function updateUserPermissions(userId, permissions) {
     const isActive = permissions.indexOf('participate') > -1;
     const isAdmin = permissions.indexOf('configure') > -1;
@@ -137,6 +210,77 @@ if (isSandstorm && Meteor.isServer) {
 }
 
 if (isSandstorm && Meteor.isClient) {
+  let rpcCounter = 0;
+  const rpcs = {};
+
+  window.addEventListener('message', (event) => {
+    if (event.source === window) {
+      // Meteor likes to postmessage itself.
+      return;
+    }
+
+    if ((event.source !== window.parent) ||
+        typeof event.data !== 'object' ||
+        typeof event.data.rpcId !== 'number') {
+      throw new Error(`got unexpected postMessage: ${event}`);
+    }
+
+    const handler = rpcs[event.data.rpcId];
+    if (!handler) {
+      throw new Error(`no such rpc ID for event ${event}`);
+    }
+
+    delete rpcs[event.data.rpcId];
+    handler(event.data);
+  });
+
+  function sendRpc(name, message) {
+    const id = rpcCounter++;
+    message.rpcId = id;
+    const obj = {};
+    obj[name] = message;
+    window.parent.postMessage(obj, '*');
+    return new Promise((resolve, reject) => {
+      rpcs[id] = (response) => {
+        if (response.error) {
+          reject(new Error(response.error));
+        } else {
+          resolve(response);
+        }
+      };
+    });
+  }
+
+  const powerboxDescriptors = {
+    identity: 'EAhQAQEAABEBF1EEAQH_GN1RqXqYhMAAQAERAREBAQ',
+    // Generated using the following code:
+    //
+    // Capnp.serializePacked(
+    //  Powerbox.PowerboxDescriptor,
+    //  { tags: [ {
+    //    id: "13872380404802116888",
+    //    value: Capnp.serialize(Identity.PowerboxTag, { permissions: [true, false] })
+    //  }]}).toString('base64')
+    //      .replace(/\//g, "_")
+    //      .replace(/\+/g, "-");
+  };
+
+  function doRequest(serializedPowerboxDescriptor, onSuccess) {
+    return sendRpc('powerboxRequest', {
+      query: [serializedPowerboxDescriptor],
+    }).then((response) => {
+      if (!response.canceled) {
+        onSuccess(response);
+      }
+    });
+  }
+
+  window.sandstormRequestIdentity = function () {
+    doRequest(powerboxDescriptors.identity, (response) => {
+      Meteor.call('sandstormClaimIdentityRequest', response.token, response.descriptor);
+    });
+  };
+
   // Since the Sandstorm grain is displayed in an iframe of the Sandstorm shell,
   // we need to explicitly expose meta data like the page title or the URL path
   // so that they could appear in the browser window.