From d80b6f9949f37817d181d42d67b840bfc98dae84 Mon Sep 17 00:00:00 2001 From: Marcus Poehls Date: Tue, 9 May 2017 05:19:05 +0200 Subject: [PATCH] add DigitalOcean provider (#312) * add digitalocean provider * add DigitalOcean to provider documentation * remove unused options from DigitalOcean provider * add DigitalOcean to supported provides in Readme --- Providers.md | 21 ++++ README.md | 2 +- lib/providers/digitalocean.js | 31 ++++++ lib/providers/index.js | 1 + test/providers/digitalocean.js | 176 +++++++++++++++++++++++++++++++++ 5 files changed, 230 insertions(+), 1 deletion(-) create mode 100755 lib/providers/digitalocean.js create mode 100755 test/providers/digitalocean.js diff --git a/Providers.md b/Providers.md index 164dfa5f..47a91bd9 100644 --- a/Providers.md +++ b/Providers.md @@ -108,6 +108,27 @@ credentials.profile = { }; ``` +### DigitalOcean + +[Provider Documentation](https://developers.digitalocean.com/documentation/oauth) + +- `scope`: defaults to `read` scope +- `config`: not applicable +- `auth`: https://cloud.digitalocean.com/v1/oauth/authorize +- `token`: https://cloud.digitalocean.com/v1/oauth/token + +The default profile response will look like this: + +```javascript +credentials.profile = { + id: profile.account.uuid, + email: profile.account.email, + status: profile.account.status, + dropletLimit: profile.account.droplet_limit, + raw: profile.account +}; +``` + ### Discord [Provider Documentation](https://discordapp.com/developers/docs/topics/oauth2) diff --git a/README.md b/README.md index b9e5a93b..5e2e707e 100755 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Lead Maintainer: [Lois Desplat](https://github.com/ldesplat) [![Build Status](https://secure.travis-ci.org/hapijs/bell.png)](http://travis-ci.org/hapijs/bell) -**bell** ships with built-in support for authentication using `Facebook`, `GitHub`, `Google`, `Google Plus`, `Instagram`, `LinkedIn`, `Slack`, `Twitter`, `Yahoo`, `Foursquare`, `VK`, `ArcGIS Online`, `Windows Live`, `Nest`, `Phabricator`, `BitBucket`, `Dropbox`, `Reddit`, `Tumblr`, `Twitch`, `Salesforce`, `Pinterest`, `Discord`, and `Okta`. It also supports any compliant `OAuth 1.0a` and `OAuth 2.0` based login services with a simple configuration object. +**bell** ships with built-in support for authentication using `Facebook`, `GitHub`, `Google`, `Google Plus`, `Instagram`, `LinkedIn`, `Slack`, `Twitter`, `Yahoo`, `Foursquare`, `VK`, `ArcGIS Online`, `Windows Live`, `Nest`, `Phabricator`, `BitBucket`, `Dropbox`, `Reddit`, `Tumblr`, `Twitch`, `Salesforce`, `Pinterest`, `Discord`, `DigitalOcean`, and `Okta`. It also supports any compliant `OAuth 1.0a` and `OAuth 2.0` based login services with a simple configuration object. ## Documentation diff --git a/lib/providers/digitalocean.js b/lib/providers/digitalocean.js new file mode 100755 index 00000000..42929553 --- /dev/null +++ b/lib/providers/digitalocean.js @@ -0,0 +1,31 @@ +'use strict'; + +exports = module.exports = function () { + + const digitalOceanUrl = 'https://cloud.digitalocean.com/v1/oauth'; + const digitalOceanUserUrl = 'https://api.digitalocean.com/v2/account'; + + return { + protocol: 'oauth2', + useParamsAuth: true, + auth: digitalOceanUrl + '/authorize', + token: digitalOceanUrl + '/token', + profile: function (credentials, params, get, callback) { + + get(digitalOceanUserUrl, null, (profile) => { + + const account = profile.account; + + credentials.profile = { + id: account.uuid, + email: account.email, + status: account.status, + dropletLimit: account.droplet_limit, + raw: account + }; + + return callback(); + }); + } + }; +}; diff --git a/lib/providers/index.js b/lib/providers/index.js index 02e2f505..3bf4f434 100755 --- a/lib/providers/index.js +++ b/lib/providers/index.js @@ -5,6 +5,7 @@ exports = module.exports = { auth0: require('./auth0'), azuread: require('./azuread'), bitbucket: require('./bitbucket'), + digitalocean: require('./digitalocean'), discord: require('./discord'), dropbox: require('./dropbox'), facebook: require('./facebook'), diff --git a/test/providers/digitalocean.js b/test/providers/digitalocean.js new file mode 100755 index 00000000..9e45a842 --- /dev/null +++ b/test/providers/digitalocean.js @@ -0,0 +1,176 @@ +'use strict'; + +// Load modules + +const Bell = require('../../'); +const Code = require('code'); +const Hapi = require('hapi'); +const Hoek = require('hoek'); +const Lab = require('lab'); +const Mock = require('../mock'); + + +// Test shortcuts + +const lab = exports.lab = Lab.script(); +const describe = lab.describe; +const it = lab.it; +const expect = Code.expect; + + +describe('digitalocean', () => { + + it('authenticates with mock', { 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.digitalocean(); + Hoek.merge(custom, provider); + + const data = { + account: { + uuid: '1234', + email: 'stevesmith@test.com', + status: 'active', + droplet_limit: 3 + } + }; + + Mock.override('https://api.digitalocean.com/v2/account', data); + + server.auth.strategy('custom', 'bell', { + password: 'cookie_encryption_password_secure', + isSecure: false, + clientId: 'digitalocean', + 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, + secret: 'secret', + query: {}, + profile: { + + id: data.account.uuid, + email: data.account.email, + status: data.account.status, + dropletLimit: data.account.droplet_limit, + raw: data.account + } + }); + + mock.stop(done); + }); + }); + }); + }); + }); + }); + + it('authenticates with mock when user has no email set', { 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.digitalocean(); + Hoek.merge(custom, provider); + + const data = { + account: { + uuid: '1234', + status: 'active', + dropletLimit: 3 + } + }; + + Mock.override('https://api.digitalocean.com/v2/account', data); + + server.auth.strategy('custom', 'bell', { + password: 'cookie_encryption_password_secure', + isSecure: false, + clientId: 'digitalocean', + 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, + secret: 'secret', + query: {}, + profile: { + + id: data.account.uuid, + email: undefined, + status: data.account.status, + dropletLimit: data.account.droplet_limit, + raw: data.account + } + }); + + mock.stop(done); + }); + }); + }); + }); + }); + }); +});