-
Notifications
You must be signed in to change notification settings - Fork 304
/
Copy patherror-pages.js
211 lines (192 loc) · 5.47 KB
/
error-pages.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
const debug = require('../debug').server
const fs = require('fs')
const util = require('../utils')
const Auth = require('../api/authn')
// Required for HACK below
const Negotiator = require('negotiator')
// End Required for hack
/**
* Serves as a last-stop error handler for all other middleware.
*
* @param err {Error}
* @param req {IncomingRequest}
* @param res {ServerResponse}
* @param next {Function}
*/
async function handler (err, req, res, next) {
debug('Error page because of:', err)
const locals = req.app.locals
const authMethod = locals.authMethod
const ldp = locals.ldp
// If the user specifies this function,
// they can customize the error programmatically
if (ldp.errorHandler) {
debug('Using custom error handler')
return ldp.errorHandler(err, req, res, next)
}
const statusCode = statusCodeFor(err, req, authMethod)
switch (statusCode) {
case 401:
setAuthenticateHeader(req, res, err)
renderDataBrowser(req, res, err, ldp)
break
case 403:
renderNoPermission(req, res, err)
break
default:
if (ldp.noErrorPages) {
sendErrorResponse(statusCode, res, err)
} else {
sendErrorPage(statusCode, res, err, ldp)
}
}
}
/**
* Returns the HTTP status code for a given request error.
*
* @param err {Error}
* @param req {IncomingRequest}
* @param authMethod {string}
*
* @returns {number}
*/
function statusCodeFor (err, req, authMethod) {
let statusCode = err.status || err.statusCode || 500
if (authMethod === 'oidc') {
statusCode = Auth.oidc.statusCodeOverride(statusCode, req)
}
return statusCode
}
/**
* Dispatches the writing of the `WWW-Authenticate` response header (used for
* 401 Unauthorized responses).
*
* @param req {IncomingRequest}
* @param res {ServerResponse}
* @param err {Error}
*/
function setAuthenticateHeader (req, res, err) {
const locals = req.app.locals
const authMethod = locals.authMethod
switch (authMethod) {
case 'oidc':
Auth.oidc.setAuthenticateHeader(req, res, err)
break
case 'tls':
Auth.tls.setAuthenticateHeader(req, res)
break
default:
break
}
}
/**
* Sends the HTTP status code and error message in the response.
*
* @param statusCode {number}
* @param res {ServerResponse}
* @param err {Error}
*/
function sendErrorResponse (statusCode, res, err) {
res.status(statusCode)
res.header('Content-Type', 'text/plain;charset=utf-8')
res.send(err.message + '\n')
}
/**
* Sends the HTTP status code and error message as a custom error page.
*
* @param statusCode {number}
* @param res {ServerResponse}
* @param err {Error}
* @param ldp {LDP}
*/
function sendErrorPage (statusCode, res, err, ldp) {
const errorPage = ldp.errorPages + statusCode.toString() + '.html'
return new Promise((resolve) => {
fs.readFile(errorPage, 'utf8', (readErr, text) => {
if (readErr) {
// Fall back on plain error response
return resolve(sendErrorResponse(statusCode, res, err))
}
res.status(statusCode)
res.header('Content-Type', 'text/html')
res.send(text)
resolve()
})
})
}
function renderDataBrowser (req, res, err, ldp) {
const currentUrl = util.fullUrlForReq(req)
debug(`Display login-required for ${currentUrl}`)
// Render the databrowser if applicable
// HACK: this is basically a copy of the DB render in get.js. This could be
// better structured.
const negotiator = new Negotiator(req)
const requestedType = negotiator.mediaType()
res.statusMessage = err.message
res.status(401)
if (requestedType && requestedType.includes('text/html')) {
res.set('Content-Type', 'text/html')
const defaultDataBrowser = require.resolve('mashlib/dist/databrowser.html')
const dataBrowserPath = ldp.dataBrowserPath === 'default' ? defaultDataBrowser : ldp.dataBrowserPath
debug(' sending data browser file: ' + dataBrowserPath)
res.sendFile(dataBrowserPath)
} else {
renderLoginRequired(req, res, err)
}
// END HACK
}
/**
* Renders a 401 response explaining that a login is required.
*
* @param req {IncomingRequest}
* @param res {ServerResponse}
*/
function renderLoginRequired (req, res, err) {
const currentUrl = util.fullUrlForReq(req)
debug(`Display login-required for ${currentUrl}`)
res.render('auth/login-required', { currentUrl })
}
/**
* Renders a 403 response explaining that the user has no permission.
*
* @param req {IncomingRequest}
* @param res {ServerResponse}
*/
function renderNoPermission (req, res, err) {
const currentUrl = util.fullUrlForReq(req)
const webId = req.session.userId
debug(`Display no-permission for ${currentUrl}`)
res.statusMessage = err.message
res.status(403)
res.render('auth/no-permission', { currentUrl, webId })
}
/**
* Returns a response body for redirecting browsers to a Select Provider /
* login workflow page. Uses either a JS location.href redirect or an
* http-equiv type html redirect for no-script conditions.
*
* @param url {string}
*
* @returns {string} Response body
*/
function redirectBody (url) {
return `<!DOCTYPE HTML>
<meta charset="UTF-8">
<script>
window.location.href = "${url}" + encodeURIComponent(window.location.hash)
</script>
<noscript>
<meta http-equiv="refresh" content="0; url=${url}">
</noscript>
<title>Redirecting...</title>
If you are not redirected automatically,
follow the <a href='${url}'>link to login</a>
`
}
module.exports = {
handler,
redirectBody,
sendErrorPage,
sendErrorResponse,
setAuthenticateHeader
}