diff --git a/Dockerfile b/Dockerfile
index 1b0a45d..beeed7f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@
# https://github.com/ether/etherpad-lite
#
# Author: muxator
-ARG BUILD_ENV=git
+ARG BUILD_ENV=copy
FROM node:lts-alpine AS adminbuild
RUN npm install -g pnpm@latest
@@ -21,7 +21,7 @@ ARG http_proxy=
ARG https_proxy=
ARG no_proxy=
-ARG TIMEZONE=
+ARG TIMEZONE=Asia/Shanghai
RUN \
[ -z "${TIMEZONE}" ] || { \
@@ -64,7 +64,7 @@ ARG ETHERPAD_GITHUB_PLUGINS=
#
# EXAMPLE:
# INSTALL_ABIWORD=true
-ARG INSTALL_ABIWORD=
+ARG INSTALL_ABIWORD=false
# Control whether libreoffice will be installed, enabling exports to DOC/PDF/ODT formats.
# By default, it is not installed.
@@ -72,7 +72,7 @@ ARG INSTALL_ABIWORD=
#
# EXAMPLE:
# INSTALL_LIBREOFFICE=true
-ARG INSTALL_SOFFICE=
+ARG INSTALL_SOFFICE=false
# Install dependencies required for modifying access.
RUN apk add --no-cache shadow bash
@@ -83,9 +83,9 @@ RUN apk add --no-cache shadow bash
#
# If any of the following args are set to the empty string, default
# values will be chosen.
-ARG EP_HOME=
+ARG EP_HOME=/opt/etherpad-lite
ARG EP_UID=5001
-ARG EP_GID=0
+ARG EP_GID=5001
ARG EP_SHELL=
RUN groupadd --system ${EP_GID:+--gid "${EP_GID}" --non-unique} etherpad && \
@@ -133,7 +133,7 @@ FROM build AS build_copy
FROM build_${BUILD_ENV} AS development
-ARG ETHERPAD_PLUGINS=
+ARG ETHERPAD_PLUGINS="ep_font_size@0.4.59 ep_font_color@0.0.86 ep_font_family@0.5.44 ep_headings2@0.2.62 ep_openid_connect@3.0.7 ep_guest@1.0.37 ep_user_displayname@1.0.7 ep_stable_authorid@1.0.3"
ARG ETHERPAD_LOCAL_PLUGINS=
ARG ETHERPAD_LOCAL_PLUGINS_ENV=
ARG ETHERPAD_GITHUB_PLUGINS=
@@ -154,7 +154,7 @@ RUN bin/installDeps.sh && \
FROM build_${BUILD_ENV} AS production
-ARG ETHERPAD_PLUGINS=
+ARG ETHERPAD_PLUGINS="ep_font_size@0.4.59 ep_font_color@0.0.86 ep_font_family@0.5.44 ep_headings2@0.2.62 ep_openid_connect@3.0.7 ep_guest@1.0.37 ep_user_displayname@1.0.7 ep_stable_authorid@1.0.3"
ARG ETHERPAD_LOCAL_PLUGINS=
ARG ETHERPAD_LOCAL_PLUGINS_ENV=
ARG ETHERPAD_GITHUB_PLUGINS=
@@ -168,9 +168,9 @@ COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/static/o
COPY --chown=etherpad:etherpad ./local_plugin[s] ./local_plugins/
-RUN bash -c ./bin/installLocalPlugins.sh
+RUN chmod +x ./bin/installLocalPlugins.sh && bash -c ./bin/installLocalPlugins.sh
-RUN bin/installDeps.sh && \
+RUN chmod +x bin/installDeps.sh && bin/installDeps.sh && \
if [ ! -z "${ETHERPAD_PLUGINS}" ] || [ ! -z "${ETHERPAD_GITHUB_PLUGINS}" ]; then \
pnpm run plugins i ${ETHERPAD_PLUGINS} ${ETHERPAD_GITHUB_PLUGINS:+--github ${ETHERPAD_GITHUB_PLUGINS}}; \
fi && \
@@ -178,12 +178,13 @@ RUN bin/installDeps.sh && \
# Copy the configuration file.
COPY --chown=etherpad:etherpad ${SETTINGS} "${EP_DIR}"/settings.json
+RUN cp /opt/etherpad-lite/local_plugins/ep_openid_connect/index.js /opt/etherpad-lite/src/plugin_packages/.versions/ep_openid_connect@3.0.7/index.js
# Fix group permissions
# Note: For some reason increases image size from 257 to 334.
# RUN chmod -R g=u .
-USER etherpad
+#USER etherpad
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl --silent http://localhost:9001/health | grep -E "pass|ok|up" > /dev/null || exit 1
diff --git a/credentials.json b/credentials.json
new file mode 100644
index 0000000..d4f0c0f
--- /dev/null
+++ b/credentials.json
@@ -0,0 +1,38 @@
+{
+ "ep_openid_connect": {
+ "issuer_metadata": {
+ "authorization_endpoint": "${ISSUER_AUTHORIZATION_ENDPOINT}",
+ "token_endpoint": "${ISSUER_TOKEN_ENDPOINT}",
+ "userinfo_endpoint": "${ISSUER_USERINFO_ENDPOINT}",
+ "token_endpoint_auth_methods_supported": [
+ "${TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED:client_secret_basic}"
+ ],
+ "issuer": "${ISSUER_ENDPOINT}",
+ "jwks_uri": "${ISSUER_CLIENT_SECRET}"
+ },
+ "client_id": "${ISSUER_CLIENT_ID}",
+ "client_secret": "${ISSUER_CLIENT_SECRET}",
+ "scope": [
+ "openid",
+ "profile",
+ "id_token"
+ ],
+ "base_url": "${ISSUER_BASE_URL}",
+ "user_properties": {
+ "displayname": {
+ "claim": "username"
+ }
+ }
+ },
+ "dbType": "${DB_TYPE}",
+ "dbSettings": {
+ "host": "${DB_HOST}",
+ "port": "${DB_PORT}",
+ "database": "${DB_NAME}",
+ "user": "${DB_USER}",
+ "password": "${DB_PASS}",
+ "charset": "${DB_CHARSET}"
+ }
+}
+
+
diff --git a/local_plugins/.gitignore b/local_plugins/.gitignore
deleted file mode 100644
index 87f7edd..0000000
--- a/local_plugins/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# ignore everything
-*
-!.gitignore
diff --git a/local_plugins/ep_openid_connect/.eslintrc.cjs b/local_plugins/ep_openid_connect/.eslintrc.cjs
new file mode 100644
index 0000000..6c5a926
--- /dev/null
+++ b/local_plugins/ep_openid_connect/.eslintrc.cjs
@@ -0,0 +1,9 @@
+'use strict';
+
+// This is a workaround for https://github.com/eslint/eslint/issues/3458
+require('eslint-config-etherpad/patch/modern-module-resolution');
+
+module.exports = {
+ root: true,
+ extends: 'etherpad/plugin',
+};
diff --git a/local_plugins/ep_openid_connect/.npmrc b/local_plugins/ep_openid_connect/.npmrc
new file mode 100644
index 0000000..41583e3
--- /dev/null
+++ b/local_plugins/ep_openid_connect/.npmrc
@@ -0,0 +1 @@
+@jsr:registry=https://npm.jsr.io
diff --git a/local_plugins/ep_openid_connect/.travis.yml b/local_plugins/ep_openid_connect/.travis.yml
new file mode 100644
index 0000000..254fee7
--- /dev/null
+++ b/local_plugins/ep_openid_connect/.travis.yml
@@ -0,0 +1,70 @@
+language: node_js
+
+node_js:
+ - "lts/*"
+
+cache: false
+
+services:
+ - docker
+
+install:
+ - "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
+
+#script:
+# - "tests/frontend/travis/runner.sh"
+
+env:
+ global:
+ - secure: "WMGxFkOeTTlhWB+ChMucRtIqVmMbwzYdNHuHQjKCcj8HBEPdZLfCuK/kf4rG\nVLcLQiIsyllqzNhBGVHG1nyqWr0/LTm8JRqSCDDVIhpyzp9KpCJQQJG2Uwjk\n6/HIJJh/wbxsEdLNV2crYU/EiVO3A4Bq0YTHUlbhUqG3mSCr5Ec="
+ - secure: "gejXUAHYscbR6Bodw35XexpToqWkv2ifeECsbeEmjaLkYzXmUUNWJGknKSu7\nEUsSfQV8w+hxApr1Z+jNqk9aX3K1I4btL3cwk2trnNI8XRAvu1c1Iv60eerI\nkE82Rsd5lwUaMEh+/HoL8ztFCZamVndoNgX7HWp5J/NRZZMmh4g="
+
+jobs:
+ include:
+ - name: "Lint test package-lock"
+ install:
+ - "npm install lockfile-lint"
+ script:
+ - npx lockfile-lint --path package-lock.json --validate-https --allowed-hosts npm
+ - name: "Run the Backend tests"
+ before_install:
+ - sudo add-apt-repository -y ppa:libreoffice/ppa
+ - sudo apt-get update
+ - sudo apt-get -y install libreoffice
+ - sudo apt-get -y install libreoffice-pdfimport
+ install:
+ - "npm install"
+ - "mkdir ep_openid_connect"
+ - "mv !(ep_openid_connect) ep_openid_connect"
+ - "git clone https://github.com/ether/etherpad-lite.git etherpad"
+ - "cd etherpad"
+ - "mkdir -p node_modules"
+ - "mv ../ep_openid_connect node_modules"
+ - "bin/installDeps.sh"
+ - "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
+ - "cd src && npm install && cd -"
+ script:
+ - "tests/frontend/travis/runnerBackend.sh"
+ - name: "Test the Frontend"
+ before_script:
+ - "tests/frontend/travis/sauce_tunnel.sh"
+ install:
+ - "npm install"
+ - "mkdir ep_openid_connect"
+ - "mv !(ep_openid_connect) ep_openid_connect"
+ - "git clone https://github.com/ether/etherpad-lite.git etherpad"
+ - "cd etherpad"
+ - "mkdir -p node_modules"
+ - "mv ../ep_openid_connect node_modules"
+ - "bin/installDeps.sh"
+ - "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
+ script:
+ - "tests/frontend/travis/runner.sh"
+
+notifications:
+ irc:
+ channels:
+ - "irc.freenode.org#etherpad-lite-dev"
+
+##ETHERPAD_TRAVIS_V=9
+## Travis configuration automatically created using bin/plugins/updateAllPluginsScript.sh
diff --git a/local_plugins/ep_openid_connect/CHANGELOG.md b/local_plugins/ep_openid_connect/CHANGELOG.md
new file mode 100644
index 0000000..898ee0d
--- /dev/null
+++ b/local_plugins/ep_openid_connect/CHANGELOG.md
@@ -0,0 +1,14 @@
+# Changelog
+
+## v3.0.0
+
+Released 2022-03-04.
+
+#### Compatibility changes
+
+ * Removed the deprecated `displayname_claim` setting. Set
+ `user_properties.displayname.claim` instead.
+ * Displayname rendering was moved to the
+ [ep\_user\_displayname](https://github.com/ether/ep_user_displayname#readme)
+ plugin and the `permit_displayname_change` setting was removed.
+ * The plugin is now much more strict about what it accepts as valid settings.
diff --git a/local_plugins/ep_openid_connect/CONTRIBUTING.md b/local_plugins/ep_openid_connect/CONTRIBUTING.md
new file mode 100644
index 0000000..724e02a
--- /dev/null
+++ b/local_plugins/ep_openid_connect/CONTRIBUTING.md
@@ -0,0 +1,133 @@
+# Contributor Guidelines
+(Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/ether/etherpad-lite#get-in-touch))
+
+## Pull requests
+
+* the commit series in the PR should be _linear_ (it **should not contain merge commits**). This is necessary because we want to be able to [bisect](https://en.wikipedia.org/wiki/Bisection_(software_engineering)) bugs easily. Rewrite history/perform a rebase if necessary
+* PRs should be issued against the **develop** branch: we never pull directly into **master**
+* PRs **should not have conflicts** with develop. If there are, please resolve them rebasing and force-pushing
+* when preparing your PR, please make sure that you have included the relevant **changes to the documentation** (preferably with usage examples)
+* contain meaningful and detailed **commit messages** in the form:
+ ```
+ submodule: description
+
+ longer description of the change you have made, eventually mentioning the
+ number of the issue that is being fixed, in the form: Fixes #someIssueNumber
+ ```
+* if the PR is a **bug fix**:
+ * the first commit in the series must be a test that shows the failure
+ * subsequent commits will fix the bug and make the test pass
+ * the final commit message should include the text `Fixes: #xxx` to link it to its bug report
+* think about stability: code has to be backwards compatible as much as possible. Always **assume your code will be run with an older version of the DB/config file**
+* if you want to remove a feature, **deprecate it instead**:
+ * write an issue with your deprecation plan
+ * output a `WARN` in the log informing that the feature is going to be removed
+ * remove the feature in the next version
+* if you want to add a new feature, put it under a **feature flag**:
+ * once the new feature has reached a minimal level of stability, do a PR for it, so it can be integrated early
+ * expose a mechanism for enabling/disabling the feature
+ * the new feature should be **disabled** by default. With the feature disabled, the code path should be exactly the same as before your contribution. This is a __necessary condition__ for early integration
+* think of the PR not as something that __you wrote__, but as something that __someone else is going to read__. The commit series in the PR should tell a novice developer the story of your thoughts when developing it
+
+## How to write a bug report
+
+* Please be polite, we all are humans and problems can occur.
+* Please add as much information as possible, for example
+ * client os(s) and version(s)
+ * browser(s) and version(s), is the problem reproducible on different clients
+ * special environments like firewalls or antivirus
+ * host os and version
+ * npm and nodejs version
+ * Logfiles if available
+ * steps to reproduce
+ * what you expected to happen
+ * what actually happened
+* Please format logfiles and code examples with markdown see github Markdown help below the issue textarea for more information.
+
+If you send logfiles, please set the loglevel switch DEBUG in your settings.json file:
+
+```
+/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
+ "loglevel": "DEBUG",
+```
+
+The logfile location is defined in startup script or the log is directly shown in the commandline after you have started etherpad.
+
+## General goals of Etherpad
+To make sure everybody is going in the same direction:
+* easy to install for admins and easy to use for people
+* easy to integrate into other apps, but also usable as standalone
+* lightweight and scalable
+* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core.
+Also, keep it maintainable. We don't wanna end up as the monster Etherpad was!
+
+## How to work with git?
+* Don't work in your master branch.
+* Make a new branch for every feature you're working on. (This ensures that you can work you can do lots of small, independent pull requests instead of one big one with complete different features)
+* Don't use the online edit function of github (this only creates ugly and not working commits!)
+* Try to make clean commits that are easy readable (including descriptive commit messages!)
+* Test before you push. Sounds easy, it isn't!
+* Don't check in stuff that gets generated during build or runtime
+* Make small pull requests that are easy to review but make sure they do add value by themselves / individually
+
+## Coding style
+* Do write comments. (You don't have to comment every line, but if you come up with something that's a bit complex/weird, just leave a comment. Bear in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are worthless!)
+* Never ever use tabs
+* Indentation: JS/CSS: 2 spaces; HTML: 4 spaces
+* Don't overengineer. Don't try to solve any possible problem in one step, but try to solve problems as easy as possible and improve the solution over time!
+* Do generalize sooner or later! (if an old solution, quickly hacked together, poses more problems than it solves today, refactor it!)
+* Keep it compatible. Do not introduce changes to the public API, db schema or configurations too lightly. Don't make incompatible changes without good reasons!
+* If you do make changes, document them! (see below)
+* Use protocol independent urls "//"
+
+## Branching model / git workflow
+see git flow http://nvie.com/posts/a-successful-git-branching-model/
+
+### `master` branch
+* the stable
+* This is the branch everyone should use for production stuff
+
+### `develop`branch
+* everything that is READY to go into master at some point in time
+* This stuff is tested and ready to go out
+
+### release branches
+* stuff that should go into master very soon
+* only bugfixes go into these (see http://nvie.com/posts/a-successful-git-branching-model/ for why)
+* we should not be blocking new features to develop, just because we feel that we should be releasing it to master soon. This is the situation that release branches solve/handle.
+
+### hotfix branches
+* fixes for bugs in master
+
+### feature branches (in your own repos)
+* these are the branches where you develop your features in
+* If it's ready to go out, it will be merged into develop
+
+Over the time we pull features from feature branches into the develop branch. Every month we pull from develop into master. Bugs in master get fixed in hotfix branches. These branches will get merged into master AND develop. There should never be commits in master that aren't in develop
+
+## Documentation
+The docs are in the `doc/` folder in the git repository, so people can easily find the suitable docs for the current git revision.
+
+Documentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request.
+
+You can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet.
+
+## Testing
+Front-end tests are found in the `tests/frontend/` folder in the repository. Run them by pointing your browser to `/tests/frontend`.
+
+Back-end tests can be run from the `src` directory, via `npm test`.
+
+## Things you can help with
+Etherpad is much more than software. So if you aren't a developer then worry not, there is still a LOT you can do! A big part of what we do is community engagement. You can help in the following ways
+ * Triage bugs (applying labels) and confirming their existence
+ * Testing fixes (simply applying them and seeing if it fixes your issue or not) - Some git experience required
+ * Notifying large site admins of new releases
+ * Writing Changelogs for releases
+ * Creating Windows packages
+ * Creating releases
+ * Bumping dependencies periodically and checking they don't break anything
+ * Write proposals for grants
+ * Co-Author and Publish CVEs
+ * Work with SFC to maintain legal side of project
+ * Maintain TODO page - https://github.com/ether/etherpad-lite/wiki/TODO#IMPORTANT_TODOS
+
diff --git a/local_plugins/ep_openid_connect/LICENSE b/local_plugins/ep_openid_connect/LICENSE
new file mode 100644
index 0000000..385f563
--- /dev/null
+++ b/local_plugins/ep_openid_connect/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2020 Stefano Rivera,
+ 2016 Olivier Amblet, EPFL, The Human Brain Project, Toni Iltanen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/local_plugins/ep_openid_connect/README.md b/local_plugins/ep_openid_connect/README.md
new file mode 100644
index 0000000..fa18f1c
--- /dev/null
+++ b/local_plugins/ep_openid_connect/README.md
@@ -0,0 +1,269 @@
+ 
+
+# ep\_openid\_connect
+
+Etherpad plugin to authenticate users against an OpenID Connect provider.
+
+It uses provider discovery to keep configuration simple.
+
+Unlike other auth plugins, this one is not based around passport, for
+simplicity.
+
+This is a fork of
+[ep\_openid-client](https://github.com/stefanor/ep_openid-client).
+
+## Configuration
+
+The plugin expects an `ep_openid_connect` block in the settings, with
+this structure:
+
+```json
+ "ep_openid_connect": {
+ "issuer": "https://id.example.com",
+ "client_id": "MY CLIENT ID",
+ "client_secret": "MY CLIENT SECRET",
+ "base_url": "https://pad.example.com"
+ },
+ "requireAuthentication": true,
+```
+
+OAuth/OpenID Connect redirect URL (a.k.a. callback URL):
+`https://pad.example.com/ep_openid_connect/callback`
+
+Etherpad's `requireAuthentication` setting must be `true`.
+
+### Configuration Details
+
+* `issuer` (required if `issuer_metadata` is not set): The base URL of the
+ OpenID Connect identity provider, used to discover the relevant OpenID Connect
+ endpoints. If set, your identity provider must support the [OpenID Connect
+ Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html)
+ protocol.
+* `issuer_metadata` (required if `issuer` is not set): Object containing details
+ about your OpenID Connect identity provider. Used for manual configuration if
+ your identity provider does not support the Discovery protocol. If your
+ identity provider does support Discovery, you are encouraged to set `issuer`
+ instead. For properties, see the [documentation for the openid-client `Issuer`
+ object
+ constructor](https://github.com/panva/node-openid-client/blob/v4.7.4/docs/README.md#new-issuermetadata).
+* `client_id` (required): The OAuth2 client ID issued by the identity provider.
+* `client_secret` (required): The OAuth2 client secret issued by the identity
+ provider.
+* `base_url` (required): The public base Etherpad URL. When registering Etherpad
+ with your identity provider, the redirect URL (a.k.a. callback URL) is this
+ base URL plus `/ep_openid_connect/callback`.
+* `scope` (optional; defaults to `["openid"]`): List of OAuth2 scope strings.
+* `prohibited_usernames` (optional; defaults to `["admin", "guest"]`): List of
+ strings that will trigger an authentication error if any match the `sub`
+ (subject) claim from the identity provider. Use this to avoid conflicts with
+ the users in the `users` setting and to avoid conflicts with other plugins
+ (such as [ep\_guest](https://github.com/ether/ep_guest#readme)).
+* `user_properties` (optional): Object that controls the automatic creation of
+ additional properties on each authenticated user's account object. See below
+ for details.
+
+## Interaction with the `users` Setting
+
+When a user authenticates, the value of the `sub` (subject) claim is used as the
+user's username in Etherpad. (The `sub` claim is the identity provider's unique
+identifier for the user.) Many identity providers (such as GitLab) identify
+users by a numeric user ID, so the `sub` claim (and thus the Etherpad username)
+will probably look something like "5374".
+
+Each authenticated user gets their own account object. Default properties for a
+user's account object come from the `users` setting in `settings.json`. Etherpad
+uses the `is_admin`, `readOnly`, and `canCreate` properties to control access,
+and the
+[ep\_user\_displayname](https://github.com/ether/ep_user_displayname#readme)
+plugin uses the `displayname` property for the name displayed in the user list.
+For example, the following sets the default display name to "Firstname Lastname"
+and the default access to read-only for the user identified by "5374":
+
+```json
+ "users": {
+ "5374": {
+ "displayname": "Firstname Lastname",
+ "readOnly": true
+ }
+ },
+```
+
+To avoid unintentionally applying values to users authenticated via this plugin,
+you can use the `prohibited_usernames` settings to force an authentication error
+if the `sub` claim happens to match. This is useful for preventing a malicious
+identity provider from gaining admin access to your Etherpad instance.
+
+### Controlling user account object properties with `user_properties`
+
+The `user_properties` setting can be used to automatically add, remove, or
+change properties on a user's account object when the user authenticates. The
+`user_properties` setting maps a property name to a descriptor object that
+describes how the property's value is obtained:
+
+ * If the descriptor object has a `claim` property that names an existing
+ OpenID Connect claim, the value is set to the value of the claim. (If there
+ is no such claim, `claim` has no effect.)
+ * If the descriptor object has a `default` property and the account object
+ property would otherwise be unset, the property is set to the given value.
+ (Note that a property set to `undefined` is not the same as unset.)
+ * If the descriptor object is `null`, the property is removed if present.
+
+Furthermore:
+
+ * If `user_properties` does not specifiy a descriptor for `displayname`, one
+ is added as follows:
+
+ ```json
+ "ep_openid_connect": {
+ "user_properties": {
+ "displayname": {"claim": "name"}
+ }
+ },
+ ```
+
+ You can cancel out this default behavior by explicitly specifying an empty
+ object:
+
+ ```json
+ "ep_openid_connect": {
+ "user_properties": {
+ "displayname": {}
+ }
+ },
+ ```
+
+ * The `username` property is described as follows and cannot be overridden or
+ canceled:
+
+ ```json
+ "ep_openid_connect": {
+ "user_properties": {
+ "username": {"claim": "sub"}
+ }
+ },
+ ```
+
+Example:
+
+```json
+ "ep_openid_connect": {
+ "user_properties": {
+ "fromClaimWithDefault": {
+ "claim": "claimName",
+ "default": "default value"
+ },
+ "fromClaimOrUnset": {
+ "claim": "claimName"
+ },
+ "fixedValue": {
+ "default": "fixed value"
+ },
+ "forcedUnset": null
+ }
+ },
+```
+
+The above example sets properties as follows:
+* Each user's `fromClaimWithDefault` property is set to the value of the user's
+ `claimName` claim if present, otherwise the property is left unchanged if
+ already set, otherwise it is set to the string `"default value"`.
+* Each user's `fromClaimOrUnset` property is set to the value of the user's
+ `claimName` claim if present, otherwise the property is left unset/unchanged.
+* Each user's `fixedValue` property is set to the string `"fixed value"`
+ unless already set.
+* Each user's `forcedUnset` property is always deleted if present.
+* Each user's `displayname` property is set to to the value of the user's `name`
+ claim if present, otherwise the property is left unset/unchanged.
+* Each user's `username` property is set to the value of the `sub` claim.
+
+You can use this feature to control access in the OpenID Connect provider if it
+provides suitable claims:
+
+```json
+ "ep_openid_connect": {
+ "scope": ["openid", "etherpad"],
+ "user_properties": {
+ "is_admin": {"claim": "etherpad_is_admin"},
+ "readOnly": {"claim": "etherpad_readOnly"},
+ "canCreate": {"claim": "etherpad_canCreate"}
+ }
+ },
+```
+
+To avoid breaking assumptions made by Etherpad, the `username` property cannot
+be altered via the `user_properties` setting.
+
+## Interaction with the ep\_guest Plugin
+
+The [ep\_guest](https://github.com/ether/ep_guest#readme) plugin creates a user
+that is used for all guest accesses. It is recommended you add the username you
+chose for the guest user to the `prohibited_usernames` setting. If the identity
+provider ever uses that username in the `sub` claim, you will get an obvious
+error instead of a mysterious inability to edit pads.
+
+## Interaction with the ep\_user\_displayname Plugin
+
+By default, this plugin sets the user's `displayname` property to the value of
+the `name` claim. The
+[ep\_user\_displayname](https://github.com/ether/ep_user_displayname#readme)
+plugin uses this property (and the `displaynameChangeable` property) to control
+the name displayed in the pad's list of users.
+
+You can change the claim used to get the displayname:
+
+```json
+ "ep_openid_connect": {
+ "user_properties": {
+ "displayname": {"claim": "nickname"}
+ }
+ },
+```
+
+Or you can cancel the default behavior:
+
+
+```json
+ "ep_openid_connect": {
+ "user_properties": {
+ "displayname": {}
+ }
+ },
+```
+
+## Interaction with Etherpad's Built-in HTTP Basic Authentication
+
+If the user has not yet successfully authenticated, this plugin defers the
+access decision—it does not explicitly deny access. This causes Etherpad to fall
+back to another authentication plugin (if one is installed) or to the built-in
+HTTP basic authentication.
+
+Note: This plugin installs an authentication failure handler, so the user will
+not get a 401 error that causes the browser to prompt for a username and
+password for HTTP basic auth. To fall back to HTTP basic authentication, the
+user's browser must proactively set the `Authorization: Basic `
+header.
+
+## Interaction with Authorization Plugins
+
+This plugin sets `req.session.user` to the user's settings object from
+`settings.json` and sets `req.session.user.username` to the user's username (the
+`sub` claim). Etherpad's built-in HTTP basic authentication does the same thing,
+so any authorization plugin designed to work with Etherpad's built-in
+authentication should work with this plugin.
+
+## Support
+
+Currently only tested against GitLab instances.
+
+## Copyright and License
+
+Copyright © 2020 Stefano Rivera \
+Copyright © 2020-2021 Richard Hansen
+
+Licensed under the [MIT/Expat license](LICENSE).
+
+This is a fork of
+[ep\_openid-client](https://github.com/stefanor/ep_openid-client) by Stefano
+Rivera, which is based on
+[ep\_oauth2](https://github.com/HumanBrainProject/ep_oauth2) and
+[ep\_oidc](https://github.com/ToniIltanen/ep_oidc).
diff --git a/local_plugins/ep_openid_connect/ep.json b/local_plugins/ep_openid_connect/ep.json
new file mode 100644
index 0000000..b112e2d
--- /dev/null
+++ b/local_plugins/ep_openid_connect/ep.json
@@ -0,0 +1,15 @@
+{
+ "parts": [
+ {
+ "name": "main",
+ "hooks": {
+ "authenticate": "",
+ "authnFailure": "",
+ "expressCreateServer": "",
+ "init_ep_openid_connect": "",
+ "loadSettings": "",
+ "preAuthorize": ""
+ }
+ }
+ ]
+}
diff --git a/local_plugins/ep_openid_connect/index.js b/local_plugins/ep_openid_connect/index.js
new file mode 100644
index 0000000..8ea93ea
--- /dev/null
+++ b/local_plugins/ep_openid_connect/index.js
@@ -0,0 +1,376 @@
+'use strict';
+
+const Ajv = require('ajv/dist/jtd');
+const {URL} = require('url');
+const {Issuer, generators} = require('openid-client');
+
+let logger = {};
+for (const level of ['debug', 'info', 'warn', 'error']) {
+ logger[level] = console[level].bind(console, 'ep_openid_connect:');
+}
+const defaultSettings = {
+ prohibited_usernames: ['admin', 'guest'],
+ scope: ['openid'],
+ user_properties: {},
+};
+let settings;
+let oidcClient = null;
+
+const validSettings = new Ajv().compile({
+ properties: {
+ base_url: {type: 'string'},
+ client_id: {type: 'string'},
+ client_secret: {type: 'string'},
+ logout_url: {type: 'string'},
+ check_url: {type: 'string'},
+ },
+ optionalProperties: {
+ issuer: {type: 'string'},
+ issuer_metadata: {},
+ prohibited_usernames: {elements: {type: 'string'}},
+ scope: {elements: {type: 'string'}},
+ user_properties: {
+ values: {
+ optionalProperties: {
+ claim: {type: 'string'},
+ default: {type: 'string'},
+ },
+ nullable: true,
+ }
+ },
+ },
+});
+
+const ep = (endpoint) => `/ep_openid_connect/${endpoint}`;
+const endpointUrl = (endpoint) => new URL(ep(endpoint).substr(1), settings.base_url).toString();
+
+const validateSubClaim = (sub) => {
+ if (typeof sub !== 'string' || // 'sub' claim must exist as a string per OIDC spec.
+ sub === '' || // Empty string doesn't make sense.
+ sub === '__proto__' || // Prevent prototype pollution.
+ settings.prohibited_usernames.includes(sub)) {
+ throw new Error('invalid sub claim');
+ }
+};
+
+const discoverIssuer = async (issuerUrl) => {
+ issuerUrl = new URL(issuerUrl);
+ // https://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery says that the URI
+ // must not have query or fragment components.
+ if (issuerUrl.search) {
+ throw new Error(`Unexpected query in issuer URL (${issuerUrl}): ${issuerUrl.search}`);
+ }
+ if (issuerUrl.hash) {
+ throw new Error(`Unexpected fragment in issuer URL (${issuerUrl}): ${issuerUrl.hash}`);
+ }
+ let issuer;
+ try {
+ issuer = await Issuer.discover(issuerUrl.href);
+ } catch (err) {
+ // The URL used to get the issuer metadata doesn't exactly follow RFC 8615; see:
+ // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
+ const discoveryUrl = new URL(issuerUrl);
+ if (!discoveryUrl.pathname.includes('/.well-known/')) {
+ discoveryUrl.pathname =
+ `${discoveryUrl.pathname.replace(/\/$/, '')}/.well-known/openid-configuration`;
+ }
+ logger.error(
+ 'Failed to discover issuer metadata via OpenID Connect Discovery ' +
+ '(https://openid.net/specs/openid-connect-discovery-1_0.html). ' +
+ `Does your issuer support Discovery? (hint: ${discoveryUrl})`);
+ throw err;
+ }
+ logger.info('OpenID Connect Discovery complete.');
+ return issuer;
+};
+
+const syncRespCookies = (oldResp, newResp) => {
+ if (oldResp.headers["set-cookie"]) {
+ newResp.setHeader("set-cookie", oldResp.headers["set-cookie"])
+ }
+};
+
+const getIssuer = async (settings) => {
+ if (settings.issuer) return await discoverIssuer(settings.issuer);
+ return new Issuer(settings.issuer_metadata);
+};
+
+const checkToken = async (ygToken, token) => {
+ if (oidcClient == null) {
+ logger.warn('Not configured; ignoring request.');
+ return;
+ }
+ return await oidcClient.requestResource(settings.check_url, token, {
+ method: 'GET',
+ responseType: 'json',
+ headers: {
+ Accept: 'application/json',
+ Cookie: '_Y_G_=' + ygToken + '; _U_T_=' + token,
+ Referer: settings.base_url,
+ Token: token
+ },
+ });
+};
+
+const logout = async (ygToken, token) => {
+ if (oidcClient == null) {
+ logger.warn('Not configured; ignoring request.');
+ return;
+ }
+ return await oidcClient.requestResource(settings.logout_url, token, {
+ method: 'GET',
+ responseType: 'json',
+ headers: {
+ Accept: 'application/json',
+ Cookie: '_Y_G_=' + ygToken + '; _U_T_=' + token,
+ Referer: settings.base_url,
+ Token: token
+ },
+ });
+};
+
+exports.init_ep_openid_connect = async (hookName, {logger: l}) => {
+ if (l != null) logger = l;
+};
+
+exports.loadSettings = async (hookName, {settings: {ep_openid_connect: s = {}}}) => {
+ oidcClient = null;
+ settings = null;
+ if (!validSettings(s)) {
+ logger.error('Invalid settings. Detailed validation errors:', validSettings.errors);
+ return;
+ }
+ if ((s.issuer == null) === (s.issuer_metadata == null)) {
+ logger.error('Either ep_openid_connect.issuer or .issuer_metadata must be set (but not both)');
+ return;
+ }
+ if ('username' in (s.user_properties || {})) {
+ logger.error('ep_openid_connect.user_properties.username must not be set');
+ return;
+ }
+ settings = {
+ ...defaultSettings,
+ ...s,
+ user_properties: {
+ displayname: {claim: 'name'},
+ ...s.user_properties,
+ // The username property must always match the key used in settings.users.
+ username: {claim: 'sub'},
+ },
+ };
+ // Make sure base_url ends with '/' so that relative URLs are appended:
+ if (!settings.base_url.endsWith('/')) settings.base_url += '/';
+ logger.debug('Settings:', {...settings, client_secret: '********'});
+ oidcClient = new (await getIssuer(settings)).Client({
+ client_id: settings.client_id,
+ client_secret: settings.client_secret,
+ response_types: ['code'],
+ redirect_uris: [endpointUrl('callback')],
+ });
+ logger.info('Configured.');
+};
+
+
+exports.expressCreateServer = (hookName, {app}) => {
+ logger.debug('Configuring auth routes');
+ app.get(ep('callback'), async (req, res, next) => {
+ // This handler MUST NOT redirect to a page that requires authentication if there is a problem,
+ // otherwise the user could be caught in an infinite redirect loop.
+ try {
+ logger.debug(`Processing ${req.url}`);
+ if (oidcClient == null) {
+ logger.warn('Not configured; ignoring request.');
+ return next();
+ }
+
+ let token = req.cookies._U_T_;
+ if (token !== undefined && token !== "") {
+ let resp = await checkToken(req.cookies._Y_G_, token);
+ if (resp.statusCode === 200) {
+ // The user has successfully authenticated, but don't set req.session.user here -- do it in
+ // the authenticate hook so that Etherpad can log the authentication success. However, DO "log
+ // out" the previous user to force the authenticate hook to run in case the user was already
+ // authenticated as someone else.
+ const oidcSession = req.session.ep_openid_connect || {};
+ delete req.session.user;
+ // userinfo should not be stored in req.session until after all checks have passed. (Otherwise
+ // it would be too easy to accidentally introduce a vulnerability.)
+ let data = JSON.parse(resp.body);
+ logger.info("login username: " + data.data.username);
+ oidcSession.userinfo = data.data;
+ // todo set cookies
+ syncRespCookies(resp, res);
+ res.redirect(303, oidcSession.next || settings.base_url);
+ // Defer deletion of state until success so that the user can reload the page to retry after a
+ // transient backchannel failure.
+ delete oidcSession.callbackChecks;
+ delete oidcSession.next;
+ } else {
+ throw new Error('There is a invalid jwt in cooke:' + resp.statusCode);
+ }
+ } else {
+ const params = oidcClient.callbackParams(req);
+ const oidcSession = req.session.ep_openid_connect || {};
+ if (oidcSession.callbackChecks == null) throw new Error('missing authentication checks');
+ const tokenset =
+ await oidcClient.callback(endpointUrl('callback'), params, oidcSession.callbackChecks);
+ const userinfo = await oidcClient.userinfo(tokenset);
+ validateSubClaim(userinfo.sub);
+ // The user has successfully authenticated, but don't set req.session.user here -- do it in
+ // the authenticate hook so that Etherpad can log the authentication success. However, DO "log
+ // out" the previous user to force the authenticate hook to run in case the user was already
+ // authenticated as someone else.
+ delete req.session.user;
+ // userinfo should not be stored in req.session until after all checks have passed. (Otherwise
+ // it would be too easy to accidentally introduce a vulnerability.)
+ oidcSession.userinfo = userinfo;
+ res.redirect(303, oidcSession.next || settings.base_url);
+ // Defer deletion of state until success so that the user can reload the page to retry after a
+ // transient backchannel failure.
+ delete oidcSession.callbackChecks;
+ delete oidcSession.next;
+ }
+ } catch (err) {
+ return next(err);
+ }
+ });
+ app.get(ep('login'), async (req, res, next) => {
+ logger.debug(`Processing ${req.url}`);
+ if (oidcClient == null) {
+ logger.warn('Not configured; ignoring request.');
+ return next();
+ }
+ let token = req.cookies._U_T_;
+ if (token !== undefined && token !== "") {
+ let resp = await checkToken(req.cookies._Y_G_, token);
+ logger.info("login result:" + resp.statusCode)
+ if (resp.statusCode === 200) {
+ syncRespCookies(resp, res);
+ return true;
+ }
+ }
+ if (req.session.ep_openid_connect == null) req.session.ep_openid_connect = {};
+ const oidcSession = req.session.ep_openid_connect;
+ const commonParams = {
+ nonce: generators.nonce(),
+ scope: settings.scope.join(' '),
+ state: generators.state(),
+ };
+ oidcSession.callbackChecks = {
+ ...commonParams,
+ code_verifier: generators.codeVerifier(), // RFC7636
+ };
+ res.redirect(303, oidcClient.authorizationUrl({
+ ...commonParams,
+ // RFC7636
+ code_challenge: generators.codeChallenge(oidcSession.callbackChecks.code_verifier),
+ code_challenge_method: 'S256',
+ }));
+ });
+ app.get(ep('logout'), async (req, res, next) => {
+ logger.debug(`Processing ${req.url}`);
+ if (oidcClient == null) {
+ logger.warn('Not configured; ignoring request.');
+ } else {
+ let token = req.cookies._U_T_;
+ if (token !== undefined && token !== "") {
+ let resp = await logout(req.cookies._Y_G_, token);
+ logger.info("logout result:" + resp.statusCode)
+ }
+ }
+ req.session.destroy(() => res.redirect(303, settings.base_url));
+ });
+};
+
+exports.authenticate = async (hookName, {req, res, users}) => {
+ if (oidcClient == null) return;
+ logger.debug('authenticate hook for', req.url);
+ let userinfo = null;
+ let token = req.cookies._U_T_;
+ if (token !== undefined && token !== "" ) {
+ let resp = await checkToken(req.cookies._Y_G_, token);
+ logger.info("authenticate result:" + resp.statusCode)
+ if (resp.statusCode === 200) {
+ syncRespCookies(resp, res);
+ userinfo = JSON.parse(resp.body);
+ req.session.user = userinfo.data;
+ logger.info("authenticate user:" + userinfo.data.username);
+ } else {
+ return;
+ }
+ } else {
+ const {ep_openid_connect: {userinfo} = {}} = req.session;
+ if (userinfo == null) { // Nullish means the user isn't authenticated.
+ // Out of an abundance of caution, clear out the old state, nonce, and userinfo (if present) to
+ // force regeneration.
+ delete req.session.ep_openid_connect;
+ // Authn failed. Let another plugin try to authenticate the user.
+ return;
+ }
+ }
+ // Successfully authenticated.
+ logger.info('Successfully authenticated user with userinfo:', userinfo);
+ req.session.user = users[userinfo.sub];
+ if (req.session.user == null) req.session.user = users[userinfo.sub] = {};
+ for (const [propName, descriptor] of Object.entries(settings.user_properties)) {
+ if (descriptor == null) {
+ delete req.session.user[propName];
+ } else if (descriptor.claim != null && descriptor.claim in userinfo) {
+ req.session.user[propName] = userinfo[descriptor.claim];
+ } else if ('default' in descriptor && !(propName in req.session.user)) {
+ req.session.user[propName] = descriptor.default;
+ }
+ }
+ logger.debug('User properties:', req.session.user);
+ return true;
+};
+
+exports.authnFailure = (hookName, {req, res}) => {
+ if (oidcClient == null) return;
+ // Normally the user is redirected to the login page which would then redirect the user back once
+ // authenticated. For non-GET requests, send a 401 instead because users can't be redirected back.
+ // Also send a 401 if an Authorization header is present to facilitate API error handling.
+ //
+ // 401 is the status that most closely matches the desired semantics. However, RFC7235 section
+ // 3.1 says, "The server generating a 401 response MUST send a WWW-Authenticate header field
+ // containing at least one challenge applicable to the target resource." Etherpad uses a token
+ // (signed session identifier) transmitted via cookie for authentication, but there is no
+ // standard authentication scheme name for that. So we use a non-standard name here.
+ //
+ // We could theoretically implement Bearer authorization (RFC6750), but it's unclear to me how
+ // to do this correctly and securely:
+ // * The userinfo endpoint is meant for the OAuth client, not the resource server, so it
+ // shouldn't be used to look up claims.
+ // * In general, access tokens might be opaque (not JWTs) so we can't get claims by parsing
+ // them.
+ // * The token introspection endpoint should return scope and subject (I think?), but probably
+ // not claims.
+ // * If claims can't be used to convey access level, how is it conveyed? Scope? Resource
+ // indicators (RFC8707)?
+ // * How is intended audience checked? Or is introspection guaranteed to do that for us?
+ // * Should tokens be limited to a particular pad?
+ // * Bearer tokens are only meant to convey authorization; authentication is handled by the
+ // authorization server. Should Bearer tokens be processed during the authorize hook?
+ // * How should bearer authentication interact with authorization plugins?
+ // * How should bearer authentication interact with plugins that add new endpoints?
+ // * Would we have to implement our own OAuth server to issue access tokens?
+ res.header('WWW-Authenticate', 'Etherpad');
+ if (!['GET', 'HEAD'].includes(req.method) || req.headers.authorization) {
+ res.status(401).end();
+ return true;
+ }
+ if (req.session.ep_openid_connect == null) req.session.ep_openid_connect = {};
+ req.session.ep_openid_connect.next = new URL(req.url.slice(1), settings.base_url).toString();
+ res.redirect(303, endpointUrl('login'));
+ return true;
+};
+
+exports.preAuthorize = (hookName, {req}) => {
+ if (oidcClient == null) return;
+ if (req.path.startsWith(ep(''))) return true;
+ return;
+};
+
+exports.exportedForTestingOnly = {
+ defaultSettings,
+};
diff --git a/local_plugins/ep_openid_connect/package.json b/local_plugins/ep_openid_connect/package.json
new file mode 100644
index 0000000..7007458
--- /dev/null
+++ b/local_plugins/ep_openid_connect/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "ep_openid_connect",
+ "version": "3.0.7",
+ "description": "Etherpad plugin to authenticate users against an OpenID Connect provider.",
+ "main": "index.js",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/ether/ep_openid_connect.git"
+ },
+ "license": "MIT",
+ "keywords": [
+ "OAuth",
+ "OpenID Connect",
+ "auth",
+ "authentication",
+ "ep",
+ "etherpad",
+ "oidc",
+ "plugin"
+ ],
+ "bugs": {
+ "url": "https://github.com/ether/ep_openid_connect/issues"
+ },
+ "homepage": "https://github.com/ether/ep_openid_connect#readme",
+ "dependencies": {
+ "@etherpad/node-openid-client": "npm:@jsr/etherpad__node-openid-client@^0.1.0",
+ "ajv": "^8.11.0",
+ "openid-client": "^5.1.5"
+ },
+ "devDependencies": {
+ "eslint": "^8.57.0",
+ "eslint-config-etherpad": "^4.0.4",
+ "oidc-provider": "^8.4.5",
+ "supertest": "^6.2.2",
+ "typescript": "^5.4.2"
+ },
+ "scripts": {
+ "lint": "eslint .",
+ "lint:fix": "eslint --fix ."
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://etherpad.org/"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+}
diff --git a/local_plugins/ep_openid_connect/pnpm-lock.yaml b/local_plugins/ep_openid_connect/pnpm-lock.yaml
new file mode 100644
index 0000000..e45abb3
--- /dev/null
+++ b/local_plugins/ep_openid_connect/pnpm-lock.yaml
@@ -0,0 +1,2933 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@etherpad/node-openid-client':
+ specifier: npm:@jsr/etherpad__node-openid-client@^0.1.0
+ version: '@jsr/etherpad__node-openid-client@0.1.0'
+ ajv:
+ specifier: ^8.11.0
+ version: 8.12.0
+ openid-client:
+ specifier: ^5.1.5
+ version: 5.6.5
+ devDependencies:
+ eslint:
+ specifier: ^8.57.0
+ version: 8.57.0
+ eslint-config-etherpad:
+ specifier: ^4.0.4
+ version: 4.0.4(eslint@8.57.0)(typescript@5.4.2)
+ oidc-provider:
+ specifier: ^8.4.5
+ version: 8.4.5
+ supertest:
+ specifier: ^6.2.2
+ version: 6.3.4
+ typescript:
+ specifier: ^5.4.2
+ version: 5.4.2
+
+packages:
+
+ '@aashutoshrathi/word-wrap@1.2.6':
+ resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
+ engines: {node: '>=0.10.0'}
+
+ '@eslint-community/eslint-utils@4.4.0':
+ resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.10.0':
+ resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/eslintrc@2.1.4':
+ resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ '@eslint/js@8.57.0':
+ resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ '@humanwhocodes/config-array@0.11.14':
+ resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
+ engines: {node: '>=10.10.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/object-schema@2.0.2':
+ resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
+
+ '@jsr/etherpad__node-openid-client@0.1.0':
+ resolution: {integrity: sha512-gFmZeeJQoPlG+cSI7XrIVGOtssAUNdFh8+anbuTJm68PCrmIOEp/xxZp1Cnsm94XjgZ5HQYU8PDMfr2QD8v6Hw==, tarball: https://npm.jsr.io/~/10/@jsr/etherpad__node-openid-client/0.1.0.tgz}
+
+ '@koa/cors@5.0.0':
+ resolution: {integrity: sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==}
+ engines: {node: '>= 14.0.0'}
+
+ '@koa/router@12.0.1':
+ resolution: {integrity: sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==}
+ engines: {node: '>= 12'}
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@rushstack/eslint-patch@1.7.2':
+ resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==}
+
+ '@sindresorhus/is@5.6.0':
+ resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
+ engines: {node: '>=14.16'}
+
+ '@szmarczak/http-timer@5.0.1':
+ resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
+ engines: {node: '>=14.16'}
+
+ '@types/http-cache-semantics@4.0.4':
+ resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/json5@0.0.29':
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+
+ '@types/semver@7.5.8':
+ resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
+
+ '@typescript-eslint/eslint-plugin@7.2.0':
+ resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^7.0.0
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@typescript-eslint/parser@7.2.0':
+ resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@typescript-eslint/scope-manager@7.2.0':
+ resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+
+ '@typescript-eslint/type-utils@7.2.0':
+ resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@typescript-eslint/types@7.2.0':
+ resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+
+ '@typescript-eslint/typescript-estree@7.2.0':
+ resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@typescript-eslint/utils@7.2.0':
+ resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+
+ '@typescript-eslint/visitor-keys@7.2.0':
+ resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+
+ '@ungap/structured-clone@1.2.0':
+ resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
+
+ accepts@1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.11.3:
+ resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ ajv@8.12.0:
+ resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ array-buffer-byte-length@1.0.1:
+ resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
+ engines: {node: '>= 0.4'}
+
+ array-includes@3.1.7:
+ resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==}
+ engines: {node: '>= 0.4'}
+
+ array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+
+ array.prototype.filter@1.0.3:
+ resolution: {integrity: sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlastindex@1.2.4:
+ resolution: {integrity: sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flat@1.3.2:
+ resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flatmap@1.3.2:
+ resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
+ engines: {node: '>= 0.4'}
+
+ arraybuffer.prototype.slice@1.0.3:
+ resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
+ engines: {node: '>= 0.4'}
+
+ asap@2.0.6:
+ resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ brace-expansion@1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+ brace-expansion@2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+
+ braces@3.0.2:
+ resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+ engines: {node: '>=8'}
+
+ builtin-modules@3.3.0:
+ resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
+ engines: {node: '>=6'}
+
+ builtins@5.0.1:
+ resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==}
+
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
+ cache-content-type@1.0.1:
+ resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==}
+ engines: {node: '>= 6.0.0'}
+
+ cacheable-lookup@7.0.0:
+ resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
+ engines: {node: '>=14.16'}
+
+ cacheable-request@10.2.14:
+ resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==}
+ engines: {node: '>=14.16'}
+
+ call-bind@1.0.7:
+ resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ co@4.6.0:
+ resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
+ engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ component-emitter@1.3.1:
+ resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ content-disposition@0.5.4:
+ resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
+ engines: {node: '>= 0.6'}
+
+ content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+
+ cookiejar@2.1.4:
+ resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}
+
+ cookies@0.9.1:
+ resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
+ engines: {node: '>= 0.8'}
+
+ cross-spawn@7.0.3:
+ resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
+ engines: {node: '>= 8'}
+
+ debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.3.4:
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decompress-response@6.0.0:
+ resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
+ engines: {node: '>=10'}
+
+ deep-equal@1.0.1:
+ resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==}
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ defer-to-connect@2.0.1:
+ resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
+ engines: {node: '>=10'}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ delegates@1.0.0:
+ resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
+
+ depd@1.1.2:
+ resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
+ engines: {node: '>= 0.6'}
+
+ depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+
+ destroy@1.2.0:
+ resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
+ dezalgo@1.0.4:
+ resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==}
+
+ dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+
+ doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+
+ doctrine@3.0.0:
+ resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
+ engines: {node: '>=6.0.0'}
+
+ ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
+ encodeurl@1.0.2:
+ resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
+ engines: {node: '>= 0.8'}
+
+ enhanced-resolve@5.16.0:
+ resolution: {integrity: sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==}
+ engines: {node: '>=10.13.0'}
+
+ es-abstract@1.22.5:
+ resolution: {integrity: sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==}
+ engines: {node: '>= 0.4'}
+
+ es-array-method-boxes-properly@1.0.0:
+ resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==}
+
+ es-define-property@1.0.0:
+ resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.0.3:
+ resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==}
+ engines: {node: '>= 0.4'}
+
+ es-shim-unscopables@1.0.2:
+ resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
+
+ es-to-primitive@1.2.1:
+ resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
+ engines: {node: '>= 0.4'}
+
+ escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
+ escape-string-regexp@1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-compat-utils@0.1.2:
+ resolution: {integrity: sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ eslint: '>=6.0.0'
+
+ eslint-config-etherpad@4.0.4:
+ resolution: {integrity: sha512-y1riT+lmFwd+TZR9LzTlF4ntcTWRUpjqspdJ8kekLY9gcwyBsKTaW/Jj8mO4DyfDR72/3o4t6v7A8d8SqXybUQ==}
+ engines: {node: '>=12.17.0'}
+
+ eslint-import-resolver-node@0.3.9:
+ resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+
+ eslint-import-resolver-typescript@3.6.1:
+ resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '*'
+ eslint-plugin-import: '*'
+
+ eslint-module-utils@2.8.1:
+ resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+
+ eslint-plugin-cypress@2.15.1:
+ resolution: {integrity: sha512-eLHLWP5Q+I4j2AWepYq0PgFEei9/s5LvjuSqWrxurkg1YZ8ltxdvMNmdSf0drnsNo57CTgYY/NIHHLRSWejR7w==}
+ peerDependencies:
+ eslint: '>= 3.2.1'
+
+ eslint-plugin-es-x@7.5.0:
+ resolution: {integrity: sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '>=8'
+
+ eslint-plugin-eslint-comments@3.2.0:
+ resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==}
+ engines: {node: '>=6.5.0'}
+ peerDependencies:
+ eslint: '>=4.19.1'
+
+ eslint-plugin-import@2.29.1:
+ resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+
+ eslint-plugin-mocha@10.4.1:
+ resolution: {integrity: sha512-G85ALUgKaLzuEuHhoW3HVRgPTmia6njQC3qCG6CEvA8/Ja9PDZnRZOuzekMki+HaViEQXINuYsmhp5WR5/4MfA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ eslint: '>=7.0.0'
+
+ eslint-plugin-n@16.6.2:
+ resolution: {integrity: sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==}
+ engines: {node: '>=16.0.0'}
+ peerDependencies:
+ eslint: '>=7.0.0'
+
+ eslint-plugin-prefer-arrow@1.2.3:
+ resolution: {integrity: sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ==}
+ peerDependencies:
+ eslint: '>=2.0.0'
+
+ eslint-plugin-promise@6.1.1:
+ resolution: {integrity: sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+
+ eslint-plugin-you-dont-need-lodash-underscore@6.13.0:
+ resolution: {integrity: sha512-6FkFLp/R/QlgfJl5NrxkIXMQ36jMVLczkWDZJvMd7/wr/M3K0DS7mtX7plZ3giTDcbDD7VBfNYUfUVaBCZOXKA==}
+ engines: {node: '>=4.0'}
+
+ eslint-scope@7.2.2:
+ resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-utils@3.0.0:
+ resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
+ engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
+ peerDependencies:
+ eslint: '>=5'
+
+ eslint-visitor-keys@2.1.0:
+ resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
+ engines: {node: '>=10'}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint@8.57.0:
+ resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ hasBin: true
+
+ espree@9.6.1:
+ resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ esquery@1.5.0:
+ resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ eta@3.4.0:
+ resolution: {integrity: sha512-tCsc7WXTjrTx4ZjYLplcqrI3o4mYJ+Z6YspeuGL8tbt/hHoMchwBwtKfwM09svEY86iRapY93vUqQttcNuIO5Q==}
+ engines: {node: '>=6.0.0'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-glob@3.3.2:
+ resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fast-safe-stringify@2.1.1:
+ resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
+
+ fastq@1.17.1:
+ resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
+
+ file-entry-cache@6.0.1:
+ resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+
+ fill-range@7.0.1:
+ resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@3.2.0:
+ resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+
+ flatted@3.3.1:
+ resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
+
+ for-each@0.3.3:
+ resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
+
+ form-data-encoder@2.1.4:
+ resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==}
+ engines: {node: '>= 14.17'}
+
+ form-data@4.0.0:
+ resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+ engines: {node: '>= 6'}
+
+ formidable@2.1.2:
+ resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==}
+
+ fresh@0.5.2:
+ resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
+ engines: {node: '>= 0.6'}
+
+ fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ function.prototype.name@1.1.6:
+ resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
+ engines: {node: '>= 0.4'}
+
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ get-intrinsic@1.2.4:
+ resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+ engines: {node: '>= 0.4'}
+
+ get-stream@6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+
+ get-symbol-description@1.0.2:
+ resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
+ engines: {node: '>= 0.4'}
+
+ get-tsconfig@4.7.3:
+ resolution: {integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+
+ globals@13.24.0:
+ resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
+ engines: {node: '>=8'}
+
+ globalthis@1.0.3:
+ resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
+ engines: {node: '>= 0.4'}
+
+ globby@11.1.0:
+ resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+ engines: {node: '>=10'}
+
+ gopd@1.0.1:
+ resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
+
+ got@13.0.0:
+ resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==}
+ engines: {node: '>=16'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ graphemer@1.4.0:
+ resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+
+ has-bigints@1.0.2:
+ resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.0.3:
+ resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.0.3:
+ resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ hexoid@1.0.0:
+ resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==}
+ engines: {node: '>=8'}
+
+ http-assert@1.5.0:
+ resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==}
+ engines: {node: '>= 0.8'}
+
+ http-cache-semantics@4.1.1:
+ resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
+
+ http-errors@1.8.1:
+ resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==}
+ engines: {node: '>= 0.6'}
+
+ http-errors@2.0.0:
+ resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+ engines: {node: '>= 0.8'}
+
+ http2-wrapper@2.2.1:
+ resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==}
+ engines: {node: '>=10.19.0'}
+
+ iconv-lite@0.4.24:
+ resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+ engines: {node: '>=0.10.0'}
+
+ ignore@5.3.1:
+ resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.0:
+ resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ internal-slot@1.0.7:
+ resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
+ engines: {node: '>= 0.4'}
+
+ is-array-buffer@3.0.4:
+ resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
+ engines: {node: '>= 0.4'}
+
+ is-bigint@1.0.4:
+ resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
+
+ is-boolean-object@1.1.2:
+ resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
+ engines: {node: '>= 0.4'}
+
+ is-builtin-module@3.2.1:
+ resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
+ engines: {node: '>=6'}
+
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ is-core-module@2.13.1:
+ resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
+
+ is-date-object@1.0.5:
+ resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-generator-function@1.0.10:
+ resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
+ engines: {node: '>= 0.4'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+
+ is-number-object@1.0.7:
+ resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
+ engines: {node: '>= 0.4'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-path-inside@3.0.3:
+ resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+ engines: {node: '>=8'}
+
+ is-regex@1.1.4:
+ resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
+ engines: {node: '>= 0.4'}
+
+ is-shared-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==}
+ engines: {node: '>= 0.4'}
+
+ is-string@1.0.7:
+ resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
+ engines: {node: '>= 0.4'}
+
+ is-symbol@1.0.4:
+ resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
+ engines: {node: '>= 0.4'}
+
+ is-typed-array@1.1.13:
+ resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==}
+ engines: {node: '>= 0.4'}
+
+ is-weakref@1.0.2:
+ resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
+
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ jose@4.15.5:
+ resolution: {integrity: sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==}
+
+ jose@5.2.4:
+ resolution: {integrity: sha512-6ScbIk2WWCeXkmzF6bRPmEuaqy1m8SbsRFMa/FLrSCkGIhj8OLVG/IH+XHVmNMx/KUo8cVWEE6oKR4dJ+S0Rkg==}
+
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+
+ jsesc@3.0.2:
+ resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+
+ kebab-case@1.0.2:
+ resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==}
+
+ keygrip@1.1.0:
+ resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}
+ engines: {node: '>= 0.6'}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ koa-compose@4.1.0:
+ resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==}
+
+ koa-convert@2.0.0:
+ resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==}
+ engines: {node: '>= 10'}
+
+ koa@2.15.1:
+ resolution: {integrity: sha512-kpxzGxsv7tlc0WmccWd6CfdWqYXk4o/FsCTjnKaDnHLjPK/Sy1MpoBkuKO5LN7GdPHgPljrAVmMO3wbFxEJTeA==}
+ engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ lowercase-keys@3.0.0:
+ resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ lru-cache@6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
+
+ media-typer@0.3.0:
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+ engines: {node: '>= 0.6'}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ methods@1.1.2:
+ resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
+ engines: {node: '>= 0.6'}
+
+ micromatch@4.0.5:
+ resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mime@2.6.0:
+ resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
+ engines: {node: '>=4.0.0'}
+ hasBin: true
+
+ mimic-response@3.1.0:
+ resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
+ engines: {node: '>=10'}
+
+ mimic-response@4.0.0:
+ resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimatch@9.0.3:
+ resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ ms@2.1.2:
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@5.0.7:
+ resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ negotiator@0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+
+ normalize-url@8.0.1:
+ resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==}
+ engines: {node: '>=14.16'}
+
+ object-hash@2.2.0:
+ resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
+ engines: {node: '>= 6'}
+
+ object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+
+ object-inspect@1.13.1:
+ resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ object.assign@4.1.5:
+ resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
+ engines: {node: '>= 0.4'}
+
+ object.fromentries@2.0.7:
+ resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==}
+ engines: {node: '>= 0.4'}
+
+ object.groupby@1.0.2:
+ resolution: {integrity: sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==}
+
+ object.values@1.1.7:
+ resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==}
+ engines: {node: '>= 0.4'}
+
+ oidc-provider@8.4.5:
+ resolution: {integrity: sha512-2NsPrvIAX1W4ZR41cGbz2Lt2Ci8iXvECh+x+LcKcM115s/h8iB1pwnNlCdIrvAA2iBGM4/TkO75Xg7xb2FCzWA==}
+
+ oidc-token-hash@5.0.3:
+ resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==}
+ engines: {node: ^10.13.0 || >=12.0.0}
+
+ on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ only@0.0.2:
+ resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==}
+
+ openid-client@5.6.5:
+ resolution: {integrity: sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==}
+
+ optionator@0.9.3:
+ resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
+ engines: {node: '>= 0.8.0'}
+
+ p-cancelable@3.0.0:
+ resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
+ engines: {node: '>=12.20'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-to-regexp@6.2.2:
+ resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==}
+
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ possible-typed-array-names@1.0.0:
+ resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
+ engines: {node: '>= 0.4'}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ qs@6.12.0:
+ resolution: {integrity: sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==}
+ engines: {node: '>=0.6'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ quick-lru@5.1.1:
+ resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
+ engines: {node: '>=10'}
+
+ quick-lru@7.0.0:
+ resolution: {integrity: sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==}
+ engines: {node: '>=18'}
+
+ rambda@7.5.0:
+ resolution: {integrity: sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==}
+
+ raw-body@2.5.2:
+ resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
+ engines: {node: '>= 0.8'}
+
+ regexp.prototype.flags@1.5.2:
+ resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
+ engines: {node: '>= 0.4'}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ resolve-alpn@1.2.1:
+ resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ resolve@1.22.8:
+ resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+ hasBin: true
+
+ responselike@3.0.0:
+ resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==}
+ engines: {node: '>=14.16'}
+
+ reusify@1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rimraf@3.0.2:
+ resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ hasBin: true
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ safe-array-concat@1.1.2:
+ resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==}
+ engines: {node: '>=0.4'}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-regex-test@1.0.3:
+ resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
+ engines: {node: '>= 0.4'}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.6.0:
+ resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+
+ setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ side-channel@1.0.6:
+ resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
+ engines: {node: '>= 0.4'}
+
+ slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+
+ statuses@1.5.0:
+ resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
+ engines: {node: '>= 0.6'}
+
+ statuses@2.0.1:
+ resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+ engines: {node: '>= 0.8'}
+
+ string.prototype.trim@1.2.8:
+ resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimend@1.0.7:
+ resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==}
+
+ string.prototype.trimstart@1.0.7:
+ resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ superagent@8.1.2:
+ resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==}
+ engines: {node: '>=6.4.0 <13 || >=14'}
+
+ supertest@6.3.4:
+ resolution: {integrity: sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==}
+ engines: {node: '>=6.4.0'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ tapable@2.2.1:
+ resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
+ engines: {node: '>=6'}
+
+ text-table@0.2.0:
+ resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+
+ ts-api-utils@1.3.0:
+ resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ typescript: '>=4.2.0'
+
+ tsconfig-paths@3.15.0:
+ resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+
+ tsscmp@1.0.6:
+ resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
+ engines: {node: '>=0.6.x'}
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ type-fest@0.20.2:
+ resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
+ engines: {node: '>=10'}
+
+ type-is@1.6.18:
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+ engines: {node: '>= 0.6'}
+
+ typed-array-buffer@1.0.2:
+ resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-length@1.0.1:
+ resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-offset@1.0.2:
+ resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-length@1.0.5:
+ resolution: {integrity: sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==}
+ engines: {node: '>= 0.4'}
+
+ typescript@5.4.2:
+ resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ unbox-primitive@1.0.2:
+ resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
+
+ unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
+ which-boxed-primitive@1.0.2:
+ resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+
+ which-typed-array@1.1.15:
+ resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
+ engines: {node: '>= 0.4'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ yallist@4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+
+ ylru@1.3.2:
+ resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==}
+ engines: {node: '>= 4.0.0'}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+snapshots:
+
+ '@aashutoshrathi/word-wrap@1.2.6': {}
+
+ '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)':
+ dependencies:
+ eslint: 8.57.0
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.10.0': {}
+
+ '@eslint/eslintrc@2.1.4':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.3.4
+ espree: 9.6.1
+ globals: 13.24.0
+ ignore: 5.3.1
+ import-fresh: 3.3.0
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@8.57.0': {}
+
+ '@humanwhocodes/config-array@0.11.14':
+ dependencies:
+ '@humanwhocodes/object-schema': 2.0.2
+ debug: 4.3.4
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/object-schema@2.0.2': {}
+
+ '@jsr/etherpad__node-openid-client@0.1.0':
+ dependencies:
+ jose: 4.15.5
+
+ '@koa/cors@5.0.0':
+ dependencies:
+ vary: 1.1.2
+
+ '@koa/router@12.0.1':
+ dependencies:
+ debug: 4.3.4
+ http-errors: 2.0.0
+ koa-compose: 4.1.0
+ methods: 1.1.2
+ path-to-regexp: 6.2.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.17.1
+
+ '@rushstack/eslint-patch@1.7.2': {}
+
+ '@sindresorhus/is@5.6.0': {}
+
+ '@szmarczak/http-timer@5.0.1':
+ dependencies:
+ defer-to-connect: 2.0.1
+
+ '@types/http-cache-semantics@4.0.4': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/json5@0.0.29': {}
+
+ '@types/semver@7.5.8': {}
+
+ '@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint@8.57.0)(typescript@5.4.2)':
+ dependencies:
+ '@eslint-community/regexpp': 4.10.0
+ '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/scope-manager': 7.2.0
+ '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/visitor-keys': 7.2.0
+ debug: 4.3.4
+ eslint: 8.57.0
+ graphemer: 1.4.0
+ ignore: 5.3.1
+ natural-compare: 1.4.0
+ semver: 7.6.0
+ ts-api-utils: 1.3.0(typescript@5.4.2)
+ optionalDependencies:
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 7.2.0
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
+ '@typescript-eslint/visitor-keys': 7.2.0
+ debug: 4.3.4
+ eslint: 8.57.0
+ optionalDependencies:
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@7.2.0':
+ dependencies:
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/visitor-keys': 7.2.0
+
+ '@typescript-eslint/type-utils@7.2.0(eslint@8.57.0)(typescript@5.4.2)':
+ dependencies:
+ '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
+ '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ debug: 4.3.4
+ eslint: 8.57.0
+ ts-api-utils: 1.3.0(typescript@5.4.2)
+ optionalDependencies:
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@7.2.0': {}
+
+ '@typescript-eslint/typescript-estree@7.2.0(typescript@5.4.2)':
+ dependencies:
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/visitor-keys': 7.2.0
+ debug: 4.3.4
+ globby: 11.1.0
+ is-glob: 4.0.3
+ minimatch: 9.0.3
+ semver: 7.6.0
+ ts-api-utils: 1.3.0(typescript@5.4.2)
+ optionalDependencies:
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@7.2.0(eslint@8.57.0)(typescript@5.4.2)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.5.8
+ '@typescript-eslint/scope-manager': 7.2.0
+ '@typescript-eslint/types': 7.2.0
+ '@typescript-eslint/typescript-estree': 7.2.0(typescript@5.4.2)
+ eslint: 8.57.0
+ semver: 7.6.0
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ '@typescript-eslint/visitor-keys@7.2.0':
+ dependencies:
+ '@typescript-eslint/types': 7.2.0
+ eslint-visitor-keys: 3.4.3
+
+ '@ungap/structured-clone@1.2.0': {}
+
+ accepts@1.3.8:
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+
+ acorn-jsx@5.3.2(acorn@8.11.3):
+ dependencies:
+ acorn: 8.11.3
+
+ acorn@8.11.3: {}
+
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ajv@8.12.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+ uri-js: 4.4.1
+
+ ansi-regex@5.0.1: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ argparse@2.0.1: {}
+
+ array-buffer-byte-length@1.0.1:
+ dependencies:
+ call-bind: 1.0.7
+ is-array-buffer: 3.0.4
+
+ array-includes@3.1.7:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+ get-intrinsic: 1.2.4
+ is-string: 1.0.7
+
+ array-union@2.1.0: {}
+
+ array.prototype.filter@1.0.3:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+ es-array-method-boxes-properly: 1.0.0
+ is-string: 1.0.7
+
+ array.prototype.findlastindex@1.2.4:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.0.2
+
+ array.prototype.flat@1.3.2:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+ es-shim-unscopables: 1.0.2
+
+ array.prototype.flatmap@1.3.2:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+ es-shim-unscopables: 1.0.2
+
+ arraybuffer.prototype.slice@1.0.3:
+ dependencies:
+ array-buffer-byte-length: 1.0.1
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ is-array-buffer: 3.0.4
+ is-shared-array-buffer: 1.0.3
+
+ asap@2.0.6: {}
+
+ asynckit@0.4.0: {}
+
+ available-typed-arrays@1.0.7:
+ dependencies:
+ possible-typed-array-names: 1.0.0
+
+ balanced-match@1.0.2: {}
+
+ brace-expansion@1.1.11:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.0.1:
+ dependencies:
+ balanced-match: 1.0.2
+
+ braces@3.0.2:
+ dependencies:
+ fill-range: 7.0.1
+
+ builtin-modules@3.3.0: {}
+
+ builtins@5.0.1:
+ dependencies:
+ semver: 7.6.0
+
+ bytes@3.1.2: {}
+
+ cache-content-type@1.0.1:
+ dependencies:
+ mime-types: 2.1.35
+ ylru: 1.3.2
+
+ cacheable-lookup@7.0.0: {}
+
+ cacheable-request@10.2.14:
+ dependencies:
+ '@types/http-cache-semantics': 4.0.4
+ get-stream: 6.0.1
+ http-cache-semantics: 4.1.1
+ keyv: 4.5.4
+ mimic-response: 4.0.0
+ normalize-url: 8.0.1
+ responselike: 3.0.0
+
+ call-bind@1.0.7:
+ dependencies:
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.4
+ set-function-length: 1.2.2
+
+ callsites@3.1.0: {}
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ co@4.6.0: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ component-emitter@1.3.1: {}
+
+ concat-map@0.0.1: {}
+
+ content-disposition@0.5.4:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ content-type@1.0.5: {}
+
+ cookiejar@2.1.4: {}
+
+ cookies@0.9.1:
+ dependencies:
+ depd: 2.0.0
+ keygrip: 1.1.0
+
+ cross-spawn@7.0.3:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ debug@3.2.7:
+ dependencies:
+ ms: 2.1.3
+
+ debug@4.3.4:
+ dependencies:
+ ms: 2.1.2
+
+ decompress-response@6.0.0:
+ dependencies:
+ mimic-response: 3.1.0
+
+ deep-equal@1.0.1: {}
+
+ deep-is@0.1.4: {}
+
+ defer-to-connect@2.0.1: {}
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
+ gopd: 1.0.1
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ delayed-stream@1.0.0: {}
+
+ delegates@1.0.0: {}
+
+ depd@1.1.2: {}
+
+ depd@2.0.0: {}
+
+ destroy@1.2.0: {}
+
+ dezalgo@1.0.4:
+ dependencies:
+ asap: 2.0.6
+ wrappy: 1.0.2
+
+ dir-glob@3.0.1:
+ dependencies:
+ path-type: 4.0.0
+
+ doctrine@2.1.0:
+ dependencies:
+ esutils: 2.0.3
+
+ doctrine@3.0.0:
+ dependencies:
+ esutils: 2.0.3
+
+ ee-first@1.1.1: {}
+
+ encodeurl@1.0.2: {}
+
+ enhanced-resolve@5.16.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.2.1
+
+ es-abstract@1.22.5:
+ dependencies:
+ array-buffer-byte-length: 1.0.1
+ arraybuffer.prototype.slice: 1.0.3
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.7
+ es-define-property: 1.0.0
+ es-errors: 1.3.0
+ es-set-tostringtag: 2.0.3
+ es-to-primitive: 1.2.1
+ function.prototype.name: 1.1.6
+ get-intrinsic: 1.2.4
+ get-symbol-description: 1.0.2
+ globalthis: 1.0.3
+ gopd: 1.0.1
+ has-property-descriptors: 1.0.2
+ has-proto: 1.0.3
+ has-symbols: 1.0.3
+ hasown: 2.0.2
+ internal-slot: 1.0.7
+ is-array-buffer: 3.0.4
+ is-callable: 1.2.7
+ is-negative-zero: 2.0.3
+ is-regex: 1.1.4
+ is-shared-array-buffer: 1.0.3
+ is-string: 1.0.7
+ is-typed-array: 1.1.13
+ is-weakref: 1.0.2
+ object-inspect: 1.13.1
+ object-keys: 1.1.1
+ object.assign: 4.1.5
+ regexp.prototype.flags: 1.5.2
+ safe-array-concat: 1.1.2
+ safe-regex-test: 1.0.3
+ string.prototype.trim: 1.2.8
+ string.prototype.trimend: 1.0.7
+ string.prototype.trimstart: 1.0.7
+ typed-array-buffer: 1.0.2
+ typed-array-byte-length: 1.0.1
+ typed-array-byte-offset: 1.0.2
+ typed-array-length: 1.0.5
+ unbox-primitive: 1.0.2
+ which-typed-array: 1.1.15
+
+ es-array-method-boxes-properly@1.0.0: {}
+
+ es-define-property@1.0.0:
+ dependencies:
+ get-intrinsic: 1.2.4
+
+ es-errors@1.3.0: {}
+
+ es-set-tostringtag@2.0.3:
+ dependencies:
+ get-intrinsic: 1.2.4
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ es-shim-unscopables@1.0.2:
+ dependencies:
+ hasown: 2.0.2
+
+ es-to-primitive@1.2.1:
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.0.5
+ is-symbol: 1.0.4
+
+ escape-html@1.0.3: {}
+
+ escape-string-regexp@1.0.5: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-compat-utils@0.1.2(eslint@8.57.0):
+ dependencies:
+ eslint: 8.57.0
+
+ eslint-config-etherpad@4.0.4(eslint@8.57.0)(typescript@5.4.2):
+ dependencies:
+ '@rushstack/eslint-patch': 1.7.2
+ '@typescript-eslint/eslint-plugin': 7.2.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint@8.57.0)(typescript@5.4.2)
+ '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ eslint-plugin-cypress: 2.15.1(eslint@8.57.0)
+ eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-plugin-mocha: 10.4.1(eslint@8.57.0)
+ eslint-plugin-n: 16.6.2(eslint@8.57.0)
+ eslint-plugin-prefer-arrow: 1.2.3(eslint@8.57.0)
+ eslint-plugin-promise: 6.1.1(eslint@8.57.0)
+ eslint-plugin-you-dont-need-lodash-underscore: 6.13.0
+ transitivePeerDependencies:
+ - eslint
+ - eslint-import-resolver-node
+ - eslint-import-resolver-webpack
+ - supports-color
+ - typescript
+
+ eslint-import-resolver-node@0.3.9:
+ dependencies:
+ debug: 3.2.7
+ is-core-module: 2.13.1
+ resolve: 1.22.8
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0):
+ dependencies:
+ debug: 4.3.4
+ enhanced-resolve: 5.16.0
+ eslint: 8.57.0
+ eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ fast-glob: 3.3.2
+ get-tsconfig: 4.7.3
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ transitivePeerDependencies:
+ - '@typescript-eslint/parser'
+ - eslint-import-resolver-node
+ - eslint-import-resolver-webpack
+ - supports-color
+
+ eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
+ dependencies:
+ debug: 3.2.7
+ optionalDependencies:
+ '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ eslint: 8.57.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-cypress@2.15.1(eslint@8.57.0):
+ dependencies:
+ eslint: 8.57.0
+ globals: 13.24.0
+
+ eslint-plugin-es-x@7.5.0(eslint@8.57.0):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@eslint-community/regexpp': 4.10.0
+ eslint: 8.57.0
+ eslint-compat-utils: 0.1.2(eslint@8.57.0)
+
+ eslint-plugin-eslint-comments@3.2.0(eslint@8.57.0):
+ dependencies:
+ escape-string-regexp: 1.0.5
+ eslint: 8.57.0
+ ignore: 5.3.1
+
+ eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
+ dependencies:
+ array-includes: 3.1.7
+ array.prototype.findlastindex: 1.2.4
+ array.prototype.flat: 1.3.2
+ array.prototype.flatmap: 1.3.2
+ debug: 3.2.7
+ doctrine: 2.1.0
+ eslint: 8.57.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0)
+ hasown: 2.0.2
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ minimatch: 3.1.2
+ object.fromentries: 2.0.7
+ object.groupby: 1.0.2
+ object.values: 1.1.7
+ semver: 6.3.1
+ tsconfig-paths: 3.15.0
+ optionalDependencies:
+ '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.2)
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+
+ eslint-plugin-mocha@10.4.1(eslint@8.57.0):
+ dependencies:
+ eslint: 8.57.0
+ eslint-utils: 3.0.0(eslint@8.57.0)
+ globals: 13.24.0
+ rambda: 7.5.0
+
+ eslint-plugin-n@16.6.2(eslint@8.57.0):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ builtins: 5.0.1
+ eslint: 8.57.0
+ eslint-plugin-es-x: 7.5.0(eslint@8.57.0)
+ get-tsconfig: 4.7.3
+ globals: 13.24.0
+ ignore: 5.3.1
+ is-builtin-module: 3.2.1
+ is-core-module: 2.13.1
+ minimatch: 3.1.2
+ resolve: 1.22.8
+ semver: 7.6.0
+
+ eslint-plugin-prefer-arrow@1.2.3(eslint@8.57.0):
+ dependencies:
+ eslint: 8.57.0
+
+ eslint-plugin-promise@6.1.1(eslint@8.57.0):
+ dependencies:
+ eslint: 8.57.0
+
+ eslint-plugin-you-dont-need-lodash-underscore@6.13.0:
+ dependencies:
+ kebab-case: 1.0.2
+
+ eslint-scope@7.2.2:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-utils@3.0.0(eslint@8.57.0):
+ dependencies:
+ eslint: 8.57.0
+ eslint-visitor-keys: 2.1.0
+
+ eslint-visitor-keys@2.1.0: {}
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint@8.57.0:
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+ '@eslint-community/regexpp': 4.10.0
+ '@eslint/eslintrc': 2.1.4
+ '@eslint/js': 8.57.0
+ '@humanwhocodes/config-array': 0.11.14
+ '@humanwhocodes/module-importer': 1.0.1
+ '@nodelib/fs.walk': 1.2.8
+ '@ungap/structured-clone': 1.2.0
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.3
+ debug: 4.3.4
+ doctrine: 3.0.0
+ escape-string-regexp: 4.0.0
+ eslint-scope: 7.2.2
+ eslint-visitor-keys: 3.4.3
+ espree: 9.6.1
+ esquery: 1.5.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 6.0.1
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ globals: 13.24.0
+ graphemer: 1.4.0
+ ignore: 5.3.1
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ is-path-inside: 3.0.3
+ js-yaml: 4.1.0
+ json-stable-stringify-without-jsonify: 1.0.1
+ levn: 0.4.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.3
+ strip-ansi: 6.0.1
+ text-table: 0.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@9.6.1:
+ dependencies:
+ acorn: 8.11.3
+ acorn-jsx: 5.3.2(acorn@8.11.3)
+ eslint-visitor-keys: 3.4.3
+
+ esquery@1.5.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ esutils@2.0.3: {}
+
+ eta@3.4.0: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-glob@3.3.2:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.5
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fast-safe-stringify@2.1.1: {}
+
+ fastq@1.17.1:
+ dependencies:
+ reusify: 1.0.4
+
+ file-entry-cache@6.0.1:
+ dependencies:
+ flat-cache: 3.2.0
+
+ fill-range@7.0.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@3.2.0:
+ dependencies:
+ flatted: 3.3.1
+ keyv: 4.5.4
+ rimraf: 3.0.2
+
+ flatted@3.3.1: {}
+
+ for-each@0.3.3:
+ dependencies:
+ is-callable: 1.2.7
+
+ form-data-encoder@2.1.4: {}
+
+ form-data@4.0.0:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.35
+
+ formidable@2.1.2:
+ dependencies:
+ dezalgo: 1.0.4
+ hexoid: 1.0.0
+ once: 1.4.0
+ qs: 6.12.0
+
+ fresh@0.5.2: {}
+
+ fs.realpath@1.0.0: {}
+
+ function-bind@1.1.2: {}
+
+ function.prototype.name@1.1.6:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+ functions-have-names: 1.2.3
+
+ functions-have-names@1.2.3: {}
+
+ get-intrinsic@1.2.4:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ has-proto: 1.0.3
+ has-symbols: 1.0.3
+ hasown: 2.0.2
+
+ get-stream@6.0.1: {}
+
+ get-symbol-description@1.0.2:
+ dependencies:
+ call-bind: 1.0.7
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+
+ get-tsconfig@4.7.3:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@7.2.3:
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+
+ globals@13.24.0:
+ dependencies:
+ type-fest: 0.20.2
+
+ globalthis@1.0.3:
+ dependencies:
+ define-properties: 1.2.1
+
+ globby@11.1.0:
+ dependencies:
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.2
+ ignore: 5.3.1
+ merge2: 1.4.1
+ slash: 3.0.0
+
+ gopd@1.0.1:
+ dependencies:
+ get-intrinsic: 1.2.4
+
+ got@13.0.0:
+ dependencies:
+ '@sindresorhus/is': 5.6.0
+ '@szmarczak/http-timer': 5.0.1
+ cacheable-lookup: 7.0.0
+ cacheable-request: 10.2.14
+ decompress-response: 6.0.0
+ form-data-encoder: 2.1.4
+ get-stream: 6.0.1
+ http2-wrapper: 2.2.1
+ lowercase-keys: 3.0.0
+ p-cancelable: 3.0.0
+ responselike: 3.0.0
+
+ graceful-fs@4.2.11: {}
+
+ graphemer@1.4.0: {}
+
+ has-bigints@1.0.2: {}
+
+ has-flag@4.0.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.0
+
+ has-proto@1.0.3: {}
+
+ has-symbols@1.0.3: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.0.3
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ hexoid@1.0.0: {}
+
+ http-assert@1.5.0:
+ dependencies:
+ deep-equal: 1.0.1
+ http-errors: 1.8.1
+
+ http-cache-semantics@4.1.1: {}
+
+ http-errors@1.8.1:
+ dependencies:
+ depd: 1.1.2
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 1.5.0
+ toidentifier: 1.0.1
+
+ http-errors@2.0.0:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ toidentifier: 1.0.1
+
+ http2-wrapper@2.2.1:
+ dependencies:
+ quick-lru: 5.1.1
+ resolve-alpn: 1.2.1
+
+ iconv-lite@0.4.24:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ ignore@5.3.1: {}
+
+ import-fresh@3.3.0:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
+ inflight@1.0.6:
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+
+ inherits@2.0.4: {}
+
+ internal-slot@1.0.7:
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.2
+ side-channel: 1.0.6
+
+ is-array-buffer@3.0.4:
+ dependencies:
+ call-bind: 1.0.7
+ get-intrinsic: 1.2.4
+
+ is-bigint@1.0.4:
+ dependencies:
+ has-bigints: 1.0.2
+
+ is-boolean-object@1.1.2:
+ dependencies:
+ call-bind: 1.0.7
+ has-tostringtag: 1.0.2
+
+ is-builtin-module@3.2.1:
+ dependencies:
+ builtin-modules: 3.3.0
+
+ is-callable@1.2.7: {}
+
+ is-core-module@2.13.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-date-object@1.0.5:
+ dependencies:
+ has-tostringtag: 1.0.2
+
+ is-extglob@2.1.1: {}
+
+ is-generator-function@1.0.10:
+ dependencies:
+ has-tostringtag: 1.0.2
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-negative-zero@2.0.3: {}
+
+ is-number-object@1.0.7:
+ dependencies:
+ has-tostringtag: 1.0.2
+
+ is-number@7.0.0: {}
+
+ is-path-inside@3.0.3: {}
+
+ is-regex@1.1.4:
+ dependencies:
+ call-bind: 1.0.7
+ has-tostringtag: 1.0.2
+
+ is-shared-array-buffer@1.0.3:
+ dependencies:
+ call-bind: 1.0.7
+
+ is-string@1.0.7:
+ dependencies:
+ has-tostringtag: 1.0.2
+
+ is-symbol@1.0.4:
+ dependencies:
+ has-symbols: 1.0.3
+
+ is-typed-array@1.1.13:
+ dependencies:
+ which-typed-array: 1.1.15
+
+ is-weakref@1.0.2:
+ dependencies:
+ call-bind: 1.0.7
+
+ isarray@2.0.5: {}
+
+ isexe@2.0.0: {}
+
+ jose@4.15.5: {}
+
+ jose@5.2.4: {}
+
+ js-yaml@4.1.0:
+ dependencies:
+ argparse: 2.0.1
+
+ jsesc@3.0.2: {}
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@1.0.2:
+ dependencies:
+ minimist: 1.2.8
+
+ kebab-case@1.0.2: {}
+
+ keygrip@1.1.0:
+ dependencies:
+ tsscmp: 1.0.6
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ koa-compose@4.1.0: {}
+
+ koa-convert@2.0.0:
+ dependencies:
+ co: 4.6.0
+ koa-compose: 4.1.0
+
+ koa@2.15.1:
+ dependencies:
+ accepts: 1.3.8
+ cache-content-type: 1.0.1
+ content-disposition: 0.5.4
+ content-type: 1.0.5
+ cookies: 0.9.1
+ debug: 4.3.4
+ delegates: 1.0.0
+ depd: 2.0.0
+ destroy: 1.2.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ fresh: 0.5.2
+ http-assert: 1.5.0
+ http-errors: 1.8.1
+ is-generator-function: 1.0.10
+ koa-compose: 4.1.0
+ koa-convert: 2.0.0
+ on-finished: 2.4.1
+ only: 0.0.2
+ parseurl: 1.3.3
+ statuses: 1.5.0
+ type-is: 1.6.18
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.merge@4.6.2: {}
+
+ lowercase-keys@3.0.0: {}
+
+ lru-cache@6.0.0:
+ dependencies:
+ yallist: 4.0.0
+
+ media-typer@0.3.0: {}
+
+ merge2@1.4.1: {}
+
+ methods@1.1.2: {}
+
+ micromatch@4.0.5:
+ dependencies:
+ braces: 3.0.2
+ picomatch: 2.3.1
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mime@2.6.0: {}
+
+ mimic-response@3.1.0: {}
+
+ mimic-response@4.0.0: {}
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.11
+
+ minimatch@9.0.3:
+ dependencies:
+ brace-expansion: 2.0.1
+
+ minimist@1.2.8: {}
+
+ ms@2.1.2: {}
+
+ ms@2.1.3: {}
+
+ nanoid@5.0.7: {}
+
+ natural-compare@1.4.0: {}
+
+ negotiator@0.6.3: {}
+
+ normalize-url@8.0.1: {}
+
+ object-hash@2.2.0: {}
+
+ object-hash@3.0.0: {}
+
+ object-inspect@1.13.1: {}
+
+ object-keys@1.1.1: {}
+
+ object.assign@4.1.5:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ has-symbols: 1.0.3
+ object-keys: 1.1.1
+
+ object.fromentries@2.0.7:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+
+ object.groupby@1.0.2:
+ dependencies:
+ array.prototype.filter: 1.0.3
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+ es-errors: 1.3.0
+
+ object.values@1.1.7:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+
+ oidc-provider@8.4.5:
+ dependencies:
+ '@koa/cors': 5.0.0
+ '@koa/router': 12.0.1
+ debug: 4.3.4
+ eta: 3.4.0
+ got: 13.0.0
+ jose: 5.2.4
+ jsesc: 3.0.2
+ koa: 2.15.1
+ nanoid: 5.0.7
+ object-hash: 3.0.0
+ oidc-token-hash: 5.0.3
+ quick-lru: 7.0.0
+ raw-body: 2.5.2
+ transitivePeerDependencies:
+ - supports-color
+
+ oidc-token-hash@5.0.3: {}
+
+ on-finished@2.4.1:
+ dependencies:
+ ee-first: 1.1.1
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ only@0.0.2: {}
+
+ openid-client@5.6.5:
+ dependencies:
+ jose: 4.15.5
+ lru-cache: 6.0.0
+ object-hash: 2.2.0
+ oidc-token-hash: 5.0.3
+
+ optionator@0.9.3:
+ dependencies:
+ '@aashutoshrathi/word-wrap': 1.2.6
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ p-cancelable@3.0.0: {}
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ parseurl@1.3.3: {}
+
+ path-exists@4.0.0: {}
+
+ path-is-absolute@1.0.1: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ path-to-regexp@6.2.2: {}
+
+ path-type@4.0.0: {}
+
+ picomatch@2.3.1: {}
+
+ possible-typed-array-names@1.0.0: {}
+
+ prelude-ls@1.2.1: {}
+
+ punycode@2.3.1: {}
+
+ qs@6.12.0:
+ dependencies:
+ side-channel: 1.0.6
+
+ queue-microtask@1.2.3: {}
+
+ quick-lru@5.1.1: {}
+
+ quick-lru@7.0.0: {}
+
+ rambda@7.5.0: {}
+
+ raw-body@2.5.2:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+
+ regexp.prototype.flags@1.5.2:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ set-function-name: 2.0.2
+
+ require-from-string@2.0.2: {}
+
+ resolve-alpn@1.2.1: {}
+
+ resolve-from@4.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ resolve@1.22.8:
+ dependencies:
+ is-core-module: 2.13.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ responselike@3.0.0:
+ dependencies:
+ lowercase-keys: 3.0.0
+
+ reusify@1.0.4: {}
+
+ rimraf@3.0.2:
+ dependencies:
+ glob: 7.2.3
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ safe-array-concat@1.1.2:
+ dependencies:
+ call-bind: 1.0.7
+ get-intrinsic: 1.2.4
+ has-symbols: 1.0.3
+ isarray: 2.0.5
+
+ safe-buffer@5.2.1: {}
+
+ safe-regex-test@1.0.3:
+ dependencies:
+ call-bind: 1.0.7
+ es-errors: 1.3.0
+ is-regex: 1.1.4
+
+ safer-buffer@2.1.2: {}
+
+ semver@6.3.1: {}
+
+ semver@7.6.0:
+ dependencies:
+ lru-cache: 6.0.0
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.4
+ gopd: 1.0.1
+ has-property-descriptors: 1.0.2
+
+ set-function-name@2.0.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+
+ setprototypeof@1.2.0: {}
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ side-channel@1.0.6:
+ dependencies:
+ call-bind: 1.0.7
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ object-inspect: 1.13.1
+
+ slash@3.0.0: {}
+
+ statuses@1.5.0: {}
+
+ statuses@2.0.1: {}
+
+ string.prototype.trim@1.2.8:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+
+ string.prototype.trimend@1.0.7:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+
+ string.prototype.trimstart@1.0.7:
+ dependencies:
+ call-bind: 1.0.7
+ define-properties: 1.2.1
+ es-abstract: 1.22.5
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-bom@3.0.0: {}
+
+ strip-json-comments@3.1.1: {}
+
+ superagent@8.1.2:
+ dependencies:
+ component-emitter: 1.3.1
+ cookiejar: 2.1.4
+ debug: 4.3.4
+ fast-safe-stringify: 2.1.1
+ form-data: 4.0.0
+ formidable: 2.1.2
+ methods: 1.1.2
+ mime: 2.6.0
+ qs: 6.12.0
+ semver: 7.6.0
+ transitivePeerDependencies:
+ - supports-color
+
+ supertest@6.3.4:
+ dependencies:
+ methods: 1.1.2
+ superagent: 8.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ tapable@2.2.1: {}
+
+ text-table@0.2.0: {}
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ toidentifier@1.0.1: {}
+
+ ts-api-utils@1.3.0(typescript@5.4.2):
+ dependencies:
+ typescript: 5.4.2
+
+ tsconfig-paths@3.15.0:
+ dependencies:
+ '@types/json5': 0.0.29
+ json5: 1.0.2
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
+ tsscmp@1.0.6: {}
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ type-fest@0.20.2: {}
+
+ type-is@1.6.18:
+ dependencies:
+ media-typer: 0.3.0
+ mime-types: 2.1.35
+
+ typed-array-buffer@1.0.2:
+ dependencies:
+ call-bind: 1.0.7
+ es-errors: 1.3.0
+ is-typed-array: 1.1.13
+
+ typed-array-byte-length@1.0.1:
+ dependencies:
+ call-bind: 1.0.7
+ for-each: 0.3.3
+ gopd: 1.0.1
+ has-proto: 1.0.3
+ is-typed-array: 1.1.13
+
+ typed-array-byte-offset@1.0.2:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.7
+ for-each: 0.3.3
+ gopd: 1.0.1
+ has-proto: 1.0.3
+ is-typed-array: 1.1.13
+
+ typed-array-length@1.0.5:
+ dependencies:
+ call-bind: 1.0.7
+ for-each: 0.3.3
+ gopd: 1.0.1
+ has-proto: 1.0.3
+ is-typed-array: 1.1.13
+ possible-typed-array-names: 1.0.0
+
+ typescript@5.4.2: {}
+
+ unbox-primitive@1.0.2:
+ dependencies:
+ call-bind: 1.0.7
+ has-bigints: 1.0.2
+ has-symbols: 1.0.3
+ which-boxed-primitive: 1.0.2
+
+ unpipe@1.0.0: {}
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ vary@1.1.2: {}
+
+ which-boxed-primitive@1.0.2:
+ dependencies:
+ is-bigint: 1.0.4
+ is-boolean-object: 1.1.2
+ is-number-object: 1.0.7
+ is-string: 1.0.7
+ is-symbol: 1.0.4
+
+ which-typed-array@1.1.15:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.7
+ for-each: 0.3.3
+ gopd: 1.0.1
+ has-tostringtag: 1.0.2
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ wrappy@1.0.2: {}
+
+ yallist@4.0.0: {}
+
+ ylru@1.3.2: {}
+
+ yocto-queue@0.1.0: {}
diff --git a/local_plugins/ep_openid_connect/static/tests/backend/login.js b/local_plugins/ep_openid_connect/static/tests/backend/login.js
new file mode 100644
index 0000000..f973a15
--- /dev/null
+++ b/local_plugins/ep_openid_connect/static/tests/backend/login.js
@@ -0,0 +1,53 @@
+'use strict';
+
+const assert = require('assert').strict;
+const common = require('ep_etherpad-lite/tests/backend/common');
+
+const logger = common.logger;
+
+const followRedirects = async (agent, res) => {
+ if (res.status !== 303) return res;
+ const url = new URL(res.headers.location, res.request.url);
+ logger.debug(`redirected to ${url}`);
+ return await followRedirects(agent, await agent.get(url));
+};
+
+// There might not be a set-cookie header on the response if the request sent a cookie and the
+// cookie value hasn't changed. If there is no set-cookie header, use the request's cookie.
+const fakeSetCookie = (jar, res) => {
+ if (!res.headers['set-cookie']) {
+ const url = new URL(res.request.url);
+ const cookies = jar.getCookies({
+ domain: url.hostname, // No port.
+ path: url.pathname,
+ secure: url.protocl === 'https:',
+ script: false,
+ }).toString();
+ if (cookies) res.headers['set-cookie'] = cookies;
+ }
+ return res;
+};
+
+module.exports = async (agent, issuer, startUrl, username) => {
+ // Visit the start URL and follow the redirects to the login page.
+ let res = await followRedirects(agent, await agent.get(startUrl));
+ const wantUrlBase = new URL('/interaction/', issuer).toString();
+ assert(res.request.url.startsWith(wantUrlBase),
+ `expected ${res.request.url} to start with ${wantUrlBase}`);
+ assert.equal(res.status, 200);
+
+ // Log in and follow redirects to the consent page.
+ logger.debug(`submitting login=${username}`);
+ res = await followRedirects(agent, await agent.get(`${res.request.url}?login=${username}`));
+ if (!res.request.url.startsWith(new URL('/interaction/', issuer).toString())) {
+ logger.debug('did not redirect to the consent page');
+ return fakeSetCookie(agent.jar, res);
+ }
+ assert.equal(res.status, 200);
+
+ // Indicate consent and follow redirects. (It should redirect back to the original URL, but that
+ // verification is done by the caller so that this function can be used to test error cases.)
+ logger.debug('submitting consent=true');
+ res = await followRedirects(agent, await agent.get(`${res.request.url}?consent=true`));
+ return fakeSetCookie(agent.jar, res);
+};
diff --git a/local_plugins/ep_openid_connect/static/tests/backend/oidc-provider.js b/local_plugins/ep_openid_connect/static/tests/backend/oidc-provider.js
new file mode 100644
index 0000000..b737f01
--- /dev/null
+++ b/local_plugins/ep_openid_connect/static/tests/backend/oidc-provider.js
@@ -0,0 +1,147 @@
+'use strict';
+
+const MemoryAdapter = require('oidc-provider/lib/adapters/memory_adapter');
+const Provider = require('oidc-provider');
+const common = require('ep_etherpad-lite/tests/backend/common');
+const http = require('http');
+const util = require('util');
+
+const httpListen = util.promisify(http.Server.prototype.listen);
+const httpClose = util.promisify(http.Server.prototype.close);
+const logger = common.logger;
+
+// No-op subclass to silence warnings about using MemoryAdapter.
+class Adapter extends MemoryAdapter {}
+
+class OidcProvider {
+ async start(options) {
+ await this.stop();
+ this._server = http.createServer();
+ await httpListen.call(this._server, 0, 'localhost');
+ this.issuer = `http://localhost:${this._server.address().port}/`;
+ this._provider = new Provider(this.issuer, Object.assign({
+ adapter: Adapter,
+ claims: {
+ etherpad: [
+ 'etherpad_is_admin',
+ 'etherpad_readOnly',
+ 'etherpad_canCreate',
+ 'name',
+ 'prop',
+ ],
+ },
+ cookies: {
+ keys: [common.randomString()],
+ },
+ features: {
+ devInteractions: {enabled: false},
+ },
+ findAccount: async (ctx, sub, token) => ({
+ accountId: sub,
+ claims: async (use, scope, claims, rejected) => ({
+ sub,
+ name: 'Firstname Lastname',
+ etherpad_is_admin: sub.includes('admin'),
+ etherpad_readOnly: !sub.includes('admin') && sub.includes('readOnly'),
+ etherpad_canCreate:
+ sub.includes('admin') || (!sub.includes('readOnly') && !sub.includes('noCreate')),
+ ...(sub === 'claimNull' ? {prop: null} : sub === 'claimVal' ? {prop: 'claimValue'} : {}),
+ }),
+ }),
+ jwks: {
+ /* eslint-disable max-len */
+ keys: [
+ {
+ d: 'VEZOsY07JTFzGTqv6cC2Y32vsfChind2I_TTuvV225_-0zrSej3XLRg8iE_u0-3GSgiGi4WImmTwmEgLo4Qp3uEcxCYbt4NMJC7fwT2i3dfRZjtZ4yJwFl0SIj8TgfQ8ptwZbFZUlcHGXZIr4nL8GXyQT0CK8wy4COfmymHrrUoyfZA154ql_OsoiupSUCRcKVvZj2JHL2KILsq_sh_l7g2dqAN8D7jYfJ58MkqlknBMa2-zi5I0-1JUOwztVNml_zGrp27UbEU60RqV3GHjoqwI6m01U7K0a8Q_SQAKYGqgepbAYOA-P4_TLl5KC4-WWBZu_rVfwgSENwWNEhw8oQ',
+ dp: 'E1Y-SN4bQqX7kP-bNgZ_gEv-pixJ5F_EGocHKfS56jtzRqQdTurrk4jIVpI-ZITA88lWAHxjD-OaoJUh9Jupd_lwD5Si80PyVxOMI2xaGQiF0lbKJfD38Sh8frRpgelZVaK_gm834B6SLfxKdNsP04DsJqGKktODF_fZeaGFPH0',
+ dq: 'F90JPxevQYOlAgEH0TUt1-3_hyxY6cfPRU2HQBaahyWrtCWpaOzenKZnvGFZdg-BuLVKjCchq3G_70OLE-XDP_ol0UTJmDTT-WyuJQdEMpt_WFF9yJGoeIu8yohfeLatU-67ukjghJ0s9CBzNE_LrGEV6Cup3FXywpSYZAV3iqc',
+ e: 'AQAB',
+ kty: 'RSA',
+ n: 'xwQ72P9z9OYshiQ-ntDYaPnnfwG6u9JAdLMZ5o0dmjlcyrvwQRdoFIKPnO65Q8mh6F_LDSxjxa2Yzo_wdjhbPZLjfUJXgCzm54cClXzT5twzo7lzoAfaJlkTsoZc2HFWqmcri0BuzmTFLZx2Q7wYBm0pXHmQKF0V-C1O6NWfd4mfBhbM-I1tHYSpAMgarSm22WDMDx-WWI7TEzy2QhaBVaENW9BKaKkJklocAZCxk18WhR0fckIGiWiSM5FcU1PY2jfGsTmX505Ub7P5Dz75Ygqrutd5tFrcqyPAtPTFDk8X1InxkkUwpP3nFU5o50DGhwQolGYKPGtQ-ZtmbOfcWQ',
+ p: '5wC6nY6Ev5FqcLPCqn9fC6R9KUuBej6NaAVOKW7GXiOJAq2WrileGKfMc9kIny20zW3uWkRLm-O-3Yzze1zFpxmqvsvCxZ5ERVZ6leiNXSu3tez71ZZwp0O9gys4knjrI-9w46l_vFuRtjL6XEeFfHEZFaNJpz-lcnb3w0okrbM',
+ q: '3I1qeEDslZFB8iNfpKAdWtz_Wzm6-jayT_V6aIvhvMj5mnU-Xpj75zLPQSGa9wunMlOoZW9w1wDO1FVuDhwzeOJaTm-Ds0MezeC4U6nVGyyDHb4CUA3ml2tzt4yLrqGYMT7XbADSvuWYADHw79OFjEi4T3s3tJymhaBvy1ulv8M',
+ qi: 'wSbXte9PcPtr788e713KHQ4waE26CzoXx-JNOgN0iqJMN6C4_XJEX-cSvCZDf4rh7xpXN6SGLVd5ibIyDJi7bbi5EQ5AXjazPbLBjRthcGXsIuZ3AtQyR0CEWNSdM7EyM5TRdyZQ9kftfz9nI03guW3iKKASETqX2vh0Z8XRjyU',
+ use: 'sig',
+ },
+ {
+ crv: 'P-256',
+ d: 'K9xfPv773dZR22TVUB80xouzdF7qCg5cWjPjkHyv7Ws',
+ kty: 'EC',
+ use: 'sig',
+ x: 'FWZ9rSkLt6Dx9E3pxLybhdM6xgR5obGsj5_pqmnz5J4',
+ y: '_n8G69C-A2Xl4xUW2lF0i8ZGZnk_KPYrhv4GbTGu5G4',
+ },
+ ],
+ /* eslint-enable max-len */
+ },
+ ttl: {
+ // Silence warnings.
+ AccessToken: 3600,
+ Grant: 3600,
+ IdToken: 3600,
+ Interaction: 3600,
+ Session: 3600,
+ },
+ }, options));
+ this._server.on('request', this._provider.callback());
+
+ // Install middleware that provides fake login and consent pages. This code would be prettier,
+ // but I wanted to avoid pulling in dependencies such as express or koa-route + koa-bodyparser.
+ this._provider.app.use(async (ctx, next) => {
+ const {request: {method, path, query}} = ctx;
+ if (!path.startsWith('/interaction/') || method !== 'GET') return await next();
+ const interactionDetails = await this._provider.interactionDetails(ctx.req, ctx.res);
+ if (Object.entries(query).length === 0) {
+ // This is where we would normally present a login or consent page. Just print the prompt
+ // name to help with debugging.
+ ctx.response.body = `waiting for ${interactionDetails.prompt.name}`;
+ ctx.response.status = 200;
+ return;
+ }
+ // Normally the login and consent pages would have a form that POSTs to the same URL, but for
+ // convenience this code just uses GET with a query parameter.
+ switch (interactionDetails.prompt.name) {
+ case 'login': {
+ const result = query.login.includes('fail')
+ ? {error: 'access_denied', error_description: 'injected failure'}
+ : {login: {accountId: query.login, remember: false}};
+ // This is test code; no need for username or password checking.
+ return await this._provider.interactionFinished(ctx.req, ctx.res, result);
+ }
+ case 'consent': {
+ // The amount of boilerplate code required to handle basic consent interaction is much
+ // higher than I would have expected. I don't understand this code; it is based on
+ // https://github.com/panva/node-oidc-provider/blob/039a55a0ab24cd4f6aa5831b829cfb2cc1337866/example/routes/express.js#L116-L152
+ const grant = interactionDetails.grantId
+ ? await this._provider.Grant.find(interactionDetails.grantId)
+ : new this._provider.Grant({
+ accountId: interactionDetails.session.accountId,
+ clientId: interactionDetails.params.client_id,
+ });
+ const {prompt: {details}} = interactionDetails;
+ if (details.missingOIDCScope) grant.addOIDCScope(details.missingOIDCScope.join(' '));
+ if (details.missingOIDCClaims) grant.addOIDCClaims(details.missingOIDCClaims);
+ if (details.missingResourceScopes) {
+ for (const [indicator, scopes] of Object.entries(details.missingResourceScopes)) {
+ grant.addResourceScope(indicator, scopes.join(' '));
+ }
+ }
+ const grantId = await grant.save();
+ return await this._provider.interactionFinished(ctx.req, ctx.res, {
+ consent: interactionDetails.grantId ? {} : {grantId},
+ });
+ }
+ }
+ return await next();
+ });
+
+ logger.info(`Test OpenID Connect provider listening at ${this.issuer}`);
+ }
+
+ async stop() {
+ if (this._server == null) return;
+ await httpClose.call(this._server);
+ this._server = null;
+ }
+}
+module.exports = OidcProvider;
diff --git a/local_plugins/ep_openid_connect/static/tests/backend/specs/tests.js b/local_plugins/ep_openid_connect/static/tests/backend/specs/tests.js
new file mode 100644
index 0000000..aa6aa73
--- /dev/null
+++ b/local_plugins/ep_openid_connect/static/tests/backend/specs/tests.js
@@ -0,0 +1,395 @@
+'use strict';
+
+const OidcProvider = require('../oidc-provider');
+const assert = require('assert').strict;
+const common = require('ep_etherpad-lite/tests/backend/common');
+const epOpenidConnect = require('../../../../index');
+const login = require('../login');
+const padManager = require('ep_etherpad-lite/node/db/PadManager');
+const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
+const settings = require('ep_etherpad-lite/node/utils/Settings');
+const supertest = require('supertest');
+
+describe(__filename, function () {
+ let agent;
+ const backup = {};
+ let provider;
+ let issuer;
+ const client = {
+ client_id: 'the_client_id',
+ client_secret: 'the_client_secret',
+ };
+ const pluginSettings = {
+ ...epOpenidConnect.exportedForTestingOnly.defaultSettings,
+ ...client,
+ // base_url and issuer will be filled in later.
+ scope: ['openid', 'etherpad'],
+ user_properties: {
+ is_admin: {claim: 'etherpad_is_admin'},
+ readOnly: {claim: 'etherpad_readOnly'},
+ canCreate: {claim: 'etherpad_canCreate'},
+ },
+ };
+ let socket;
+
+ before(async function () {
+ await common.init();
+ if (!plugins.hooks.clientVars) plugins.hooks.clientVars = [];
+ backup.hooks = {clientVars: plugins.hooks.clientVars};
+ backup.settings = {...settings};
+ provider = new OidcProvider();
+ const clients =
+ [{...client, redirect_uris: [new URL('/ep_openid_connect/callback', common.baseUrl)]}];
+ await provider.start({clients});
+ });
+
+ beforeEach(async function () {
+ agent = supertest.agent('');
+ issuer = provider.issuer;
+ pluginSettings.base_url = common.baseUrl;
+ pluginSettings.issuer = issuer;
+ settings.requireAuthentication = true;
+ settings.requireAuthorization = false;
+ settings.users = {};
+ await epOpenidConnect.loadSettings(
+ 'loadSettings', {settings: {ep_openid_connect: pluginSettings}});
+ });
+
+ afterEach(async function () {
+ if (socket != null) socket.close();
+ socket = null;
+ Object.assign(plugins.hooks, backup.hooks);
+ Object.assign(settings, backup.settings);
+ });
+
+ after(async function () {
+ if (provider != null) await provider.stop();
+ provider = null;
+ });
+
+ it('not logged in redirects to login endpoint', async function () {
+ await agent.get(common.baseUrl)
+ .expect(303)
+ .expect('location', new URL('/ep_openid_connect/login', common.baseUrl).toString());
+ });
+
+ it('login endpoint redirects to authorization URL', async function () {
+ await agent.get(new URL('/ep_openid_connect/login', common.baseUrl))
+ .expect(303)
+ .expect('location', /.*/)
+ .expect((res) => {
+ assert(res.headers.location.startsWith(`${new URL('/auth', issuer)}?`));
+ const location = new URL(res.headers.location);
+ const params = location.searchParams;
+ assert.deepEqual(params.getAll('redirect_uri'),
+ [new URL('/ep_openid_connect/callback', common.baseUrl).toString()]);
+ assert.deepEqual(params.getAll('client_id'), [pluginSettings.client_id]);
+ assert.deepEqual(params.getAll('response_type'), ['code']);
+ assert.deepEqual(params.getAll('scope'), [pluginSettings.scope.join(' ')]);
+ for (const param of ['code_challenge', 'nonce', 'state']) {
+ assert.equal(params.getAll(param).length, 1);
+ assert.notEqual(params.get(param), '');
+ }
+ });
+ });
+
+ it('normalUser can create and edit a pad', async function () {
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'normalUser');
+ assert.equal(res.request.url, url); // Should have been redirected back after authenticating.
+ assert.equal(res.status, 200);
+ socket = await common.connect(res);
+ const msg = await common.handshake(socket, padId);
+ assert.equal(msg.type, 'CLIENT_VARS', `not a CLIENT_VARS message: ${JSON.stringify(msg)}`);
+ assert.equal(msg.data.readonly, false);
+ });
+
+ it('noCreate user is unable to create a pad', async function () {
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'noCreate');
+ assert.equal(res.request.url, url); // Should have been redirected back after authenticating.
+ assert.equal(res.status, 200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, padId);
+ assert.equal(message.accessStatus, 'deny');
+ });
+
+ it('noCreate user can edit an existing pad', async function () {
+ const padId = common.randomString();
+ await padManager.getPad(padId, 'this is a test');
+ assert(await padManager.doesPadExist(padId));
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'noCreate');
+ assert.equal(res.request.url, url); // Should have been redirected back after authenticating.
+ assert.equal(res.status, 200);
+ socket = await common.connect(res);
+ const msg = await common.handshake(socket, padId);
+ assert.equal(msg.type, 'CLIENT_VARS', `not a CLIENT_VARS message: ${JSON.stringify(msg)}`);
+ assert.equal(msg.data.readonly, false);
+ });
+
+ it('readOnly user is unable to create a pad', async function () {
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'readOnly');
+ assert.equal(res.request.url, url); // Should have been redirected back after authenticating.
+ assert.equal(res.status, 200);
+ socket = await common.connect(res);
+ const message = await common.handshake(socket, padId);
+ assert.equal(message.accessStatus, 'deny');
+ });
+
+ it('readOnly user can view but not edit an existing pad', async function () {
+ const padId = common.randomString();
+ await padManager.getPad(padId, 'this is a test');
+ assert(await padManager.doesPadExist(padId));
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'readOnly');
+ assert.equal(res.request.url, url); // Should have been redirected back after authenticating.
+ assert.equal(res.status, 200);
+ socket = await common.connect(res);
+ const msg = await common.handshake(socket, padId);
+ assert.equal(msg.type, 'CLIENT_VARS', `not a CLIENT_VARS message: ${JSON.stringify(msg)}`);
+ assert.equal(msg.data.readonly, true);
+ });
+
+ it('authentication failure', async function () {
+ const res = await login(agent, issuer, new URL('/', common.baseUrl).toString(), 'fail');
+ assert.equal(res.status, 500);
+ const gotUrl = res.request.url;
+ const wantUrlBase = new URL('/ep_openid_connect/callback?', common.baseUrl).toString();
+ assert(gotUrl.startsWith(wantUrlBase), `expected ${gotUrl} to start with ${wantUrlBase}`);
+ });
+
+ describe('prohibited usernames', function () {
+ // The OIDC provider rejects '' as a username so that can't be tested here.
+ for (const username of [...pluginSettings.prohibited_usernames, '__proto__']) {
+ it(JSON.stringify(username), async function () {
+ const res = await login(agent, issuer, new URL('/', common.baseUrl).toString(), username);
+ assert(res.request.url.startsWith(new URL('/ep_openid_connect/callback?', common.baseUrl)));
+ assert.equal(res.status, 500);
+ });
+ }
+ });
+
+ describe('admin access', function () {
+ it('adminUser can access /admin/', async function () {
+ const url = new URL('/admin/', common.baseUrl).toString();
+ // Note that the username is "adminUser", not "admin", because "admin" is prohibited.
+ const res = await login(agent, issuer, url, 'adminUser');
+ assert.equal(res.request.url, url); // Should have been redirected back after authenticating.
+ assert.equal(res.status, 200);
+ });
+
+ it('normalUser is unable to access /admin/', async function () {
+ const url = new URL('/admin/', common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'normalUser');
+ assert.equal(res.request.url, url); // Should have been redirected back after authenticating.
+ assert.equal(res.status, 403);
+ });
+ });
+
+ it('visiting callback endpoint without session does not crash', async function () {
+ await agent.get(new URL('/ep_openid_connect/callback', common.baseUrl))
+ .expect(500);
+ });
+
+ it('visiting login endpoint directly redirects to base URL', async function () {
+ const url = new URL('/ep_openid_connect/login', common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'normalUser');
+ assert.equal(res.request.url, new URL('/', common.baseUrl).toString());
+ assert.equal(res.status, 200);
+ });
+
+ it('visiting logout endpoint destroys session, redirects to base URL', async function () {
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'normalUser');
+ assert.equal(res.request.url, url);
+ assert.equal(res.status, 200);
+ await agent.get(new URL('/ep_openid_connect/logout', common.baseUrl))
+ .expect(303)
+ .expect('location', new URL('/', common.baseUrl).toString());
+ // Visiting any page that requires authentication should now redirect to the login page.
+ await agent.get(new URL('/', common.baseUrl))
+ .expect(303)
+ .expect('location', new URL('/ep_openid_connect/login', common.baseUrl).toString());
+ });
+
+ it('HTTP PUT gives 401 instead of redirect', async function () {
+ await agent.put(common.baseUrl)
+ .expect(401)
+ .expect('www-authenticate', 'Etherpad');
+ });
+
+ it('Authorization header results in 401 instead of redirect', async function () {
+ await agent.get(common.baseUrl)
+ .set('Authorization', 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')
+ .expect(401)
+ .expect('www-authenticate', 'Etherpad');
+ });
+
+ describe('user_properties', function () {
+ it('uses existing settings.users[username] object', async function () {
+ const wantUser = {foo: 'bar'};
+ settings.users.normalUser = wantUser;
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'normalUser');
+ assert.equal(res.request.url, url);
+ assert.equal(res.status, 200);
+ socket = await common.connect(res);
+ const gotUserP = new Promise((resolve) => {
+ plugins.hooks.clientVars =
+ [{hook_fn: (hn, ctx) => resolve(ctx.socket.client.request.session.user)}];
+ });
+ const msg = await common.handshake(socket, padId);
+ assert.equal(msg.type, 'CLIENT_VARS', `not a CLIENT_VARS message: ${JSON.stringify(msg)}`);
+ assert.equal(settings.users.normalUser, wantUser);
+ assert.equal(settings.users.normalUser.foo, 'bar');
+ // They won't be reference equal because each request loads the user object from the session
+ // store.
+ assert.deepEqual(await gotUserP, settings.users.normalUser);
+ });
+
+ it('sets settings.users[username] to new object if missing', async function () {
+ assert(settings.users.normalUser == null);
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'normalUser');
+ assert.equal(res.request.url, url);
+ assert.equal(res.status, 200);
+ socket = await common.connect(res);
+ const gotUserP = new Promise((resolve) => {
+ plugins.hooks.clientVars =
+ [{hook_fn: (hn, ctx) => resolve(ctx.socket.client.request.session.user)}];
+ });
+ const msg = await common.handshake(socket, padId);
+ assert.equal(msg.type, 'CLIENT_VARS', `not a CLIENT_VARS message: ${JSON.stringify(msg)}`);
+ assert(settings.users.normalUser != null);
+ // They won't be reference equal because each request loads the user object from the session
+ // store.
+ assert.deepEqual(await gotUserP, settings.users.normalUser);
+ });
+
+ it('displayname defaults to name claim', async function () {
+ assert(settings.users.normalUser == null);
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'normalUser');
+ assert.equal(res.request.url, url);
+ assert.equal(res.status, 200);
+ assert.equal(settings.users.normalUser.displayname, 'Firstname Lastname');
+ });
+
+ it('setting empty displayname descriptor cancels default behavior', async function () {
+ assert(settings.users.normalUser == null);
+ await epOpenidConnect.loadSettings('loadSettings', {settings: {ep_openid_connect: {
+ ...pluginSettings,
+ user_properties: {displayname: {}},
+ }}});
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'normalUser');
+ assert.equal(res.request.url, url);
+ assert.equal(res.status, 200);
+ assert(!('displayname' in settings.users.normalUser));
+ });
+
+ it('username is set to sub claim', async function () {
+ assert(settings.users.normalUser == null);
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, 'normalUser');
+ assert.equal(res.request.url, url);
+ assert.equal(res.status, 200);
+ assert.equal(settings.users.normalUser.username, 'normalUser');
+ });
+
+ describe('username can\'t be changed', function () {
+ for (const dsc of [
+ null,
+ {},
+ {default: 'something else'},
+ {claim: 'preferred_username'},
+ {claim: 'preferred_username', default: 'something else'},
+ ]) {
+ it(`descriptor: ${JSON.stringify(dsc)}`, async function () {
+ await epOpenidConnect.loadSettings('loadSettings', {settings: {ep_openid_connect: {
+ ...pluginSettings,
+ user_properties: {username: dsc},
+ }}});
+ // Settings validation should fail, resulting in an unconfigured state and causing
+ // Etherpad to fall back to built-in authn.
+ const res = await agent.get(new URL('/ep_openid_connect/login', common.baseUrl));
+ assert.equal(res.status, 401);
+ });
+ }
+ });
+
+ describe('{settings.json, claim value, user_properties} combinations', function () {
+ const testCases = [];
+ for (const user of [{}, {prop: undefined}, {prop: null}, {prop: 'userValue'}]) {
+ for (const claimValue of [undefined, null, 'claimValue']) {
+ for (const cfg of [
+ {},
+ {prop: null},
+ {prop: {}},
+ {prop: {default: 'defaultValue'}},
+ {prop: {claim: 'prop'}},
+ {prop: {claim: 'prop', default: 'defaultValue'}},
+ ]) {
+ const p = cfg.prop || {};
+ const wantIn = !!(
+ p.default ||
+ (p.claim && claimValue !== undefined) ||
+ ('prop' in user && cfg.prop !== null)); // eslint-disable-line eqeqeq
+ const wantValue =
+ p.claim && claimValue !== undefined ? claimValue
+ : 'prop' in user ? user.prop
+ : p.default ? p.default
+ : undefined;
+ const desc =
+ `user.prop=${'prop' in user ? user.prop : ''}, ` +
+ `claimValue=${claimValue === undefined ? '' : claimValue}, ` +
+ `user_properties.prop=${'prop' in cfg ? JSON.stringify(cfg.prop) : ''} -> ` +
+ `${wantIn ? wantValue : ''}`;
+ testCases.push({desc, user, claimValue, cfg, wantIn, wantValue});
+ }
+ }
+ }
+
+ for (const {desc, user, claimValue, cfg, wantIn, wantValue} of testCases) {
+ it(desc, async function () {
+ const username =
+ claimValue === undefined ? 'claimUnset'
+ : claimValue != null ? 'claimVal'
+ : 'claimNull';
+ settings.users[username] = {...user};
+ await epOpenidConnect.loadSettings('loadSettings', {settings: {ep_openid_connect: {
+ ...pluginSettings,
+ user_properties: cfg,
+ }}});
+ const padId = common.randomString();
+ const url = new URL(`/p/${padId}`, common.baseUrl).toString();
+ const res = await login(agent, issuer, url, username);
+ assert.equal(res.request.url, url);
+ assert.equal(res.status, 200);
+ socket = await common.connect(res);
+ const userP = new Promise((resolve) => {
+ plugins.hooks.clientVars =
+ [{hook_fn: (hn, ctx) => resolve(ctx.socket.client.request.session.user)}];
+ });
+ const msg = await common.handshake(socket, padId);
+ assert.equal(
+ msg.type, 'CLIENT_VARS', `not a CLIENT_VARS message: ${JSON.stringify(msg)}`);
+ const gotUser = await userP;
+ assert.equal('prop' in gotUser, wantIn);
+ if (wantIn) assert.equal(gotUser.prop, wantValue);
+ });
+ }
+ });
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d6bf8cf..97f0547 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -171,8 +171,8 @@ importers:
specifier: ^8.2.1
version: 8.2.1(express@5.2.1)
express-session:
- specifier: ^1.18.2
- version: 1.18.2
+ specifier: ^1.19.0
+ version: 1.19.0
find-root:
specifier: 1.1.0
version: 1.1.0
@@ -3162,8 +3162,8 @@ packages:
peerDependencies:
express: '>= 4.11'
- express-session@1.18.2:
- resolution: {integrity: sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==}
+ express-session@1.19.0:
+ resolution: {integrity: sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==}
engines: {node: '>= 0.8.0'}
express@5.2.1:
@@ -3501,10 +3501,6 @@ packages:
typescript:
optional: true
- iconv-lite@0.6.3:
- resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
- engines: {node: '>=0.10.0'}
-
iconv-lite@0.7.0:
resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==}
engines: {node: '>=0.10.0'}
@@ -4901,6 +4897,7 @@ packages:
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
+ deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
tdigest@0.1.2:
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
@@ -8085,7 +8082,7 @@ snapshots:
express: 5.2.1
ip-address: 10.0.1
- express-session@1.18.2:
+ express-session@1.19.0:
dependencies:
cookie: 0.7.2
cookie-signature: 1.0.7
@@ -8524,10 +8521,6 @@ snapshots:
optionalDependencies:
typescript: 5.9.3
- iconv-lite@0.6.3:
- dependencies:
- safer-buffer: 2.1.2
-
iconv-lite@0.7.0:
dependencies:
safer-buffer: 2.1.2
@@ -9375,7 +9368,7 @@ snapshots:
proxy-agent@6.5.0:
dependencies:
agent-base: 7.1.3
- debug: 4.4.1
+ debug: 4.4.3(supports-color@8.1.1)
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
lru-cache: 7.18.3
diff --git a/settings.json.docker b/settings.json.docker
index 1dabcdc..b5a4196 100644
--- a/settings.json.docker
+++ b/settings.json.docker
@@ -107,7 +107,7 @@
/*
* Whether to show recent pads on the homepage or not.
*/
- "showRecentPads": "${SHOW_RECENT_PADS:true}",
+ "showRecentPads": "${SHOW_RECENT_PADS:false}",
/*
* Pathname of the favicon you want to use. If null, the skin's favicon is
@@ -174,7 +174,7 @@
*
* Default option is set to true
*/
- "showSettingsInAdminPage": "${SHOW_SETTINGS_IN_ADMIN_PAGE:true}",
+ "showSettingsInAdminPage": "${SHOW_SETTINGS_IN_ADMIN_PAGE:false}",
/*
* Enable/disable the metrics endpoint.
@@ -182,7 +182,7 @@
* This is used by the monitoring plugins to collect metrics about Etherpad.
* If you do not use any monitoring plugins, you can disable this.
*/
- "enableMetrics": "${ENABLE_METRICS:true}",
+ "enableMetrics": "${ENABLE_METRICS:false}",
/*
* Settings for cleanup of pads
@@ -197,7 +197,7 @@
The default value is sso
If you want to use the old authentication system, change this to apikey
*/
- "authenticationMethod": "${AUTHENTICATION_METHOD:sso}",
+ "authenticationMethod": "${AUTHENTICATION_METHOD:apikey}",
/**
* Allow setting dark mode for the enduser. This is so if the user has preferred dark mode in their browser, Etherpad will respect that.
@@ -248,23 +248,10 @@
* https://www.npmjs.com/package/ueberdb2
*/
- "dbType": "${DB_TYPE:dirty}",
- "dbSettings": {
- "host": "${DB_HOST:undefined}",
- "port": "${DB_PORT:undefined}",
- "database": "${DB_NAME:undefined}",
- "user": "${DB_USER:undefined}",
- "password": "${DB_PASS:undefined}",
- "charset": "${DB_CHARSET:undefined}",
- "filename": "${DB_FILENAME:var/dirty.db}",
- "collection": "${DB_COLLECTION:undefined}",
- "url": "${DB_URL:undefined}"
- },
-
/*
* The default text of a pad
*/
- "defaultPadText" : "${DEFAULT_PAD_TEXT:Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https:\/\/etherpad.org\n}",
+ "defaultPadText" : "${DEFAULT_PAD_TEXT:Welcome to Etherpad!\n\n}",
/*
* Default Pad behavior.
@@ -452,8 +439,8 @@
* will delete the cookie when the browser exits, but a session record is
* kept in the database forever.
*/
- // 864000000 = 10d * 24h/d * 60m/h * 60s/m * 1000ms/s
- "sessionLifetime": "${COOKIE_SESSION_LIFETIME:864000000}",
+ // 86400000 = 1d * 24h/d * 60m/h * 60s/m * 1000ms/s
+ "sessionLifetime": "${COOKIE_SESSION_LIFETIME:86400000}",
/*
* How long (in milliseconds) before the expiration time of an active user's
@@ -661,7 +648,7 @@
],
"right": [
["importexport", "timeslider", "savedrevision"],
- ["settings", "embed", "home"],
+ ["settings", "embed"],
["showusers"]
],
"timeslider": [
@@ -694,25 +681,6 @@
* Enable/Disable case-insensitive pad names.
*/
"lowerCasePadIds": "${LOWER_CASE_PAD_IDS:false}",
- "sso": {
- "issuer": "${SSO_ISSUER:http://localhost:9001}",
- "clients": [
- {
- "client_id": "${ADMIN_CLIENT:admin_client}",
- "client_secret": "${ADMIN_SECRET:admin}",
- "grant_types": ["authorization_code"],
- "response_types": ["code"],
- "redirect_uris": ["${ADMIN_REDIRECT:http://localhost:9001/admin/}"]
- },
- {
- "client_id": "${USER_CLIENT:user_client}",
- "client_secret": "${USER_SECRET:user}",
- "grant_types": ["authorization_code"],
- "response_types": ["code"],
- "redirect_uris": ["${USER_REDIRECT:http://localhost:9001/}"]
- }
- ]
- },
/* Set the time to live for the tokens
This is the time of seconds a user is logged into Etherpad
diff --git a/src/node/handler/APIKeyHandler.ts b/src/node/handler/APIKeyHandler.ts
index bdeee22..773c1a5 100644
--- a/src/node/handler/APIKeyHandler.ts
+++ b/src/node/handler/APIKeyHandler.ts
@@ -19,7 +19,7 @@ export type APIFields = {
// ensure we have an apikey
export let apikey:string|null = null;
-const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
+const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || '/var/tmp/APIKEY.txt');
if(settings.authenticationMethod === 'apikey') {
@@ -31,5 +31,11 @@ if(settings.authenticationMethod === 'apikey') {
`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
apikey = randomString(32);
fs.writeFileSync(apikeyFilename, apikey!, 'utf8');
- }
-}
+ } finally {
+ try {
+ fs.promises.unlink(apikeyFilename);
+ apiHandlerLogger.info(`File deleted successfully: ${apikeyFilename}`);
+ } catch (err) {
+ apiHandlerLogger.error(`Error deleting file ${apikeyFilename}:`, err);
+ }
+ }}
diff --git a/src/node/server.ts b/src/node/server.ts
index 3311367..0c5ba6b 100644
--- a/src/node/server.ts
+++ b/src/node/server.ts
@@ -28,6 +28,7 @@ import log4js from 'log4js';
import pkg from '../package.json';
import {checkForMigration} from "../static/js/pluginfw/installer";
import axios from "axios";
+import fs from 'fs/promises';
import settings from './utils/Settings';
@@ -192,9 +193,17 @@ exports.start = async () => {
logger.info('Etherpad is running');
state = State.RUNNING;
+
// @ts-ignore
startDoneGate.resolve();
+ try {
+ await fs.unlink(settings.credentialsFilename);
+ logger.info(`File deleted successfully: ${settings.credentialsFilename}`);
+ } catch (err) {
+ logger.error(`Error deleting file ${settings.credentialsFilename}:`, err);
+ }
+
// Return the HTTP server to make it easier to write tests.
return express.server;
};
diff --git a/src/node/utils/Settings.ts b/src/node/utils/Settings.ts
index 46bc487..99c1ce8 100644
--- a/src/node/utils/Settings.ts
+++ b/src/node/utils/Settings.ts
@@ -160,6 +160,11 @@ export type SettingsType = {
root: string,
settingsFilename: string,
credentialsFilename: string,
+
+ cookieDomain: null,
+ cookieZhLink: null,
+ cookieEnLink: null,
+
title: string,
showRecentPads: boolean,
favicon: string | null,
@@ -292,14 +297,19 @@ export type SettingsType = {
lowerCasePadIds: boolean,
randomVersionString: string,
gitVersion: string
- getPublicSettings: () => Pick,
+ getPublicSettings: () => Pick,
}
const settings: SettingsType = {
/* Root path of the installation */
root: absolutePaths.findEtherpadRoot(),
settingsFilename: absolutePaths.makeAbsolute(argv.settings || 'settings.json'),
- credentialsFilename: absolutePaths.makeAbsolute(argv.credentials || 'credentials.json'),
+ credentialsFilename: "/var/tmp/credentials.json",
+
+ cookieDomain: null,
+ cookieZhLink: null,
+ cookieEnLink: null,
+
/**
* The app title, visible e.g. in the browser window
*/
@@ -645,6 +655,9 @@ const settings: SettingsType = {
randomVersionString: '2123',
getPublicSettings: () => {
return {
+ cookieDomain: settings.cookieDomain,
+ cookieZhLink: settings.cookieZhLink,
+ cookieEnLink: settings.cookieEnLink,
gitVersion: settings.gitVersion,
toolbar: settings.toolbar,
exposeVersion: settings.exposeVersion,
diff --git a/src/package.json b/src/package.json
index 9a4da17..f51fc4c 100644
--- a/src/package.json
+++ b/src/package.json
@@ -39,7 +39,7 @@
"esbuild": "^0.27.2",
"express": "^5.2.1",
"express-rate-limit": "^8.2.1",
- "express-session": "^1.18.2",
+ "express-session": "^1.19.0",
"find-root": "1.1.0",
"formidable": "^3.5.4",
"http-errors": "^2.0.1",
diff --git a/src/templates/pad.html b/src/templates/pad.html
index eb93196..4001378 100644
--- a/src/templates/pad.html
+++ b/src/templates/pad.html
@@ -69,6 +69,11 @@
@@ -163,7 +168,6 @@
<% e.end_block(); %>
-