aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRubén Beltrán del Río <ben@nsovocal.com>2017-01-19 00:25:01 -0600
committerGitHub <noreply@github.com>2017-01-19 00:25:01 -0600
commit287fa13b3e600b2340895a5463a288bf08101bb5 (patch)
tree68b35cdd1bea40a6d51665ac7ed6dc4044eeda49 /lib
parentcc69fef4b7e1587a91a0603d4bd1a46d0b133dd5 (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.js95
-rw-r--r--lib/handlers/auth.js98
-rw-r--r--lib/twitter_helper.js78
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);
+ });
+ }
+};