ep_openid_connect 3.0.42 → 3.0.44
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/workflows/backend-tests.yml +2 -2
- package/.github/workflows/frontend-tests.yml +2 -2
- package/.github/workflows/lint.yml +39 -0
- package/.github/workflows/npmpublish.yml +12 -8
- package/.github/workflows/test-and-release.yml +4 -0
- package/index.js +2 -2
- package/package.json +8 -8
- package/static/tests/backend/oidc-provider.js +2 -2
- package/static/tests/backend/specs/auto_pick_auth_method.js +1 -1
- package/static/tests/backend/specs/custom_ca.js +39 -17
- package/static/tests/backend/specs/default_type.js +5 -5
- package/static/tests/backend/specs/pick_auth_method.js +16 -15
|
@@ -26,11 +26,11 @@ jobs:
|
|
|
26
26
|
with:
|
|
27
27
|
repository: ether/etherpad-lite
|
|
28
28
|
path: etherpad-lite
|
|
29
|
-
- uses: actions/setup-node@
|
|
29
|
+
- uses: actions/setup-node@v7
|
|
30
30
|
name: Install Node.js
|
|
31
31
|
with:
|
|
32
32
|
node-version: 25
|
|
33
|
-
- uses: pnpm/action-setup@v6
|
|
33
|
+
- uses: pnpm/action-setup@v6.1.0
|
|
34
34
|
name: Install pnpm
|
|
35
35
|
with:
|
|
36
36
|
package_json_file: ./etherpad-lite/package.json
|
|
@@ -16,11 +16,11 @@ jobs:
|
|
|
16
16
|
with:
|
|
17
17
|
repository: ether/etherpad-lite
|
|
18
18
|
path: etherpad-lite
|
|
19
|
-
- uses: actions/setup-node@
|
|
19
|
+
- uses: actions/setup-node@v7
|
|
20
20
|
name: Install Node.js
|
|
21
21
|
with:
|
|
22
22
|
node-version: 25
|
|
23
|
-
- uses: pnpm/action-setup@v6
|
|
23
|
+
- uses: pnpm/action-setup@v6.1.0
|
|
24
24
|
name: Install pnpm
|
|
25
25
|
with:
|
|
26
26
|
package_json_file: ./etherpad-lite/package.json
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Lint
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
lint:
|
|
8
|
+
name: ESLint
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
# `eslint .` executes .eslintrc.cjs and every installed ESLint plugin, so
|
|
11
|
+
# don't hand it the caller's contents:write / id-token:write token.
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check out plugin repository
|
|
16
|
+
uses: actions/checkout@v7
|
|
17
|
+
- uses: actions/setup-node@v7
|
|
18
|
+
name: Install Node.js
|
|
19
|
+
with:
|
|
20
|
+
node-version: 25
|
|
21
|
+
- uses: pnpm/action-setup@v6.1.0
|
|
22
|
+
name: Install pnpm
|
|
23
|
+
with:
|
|
24
|
+
version: 10
|
|
25
|
+
run_install: false
|
|
26
|
+
- name: Get pnpm store directory
|
|
27
|
+
shell: bash
|
|
28
|
+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
|
29
|
+
- uses: actions/cache@v6
|
|
30
|
+
name: Setup pnpm cache
|
|
31
|
+
with:
|
|
32
|
+
path: ${{ env.STORE_PATH }}
|
|
33
|
+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
34
|
+
restore-keys: |
|
|
35
|
+
${{ runner.os }}-pnpm-store-
|
|
36
|
+
- name: Install dependencies
|
|
37
|
+
run: pnpm install --ignore-scripts
|
|
38
|
+
- name: Run ESLint
|
|
39
|
+
run: pnpm run lint
|
|
@@ -18,20 +18,24 @@ jobs:
|
|
|
18
18
|
contents: write # for the atomic version-bump push (branch + tag)
|
|
19
19
|
id-token: write # for npm OIDC trusted publishing
|
|
20
20
|
steps:
|
|
21
|
-
- uses: actions/setup-node@
|
|
21
|
+
- uses: actions/setup-node@v7
|
|
22
22
|
with:
|
|
23
|
-
# OIDC trusted publishing needs npm >= 11.5.1
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
23
|
+
# OIDC trusted publishing needs npm >= 11.5.1. Node 24 is an LTS
|
|
24
|
+
# line that every npm 11.x and 12.x supports; Node 25 is not —
|
|
25
|
+
# npm 12 requires ^22.22.2 || ^24.15.0 || >=26.0.0, so on Node 25
|
|
26
|
+
# the upgrade step below died with EBADENGINE and no plugin could
|
|
27
|
+
# publish.
|
|
28
|
+
node-version: 24
|
|
27
29
|
registry-url: https://registry.npmjs.org/
|
|
30
|
+
# Pinned to a range rather than @latest: the next npm major dropping
|
|
31
|
+
# this Node line would silently break publishing fleet-wide again.
|
|
28
32
|
- name: Upgrade npm to >=11.5.1 (required for trusted publishing)
|
|
29
|
-
run: npm install -g npm
|
|
33
|
+
run: npm install -g npm@^11.5.1
|
|
30
34
|
- name: Check out Etherpad core
|
|
31
35
|
uses: actions/checkout@v7
|
|
32
36
|
with:
|
|
33
37
|
repository: ether/etherpad-lite
|
|
34
|
-
- uses: pnpm/action-setup@v6
|
|
38
|
+
- uses: pnpm/action-setup@v6.1.0
|
|
35
39
|
name: Install pnpm
|
|
36
40
|
with:
|
|
37
41
|
run_install: false
|
|
@@ -73,7 +77,7 @@ jobs:
|
|
|
73
77
|
# `pnpm version patch` bumps package.json, makes a commit, and creates
|
|
74
78
|
# a `v<new-version>` tag. Capture the new tag name from package.json
|
|
75
79
|
# rather than parsing pnpm's output, which has historically varied.
|
|
76
|
-
# Bump the patch component directly with Node. pnpm/action-setup@v6
|
|
80
|
+
# Bump the patch component directly with Node. pnpm/action-setup@v6.0.10
|
|
77
81
|
# sometimes installs pnpm 11 pre-releases even when version: 10.x is
|
|
78
82
|
# requested (pnpm/action-setup#225); those pre-releases either skip
|
|
79
83
|
# the git commit/tag or reject --no-git-tag-version as unknown.
|
|
@@ -8,6 +8,9 @@ permissions:
|
|
|
8
8
|
id-token: write
|
|
9
9
|
|
|
10
10
|
jobs:
|
|
11
|
+
lint:
|
|
12
|
+
uses: ./.github/workflows/lint.yml
|
|
13
|
+
secrets: inherit
|
|
11
14
|
backend:
|
|
12
15
|
uses: ./.github/workflows/backend-tests.yml
|
|
13
16
|
secrets: inherit
|
|
@@ -17,6 +20,7 @@ jobs:
|
|
|
17
20
|
release:
|
|
18
21
|
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
|
|
19
22
|
needs:
|
|
23
|
+
- lint
|
|
20
24
|
- backend
|
|
21
25
|
- frontend
|
|
22
26
|
permissions:
|
package/index.js
CHANGED
|
@@ -222,8 +222,8 @@ const buildConfig = async (settings) => {
|
|
|
222
222
|
}
|
|
223
223
|
if (customFetch != null) config[oidc.customFetch] = customFetch;
|
|
224
224
|
const source = settings.token_endpoint_auth_method ? 'configured' : 'auto-picked';
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
const caNote = caBundle ? ' with custom CA bundle.' : '.';
|
|
226
|
+
logger.info(`${phaseLog} Token endpoint auth method: ${method} (${source})${caNote}`);
|
|
227
227
|
return config;
|
|
228
228
|
};
|
|
229
229
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ep_openid_connect",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.44",
|
|
4
4
|
"description": "Etherpad plugin to authenticate users against an OpenID Connect provider.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"homepage": "https://github.com/ether/ep_openid_connect#readme",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"ajv": "^8.20.0",
|
|
27
|
-
"openid-client": "^6.8.
|
|
28
|
-
"undici": "^8.
|
|
27
|
+
"openid-client": "^6.8.8",
|
|
28
|
+
"undici": "^8.11.2"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"eslint": "^8.57.1",
|
|
32
|
-
"eslint-config-etherpad": "^
|
|
33
|
-
"oidc-provider": "^9.
|
|
34
|
-
"supertest": "^
|
|
35
|
-
"typescript": "
|
|
32
|
+
"eslint-config-etherpad": "^5.0.0",
|
|
33
|
+
"oidc-provider": "^9.12.2",
|
|
34
|
+
"supertest": "^7.3.0",
|
|
35
|
+
"typescript": "~6.0.3"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"lint": "eslint .",
|
|
@@ -43,6 +43,6 @@
|
|
|
43
43
|
"url": "https://etherpad.org/"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
|
-
"node": ">=
|
|
46
|
+
"node": ">=22.0.0"
|
|
47
47
|
}
|
|
48
48
|
}
|
|
@@ -60,8 +60,8 @@ class OidcProvider {
|
|
|
60
60
|
// are unaffected. `emptyRoles` gets an empty array to exercise the
|
|
61
61
|
// "no matching role" path.
|
|
62
62
|
...(sub.includes('emptyRoles') ? {roles: []}
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
: sub.includes('role') ? {roles: ['etherpad_admin', 'unrelated_role']}
|
|
64
|
+
: {}),
|
|
65
65
|
}),
|
|
66
66
|
}),
|
|
67
67
|
jwks: {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const Provider = require('oidc-provider').default;
|
|
12
12
|
const MemoryAdapter = require('oidc-provider/lib/adapters/memory_adapter').default;
|
|
13
13
|
const assert = require('assert').strict;
|
|
14
|
-
const
|
|
14
|
+
const childProcess = require('child_process');
|
|
15
15
|
const common = require('ep_etherpad-lite/tests/backend/common');
|
|
16
16
|
const epOpenidConnect = require('../../../../index');
|
|
17
17
|
const fs = require('fs');
|
|
@@ -21,6 +21,17 @@ const path = require('path');
|
|
|
21
21
|
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
|
22
22
|
const util = require('util');
|
|
23
23
|
|
|
24
|
+
// The TLS failure surfaces with different wording depending on the Node and
|
|
25
|
+
// undici versions in play, so match any of the known phrasings.
|
|
26
|
+
const TLS_ERROR_RE = new RegExp([
|
|
27
|
+
'self-signed',
|
|
28
|
+
'self signed',
|
|
29
|
+
'unable to verify',
|
|
30
|
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
|
31
|
+
'CERT',
|
|
32
|
+
'TLS',
|
|
33
|
+
].join('|'), 'i');
|
|
34
|
+
|
|
24
35
|
// Generate a self-signed cert valid for localhost via the OpenSSL CLI.
|
|
25
36
|
// Using a CLI avoids pulling in `selfsigned`+`@peculiar/x509`+`node-forge`
|
|
26
37
|
// as devDependencies just for one test, and `openssl` is available on every
|
|
@@ -29,15 +40,22 @@ const generateSelfSignedCert = () => {
|
|
|
29
40
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ep-openid-cert-'));
|
|
30
41
|
const keyPath = path.join(dir, 'key.pem');
|
|
31
42
|
const certPath = path.join(dir, 'cert.pem');
|
|
32
|
-
|
|
33
|
-
'req',
|
|
34
|
-
'-
|
|
43
|
+
childProcess.execFileSync('openssl', [
|
|
44
|
+
'req',
|
|
45
|
+
'-x509',
|
|
46
|
+
'-newkey',
|
|
47
|
+
'rsa:2048',
|
|
35
48
|
'-nodes',
|
|
36
|
-
'-keyout',
|
|
37
|
-
|
|
38
|
-
'-
|
|
39
|
-
|
|
40
|
-
'-
|
|
49
|
+
'-keyout',
|
|
50
|
+
keyPath,
|
|
51
|
+
'-out',
|
|
52
|
+
certPath,
|
|
53
|
+
'-days',
|
|
54
|
+
'1',
|
|
55
|
+
'-subj',
|
|
56
|
+
'/CN=localhost',
|
|
57
|
+
'-addext',
|
|
58
|
+
'subjectAltName=DNS:localhost,IP:127.0.0.1',
|
|
41
59
|
], {stdio: 'pipe'});
|
|
42
60
|
return {
|
|
43
61
|
dir,
|
|
@@ -156,13 +174,13 @@ describe(__filename, function () {
|
|
|
156
174
|
});
|
|
157
175
|
});
|
|
158
176
|
|
|
159
|
-
beforeEach(function () {
|
|
177
|
+
beforeEach(async function () {
|
|
160
178
|
settings.requireAuthentication = true;
|
|
161
179
|
settings.requireAuthorization = false;
|
|
162
180
|
settings.users = {};
|
|
163
181
|
});
|
|
164
182
|
|
|
165
|
-
afterEach(function () {
|
|
183
|
+
afterEach(async function () {
|
|
166
184
|
settings.requireAuthentication = backup.settings.requireAuthentication;
|
|
167
185
|
settings.requireAuthorization = backup.settings.requireAuthorization;
|
|
168
186
|
settings.users = backup.settings.users;
|
|
@@ -171,27 +189,31 @@ describe(__filename, function () {
|
|
|
171
189
|
after(async function () {
|
|
172
190
|
if (provider != null) await provider.stop();
|
|
173
191
|
provider = null;
|
|
174
|
-
if (pems && pems.dir)
|
|
192
|
+
if (pems && pems.dir) {
|
|
193
|
+
try {
|
|
194
|
+
fs.rmSync(pems.dir, {recursive: true, force: true});
|
|
195
|
+
} catch (_) { /* ignore */ }
|
|
196
|
+
}
|
|
175
197
|
});
|
|
176
198
|
|
|
177
199
|
describe('loadCaBundle', function () {
|
|
178
200
|
const {loadCaBundle} = epOpenidConnect.exportedForTestingOnly;
|
|
179
201
|
|
|
180
|
-
it('returns null for an empty / missing value', function () {
|
|
202
|
+
it('returns null for an empty / missing value', async function () {
|
|
181
203
|
assert.equal(loadCaBundle(undefined), null);
|
|
182
204
|
assert.equal(loadCaBundle(null), null);
|
|
183
205
|
assert.equal(loadCaBundle(''), null);
|
|
184
206
|
});
|
|
185
207
|
|
|
186
|
-
it('returns inline PEM content unchanged', function () {
|
|
208
|
+
it('returns inline PEM content unchanged', async function () {
|
|
187
209
|
assert.equal(loadCaBundle(pems.cert), pems.cert);
|
|
188
210
|
});
|
|
189
211
|
|
|
190
|
-
it('reads PEM content from a path', function () {
|
|
212
|
+
it('reads PEM content from a path', async function () {
|
|
191
213
|
assert.equal(loadCaBundle(pems.certPath), pems.cert);
|
|
192
214
|
});
|
|
193
215
|
|
|
194
|
-
it('throws on a non-existent path', function () {
|
|
216
|
+
it('throws on a non-existent path', async function () {
|
|
195
217
|
assert.throws(() => loadCaBundle('/this/path/does/not/exist.pem'),
|
|
196
218
|
/ENOENT|no such file/);
|
|
197
219
|
});
|
|
@@ -221,7 +243,7 @@ describe(__filename, function () {
|
|
|
221
243
|
if (e.code) messages.push(String(e.code));
|
|
222
244
|
}
|
|
223
245
|
const joined = messages.join(' | ');
|
|
224
|
-
return
|
|
246
|
+
return TLS_ERROR_RE.test(joined);
|
|
225
247
|
});
|
|
226
248
|
});
|
|
227
249
|
|
|
@@ -10,7 +10,7 @@ const baseSettings = () => ({
|
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
describe(__filename, function () {
|
|
13
|
-
it('still accepts a string default on user_properties', function () {
|
|
13
|
+
it('still accepts a string default on user_properties', async function () {
|
|
14
14
|
const s = {
|
|
15
15
|
...baseSettings(),
|
|
16
16
|
user_properties: {displayname: {claim: 'name', default: 'Anon'}},
|
|
@@ -19,7 +19,7 @@ describe(__filename, function () {
|
|
|
19
19
|
`expected valid settings, got errors: ${JSON.stringify(validSettings.errors)}`);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
it('accepts a boolean default on user_properties (regression for #100)', function () {
|
|
22
|
+
it('accepts a boolean default on user_properties (regression for #100)', async function () {
|
|
23
23
|
const s = {
|
|
24
24
|
...baseSettings(),
|
|
25
25
|
user_properties: {
|
|
@@ -29,17 +29,17 @@ describe(__filename, function () {
|
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
assert(validSettings(s),
|
|
32
|
-
|
|
32
|
+
'expected boolean defaults to validate, got errors: ' +
|
|
33
33
|
`${JSON.stringify(validSettings.errors)}`);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
it('accepts a numeric default on user_properties', function () {
|
|
36
|
+
it('accepts a numeric default on user_properties', async function () {
|
|
37
37
|
const s = {
|
|
38
38
|
...baseSettings(),
|
|
39
39
|
user_properties: {quota: {default: 100}},
|
|
40
40
|
};
|
|
41
41
|
assert(validSettings(s),
|
|
42
|
-
|
|
42
|
+
'expected numeric defaults to validate, got errors: ' +
|
|
43
43
|
`${JSON.stringify(validSettings.errors)}`);
|
|
44
44
|
});
|
|
45
45
|
});
|
|
@@ -5,21 +5,21 @@ const {pickAuthMethod} = require('../../../..').exportedForTestingOnly;
|
|
|
5
5
|
|
|
6
6
|
describe(__filename, function () {
|
|
7
7
|
describe('with no override', function () {
|
|
8
|
-
it('prefers client_secret_post when advertised', function () {
|
|
8
|
+
it('prefers client_secret_post when advertised', async function () {
|
|
9
9
|
assert.equal(
|
|
10
10
|
pickAuthMethod(['client_secret_basic', 'client_secret_post']),
|
|
11
11
|
'client_secret_post');
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it('uses client_secret_basic when only that is advertised', function () {
|
|
14
|
+
it('uses client_secret_basic when only that is advertised', async function () {
|
|
15
15
|
assert.equal(pickAuthMethod(['client_secret_basic']), 'client_secret_basic');
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
it('uses client_secret_post when only that is advertised', function () {
|
|
18
|
+
it('uses client_secret_post when only that is advertised', async function () {
|
|
19
19
|
assert.equal(pickAuthMethod(['client_secret_post']), 'client_secret_post');
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
it('ignores unsupported methods', function () {
|
|
22
|
+
it('ignores unsupported methods', async function () {
|
|
23
23
|
// None of these are methods this plugin can implement, so the picker
|
|
24
24
|
// must fall back rather than pretend to support them.
|
|
25
25
|
assert.equal(
|
|
@@ -27,42 +27,43 @@ describe(__filename, function () {
|
|
|
27
27
|
'client_secret_basic');
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
it('prefers client_secret_post even when other methods are advertised first',
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
it('prefers client_secret_post even when other methods are advertised first',
|
|
31
|
+
async function () {
|
|
32
|
+
assert.equal(
|
|
33
|
+
pickAuthMethod(['private_key_jwt', 'client_secret_post', 'client_secret_basic']),
|
|
34
|
+
'client_secret_post');
|
|
35
|
+
});
|
|
35
36
|
|
|
36
|
-
it('falls back to client_secret_basic when supported is undefined', function () {
|
|
37
|
+
it('falls back to client_secret_basic when supported is undefined', async function () {
|
|
37
38
|
// RFC 8414 §2: absence of token_endpoint_auth_methods_supported defaults
|
|
38
39
|
// to client_secret_basic.
|
|
39
40
|
assert.equal(pickAuthMethod(undefined), 'client_secret_basic');
|
|
40
41
|
});
|
|
41
42
|
|
|
42
|
-
it('falls back to client_secret_basic when supported is empty', function () {
|
|
43
|
+
it('falls back to client_secret_basic when supported is empty', async function () {
|
|
43
44
|
assert.equal(pickAuthMethod([]), 'client_secret_basic');
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
it('falls back to client_secret_basic when supported is not an array', function () {
|
|
47
|
+
it('falls back to client_secret_basic when supported is not an array', async function () {
|
|
47
48
|
assert.equal(pickAuthMethod('client_secret_post'), 'client_secret_basic');
|
|
48
49
|
assert.equal(pickAuthMethod(null), 'client_secret_basic');
|
|
49
50
|
});
|
|
50
51
|
});
|
|
51
52
|
|
|
52
53
|
describe('with override', function () {
|
|
53
|
-
it('honours an explicit client_secret_basic override', function () {
|
|
54
|
+
it('honours an explicit client_secret_basic override', async function () {
|
|
54
55
|
assert.equal(
|
|
55
56
|
pickAuthMethod(['client_secret_post'], 'client_secret_basic'),
|
|
56
57
|
'client_secret_basic');
|
|
57
58
|
});
|
|
58
59
|
|
|
59
|
-
it('honours an explicit client_secret_post override', function () {
|
|
60
|
+
it('honours an explicit client_secret_post override', async function () {
|
|
60
61
|
assert.equal(
|
|
61
62
|
pickAuthMethod(['client_secret_basic'], 'client_secret_post'),
|
|
62
63
|
'client_secret_post');
|
|
63
64
|
});
|
|
64
65
|
|
|
65
|
-
it('honours override even when supported list is missing', function () {
|
|
66
|
+
it('honours override even when supported list is missing', async function () {
|
|
66
67
|
assert.equal(
|
|
67
68
|
pickAuthMethod(undefined, 'client_secret_post'),
|
|
68
69
|
'client_secret_post');
|