diff --git a/lib/cli.js b/lib/cli.js index d9532099..b7a83456 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -863,18 +863,30 @@ program , 'port (or $PORT)' , Number , process.env.PORT || 7106) + .option('-i, --ssid ' + , 'session SSID (or $SSID)' + , String + , process.env.SSID || 'ssid') .option('-s, --secret ' , 'secret (or $SECRET)' , String , process.env.SECRET) + .option('-a, --auth-url ' + , 'URL to auth client' + , String) .action(function(options) { if (!options.secret) { this.missingArgument('--secret') } + if (!options.authUrl) { + this.missingArgument('--auth-url') + } require('./units/api')({ port: options.port + , ssid: options.ssid , secret: options.secret + , authUrl: options.authUrl }) }) @@ -1323,6 +1335,12 @@ program 'api' , '--port', options.apiPort , '--secret', options.authSecret + , '--auth-url', options.authUrl || util.format( + 'http://%s:%d/auth/%s/' + , options.publicIp + , options.poorxyPort + , ({oauth2: 'oauth'}[options.authType]) || options.authType + ) ]) // websocket , procutil.fork(__filename, [ diff --git a/lib/db/api.js b/lib/db/api.js index 59c801f0..65c5978f 100644 --- a/lib/db/api.js +++ b/lib/db/api.js @@ -329,4 +329,8 @@ dbapi.loadAccessTokens = function(email) { })) } +dbapi.loadAccessToken = function(id) { + return db.run(r.table('accessTokens').get(id)) +} + module.exports = dbapi diff --git a/lib/units/api/index.js b/lib/units/api/index.js index 23fd5397..10a3eb2c 100644 --- a/lib/units/api/index.js +++ b/lib/units/api/index.js @@ -3,9 +3,12 @@ var path = require('path') var express = require('express') var SwaggerExpress = require('swagger-express-mw') +var cookieSession = require('cookie-session') var logger = require('../../util/logger') +var auth = require('./middleware/auth') + module.exports = function(options) { var log = logger.createLogger('api') , app = express() @@ -22,6 +25,17 @@ module.exports = function(options) { swaggerExpress.register(app); }) + // TODO: Remove this once frontend is stateless + app.use(cookieSession({ + name: options.ssid + , keys: [options.secret] + })) + + app.use(auth({ + secret: options.secret + , authUrl: options.authUrl + })) + server.listen(options.port) log.info('Listening on port %d', options.port) } diff --git a/lib/units/api/middleware/auth.js b/lib/units/api/middleware/auth.js new file mode 100644 index 00000000..742c5884 --- /dev/null +++ b/lib/units/api/middleware/auth.js @@ -0,0 +1,65 @@ +var jwtutil = require('../../../util/jwtutil') +var urlutil = require('../../../util/urlutil') +var logger = require('../../../util/logger') +var dbapi = require('../../../db/api') + +module.exports = function(options) { + return function(req, res, next) { + + var log = logger.createLogger('api:auth') + + if (req.headers.authorization) { + var tokenId = req.headers.authorization.split(" ")[1] + + if (tokenId) { + dbapi.loadAccessToken(tokenId) + .then(function(token) { + var jwt = token.jwt + , data = jwtutil.decode(jwt, options.secret) + + if (data) { + dbapi.loadUser(data.email) + .then(function(user) { + if (user) { + req.user = user + next() + } + }) + } + }) + .catch(function(err) { + log.error('Failed to load token: ', err.stack) + res.json(500, { + success: false, + description: "Bad Access Token" + }) + }) + } else { + log.error("Bad Access Token") + res.json(500, { + success: false, + description: "Bad Access Token Header" + }) + } + } + // TODO: Remove this once frontend become stateless + else if (req.session && req.session.jwt) { + dbapi.loadUser(req.session.jwt.email) + .then(function(user) { + if (user) { + req.user = user + next() + } + else { + // We no longer have the user in the database + res.redirect(options.authUrl) + } + }) + .catch(next) + } + else { + // No session, forward to auth client + res.redirect(options.authUrl) + } + } +} diff --git a/lib/units/api/swagger/api_v1.yaml b/lib/units/api/swagger/api_v1.yaml index fd5cbd8f..0821c842 100644 --- a/lib/units/api/swagger/api_v1.yaml +++ b/lib/units/api/swagger/api_v1.yaml @@ -33,6 +33,7 @@ paths: description: Unexpected Error schema: $ref: "#/definitions/ErrorResponse" + definitions: UserResponse: required: