Skip to content
This repository has been archived by the owner on Jan 31, 2020. It is now read-only.

Commit

Permalink
Update to Salesforce Provider (hapijs#313)
Browse files Browse the repository at this point in the history
* update salesforce provider to support extendedProfile config
support both oauth/userInfo and salesforce identity service profile endpoints

* update documentation

* bit of cleanup
  • Loading branch information
phitran authored and ldesplat committed Sep 24, 2017
1 parent d7a41e6 commit 3a4a7c2
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 51 deletions.
47 changes: 36 additions & 11 deletions Providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ The default profile response will look like this:
```javascript
credentials.profile = {
id: profile.id,
displayName: profile.name
displayName: profile.name,
name: {
given_name: profile.given_name,
family_name: profile.family_name
Expand Down Expand Up @@ -696,22 +696,47 @@ The default profile response will look like this:

- `scope`: not applicable
- `config`:
- `uri`: Point to your Salesforce org. Defaults to `https://login.salesforce.com`.
- `uri`: Point to your Salesforce org. Defaults to `https://login.salesforce.com`
- `extendedProfile`: Request for more profile information. Defaults to true
- `identityServiceProfile`: Determines if the profile information fetch uses the [Force.com Identity Service](https://developer.salesforce.com/page/Digging_Deeper_into_OAuth_2.0_on_Force.com#The_Force.com_Identity_Service). Defaults to false (UserInfo Endpoint)
- `auth`: /services/oauth2/authorize
- `token`: /services/oauth2/token

The default profile response will look like this:
The default profile response will look like this: [UserInfo Response](https://developer.salesforce.com/page/Inside_OpenID_Connect_on_Force.com#User_Profile_Service)

```javascript
credentials.profile = {
id: profile.user_id,
username: profile.username,
displayName: profile.display_name,
firstName: profile.first_name,
lastName: profile.last_name,
email: profile.email,
raw: profile
};
"sub": "https://login.salesforce.com/id/00Dx0000000A9y0EAC/005x0000000UnYmAAK",
"user_id": "005x0000000UnYmAAK",
"organization_id": "00Dx0000000A9y0EAC",
"preferred_username": "user@ example.com",
"nickname": "user",
"name": "Pat Patterson",
"email": "user@ example.com",
"email_verified": true,
"given_name": "Pat",
"family_name": "Patterson",
...
}
```

The Force.com Identity profile response will look like this: [Force.com Identity Response](https://developer.salesforce.com/page/Digging_Deeper_into_OAuth_2.0_on_Force.com#The_Force.com_Identity_Service)

```javascript
credentials.profile = {
"id":"https://login.salesforce.com/id/00D50000000IZ3ZEAW/00550000001fg5OAAQ",
"asserted_user":true,
"user_id":"00550000001fg5OAAQ",
"organization_id":"00D50000000IZ3ZEAW",
"username":"user@ example. com",
"nick_name":"user1.2950476911907334E12",
"display_name":"Sample User",
"email":"user@ example. com",
"email_verified": true,
"first_name": "Sample",
"last_name": "User",
...
}
```

### Stripe
Expand Down
60 changes: 40 additions & 20 deletions lib/providers/salesforce.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
'use strict';

exports = module.exports = function (options) {
const Joi = require('joi');
const Hoek = require('hoek');

const internals = {};

options = options || {};
internals.schema = Joi.object({
uri: Joi.string().uri().optional(),
extendedProfile: Joi.boolean().optional(),
identityServiceProfile: Joi.boolean().optional().when('extendedProfile', { is: false, then: Joi.invalid(true) })
});

const uri = options.uri || 'https://login.salesforce.com';
internals.defaults = {
uri: 'https://login.salesforce.com',
extendedProfile: true,
identityServiceProfile: false
};

exports = module.exports = function (options) {

const combinedSettings = Hoek.applyToDefaults(internals.defaults, options || {});
const validated = Joi.validate(combinedSettings, internals.schema);
Hoek.assert(!validated.error, validated.error);
const settings = validated.value;

return {
protocol: 'oauth2',
auth: uri + '/services/oauth2/authorize',
token: uri + '/services/oauth2/token',
auth: settings.uri + '/services/oauth2/authorize',
token: settings.uri + '/services/oauth2/token',
useParamsAuth: true,
profile: function (credentials, params, get, callback) {

const userUrl = params.id;

get(userUrl, null, (profile) => {

credentials.profile = {
id: profile.user_id,
username: profile.username,
displayName: profile.display_name,
firstName: profile.first_name,
lastName: profile.last_name,
email: profile.email,
raw: profile
};

if (settings.extendedProfile === false) {
return callback();
});
}

if (settings.identityServiceProfile) {
get(params.id, null, (profile) => {

credentials.profile = profile;
return callback();
});
}
else {
get(`${settings.uri}/services/oauth2/userinfo`, null, (profile) => {

credentials.profile = profile;
return callback();
});
}
}
};
};
192 changes: 172 additions & 20 deletions test/providers/salesforce.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,29 @@ const expect = Code.expect;

describe('salesforce', () => {

it('fails with extendedProfile false and identityServiceProfile true', { parallel: false }, (done) => {

const mock = new Mock.V2();
mock.start((provider) => {

const server = new Hapi.Server();
server.connection({ host: 'localhost', port: 80 });
server.register(Bell, (err) => {

expect(err).to.not.exist();

const throws = () => {

Bell.providers.salesforce({ extendedProfile: false, identityServiceProfile: true });
};

expect(throws).to.throw(Error);

mock.stop(done);
});
});
});

it('authenticates with mock', { parallel: false }, (done) => {

const mock = new Mock.V2();
Expand Down Expand Up @@ -47,7 +70,7 @@ describe('salesforce', () => {
email: 'steve@example.com'
};

Mock.override('https://login.salesforce.com/id/foo/bar', profile);
Mock.override('https://login.salesforce.com/services/oauth2/userinfo', profile);

server.auth.strategy('custom', 'bell', {
password: 'cookie_encryption_password_secure',
Expand Down Expand Up @@ -83,15 +106,7 @@ describe('salesforce', () => {
expiresIn: 3600,
refreshToken: undefined,
query: {},
profile: {
id: '1234567890',
username: 'steve',
displayName: 'steve',
firstName: 'steve',
lastName: 'smith',
email: 'steve@example.com',
raw: profile
}
profile
});
mock.stop(done);
});
Expand All @@ -103,6 +118,79 @@ describe('salesforce', () => {

it('authenticates with mock and custom uri', { parallel: false }, (done) => {

const mock = new Mock.V2();
mock.start((provider) => {

const server = new Hapi.Server();
server.connection({ host: 'localhost', port: 80 });
server.register(Bell, (err) => {

expect(err).to.not.exist();

const custom = Bell.providers.salesforce({ identityServiceProfile: true });

expect(custom.auth).to.equal('https://login.salesforce.com/services/oauth2/authorize');
expect(custom.token).to.equal('https://login.salesforce.com/services/oauth2/token');

Hoek.merge(custom, provider);

const profile = {
user_id: '1234567890',
username: 'steve',
display_name: 'steve',
first_name: 'steve',
last_name: 'smith',
email: 'steve@example.com'
};

Mock.override('https://login.salesforce.com/id/foo/bar', profile);

server.auth.strategy('custom', 'bell', {
password: 'cookie_encryption_password_secure',
isSecure: false,
clientId: 'salesforce',
clientSecret: 'secret',
provider: custom
});

server.route({
method: '*',
path: '/login',
config: {
auth: 'custom',
handler: function (request, reply) {

reply(request.auth.credentials);
}
}
});

server.inject('/login', (res) => {

const cookie = res.headers['set-cookie'][0].split(';')[0] + ';';
mock.server.inject(res.headers.location, (mockRes) => {

server.inject({ url: mockRes.headers.location, headers: { cookie } }, (response) => {

Mock.clear();
expect(response.result).to.equal({
provider: 'custom',
token: '456',
expiresIn: 3600,
refreshToken: undefined,
query: {},
profile
});
mock.stop(done);
});
});
});
});
});
});

it('authenticates with mock and identityServiceProfile', { parallel: false }, (done) => {

const mock = new Mock.V2();
mock.start((provider) => {

Expand All @@ -128,7 +216,7 @@ describe('salesforce', () => {
email: 'steve@example.com'
};

Mock.override('https://login.salesforce.com/id/foo/bar', profile);
Mock.override('http://example.com/services/oauth2/userinfo', profile);

server.auth.strategy('custom', 'bell', {
password: 'cookie_encryption_password_secure',
Expand Down Expand Up @@ -164,15 +252,79 @@ describe('salesforce', () => {
expiresIn: 3600,
refreshToken: undefined,
query: {},
profile: {
id: '1234567890',
username: 'steve',
displayName: 'steve',
firstName: 'steve',
lastName: 'smith',
email: 'steve@example.com',
raw: profile
}
profile
});
mock.stop(done);
});
});
});
});
});
});

it('authenticates with mock (without extended profile)', { parallel: false }, (done) => {

const mock = new Mock.V2();
mock.start((provider) => {

const server = new Hapi.Server();
server.connection({ host: 'localhost', port: 80 });
server.register(Bell, (err) => {

expect(err).to.not.exist();

const custom = Bell.providers.salesforce({ extendedProfile: false });

expect(custom.auth).to.equal('https://login.salesforce.com/services/oauth2/authorize');
expect(custom.token).to.equal('https://login.salesforce.com/services/oauth2/token');

Hoek.merge(custom, provider);

const profile = {
user_id: '1234567890',
username: 'steve',
display_name: 'steve',
first_name: 'steve',
last_name: 'smith',
email: 'steve@example.com'
};

Mock.override('https://login.salesforce.com/services/oauth2/userinfo', profile);

server.auth.strategy('custom', 'bell', {
password: 'cookie_encryption_password_secure',
isSecure: false,
clientId: 'salesforce',
clientSecret: 'secret',
provider: custom
});

server.route({
method: '*',
path: '/login',
config: {
auth: 'custom',
handler: function (request, reply) {

reply(request.auth.credentials);
}
}
});

server.inject('/login', (res) => {

const cookie = res.headers['set-cookie'][0].split(';')[0] + ';';
mock.server.inject(res.headers.location, (mockRes) => {

server.inject({ url: mockRes.headers.location, headers: { cookie } }, (response) => {

Mock.clear();
expect(response.result).to.equal({
provider: 'custom',
token: '456',
expiresIn: 3600,
refreshToken: undefined,
query: {}
});
mock.stop(done);
});
Expand Down

0 comments on commit 3a4a7c2

Please sign in to comment.