UNPKG

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