blob: 6543d7695665b9be539d3e4b2272f735ff903fcf [file] [log] [blame]
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)
}