UNPKG

7.39 kBJavaScriptView Raw
1/*!
2 * Copyright(c) 2014 Jan Blaha
3 *
4 * Extension used for authenticating user. When the extension is enabled user needs to specify
5 * credentials before the jsreport will serve the request.
6 *
7 * Browser requests are authenticated using cookie.
8 * API requests are authenticated using basic auth.
9 */
10
11var path = require('path')
12var passport = require('passport')
13var LocalStrategy = require('passport-local').Strategy
14var BasicStrategy = require('passport-http').BasicStrategy
15var sessions = require('client-sessions')
16var S = require('string')
17var _ = require('underscore')
18var url = require('url')
19var bodyParser = require('body-parser')
20var UsersRepository = require('./usersRepository')
21var viewsPath = path.join(__dirname, '../public/views')
22
23function addPassport (reporter, app, admin, definition) {
24 if (app.isAuthenticated) {
25 return
26 }
27
28 app.use(sessions({
29 cookieName: 'session',
30 cookie: definition.options.cookieSession.cookie,
31 secret: definition.options.cookieSession.secret,
32 duration: 1000 * 60 * 60 * 24 * 365 * 10 // forever
33 }))
34
35 app.use(passport.initialize())
36 app.use(passport.session())
37
38 function authenticate (username, password, done) {
39 if (admin.username === username && admin.password === password) {
40 return done(null, admin)
41 }
42
43 reporter.authentication.usersRepository.authenticate(username, password).then(function (user) {
44 if (!user) {
45 return done(null, false, {message: 'Invalid password or user does not exists.'})
46 }
47
48 return done(null, user)
49 }).catch(function (e) {
50 done(null, false, {message: e.message})
51 })
52 }
53
54 passport.use(new LocalStrategy(authenticate))
55
56 passport.use(new BasicStrategy(authenticate))
57
58 passport.serializeUser(function (user, done) {
59 done(null, user.username)
60 })
61
62 passport.deserializeUser(function (id, done) {
63 if (id === admin.username) {
64 return done(null, admin)
65 }
66
67 reporter.authentication.usersRepository.find(id).then(function (user) {
68 done(null, user)
69 }).catch(function (e) {
70 done(e)
71 })
72 })
73
74 app.get('/login', function (req, res, next) {
75 if (!req.user) {
76 var viewModel = _.extend({}, req.session.viewModel || {})
77 req.session.viewModel = null
78 return res.render(path.join(viewsPath, 'login.html'), {
79 viewModel: viewModel,
80 options: reporter.options
81 })
82 } else {
83 next()
84 }
85 })
86
87 app.post('/login', bodyParser.urlencoded({extended: true, limit: '2mb'}), function (req, res, next) {
88 req.session.viewModel = req.session.viewModel || {}
89
90 passport.authenticate('local', function (err, user, info) {
91 if (err) {
92 return next(err)
93 }
94
95 if (!user) {
96 req.session.viewModel.login = info.message
97 return res.redirect(reporter.options.appPath + '?returnUrl=' + encodeURIComponent(req.query.returnUrl || '/'))
98 }
99
100 req.session.viewModel = {}
101 req.logIn(user, function (err) {
102 if (err) {
103 return next(err)
104 }
105
106 req.user = user
107 reporter.logger.info('Logging in user ' + user.username)
108
109 return res.redirect(decodeURIComponent(req.query.returnUrl) || '/')
110 })
111 })(req, res, next)
112 })
113
114 app.post('/logout', function (req, res) {
115 req.logout()
116 res.redirect(reporter.options.appPath)
117 })
118
119 app.use(function (req, res, next) {
120 if (req.isAuthenticated()) {
121 return next()
122 }
123
124 passport.authenticate('basic', function (err, user, info) {
125 if (err) {
126 return next(err)
127 }
128
129 if (user) {
130 req.logIn(user, function () {
131 reporter.logger.debug('API logging in user ' + user.username)
132 next()
133 })
134 } else {
135 if (req.url.indexOf('/api') > -1 || req.url.indexOf('/odata') > -1) {
136 if (req.isPublic) {
137 return next()
138 }
139 res.setHeader('WWW-Authenticate', 'Basic realm=\'realm\'')
140 return res.status(401).end()
141 }
142
143 next()
144 }
145 })(req, res, next)
146 })
147}
148
149function configureRoutes (reporter, app, admin, definition) {
150 app.use(function (req, res, next) {
151 var publicRoute = _.find(reporter.authentication.publicRoutes, function (r) {
152 return S(req.url).startsWith(r)
153 })
154
155 var pathname = url.parse(req.url).pathname
156
157 req.isPublic = publicRoute || S(pathname).endsWith('.js') || S(pathname).endsWith('.css')
158 next()
159 })
160
161 addPassport(reporter, app, admin, definition)
162
163 app.use(function (req, res, next) {
164 if (req.isAuthenticated() || req.isPublic) {
165 return next()
166 }
167
168 var viewModel = _.extend({}, req.session.viewModel || {})
169 req.session.viewModel = null
170
171 return res.render(path.join(viewsPath, 'login.html'), {
172 viewModel: viewModel,
173 options: reporter.options
174 })
175 })
176
177 app.use(function (req, res, next) {
178 if (!reporter.authorization || req.isPublic) {
179 return next()
180 }
181
182 reporter.authorization.authorizeRequest(req, res).then(function (result) {
183 if (result) {
184 return next()
185 }
186
187 if (req.url.indexOf('/api') > -1 || req.url.indexOf('/odata') > -1) {
188 res.setHeader('WWW-Authenticate', 'Basic realm=\'realm\'')
189 return res.status(401).end()
190 }
191
192 return res.redirect('/login')
193 }).catch(function (e) {
194 next(e)
195 })
196 })
197
198 app.post('/api/users/:shortid/password', function (req, res, next) {
199 reporter.authentication.usersRepository.changePassword(req.user, req.params.shortid, req.body.oldPassword, req.body.newPassword).then(function (user) {
200 res.send({result: 'ok'})
201 }).catch(function (e) {
202 next(e)
203 })
204 })
205
206 app.get('/api/current-user', function (req, res, next) {
207 res.send({username: req.user.username})
208 })
209}
210
211function Authentication (reporter) {
212 this.publicRoutes = [
213 '/?studio=embed', '/css', '/img', '/js', '/lib', '/html-templates',
214 '/api/recipe', '/api/engine', '/api/settings', '/favicon.ico', '/api/extensions', '/odata/settings']
215
216 this.usersRepository = new UsersRepository(reporter)
217
218 if (reporter.compilation) {
219 reporter.compilation.resourceInTemp('login.html', path.join(__dirname, '../public/views/login.html'))
220 }
221
222 viewsPath = reporter.execution ? reporter.execution.tempDirectory : viewsPath
223}
224
225module.exports = function (reporter, definition) {
226 if (!definition.options.admin) {
227 definition.options.enabled = false
228 return
229 }
230
231 definition.options.admin.name = definition.options.admin.username
232 definition.options.admin.isAdmin = true
233
234 reporter.authentication = new Authentication(reporter)
235
236 reporter.on('export-public-route', function (route) {
237 reporter.authentication.publicRoutes.push(route)
238 })
239
240 reporter.on('after-express-static-configure', function (app) {
241 app.engine('html', require('ejs').renderFile)
242
243 reporter.emit('before-authentication-express-routes', app)
244 configureRoutes(reporter, app, definition.options.admin, definition)
245 // avoid exposing secrets and admin password through /api/extensions
246 definition.options = {}
247 reporter.emit('after-authentication-express-routes', app)
248 })
249}