| var http = require('http') |
| var url = require('url') |
| var fs = require('fs') |
| |
| var express = require('express') |
| var validator = require('express-validator') |
| var cookieSession = require('cookie-session') |
| var bodyParser = require('body-parser') |
| var serveFavicon = require('serve-favicon') |
| var serveStatic = require('serve-static') |
| var csrf = require('csurf') |
| var Promise = require('bluebird') |
| var compression = require('compression') |
| |
| var logger = require('../../util/logger') |
| var pathutil = require('../../util/pathutil') |
| var dbapi = require('../../db/api') |
| var datautil = require('../../util/datautil') |
| var config = require('../../../config'); |
| |
| var auth = require('./middleware/auth') |
| var deviceIconMiddleware = require('./middleware/device-icons') |
| var browserIconMiddleware = require('./middleware/browser-icons') |
| var appstoreIconMiddleware = require('./middleware/appstore-icons') |
| |
| var markdownServe = require('markdown-serve') |
| |
| module.exports = function(options) { |
| var log = logger.createLogger('app') |
| var app = express() |
| var server = http.createServer(app) |
| |
| app.use('/static/wiki', markdownServe.middleware({ |
| rootDirectory: pathutil.root('node_modules/stf-wiki') |
| , view: 'docs' |
| })) |
| |
| app.set('view engine', 'jade') |
| app.set('views', pathutil.resource('app/views')) |
| app.set('strict routing', true) |
| app.set('case sensitive routing', true) |
| app.set('trust proxy', true) |
| |
| if (fs.existsSync(pathutil.resource('build'))) { |
| log.info('Using pre-built resources') |
| app.use(compression()) |
| app.use('/static/app/build/entry', |
| serveStatic(pathutil.resource('build/entry'))) |
| app.use('/static/app/build', serveStatic(pathutil.resource('build'), { |
| maxAge: '10d' |
| })) |
| } |
| else { |
| log.info('Using webpack') |
| // Keep webpack-related requires here, as our prebuilt package won't |
| // have them at all. |
| var webpackServerConfig = require('./../../../webpack.config').webpackServer |
| app.use('/static/app/build', |
| require('./middleware/webpack')(webpackServerConfig)) |
| } |
| |
| app.use('/static/bower_components', |
| serveStatic(pathutil.resource('bower_components'))) |
| app.use('/static/app/data', serveStatic(pathutil.resource('data'))) |
| app.use('/static/app/status', serveStatic(pathutil.resource('common/status'))) |
| app.use('/static/app/browsers', browserIconMiddleware()) |
| app.use('/static/app/appstores', appstoreIconMiddleware()) |
| app.use('/static/app/devices', deviceIconMiddleware()) |
| app.use('/static/app', serveStatic(pathutil.resource('app'))) |
| |
| app.use('/static/logo', |
| serveStatic(pathutil.resource('common/logo'))) |
| app.use(serveFavicon(pathutil.resource( |
| 'common/logo/exports/STF-128.png'))) |
| |
| app.use(cookieSession({ |
| name: options.ssid |
| , keys: [options.secret] |
| })) |
| |
| app.use(auth({ |
| secret: options.secret |
| , authUrl: options.authUrl |
| })) |
| |
| // This needs to be before the csrf() middleware or we'll get nasty |
| // errors in the logs. The dummy endpoint is a hack used to enable |
| // autocomplete on some text fields. |
| app.all('/app/api/v1/dummy', function(req, res) { |
| res.send('OK') |
| }) |
| |
| var expireToken = function(token, res) { |
| dbapi.expireToken(token).then(function() { |
| res.sendStatus(200); |
| }).catch(function(err) { |
| log.error('Error expiring token: ', err.stack); |
| res.sendStatus(500); |
| }); |
| }; |
| |
| app.delete('/app/api/v1/token/:token', function(req, res) { |
| var token = req.params.token; |
| |
| expireToken(token, res); |
| }); |
| |
| app.delete('/app/api/v1/token', function(req, res) { |
| var serial = req.query.serial; |
| |
| dbapi.getDeviceBySerial(serial).then(function(device) { |
| if (device && device.owner && device.owner.email) { |
| expireToken(device.owner.email, res); |
| } else { |
| res.status(404); |
| res.send('No owner found for serial: ' + serial); |
| } |
| }).catch(function(err) { |
| log.error('Failed to get device by serial: ', err.stack); |
| res.sendStatus(500); |
| }); |
| }); |
| |
| app.use(bodyParser.json()) |
| app.use(csrf()) |
| app.use(validator()) |
| |
| app.use(function(req, res, next) { |
| res.cookie('XSRF-TOKEN', req.csrfToken()) |
| next() |
| }) |
| |
| app.get('/', function(req, res) { |
| res.render('index', {stfConfig: JSON.stringify(config || {})}); |
| }) |
| |
| app.get('/app/api/v1/state.js', function(req, res) { |
| var state = { |
| config: { |
| websocketUrl: (function() { |
| var wsUrl = url.parse(options.websocketUrl, true) |
| wsUrl.query.uip = req.ip |
| return url.format(wsUrl) |
| })() |
| } |
| , user: req.user |
| } |
| |
| if (options.userProfileUrl) { |
| state.config.userProfileUrl = (function() { |
| return options.userProfileUrl |
| })() |
| } |
| |
| res.type('application/javascript') |
| res.send('var GLOBAL_APPSTATE = ' + JSON.stringify(state)) |
| }) |
| |
| app.get('/app/api/v1/user', function(req, res) { |
| res.json({ |
| success: true |
| , user: req.user |
| }) |
| }) |
| |
| app.get('/app/api/v1/group', function(req, res) { |
| dbapi.loadGroup(req.user.email) |
| .then(function(cursor) { |
| return Promise.promisify(cursor.toArray, cursor)() |
| .then(function(list) { |
| list.forEach(function(device) { |
| datautil.normalize(device, req.user) |
| }) |
| res.json({ |
| success: true |
| , devices: list |
| }) |
| }) |
| }) |
| .catch(function(err) { |
| log.error('Failed to load group: ', err.stack) |
| res.json(500, { |
| success: false |
| }) |
| }) |
| }) |
| |
| app.get('/app/api/v1/devices', function(req, res) { |
| dbapi.loadDevices() |
| .then(function(cursor) { |
| return Promise.promisify(cursor.toArray, cursor)() |
| .then(function(list) { |
| list.forEach(function(device) { |
| datautil.normalize(device, req.user) |
| }) |
| |
| res.json({ |
| success: true |
| , devices: list |
| }) |
| }) |
| }) |
| .catch(function(err) { |
| log.error('Failed to load device list: ', err.stack) |
| res.json(500, { |
| success: false |
| }) |
| }) |
| }) |
| |
| app.get('/app/api/v1/devices/:serial', function(req, res) { |
| dbapi.loadDevice(req.params.serial) |
| .then(function(device) { |
| if (device) { |
| datautil.normalize(device, req.user) |
| res.json({ |
| success: true |
| , device: device |
| }) |
| } |
| else { |
| res.json(404, { |
| success: false |
| }) |
| } |
| }) |
| .catch(function(err) { |
| log.error('Failed to load device "%s": ', req.params.serial, err.stack) |
| res.json(500, { |
| success: false |
| }) |
| }) |
| }) |
| |
| app.get('/app/api/v1/accessTokens', function(req, res) { |
| dbapi.loadAccessTokens(req.user.email) |
| .then(function(cursor) { |
| return Promise.promisify(cursor.toArray, cursor)() |
| .then(function(list) { |
| var titles = [] |
| list.forEach(function(token) { |
| titles.push(token.title) |
| }) |
| res.json({ |
| success: true |
| , titles: titles |
| }) |
| }) |
| }) |
| .catch(function(err) { |
| log.error('Failed to load tokens: ', err.stack) |
| res.json(500, { |
| success: false |
| }) |
| }) |
| }) |
| |
| // Tilde pattern is REST shorthand for "my", e.g. get my token. |
| app.get('/app/api/v1/token/~', function(req, res) { |
| if (!req.user || !req.user.email) { |
| res.status(404); |
| return res.send('Missing user email in request.'); |
| } |
| |
| dbapi.getToken(req.user.email).then(function(tokenObj) { |
| if (tokenObj) { |
| res.json(tokenObj); |
| } else { |
| res.status(404); |
| res.send('Token not found for email: ' + req.user.email); |
| } |
| }).catch(function(err) { |
| log.error('Failed to get token: ', err.stack); |
| res.sendStatus(500); |
| }); |
| }); |
| |
| server.listen(options.port) |
| log.info('Listening on port %d', options.port) |
| } |