diff options
| author | Rubén Beltrán del Río <ben@nsovocal.com> | 2017-01-19 00:25:01 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-01-19 00:25:01 -0600 |
| commit | 287fa13b3e600b2340895a5463a288bf08101bb5 (patch) | |
| tree | 68b35cdd1bea40a6d51665ac7ed6dc4044eeda49 /lib | |
| parent | cc69fef4b7e1587a91a0603d4bd1a46d0b133dd5 (diff) | |
Add Login (#2)
* Add dependencies
* Add barebones app
* Ignore .DS_Store
* Add base static assets
* Expose port on docker
* Ignore env files
* Add instructions to use env.dist
* Add twitter configs
* Use latest node version
* Read env file properly
* Add dependencies to start parsing twitter
* Add helper for twitter login operations
* Add handler for titter login routes
* Use twitter handler
* Add JWT related keys
* Add jwt dependencies
* Move index static file
* Rename twitter handler to auth, add jwt support
* Add new yarn lock
* Add instructions for twitter
* Remove twitter image
* Fix return value lint errors
* Ignore require-yield errors in linter
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/dasein.js | 95 | ||||
| -rw-r--r-- | lib/handlers/auth.js | 98 | ||||
| -rw-r--r-- | lib/twitter_helper.js | 78 |
3 files changed, 270 insertions, 1 deletions
diff --git a/lib/dasein.js b/lib/dasein.js index 8de32c8..38654a4 100644 --- a/lib/dasein.js +++ b/lib/dasein.js @@ -1,13 +1,106 @@ 'use strict'; +const Koa = require('koa'); +const KoaJwt = require('koa-jwt'); +const KoaStatic = require('koa-static'); +const KoaRoute = require('koa-route'); + +const AuthHandler = require('./handlers/auth'); + const internals = {}; +internals.k401Location = '/401.html'; +internals.kMainLocation = '/'; + module.exports = internals.Dasein = class Dasein { + constructor(config) { + + Object.assign(this, config); + } + run() { - console.log('OK'); + this._initializeServer(); + this._startServer(); + this._printBanner(); return Promise.resolve(); } + + _initializeServer() { + + this._app = Koa(); + + this._app.keys = this.cookieKeys; + + this._app.use(KoaStatic(this.staticDirectory)); + + // Redirect all 401s to the 401 static page + + this._app.use(function * (next) { + + try { + yield next; + } + catch (err) { + if (err.status === 401) { + return this.redirect(internals.k401Location); + } + + throw err; + } + }); + + this._app.use(KoaJwt({ + secret: this.jwt.secret, + passthrough: true, + cookie: this.jwt.cookieName + })); + + // Handlers for Twitter Auth Related Routes + + const authHandler = new AuthHandler({ + hostname: this.hostname, + jwt: this.jwt, + twitter: this.twitter + }); + this._app.use(KoaRoute.get('/login', authHandler.login())); + this._app.use(KoaRoute.get('/login-callback', authHandler.callback())); + this._app.use(KoaRoute.get('/logout', authHandler.logout())); + + // The index + + this._app.use(function * () { + + if (this.state.user) { + this.body = `<img src="${this.state.user.profile_image_url_https}"> Hello ${this.state.user.screen_name}`; + return; + } + + this.body = 'Go to /login to login'; + return; + }); + } + + _startServer() { + + this._app.listen(this.port); + } + + // Prints the banner. + _printBanner() { + + console.log(' .'); + console.log(' /'); + console.log(' +-----+'); + console.log(` | o o | - Listening Gladly, Try me on port: ${this.port}`); + console.log(' +-----+'); + console.log(' +---------+'); + console.log(' /| [][] |\\'); + console.log(' || | |'); + console.log(' || | \\c'); + console.log(' ^+---------+'); + console.log(' (.) '); + } }; diff --git a/lib/handlers/auth.js b/lib/handlers/auth.js new file mode 100644 index 0000000..d16e15d --- /dev/null +++ b/lib/handlers/auth.js @@ -0,0 +1,98 @@ +'use strict'; + +const Co = require('co'); +const TwitterHelper = require('../twitter_helper'); +const JsonWebToken = require('jsonwebtoken'); +const Pify = require('pify'); + +const internals = {}; + +internals.kRedirectUrl = 'https://api.twitter.com/oauth/authenticate?oauth_token='; +internals.kMainLocation = '/'; + +internals.signJsonWebToken = Pify(JsonWebToken.sign); + +module.exports = internals.AuthHandler = class AuthHandler { + + constructor(config) { + + this._twitterHelper = new TwitterHelper(config.twitter); + this._jwtConfig = config.jwt; + this._hostname = config.hostname; + } + + login() { + + const twitterHelper = this._twitterHelper; + + return function *handleLogin() { + + if (this.state.user) { + return this.redirect(internals.kMainLocation); + } + + const requestToken = yield twitterHelper.getRequestToken(); + this.redirect(`${internals.kRedirectUrl}${requestToken.oAuthToken}`); + }; + } + + callback() { + + const self = this; + + return function *handleCallback() { + + if (this.request.query.denied) { + return this.throw(401); + } + + const oAuthToken = this.request.query.oauth_token; + const oAuthVerifier = this.request.query.oauth_verifier; + let user; + + try { + const accessToken = yield self._twitterHelper.getAccessToken(oAuthToken, oAuthVerifier); + user = yield self._twitterHelper.getUser(accessToken.oAuthAccessToken, accessToken.oAuthAccessTokenSecret); + } + catch (err) { + console.error(err.stack || err.message || err); + return this.throw(401); + } + + yield self._setJWT(user, this); + + this.redirect(internals.kMainLocation); + }; + } + + logout() { + + const self = this; + + return function * () { + + this.cookies.set(self._jwtConfig.cookieName, null); + this.redirect(internals.kMainLocation); + }; + } + + // Sets a JSON Web Token Cookie + _setJWT(payload, context) { + + const self = this; + + return Co(function * () { + + const token = yield internals.signJsonWebToken(payload, self._jwtConfig.secret, { + expiresIn: self._jwtConfig.duration + }); + + context.cookies.set(self._jwtConfig.cookieName, token, { + maxAge: self._jwtConfig.duration * 1000, + signed: true, + domain: self._hostname, + overwrite: true + }); + }); + } +}; diff --git a/lib/twitter_helper.js b/lib/twitter_helper.js new file mode 100644 index 0000000..3ab51fa --- /dev/null +++ b/lib/twitter_helper.js @@ -0,0 +1,78 @@ +'use strict'; + +const Co = require('co'); +const OAuth = require('oauth'); +const Pify = require('pify'); + +const internals = {}; + +internals.kRequestTokenUrl = 'https://api.twitter.com/oauth/request_token'; +internals.kAccessTokenUrl = 'https://api.twitter.com/oauth/access_token'; +internals.kVerifyCredentialsUrl = 'https://api.twitter.com/1.1/account/verify_credentials.json'; +internals.kOauthVersion = '1.0A'; +internals.kOauthSignatureMethod = 'HMAC-SHA1'; + +module.exports = internals.TwitterHelper = class TwitterHelper { + + constructor(config) { + + this._oAuth = new OAuth.OAuth( + internals.kRequestTokenUrl, + internals.kAccessTokenUrl, + config.consumerKey, + config.consumerSecret, + internals.kOauthVersion, + null, + internals.kOauthSignatureMethod + ); + } + + getRequestToken() { + + const self = this; + + return Co(function * () { + + const getOAuthRequestToken = Pify(self._oAuth.getOAuthRequestToken.bind(self._oAuth), { multiArgs: true }); + const [oAuthToken, oAuthTokenSecret] = yield getOAuthRequestToken(); + + return { + oAuthToken, + oAuthTokenSecret + }; + }); + } + + getAccessToken(oAuthToken, oAuthVerifier) { + + const self = this; + + return Co(function * () { + + const getOAuthAccessToken = Pify(self._oAuth.getOAuthAccessToken.bind(self._oAuth), { multiArgs: true }); + const [oAuthAccessToken, oAuthAccessTokenSecret] = yield getOAuthAccessToken(oAuthToken, + '', + oAuthVerifier); + + return { + oAuthAccessToken, + oAuthAccessTokenSecret + }; + }); + } + + getUser(oAuthAccessToken, oAuthAccessTokenSecret) { + + const self = this; + + return Co(function * () { + + const get = Pify(self._oAuth.get.bind(self._oAuth), { multiArgs: true }); + const [userResponse] = yield get(internals.kVerifyCredentialsUrl, + oAuthAccessToken, + oAuthAccessTokenSecret); + + return JSON.parse(userResponse); + }); + } +}; |