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.
@@ -26,11 +26,11 @@ jobs:
26
26
  with:
27
27
  repository: ether/etherpad-lite
28
28
  path: etherpad-lite
29
- - uses: actions/setup-node@v4
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@v4
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@v6
21
+ - uses: actions/setup-node@v7
22
22
  with:
23
- # OIDC trusted publishing needs npm >= 11.5.1, which requires
24
- # Node >= 20.17.0. setup-node's `20` resolves to the latest
25
- # 20.x, which satisfies that.
26
- node-version: 25
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@latest
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
- logger.info(`${phaseLog} Token endpoint auth method: ${method} (${source})` +
226
- (caBundle ? ' with custom CA bundle.' : '.'));
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.42",
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.4",
28
- "undici": "^8.7.0"
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": "^4.0.5",
33
- "oidc-provider": "^9.9.0",
34
- "supertest": "^6.3.4",
35
- "typescript": "^6.0.3"
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": ">=18.0.0"
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
- : sub.includes('role') ? {roles: ['etherpad_admin', 'unrelated_role']}
64
- : {}),
63
+ : sub.includes('role') ? {roles: ['etherpad_admin', 'unrelated_role']}
64
+ : {}),
65
65
  }),
66
66
  }),
67
67
  jwks: {
@@ -43,7 +43,7 @@ describe(__filename, function () {
43
43
  };
44
44
  });
45
45
 
46
- beforeEach(function () {
46
+ beforeEach(async function () {
47
47
  agent = supertest.agent('');
48
48
  settings.requireAuthentication = true;
49
49
  settings.requireAuthorization = false;
@@ -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 child_process = require('child_process');
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
- child_process.execFileSync('openssl', [
33
- 'req', '-x509',
34
- '-newkey', 'rsa:2048',
43
+ childProcess.execFileSync('openssl', [
44
+ 'req',
45
+ '-x509',
46
+ '-newkey',
47
+ 'rsa:2048',
35
48
  '-nodes',
36
- '-keyout', keyPath,
37
- '-out', certPath,
38
- '-days', '1',
39
- '-subj', '/CN=localhost',
40
- '-addext', 'subjectAltName=DNS:localhost,IP:127.0.0.1',
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) try { fs.rmSync(pems.dir, {recursive: true, force: true}); } catch (_) { /* ignore */ }
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 /self-signed|self signed|unable to verify|UNABLE_TO_VERIFY_LEAF_SIGNATURE|CERT|TLS/i.test(joined);
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
- `expected boolean defaults to validate, got errors: ` +
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
- `expected numeric defaults to validate, got errors: ` +
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', function () {
31
- assert.equal(
32
- pickAuthMethod(['private_key_jwt', 'client_secret_post', 'client_secret_basic']),
33
- 'client_secret_post');
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');