GitHub PullRequestReviewCommentEvent at 2014-05-16 17:13:17
Repository: loopback
Actor: ritch
Payload: {"comment":{"url":"https://api.github.com/repos/strongloop/loopback/pulls/comments/12749328","id":12749328,"diff_hunk":"@@ -0,0 +1,182 @@\n+var loopback = require('../loopback');\n+var DataModel = require('./data-model');\n+var Role = require('./role').Role;\n+var ACL = require('./acl').ACL;\n+\n+/*!\n+ * Default UserIdentity properties.\n+ */\n+\n+var properties = {\n+  provider: {type: String}, // facebook, google, twitter, linkedin\n+  authScheme: {type: String}, // oAuth, oAuth 2.0, OpenID, OpenID Connect\n+  externalId: {type: String}, // The provider specific id\n+  profile: {type: Object},\n+  /*\n+   * oAuth: token, tokenSecret\n+   * oAuth 2.0: accessToken, refreshToken\n+   * OpenID: openId\n+   * OpenID Connect: accessToken, refreshToken, profile\n+   */\n+  credentials: {type: Object},\n+  // userId: {type: String}, // Allow LoopBack to inject it based on the relation\n+  created: Date,\n+  modified: Date\n+};\n+\n+var options = {\n+  acls: [\n+    {\n+      principalType: ACL.ROLE,\n+      principalId: Role.EVERYONE,\n+      permission: ACL.DENY\n+    },\n+    {\n+      principalType: ACL.ROLE,\n+      principalId: Role.OWNER,\n+      permission: ACL.ALLOW\n+    }\n+  ],\n+  relations: {\n+    user: {\n+      type: 'belongsTo',\n+      model: 'User',\n+      foreignKey: 'userId'\n+    }\n+  }\n+};\n+\n+/**\n+ * UserIdentity model keeps track of 3rd party logins/profiles\n+ */\n+var UserIdentity = DataModel.extend('UserIdentity', properties, options);\n+\n+var crypto = require('crypto');\n+\n+function generateKey(hmacKey, algorithm, encoding) {\n+  hmacKey = hmacKey || 'loopback';\n+  algorithm = algorithm || 'sha1';\n+  encoding = encoding || 'hex';\n+  var hmac = crypto.createHmac(algorithm, hmacKey);\n+  var buf = crypto.randomBytes(32);\n+  hmac.update(buf);\n+  var key = hmac.digest(encoding);\n+  return key;\n+}\n+\n+/**\n+ * Log in with a third party provider such as facebook or google\n+ * @param {String} provider The provider name\n+ * @param {String} authScheme The authentication scheme\n+ * @param {Object} profile The profile\n+ * @param {Object} credentials The credentials\n+ * @callback {Function} cb The callback function\n+ * @param {Error|String} err The error object or string\n+ * @param {Object} user The user object\n+ * @param {Object} [identity] The user identity object\n+ */\n+UserIdentity.login = function (provider, authScheme, profile, credentials, cb) {\n+  var userIdentityModel = (this instanceof UserIdentity) ? this : UserIdentity;\n+  userIdentityModel.findOne({where: {\n+    provider: provider,\n+    externalId: profile.id\n+  }}, function (err, identity) {\n+    if (err) {\n+      return cb(err);\n+    }\n+    if (identity) {\n+      identity.credentials = credentials;\n+      return identity.updateAttributes({profile: profile,\n+        credentials: credentials, modified: new Date()}, function (err, i) {\n+        // Find the user for the given identity\n+        return identity.user(function (err, user) {\n+          cb(err, user, identity);\n+        });\n+      });\n+    }\n+    // Find the user model\n+    var userModel = (userIdentityModel.relations.user\n+      && userIdentityModel.relations.user.modelTo)\n+      || loopback.getModelByType(loopback.User);\n+    // Let's create a user for that\n+    var email = profile.emails && profile.emails[0] && profile.emails[0].value;\n+    userModel.create({\n+      username: provider + '.' + (profile.username || profile.id),\n+      password: generateKey('password'),\n+      email: email\n+    }, function (err, user) {\n+      if (err) {\n+        return cb(err);\n+      }\n+      var date = new Date();\n+      userIdentityModel.create({\n+        provider: provider,\n+        externalId: profile.id,\n+        authScheme: authScheme,\n+        profile: profile,\n+        credentials: credentials,\n+        userId: user.id,\n+        created: date,\n+        modified: date\n+      }, function (err, identity) {\n+        cb(err, user, identity);\n+      });\n+    });\n+  });\n+}\n+\n+/**\n+ * UserCredential model keeps track of 3rd party credentials for linked accounts\n+ */\n+var UserCredential = DataModel.extend('UserCredential', properties, options);\n+\n+/**\n+ * Link a third party account to a LoopBack user\n+ * @param {String} provider The provider name\n+ * @param {String} authScheme The authentication scheme\n+ * @param {Object} profile The profile\n+ * @param {Object} credentials The credentials\n+ * @callback {Function} cb The callback function\n+ * @param {Error|String} err The error object or string\n+ * @param {Object} [credential] The user credential object\n+ */\n+UserCredential.link = function (userId, provider, authScheme, profile, credentials, cb) {\n+  var userCredentialModel = (this instanceof UserCredential) ? this : UserCredential;\n+  userCredentialModel.findOne({where: {\n+    userId: userId,\n+    provider: provider,\n+    externalId: profile.id\n+  }}, function (err, extCredential) {\n+    if (err) {\n+      return cb(err);\n+    }\n+\n+    var date = new Date();\n+    if (extCredential) {\n+      // Find the user for the given extCredential\n+      extCredential.credentials = credentials;\n+      return extCredential.updateAttributes({profile: profile,\n+        credentials: credentials, modified: date}, cb);\n+    }\n+\n+    // Create the linked account\n+    userCredentialModel.create({\n+      provider: provider,\n+      externalId: profile.id,\n+      authScheme: authScheme,\n+      profile: profile,\n+      credentials: credentials,\n+      userId: userId,\n+      created: date,\n+      modified: date\n+    }, function (err, i) {\n+      cb(err, i);\n+    });\n+\n+  });\n+}\n+\n+module.exports = {","path":"lib/models/user-identity.js","position":195,"original_position":179,"commit_id":"002e515ecf800bdfea222d12400e596ea73985ee","original_commit_id":"3b5c9b0b4745d06508f26077964651068d0837d4","user":{"login":"ritch","id":462228,"avatar_url":"https://avatars.githubusercontent.com/u/462228?","gravatar_id":"9c9b14104d3df09372c22605441a40da","url":"https://api.github.com/users/ritch","html_url":"https://github.com/ritch","followers_url":"https://api.github.com/users/ritch/followers","following_url":"https://api.github.com/users/ritch/following{/other_user}","gists_url":"https://api.github.com/users/ritch/gists{/gist_id}","starred_url":"https://api.github.com/users/ritch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ritch/subscriptions","organizations_url":"https://api.github.com/users/ritch/orgs","repos_url":"https://api.github.com/users/ritch/repos","events_url":"https://api.github.com/users/ritch/events{/privacy}","received_events_url":"https://api.github.com/users/ritch/received_events","type":"User","site_admin":false},"body":"Its just a best practice that prevents introducing race conditions. Its really hard to track them down the other way.","created_at":"2014-05-16T17:13:17Z","updated_at":"2014-05-16T17:13:17Z","html_url":"https://github.com/strongloop/loopback/pull/253#discussion_r12749328","pull_request_url":"https://api.github.com/repos/strongloop/loopback/pulls/253","_links":{"self":{"href":"https://api.github.com/repos/strongloop/loopback/pulls/comments/12749328"},"html":{"href":"https://github.com/strongloop/loopback/pull/253#discussion_r12749328"},"pull_request":{"href":"https://api.github.com/repos/strongloop/loopback/pulls/253"}}}}