Permalink
Please sign in to comment.
Showing
with
463 additions
and 395 deletions.
- +1 −0 docs.json
- +20 −5 lib/index.js
- +48 −72 lib/models/application-credential.js
- +25 −0 lib/models/application-credential.json
- +77 −0 lib/models/user-credential.js
- +42 −0 lib/models/user-credential.json
- +113 −232 lib/models/user-identity.js
- +43 −0 lib/models/user-identity.json
- +5 −4 lib/passport-configurator.js
- +88 −0 test/model.user-credential.test.js
- +1 −82 test/model.user-identity.test.js
1
docs.json
25
lib/index.js
| @@ -1,10 +1,25 @@ | ||
| -var uid = require('./models/user-identity'); | ||
| -exports.UserIdentity = uid.UserIdentity; | ||
| -exports.UserCredential = uid.UserCredential; | ||
| -exports.ApplicationCredential = require('./models/application-credential'); | ||
| +var loopback = require('loopback'); | ||
| +var DataModel = loopback.PersistedModel || loopback.DataModel; | ||
| + | ||
| +function loadModel(jsonFile) { | ||
| + var modelDefinition = require(jsonFile); | ||
| + return DataModel.extend(modelDefinition.name, | ||
| + modelDefinition.properties, | ||
| + { | ||
| + relations: modelDefinition.relations | ||
| + }); | ||
| +} | ||
| + | ||
| +var UserIdentityModel = loadModel('./models/user-identity.json'); | ||
| +var UserCredentialModel = loadModel('./models/user-credential.json'); | ||
| +var ApplicationCredentialModel = loadModel('./models/application-credential.json'); | ||
| + | ||
| +exports.UserIdentity = require('./models/user-identity')(UserIdentityModel); | ||
| +exports.UserCredential = require('./models/user-credential')(UserCredentialModel); | ||
| +exports.ApplicationCredential = require('./models/application-credential')(ApplicationCredentialModel); | ||
| exports.UserIdentity.autoAttach = 'db'; | ||
| exports.UserCredential.autoAttach = 'db'; | ||
| exports.ApplicationCredential.autoAttach = 'db'; | ||
| -exports.PassportConfigurator = require('./passport-configurator'); | ||
| +exports.PassportConfigurator = require('./passport-configurator'); |
120
lib/models/application-credential.js
25
lib/models/application-credential.json
| @@ -0,0 +1,25 @@ | ||
| +{ | ||
| + "name": "ApplicationCredential", | ||
| + "base": "PersistedModel", | ||
| + "properties": { | ||
| + "provider": { | ||
| + "type": "String", | ||
| + "required": true, | ||
| + "comments": "Facebook, google, twitter, linkedIn" | ||
| + }, | ||
| + "authScheme": { | ||
| + "type": "String", | ||
| + "comments": "oAuth, oAuth 2.0, OpenID, OpenID Connect" | ||
| + }, | ||
| + "credentials": "Object", | ||
| + "created": "Date", | ||
| + "modified": "Date" | ||
| + }, | ||
| + "relations": { | ||
| + "application": { | ||
| + "type": "belongsTo", | ||
| + "model": "Application", | ||
| + "foreignKey": "appId" | ||
| + } | ||
| + } | ||
| +} |
77
lib/models/user-credential.js
| @@ -0,0 +1,77 @@ | ||
| +/** | ||
| + * Tracks third-party logins and profiles. | ||
| + * | ||
| + * @param {String} provider Auth provider name, such as facebook, google, twitter, linkedin. | ||
| + * @param {String} authScheme Auth scheme, such as oAuth, oAuth 2.0, OpenID, OpenID Connect. | ||
| + * @param {String} externalId Provider specific user ID. | ||
| + * @param {Object} profile User profile, see http://passportjs.org/guide/profile. | ||
| + * @param {Object} credentials Credentials. Actual properties depend on the auth scheme being used: | ||
| + * | ||
| + * - oAuth: token, tokenSecret | ||
| + * - oAuth 2.0: accessToken, refreshToken | ||
| + * - OpenID: openId | ||
| + * - OpenID: Connect: accessToken, refreshToken, profile | ||
| + * @param {*} userId The LoopBack user ID. | ||
| + * @param {Date} created The created date | ||
| + * @param {Date} modified The last modified date | ||
| + * | ||
| + * @class | ||
| + * @inherits {DataModel} | ||
| + */ | ||
| +module.exports = function(UserCredential) { | ||
| + var utils = require('./utils'); | ||
| + | ||
| + /** | ||
| + * Link a third party account to a LoopBack user | ||
| + * @param {String} provider The provider name | ||
| + * @param {String} authScheme The authentication scheme | ||
| + * @param {Object} profile The profile | ||
| + * @param {Object} credentials The credentials | ||
| + * @param {Object} [options] The options | ||
| + * @callback {Function} cb The callback function | ||
| + * @param {Error|String} err The error object or string | ||
| + * @param {Object} [credential] The user credential object | ||
| + */ | ||
| + UserCredential.link = function (userId, provider, authScheme, profile, | ||
| + credentials, options, cb) { | ||
| + options = options || {}; | ||
| + if(typeof options === 'function' && cb === undefined) { | ||
| + cb = options; | ||
| + options = {}; | ||
| + } | ||
| + var userCredentialModel = utils.getModel(this, UserCredential); | ||
| + userCredentialModel.findOne({where: { | ||
| + userId: userId, | ||
| + provider: provider, | ||
| + externalId: profile.id | ||
| + }}, function (err, extCredential) { | ||
| + if (err) { | ||
| + return cb(err); | ||
| + } | ||
| + | ||
| + var date = new Date(); | ||
| + if (extCredential) { | ||
| + // Find the user for the given extCredential | ||
| + extCredential.credentials = credentials; | ||
| + return extCredential.updateAttributes({profile: profile, | ||
| + credentials: credentials, modified: date}, cb); | ||
| + } | ||
| + | ||
| + // Create the linked account | ||
| + userCredentialModel.create({ | ||
| + provider: provider, | ||
| + externalId: profile.id, | ||
| + authScheme: authScheme, | ||
| + profile: profile, | ||
| + credentials: credentials, | ||
| + userId: userId, | ||
| + created: date, | ||
| + modified: date | ||
| + }, function (err, i) { | ||
| + cb(err, i); | ||
| + }); | ||
| + | ||
| + }); | ||
| + } | ||
| + return UserCredential; | ||
| +}; |
42
lib/models/user-credential.json
| @@ -0,0 +1,42 @@ | ||
| +{ | ||
| + "name": "UserCredential", | ||
| + "base": "PersistedModel", | ||
| + "properties": { | ||
| + "provider": { | ||
| + "type": "String", | ||
| + "comments": "facebook, google, twitter, linkedin" | ||
| + }, | ||
| + "authScheme": { | ||
| + "type": "String", | ||
| + "comments": "oAuth, oAuth 2.0, OpenID, OpenID Connect" | ||
| + }, | ||
| + "externalId": { | ||
| + "type": "String", | ||
| + "comments": "The provider specific id" | ||
| + }, | ||
| + "profile": { | ||
| + "type": "Object" | ||
| + }, | ||
| + "credentials": { | ||
| + "type": "Object" | ||
| + }, | ||
| + "created": "Date", | ||
| + "modified": "Date" | ||
| + }, | ||
| + "acls": [{ | ||
| + "principalType": "ROLE", | ||
| + "principalId": "$everyone", | ||
| + "permission": "DENY" | ||
| + }, { | ||
| + "principalType": "ROLE", | ||
| + "principalId": "$owner", | ||
| + "permission": "ALLOW" | ||
| + }], | ||
| + "relations": { | ||
| + "user": { | ||
| + "type": "belongsTo", | ||
| + "model": "User", | ||
| + "foreignKey": "userId" | ||
| + } | ||
| + } | ||
| +} |
345
lib/models/user-identity.js
43
lib/models/user-identity.json
| @@ -0,0 +1,43 @@ | ||
| +{ | ||
| + "name": "UserIdentity", | ||
| + "plural": "UserIdentities", | ||
| + "base": "PersistedModel", | ||
| + "properties": { | ||
| + "provider": { | ||
| + "type": "String", | ||
| + "comments": "facebook, google, twitter, linkedin" | ||
| + }, | ||
| + "authScheme": { | ||
| + "type": "String", | ||
| + "comments": "oAuth, oAuth 2.0, OpenID, OpenID Connect" | ||
| + }, | ||
| + "externalId": { | ||
| + "type": "String", | ||
| + "comments": "The provider specific id" | ||
| + }, | ||
| + "profile": { | ||
| + "type": "Object" | ||
| + }, | ||
| + "credentials": { | ||
| + "type": "Object" | ||
| + }, | ||
| + "created": "Date", | ||
| + "modified": "Date" | ||
| + }, | ||
| + "acls": [{ | ||
| + "principalType": "ROLE", | ||
| + "principalId": "$everyone", | ||
| + "permission": "DENY" | ||
| + }, { | ||
| + "principalType": "ROLE", | ||
| + "principalId": "$owner", | ||
| + "permission": "ALLOW" | ||
| + }], | ||
| + "relations": { | ||
| + "user": { | ||
| + "type": "belongsTo", | ||
| + "model": "User", | ||
| + "foreignKey": "userId" | ||
| + } | ||
| + } | ||
| +} |
9
lib/passport-configurator.js
88
test/model.user-credential.test.js
| @@ -0,0 +1,88 @@ | ||
| +var m = require('./init'); | ||
| +var loopback = require('loopback'); | ||
| +var assert = require('assert'); | ||
| +var UserCredential = m.UserCredential; | ||
| +var User = loopback.User; | ||
| + | ||
| +before(function (done) { | ||
| + User.destroyAll(done); | ||
| +}); | ||
| + | ||
| +describe('UserCredential', function () { | ||
| + var userId = null; | ||
| + before(function (done) { | ||
| + var ds = loopback.createDataSource({ | ||
| + connector: 'memory' | ||
| + }); | ||
| + | ||
| + UserCredential.attachTo(ds); | ||
| + User.attachTo(ds); | ||
| + UserCredential.belongsTo(User); | ||
| + | ||
| + User.create({ | ||
| + username: 'facebook.abc', | ||
| + email: 'uuu@facebook.com', | ||
| + password: 'pass' | ||
| + }, function (err, user) { | ||
| + userId = user.id; | ||
| + done(err); | ||
| + }); | ||
| + }); | ||
| + | ||
| + it('supports linked 3rd party accounts', function (done) { | ||
| + UserCredential.link(userId, 'facebook', 'oAuth 2.0', | ||
| + {emails: [ | ||
| + {value: 'foo@bar.com'} | ||
| + ], id: 'f123', username: 'xyz' | ||
| + }, {accessToken: 'at1', refreshToken: 'rt1'}, function (err, cred) { | ||
| + assert(!err, 'No error should be reported'); | ||
| + | ||
| + assert.equal(cred.externalId, 'f123'); | ||
| + assert.equal(cred.provider, 'facebook'); | ||
| + assert.equal(cred.authScheme, 'oAuth 2.0'); | ||
| + assert.deepEqual(cred.credentials, {accessToken: 'at1', refreshToken: 'rt1'}); | ||
| + | ||
| + assert.equal(userId, cred.userId); | ||
| + | ||
| + // Follow the belongsTo relation | ||
| + cred.user(function (err, user) { | ||
| + assert(!err, 'No error should be reported'); | ||
| + assert.equal(user.username, 'facebook.abc'); | ||
| + assert.equal(user.email, 'uuu@facebook.com'); | ||
| + done(); | ||
| + }); | ||
| + }); | ||
| + }); | ||
| + | ||
| + it('supports linked 3rd party accounts if exists', function (done) { | ||
| + UserCredential.create({ | ||
| + externalId: 'f456', | ||
| + provider: 'facebook', | ||
| + userId: userId, | ||
| + credentials: {accessToken: 'at1', refreshToken: 'rt1'} | ||
| + }, function (err, cred) { | ||
| + UserCredential.link(userId, 'facebook', 'oAuth 2.0', | ||
| + {emails: [ | ||
| + {value: 'abc1@facebook.com'} | ||
| + ], id: 'f456', username: 'xyz' | ||
| + }, {accessToken: 'at2', refreshToken: 'rt2'}, function (err, cred) { | ||
| + assert(!err, 'No error should be reported'); | ||
| + | ||
| + assert.equal(cred.externalId, 'f456'); | ||
| + assert.equal(cred.provider, 'facebook'); | ||
| + assert.deepEqual(cred.credentials, {accessToken: 'at2', refreshToken: 'rt2'}); | ||
| + | ||
| + assert.equal(userId, cred.userId); | ||
| + | ||
| + // Follow the belongsTo relation | ||
| + cred.user(function (err, user) { | ||
| + assert(!err, 'No error should be reported'); | ||
| + assert.equal(user.username, 'facebook.abc'); | ||
| + assert.equal(user.email, 'uuu@facebook.com'); | ||
| + done(); | ||
| + }); | ||
| + }); | ||
| + }); | ||
| + }); | ||
| + | ||
| +}); |
83
test/model.user-identity.test.js
0 comments on commit
9ef62a4