ep_openid_connect 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +36 -4
- package/package.json +1 -1
- package/static/tests/backend/login.js +1 -1
- package/static/tests/backend/specs/tests.js +18 -7
package/index.js
CHANGED
|
@@ -135,7 +135,7 @@ exports.expressCreateServer = (hookName, {app}) => {
|
|
|
135
135
|
// userinfo should not be stored in req.session until after all checks have passed. (Otherwise
|
|
136
136
|
// it would be too easy to accidentally introduce a vulnerability.)
|
|
137
137
|
oidcSession.userinfo = userinfo;
|
|
138
|
-
res.redirect(oidcSession.next || settings.base_url);
|
|
138
|
+
res.redirect(303, oidcSession.next || settings.base_url);
|
|
139
139
|
// Defer deletion of state until success so that the user can reload the page to retry after a
|
|
140
140
|
// transient backchannel failure.
|
|
141
141
|
delete oidcSession.callbackChecks;
|
|
@@ -161,7 +161,7 @@ exports.expressCreateServer = (hookName, {app}) => {
|
|
|
161
161
|
...commonParams,
|
|
162
162
|
code_verifier: generators.codeVerifier(), // RFC7636
|
|
163
163
|
};
|
|
164
|
-
res.redirect(oidcClient.authorizationUrl({
|
|
164
|
+
res.redirect(303, oidcClient.authorizationUrl({
|
|
165
165
|
...commonParams,
|
|
166
166
|
// RFC7636
|
|
167
167
|
code_challenge: generators.codeChallenge(oidcSession.callbackChecks.code_verifier),
|
|
@@ -174,7 +174,7 @@ exports.expressCreateServer = (hookName, {app}) => {
|
|
|
174
174
|
logger.warn('Not configured; ignoring request.');
|
|
175
175
|
return next();
|
|
176
176
|
}
|
|
177
|
-
req.session.destroy(() => res.redirect(settings.base_url));
|
|
177
|
+
req.session.destroy(() => res.redirect(303, settings.base_url));
|
|
178
178
|
});
|
|
179
179
|
};
|
|
180
180
|
|
|
@@ -206,9 +206,41 @@ exports.authenticate = (hookName, {req, res, users}) => {
|
|
|
206
206
|
|
|
207
207
|
exports.authnFailure = (hookName, {req, res}) => {
|
|
208
208
|
if (oidcClient == null) return;
|
|
209
|
+
// Normally the user is redirected to the login page which would then redirect the user back once
|
|
210
|
+
// authenticated. For non-GET requests, send a 401 instead because users can't be redirected back.
|
|
211
|
+
// Also send a 401 if an Authorization header is present to facilitate API error handling.
|
|
212
|
+
//
|
|
213
|
+
// 401 is the status that most closely matches the desired semantics. However, RFC7235 section
|
|
214
|
+
// 3.1 says, "The server generating a 401 response MUST send a WWW-Authenticate header field
|
|
215
|
+
// containing at least one challenge applicable to the target resource." Etherpad uses a token
|
|
216
|
+
// (signed session identifier) transmitted via cookie for authentication, but there is no
|
|
217
|
+
// standard authentication scheme name for that. So we use a non-standard name here.
|
|
218
|
+
//
|
|
219
|
+
// We could theoretically implement Bearer authorization (RFC6750), but it's unclear to me how
|
|
220
|
+
// to do this correctly and securely:
|
|
221
|
+
// * The userinfo endpoint is meant for the OAuth client, not the resource server, so it
|
|
222
|
+
// shouldn't be used to look up claims.
|
|
223
|
+
// * In general, access tokens might be opaque (not JWTs) so we can't get claims by parsing
|
|
224
|
+
// them.
|
|
225
|
+
// * The token introspection endpoint should return scope and subject (I think?), but probably
|
|
226
|
+
// not claims.
|
|
227
|
+
// * If claims can't be used to convey access level, how is it conveyed? Scope? Resource
|
|
228
|
+
// indicators (RFC8707)?
|
|
229
|
+
// * How is intended audience checked? Or is introspection guaranteed to do that for us?
|
|
230
|
+
// * Should tokens be limited to a particular pad?
|
|
231
|
+
// * Bearer tokens are only meant to convey authorization; authentication is handled by the
|
|
232
|
+
// authorization server. Should Bearer tokens be processed during the authorize hook?
|
|
233
|
+
// * How should bearer authentication interact with authorization plugins?
|
|
234
|
+
// * How should bearer authentication interact with plugins that add new endpoints?
|
|
235
|
+
// * Would we have to implement our own OAuth server to issue access tokens?
|
|
236
|
+
res.header('WWW-Authenticate', 'Etherpad');
|
|
237
|
+
if (!['GET', 'HEAD'].includes(req.method) || req.headers.authorization) {
|
|
238
|
+
res.status(401).end();
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
209
241
|
if (req.session.ep_openid_connect == null) req.session.ep_openid_connect = {};
|
|
210
242
|
req.session.ep_openid_connect.next = new URL(req.url.slice(1), settings.base_url).toString();
|
|
211
|
-
res.redirect(endpointUrl('login'));
|
|
243
|
+
res.redirect(303, endpointUrl('login'));
|
|
212
244
|
return true;
|
|
213
245
|
};
|
|
214
246
|
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ const common = require('ep_etherpad-lite/tests/backend/common');
|
|
|
6
6
|
const logger = common.logger;
|
|
7
7
|
|
|
8
8
|
const followRedirects = async (agent, res) => {
|
|
9
|
-
if (
|
|
9
|
+
if (res.status !== 303) return res;
|
|
10
10
|
const url = new URL(res.headers.location, res.request.url);
|
|
11
11
|
logger.debug(`redirected to ${url}`);
|
|
12
12
|
return await followRedirects(agent, await agent.get(url));
|
|
@@ -64,13 +64,13 @@ describe(__filename, function () {
|
|
|
64
64
|
|
|
65
65
|
it('not logged in redirects to login endpoint', async function () {
|
|
66
66
|
await agent.get(common.baseUrl)
|
|
67
|
-
.expect(
|
|
67
|
+
.expect(303)
|
|
68
68
|
.expect('location', new URL('/ep_openid_connect/login', common.baseUrl).toString());
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
it('login endpoint redirects to authorization URL', async function () {
|
|
72
72
|
await agent.get(new URL('/ep_openid_connect/login', common.baseUrl))
|
|
73
|
-
.expect(
|
|
73
|
+
.expect(303)
|
|
74
74
|
.expect('location', /.*/)
|
|
75
75
|
.expect((res) => {
|
|
76
76
|
assert(res.headers.location.startsWith(`${new URL('/auth', issuer)}?`));
|
|
@@ -205,13 +205,24 @@ describe(__filename, function () {
|
|
|
205
205
|
assert.equal(res.request.url, url);
|
|
206
206
|
assert.equal(res.status, 200);
|
|
207
207
|
await agent.get(new URL('/ep_openid_connect/logout', common.baseUrl))
|
|
208
|
-
.expect(
|
|
208
|
+
.expect(303)
|
|
209
209
|
.expect('location', new URL('/', common.baseUrl).toString());
|
|
210
210
|
// Visiting any page that requires authentication should now redirect to the login page.
|
|
211
211
|
await agent.get(new URL('/', common.baseUrl))
|
|
212
|
-
.expect(
|
|
213
|
-
.expect('location',
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
.expect(303)
|
|
213
|
+
.expect('location', new URL('/ep_openid_connect/login', common.baseUrl).toString());
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('HTTP PUT gives 401 instead of redirect', async function () {
|
|
217
|
+
await agent.put(common.baseUrl)
|
|
218
|
+
.expect(401)
|
|
219
|
+
.expect('www-authenticate', 'Etherpad');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('Authorization header results in 401 instead of redirect', async function () {
|
|
223
|
+
await agent.get(common.baseUrl)
|
|
224
|
+
.set('Authorization', 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')
|
|
225
|
+
.expect(401)
|
|
226
|
+
.expect('www-authenticate', 'Etherpad');
|
|
216
227
|
});
|
|
217
228
|
});
|