ep_openid_connect 2.4.0 → 2.5.3
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/.github/dependabot.yml +10 -0
- package/.github/workflows/backend-tests.yml +19 -7
- package/.github/workflows/frontend-tests.yml +8 -4
- package/.github/workflows/npmpublish.yml +18 -2
- package/index.js +36 -4
- package/package.json +2 -2
- package/static/tests/backend/login.js +1 -1
- package/static/tests/backend/specs/tests.js +18 -7
|
@@ -14,10 +14,6 @@ jobs:
|
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
|
|
16
16
|
steps:
|
|
17
|
-
- uses: actions/setup-node@v2
|
|
18
|
-
with:
|
|
19
|
-
node-version: 12
|
|
20
|
-
|
|
21
17
|
- name: Install libreoffice
|
|
22
18
|
run: |
|
|
23
19
|
sudo add-apt-repository -y ppa:libreoffice/ppa
|
|
@@ -30,9 +26,6 @@ jobs:
|
|
|
30
26
|
with:
|
|
31
27
|
repository: ether/etherpad-lite
|
|
32
28
|
|
|
33
|
-
- name: Install all dependencies and symlink for ep_etherpad-lite
|
|
34
|
-
run: src/bin/installDeps.sh
|
|
35
|
-
|
|
36
29
|
- name: Checkout plugin repository
|
|
37
30
|
uses: actions/checkout@v2
|
|
38
31
|
with:
|
|
@@ -50,6 +43,15 @@ jobs:
|
|
|
50
43
|
env:
|
|
51
44
|
PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
|
|
52
45
|
|
|
46
|
+
- uses: actions/setup-node@v2
|
|
47
|
+
with:
|
|
48
|
+
node-version: 12
|
|
49
|
+
cache: 'npm'
|
|
50
|
+
cache-dependency-path: |
|
|
51
|
+
src/package-lock.json
|
|
52
|
+
src/bin/doc/package-lock.json
|
|
53
|
+
node_modules/${{ steps.plugin_name.outputs.plugin_name }}/package-lock.json
|
|
54
|
+
|
|
53
55
|
- name: Install plugin dependencies
|
|
54
56
|
run: |
|
|
55
57
|
cd ./node_modules/"${PLUGIN_NAME}"
|
|
@@ -57,5 +59,15 @@ jobs:
|
|
|
57
59
|
env:
|
|
58
60
|
PLUGIN_NAME: ${{ steps.plugin_name.outputs.plugin_name }}
|
|
59
61
|
|
|
62
|
+
# This must be run after setting up the plugin, otherwise npm will try to
|
|
63
|
+
# hoist common dependencies by removing them from src/node_modules and
|
|
64
|
+
# installing them in the top-level node_modules. As of v6.14.10, npm's hoist
|
|
65
|
+
# logic appears to be buggy, because it sometimes removes dependencies from
|
|
66
|
+
# src/node_modules but fails to add them to the top-level node_modules. Even
|
|
67
|
+
# if npm correctly hoists the dependencies, the hoisting seems to confuse
|
|
68
|
+
# tools such as `npm outdated`, `npm update`, and some ESLint rules.
|
|
69
|
+
- name: Install Etherpad core dependencies
|
|
70
|
+
run: src/bin/installDeps.sh
|
|
71
|
+
|
|
60
72
|
- name: Run the backend tests
|
|
61
73
|
run: cd src && npm test
|
|
@@ -15,15 +15,19 @@ jobs:
|
|
|
15
15
|
printf %s\\n '::set-output name=name::${{github.event.repository.name}} ${{ github.workflow }} - ${{ github.job }}'
|
|
16
16
|
printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}'
|
|
17
17
|
|
|
18
|
-
- uses: actions/setup-node@v2
|
|
19
|
-
with:
|
|
20
|
-
node-version: 12
|
|
21
|
-
|
|
22
18
|
- name: Check out Etherpad core
|
|
23
19
|
uses: actions/checkout@v2
|
|
24
20
|
with:
|
|
25
21
|
repository: ether/etherpad-lite
|
|
26
22
|
|
|
23
|
+
- uses: actions/setup-node@v2
|
|
24
|
+
with:
|
|
25
|
+
node-version: 12
|
|
26
|
+
cache: 'npm'
|
|
27
|
+
cache-dependency-path: |
|
|
28
|
+
src/package-lock.json
|
|
29
|
+
src/bin/doc/package-lock.json
|
|
30
|
+
|
|
27
31
|
- name: Check out the plugin
|
|
28
32
|
uses: actions/checkout@v2
|
|
29
33
|
with:
|
|
@@ -29,9 +29,20 @@ jobs:
|
|
|
29
29
|
# etherpad-lite has been moved outside of $GITHUB_WORKSPACE, so it is now
|
|
30
30
|
# safe to clone this plugin's repo to $GITHUB_WORKSPACE.
|
|
31
31
|
- uses: actions/checkout@v2
|
|
32
|
-
|
|
32
|
+
# This is necessary for actions/setup-node because '..' can't be used in
|
|
33
|
+
# cache-dependency-path.
|
|
34
|
+
- name: Create ep_etherpad-lite symlink
|
|
35
|
+
run: |
|
|
36
|
+
mkdir -p node_modules
|
|
37
|
+
ln -s ../../etherpad-lite/src node_modules/ep_etherpad-lite
|
|
38
|
+
- uses: actions/setup-node@v2
|
|
33
39
|
with:
|
|
34
40
|
node-version: 12
|
|
41
|
+
cache: 'npm'
|
|
42
|
+
cache-dependency-path: |
|
|
43
|
+
node_modules/ep_etherpad-lite/package-lock.json
|
|
44
|
+
node_modules/ep_etherpad-lite/bin/doc/package-lock.json
|
|
45
|
+
package-lock.json
|
|
35
46
|
# All of ep_etherpad-lite's devDependencies are installed because the
|
|
36
47
|
# plugin might do `require('ep_etherpad-lite/node_modules/${devDep}')`.
|
|
37
48
|
# Eventually it would be nice to create an ESLint plugin that prohibits
|
|
@@ -58,10 +69,11 @@ jobs:
|
|
|
58
69
|
- uses: actions/checkout@v2
|
|
59
70
|
with:
|
|
60
71
|
fetch-depth: 0
|
|
61
|
-
- uses: actions/setup-node@
|
|
72
|
+
- uses: actions/setup-node@v2
|
|
62
73
|
with:
|
|
63
74
|
node-version: 12
|
|
64
75
|
registry-url: https://registry.npmjs.org/
|
|
76
|
+
cache: 'npm'
|
|
65
77
|
- name: Bump version (patch)
|
|
66
78
|
run: |
|
|
67
79
|
LATEST_TAG=$(git describe --tags --abbrev=0) || exit 1
|
|
@@ -85,3 +97,7 @@ jobs:
|
|
|
85
97
|
- run: npm publish
|
|
86
98
|
env:
|
|
87
99
|
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
100
|
+
- name: Add package to etherpad organization
|
|
101
|
+
run: npm access grant read-write etherpad:developers
|
|
102
|
+
env:
|
|
103
|
+
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ep_openid_connect",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.3",
|
|
4
4
|
"description": "Etherpad plugin to authenticate users against an OpenID Connect provider.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"ep_etherpad-lite": ">=1.8.6"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
57
|
-
"node": ">=12.
|
|
57
|
+
"node": ">=12.17.0"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -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
|
});
|