From 8e79fee52f35d891789f86cff11679a499184f5a Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sat, 31 Aug 2019 14:08:22 +0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + README.md | 89 ++++++++++++++++++++++++++++++++++++++++++++ example/package.json | 17 +++++++++ example/server.js | 54 +++++++++++++++++++++++++++ lib/index.js | 14 +++++++ lib/strategy.js | 85 ++++++++++++++++++++++++++++++++++++++++++ package.json | 28 ++++++++++++++ 7 files changed, 289 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 example/package.json create mode 100644 example/server.js create mode 100644 lib/index.js create mode 100644 lib/strategy.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..929dccc --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log* \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b93a38 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# passport-icynet + +Passport strategy for authentication with [Icy Network](https://icynet.eu) through the OAuth 2.0 API. + +## Usage +`npm install passport-icynet --save` + +#### Configure Strategy +The Icy Network authentication strategy authenticates users via a Icy Network user account and OAuth 2.0 token(s). A Icy Network API client ID, secret and redirect URL must be supplied when using this strategy. The strategy also requires a `verify` callback, which receives the access token and an optional refresh token, as well as a `profile` which contains the authenticated Icy Network user's profile. The `verify` callback must also call `cb` providing a user to complete the authentication. + +```javascript +var IcyNetworkStrategy = require('passport-icynet').Strategy; + +var scopes = ['image', 'email']; + +passport.use(new Icy NetworkStrategy({ + clientID: 'id', + clientSecret: 'secret', + callbackURL: 'callbackURL', + scope: scopes +}, +function(accessToken, refreshToken, profile, cb) { + User.findOrCreate({ icynetId: profile.id }, function(err, user) { + return cb(err, user); + }); +})); +``` + +#### Authentication Requests +Use `passport.authenticate()`, and specify the `'icynet'` strategy to authenticate requests. + +For example, as a route middleware in an Express app: + +```javascript +app.get('/auth/icynet', passport.authenticate('icynet')); +app.get('/auth/icynet/callback', passport.authenticate('icynet', { + failureRedirect: '/' +}), function(req, res) { + res.redirect('/secretstuff') // Successful auth +}); +``` + +#### Refresh Token Usage +In some use cases where the profile may be fetched more than once or you want to keep the user authenticated, refresh tokens may wish to be used. A package such as `passport-oauth2-refresh` can assist in doing this. + +Example: + +`npm install passport-oauth2-refresh --save` + +```javascript +var IcyNetworkStrategy = require('passport-icynet').Strategy + , refresh = require('passport-oauth2-refresh'); + +var icynetStrat = new Icy NetworkStrategy({ + clientID: 'id', + clientSecret: 'secret', + callbackURL: 'callbackURL' +}, +function(accessToken, refreshToken, profile, cb) { + profile.refreshToken = refreshToken; // store this for later refreshes + User.findOrCreate({ icynetId: profile.id }, function(err, user) { + if (err) + return done(err); + + return cb(err, user); + }); +}); + +passport.use(icynetStrat); +refresh.use(icynetStrat); +``` + +... then if we require refreshing when fetching an update or something ... + +```javascript +refresh.requestNewAccessToken('icynet', profile.refreshToken, function(err, accessToken, refreshToken) { + if (err) + throw; // boys, we have an error here. + + profile.accessToken = accessToken; // store this new one for our new requests! +}); +``` + + +## Examples +An Express server example can be found in the `/example` directory. Be sure to `npm install` in that directory to get the dependencies. + +## Credits +* Jared Hanson - used passport-github to understand passport more and kind of as a base. diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000..bad2bcd --- /dev/null +++ b/example/package.json @@ -0,0 +1,17 @@ +{ + "name": "passport-icynet-example", + "version": "0.1.0", + "description": "", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "author": "Evert \"Diamond\" Prants ", + "license": "ISC", + "dependencies": { + "express": "^4.13.4", + "express-session": "^1.13.0", + "passport": "^0.3.2" + } +} diff --git a/example/server.js b/example/server.js new file mode 100644 index 0000000..3be1002 --- /dev/null +++ b/example/server.js @@ -0,0 +1,54 @@ +var express = require('express') + , session = require('express-session') + , passport = require('passport') + , Strategy = require('../lib').Strategy + , app = express(); + +passport.serializeUser(function(user, done) { + done(null, user); +}); +passport.deserializeUser(function(obj, done) { + done(null, obj); +}); + +var scopes = ['image', 'email']; + +passport.use(new Strategy({ + clientID: '', + clientSecret: '', + callbackURL: 'http://localhost:5000/callback', + scope: scopes +}, function(accessToken, refreshToken, profile, done) { + process.nextTick(function() { + return done(null, profile); + }); +})); + +app.use(session({ + secret: 'keyboard cat', + resave: false, + saveUninitialized: false +})); +app.use(passport.initialize()); +app.use(passport.session()); +app.get('/', passport.authenticate('icynet', { scope: scopes }), function(req, res) {}); +app.get('/callback', + passport.authenticate('icynet', { failureRedirect: '/' }), function(req, res) { res.redirect('/info') } // auth success +); +app.get('/logout', function(req, res) { + req.logout(); + res.redirect('/'); +}); +app.get('/info', checkAuth, function(req, res) { + res.json(req.user); +}); + +function checkAuth(req, res, next) { + if (req.isAuthenticated()) return next(); + res.send('not logged in :('); +} + +app.listen(5000, function (err) { + if (err) return console.log(err) + console.log('Listening at http://localhost:5000/') +}) diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..9b816f5 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,14 @@ +/** + * Module dependencies. + */ +var Strategy = require('./strategy'); + +/** + * Expose `Strategy` directly from package. + */ +exports = module.exports = Strategy; + +/** + * Export constructors. + */ +exports.Strategy = Strategy; \ No newline at end of file diff --git a/lib/strategy.js b/lib/strategy.js new file mode 100644 index 0000000..fc8cca9 --- /dev/null +++ b/lib/strategy.js @@ -0,0 +1,85 @@ +/** + * Dependencies + */ +var OAuth2Strategy = require('passport-oauth2') + , InternalOAuthError = require('passport-oauth2').InternalOAuthError + , util = require('util'); + +/** + * `Strategy` constructor. + * + * The Icy Network authentication strategy authenticates requests by delegating + * to Icy Network via the OAuth2.0 protocol + * + * Applications must supply a `verify` callback which accepts an `accessToken`, + * `refreshToken` and service-specific `profile`, and then calls the `cb` + * callback supplying a `user`, which should be set to `false` if the + * credentials are not valid. If an exception occured, `err` should be set. + * + * Options: + * - `clientID` OAuth ID to icynet + * - `clientSecret` OAuth Secret to verify client to icynet + * - `callbackURL` URL that icynet will redirect to after auth + * - `scope` Array of permission scopes to request + * Check the official documentation for valid scopes to pass as an array. + * + * @constructor + * @param {object} options + * @param {function} verify + * @access public + */ +function Strategy(options, verify) { + options = options || {}; + options.authorizationURL = options.authorizationURL || 'https://icynet.eu/oauth2/authorize'; + options.tokenURL = options.tokenURL || 'https://icynet.eu/oauth2/token'; + options.scopeSeparator = options.scopeSeparator || ' '; + + OAuth2Strategy.call(this, options, verify); + this.name = 'icynet'; + this._oauth2.useAuthorizationHeaderforGET(true); +} + +/** + * Inherits from `OAuth2Strategy` + */ +util.inherits(Strategy, OAuth2Strategy); + +/** + * Retrieve user profile from Icy Network. + * + * This function constructs a normalized profile. + * Along with the properties returned from /oauth2/user, properties returned include: + * - `email` Email address if you requested this scope + * - `image` Profile picture if you requested this scope + * - `privilege` Icy Network privilege level if you requested this scope + * - `accessToken` The access token used to fetch the (may be useful for refresh) + * + * @param {string} accessToken + * @param {function} done + * @access protected + */ +Strategy.prototype.userProfile = function(accessToken, done) { + var self = this; + this._oauth2.get('https://icynet.eu/oauth2/user', accessToken, function(err, body, res) { + if (err) { + return done(new InternalOAuthError('Failed to fetch the user profile.', err)) + } + + try { + var parsedData = JSON.parse(body); + } catch (e) { + return done(new Error('Failed to parse the user profile.')); + } + + var profile = parsedData; // has the basic user stuff + profile.provider = 'icynet'; + profile.accessToken = accessToken; + + done(null, profile) + }); +}; + +/** + * Expose `Strategy`. + */ +module.exports = Strategy; diff --git a/package.json b/package.json new file mode 100644 index 0000000..64edda5 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "passport-icynet", + "version": "0.0.1", + "description": "Passport strategy for authentication with Icy Network (icynet.eu)", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "git+https://gitlab.icynet.eu/IcyNetwork/passport-icynet.git" + }, + "keywords": [ + "passport", + "icynet", + "auth", + "authentication", + "authn", + "identity", + "oauth2" + ], + "author": "Evert \"Diamond\" Prants ", + "license": "MIT", + "bugs": { + "url": "https://gitlab.icynet.eu/IcyNetwork/passport-icynet/issues" + }, + "homepage": "https://gitlab.icynet.eu/IcyNetwork/passport-icynet#readme", + "dependencies": { + "passport-oauth2": "^1.2.0" + } +}