Compare commits
76 Commits
7fa5bdcc7c
...
ecd65d3fd3
| Author | SHA1 | Date | |
|---|---|---|---|
|
ecd65d3fd3
|
|||
|
b72c7d7f8e
|
|||
|
|
c6e2cefa8f | ||
|
|
b77044b591 | ||
|
|
aa4e83337e | ||
|
|
0e0eff2b91 | ||
|
|
afc883acb3 | ||
|
|
4891d4b178 | ||
|
|
d533c3b3d3 | ||
|
|
6ff943b837 | ||
|
|
fb67c260f4 | ||
|
|
f1c9f11d98 | ||
|
|
ffc1e5f87d | ||
|
|
389302e6bb | ||
|
|
9a45d1e2a9 | ||
|
|
637c451486 | ||
|
|
c449d3a815 | ||
|
|
4030cbbd3c | ||
|
|
6d970dbafd | ||
|
|
78cac1526f | ||
|
|
b9ee0b4baa | ||
|
|
fd879d43f2 | ||
|
|
1f586399ac | ||
|
|
c61bc6a2e0 | ||
|
|
c94ccfb6de | ||
|
|
eba0fc51f0 | ||
|
|
8c4b60a13c | ||
|
|
5fdc09512a | ||
|
|
367626ab9c | ||
|
|
c97c11df2c | ||
|
|
8dbf77b0a9 | ||
|
|
a27f58396b | ||
|
|
ce66f33a6d | ||
|
|
3f520ea59a | ||
|
|
0636b5c20b | ||
|
|
d2585fbd3b | ||
|
|
f31e591c17 | ||
|
|
86ef650765 | ||
|
|
312950aeeb | ||
|
|
e66f413507 | ||
|
|
d09abac9c6 | ||
|
|
a0c50164b6 | ||
|
|
cc74351846 | ||
|
|
639eee078d | ||
|
|
3b9a8297c2 | ||
|
|
e38ba23bc9 | ||
|
|
49c6d7f75f | ||
|
|
5ec6487f17 | ||
|
|
4c3ff41385 | ||
|
|
53076a665a | ||
|
|
894efa88d1 | ||
|
|
c6df2629d3 | ||
|
|
9ece354c70 | ||
|
|
ae85d372a4 | ||
|
|
543fb4feb4 | ||
|
|
e08feadde7 | ||
|
|
4f63cb4ec0 | ||
|
|
7d38e100b8 | ||
|
|
7e832cc408 | ||
|
|
1259ff0edc | ||
|
|
80ce58e316 | ||
|
|
9f98e9701e | ||
|
|
8bd70328dc | ||
|
|
cd1431c647 | ||
|
|
d29606bad0 | ||
|
|
24dc3451a4 | ||
|
|
21ebba3774 | ||
|
|
fd4c341924 | ||
|
|
38f25ec8cc | ||
|
|
7b61a815c1 | ||
|
|
a8d5cb754c | ||
|
|
fb6218ed82 | ||
|
|
1a5030dbc1 | ||
|
|
38578f2b4c | ||
|
|
26e705765b | ||
|
|
db7f47c13e |
4
.github/actions/setup-node/action.yml
vendored
4
.github/actions/setup-node/action.yml
vendored
@@ -19,14 +19,14 @@ runs:
|
||||
shell: bash
|
||||
|
||||
- name: Cache yarn cache
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-master
|
||||
|
||||
- name: Set up NodeJS
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version: ${{ inputs.NODEJS_VERSION }}
|
||||
|
||||
|
||||
4
.github/workflows/build-and-test.yml
vendored
4
.github/workflows/build-and-test.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
name: Node ${{ matrix.node }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
run: yarn run mocha-suite
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
if: github.ref == 'refs/heads/master' && matrix.node-version == '22'
|
||||
with:
|
||||
name: Prebuild with Node.js ${{ matrix.node-version }}
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
name: Lint files
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@ backups/
|
||||
|
||||
# ignore config files
|
||||
config.json
|
||||
!/resources/config.json
|
||||
.sequelizerc
|
||||
|
||||
# ignore webpack build
|
||||
|
||||
942
.yarn/releases/yarn-4.12.0.cjs
vendored
Executable file
942
.yarn/releases/yarn-4.12.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
934
.yarn/releases/yarn-4.6.0.cjs
vendored
934
.yarn/releases/yarn-4.6.0.cjs
vendored
File diff suppressed because one or more lines are too long
@@ -4,4 +4,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.6.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.12.0.cjs
|
||||
|
||||
85
Dockerfile
Normal file
85
Dockerfile
Normal file
@@ -0,0 +1,85 @@
|
||||
FROM docker.io/library/node:20.17.0-alpine@sha256:2d07db07a2df6830718ae2a47db6fedce6745f5bcd174c398f2acdda90a11c03 AS builder
|
||||
|
||||
# Build arguments to change source url, branch or tag
|
||||
ARG CODIMD_REPOSITORY
|
||||
ARG HEDGEDOC_REPOSITORY=https://gitea.finnvanreenen.nl/LailaTheElf/hedgedoc.git
|
||||
ARG VERSION=master
|
||||
#necessary on ARM because puppeteer doesn't provide a prebuilt binary
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=true
|
||||
ENV YARN_CACHE_FOLDER=/tmp/.yarn
|
||||
|
||||
RUN if [ -n "${CODIMD_REPOSITORY}" ]; then echo "CODIMD_REPOSITORY is deprecated. Please use HEDGEDOC_REPOSITORY instead" && exit 1; fi
|
||||
|
||||
# Clone the source and remove git repository but keep the HEAD file
|
||||
RUN --mount=target=/var/cache/apk,type=cache,sharing=locked \
|
||||
apk update && \
|
||||
apk add git jq python3 build-base
|
||||
# RUN git clone --depth 1 --branch "$VERSION" "$HEDGEDOC_REPOSITORY" /hedgedoc
|
||||
COPY . /hedgedoc
|
||||
# RUN git -C /hedgedoc log --pretty=format:'%ad %h %d' --abbrev-commit --date=short -1
|
||||
# RUN git -C /hedgedoc rev-parse HEAD > /tmp/gitref
|
||||
RUN rm -rf /hedgedoc/.git/*
|
||||
# RUN mv /tmp/gitref /hedgedoc/.git/HEAD
|
||||
# RUN jq ".repository.url = \"${HEDGEDOC_REPOSITORY}\"" /hedgedoc/package.json > /hedgedoc/package.new.json
|
||||
# RUN mv /hedgedoc/package.new.json /hedgedoc/package.json
|
||||
|
||||
# Install app dependencies and build
|
||||
WORKDIR /hedgedoc
|
||||
RUN --mount=type=cache,sharing=locked,target=/tmp/.yarn yarn install
|
||||
RUN yarn run build
|
||||
|
||||
FROM docker.io/library/node:20.17.0-alpine@sha256:2d07db07a2df6830718ae2a47db6fedce6745f5bcd174c398f2acdda90a11c03 AS modules-installer
|
||||
WORKDIR /hedgedoc
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV YARN_CACHE_FOLDER=/tmp/.yarn
|
||||
|
||||
COPY --from=builder /hedgedoc /hedgedoc
|
||||
|
||||
RUN --mount=target=/var/cache/apk,type=cache,sharing=locked \
|
||||
apk update && \
|
||||
apk add git python3 build-base
|
||||
|
||||
RUN --mount=type=cache,sharing=locked,target=/tmp/.yarn yarn workspaces focus --production
|
||||
|
||||
FROM docker.io/library/node:20.17.0-alpine@sha256:2d07db07a2df6830718ae2a47db6fedce6745f5bcd174c398f2acdda90a11c03 AS app
|
||||
|
||||
LABEL org.opencontainers.image.title='HedgeDoc production image(alpine)'
|
||||
LABEL org.opencontainers.image.url='https://hedgedoc.org'
|
||||
LABEL org.opencontainers.image.source='https://gitea.finnvanreenen.nl/LailaTheElf/hedgedoc'
|
||||
LABEL org.opencontainers.image.documentation='https://github.com/hedgedoc/container/blob/master/README.md'
|
||||
LABEL org.opencontainers.image.licenses='AGPL-3.0'
|
||||
|
||||
WORKDIR /hedgedoc
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ARG UID=10000
|
||||
ENV UPLOADS_MODE=0700
|
||||
|
||||
RUN apk add --no-cache --no-progress --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ gosu
|
||||
|
||||
# Create hedgedoc user
|
||||
RUN adduser -u $UID -h /hedgedoc/ -D -S hedgedoc
|
||||
|
||||
COPY --chown=$UID --from=modules-installer /hedgedoc /hedgedoc
|
||||
|
||||
# Add configuraton files
|
||||
COPY ["resources/config.json", "/files/"]
|
||||
|
||||
# Healthcheck
|
||||
COPY --chown=$UID /resources/healthcheck.mjs /hedgedoc/healthcheck.mjs
|
||||
HEALTHCHECK --interval=15s CMD node healthcheck.mjs
|
||||
|
||||
# For backwards compatibility
|
||||
RUN ln -s /hedgedoc /codimd
|
||||
|
||||
# Symlink configuration files
|
||||
RUN rm -f /hedgedoc/config.json
|
||||
RUN ln -s /files/config.json /hedgedoc/config.json
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
COPY ["resources/docker-entrypoint.sh", "/usr/local/bin/docker-entrypoint.sh"]
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
|
||||
CMD ["node", "app.js"]
|
||||
47
app.js
47
app.js
@@ -12,6 +12,7 @@ const session = require('express-session')
|
||||
const SequelizeStore = require('connect-session-sequelize')(session.Store)
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { Server } = require('socket.io')
|
||||
|
||||
const morgan = require('morgan')
|
||||
const passportSocketIo = require('passport.socketio')
|
||||
@@ -81,7 +82,16 @@ if (config.enableStatsApi) {
|
||||
}
|
||||
|
||||
// socket io
|
||||
const io = require('socket.io')(server, { cookie: false })
|
||||
const io = new Server(server, {
|
||||
pingInterval: config.heartbeatInterval,
|
||||
pingTimeout: config.heartbeatTimeout,
|
||||
cookie: false,
|
||||
cors: {
|
||||
origin: config.serverURL,
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true
|
||||
}
|
||||
})
|
||||
|
||||
// others
|
||||
const realtime = require('./lib/realtime.js')
|
||||
@@ -147,10 +157,29 @@ app.use(i18n.init)
|
||||
|
||||
// routes without sessions
|
||||
// static files
|
||||
app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.staticCacheTime, index: false, redirect: false }))
|
||||
app.use('/docs', express.static(path.resolve(__dirname, config.docsPath), { maxAge: config.staticCacheTime, redirect: false }))
|
||||
app.use('/uploads', express.static(path.resolve(__dirname, config.uploadsPath), { maxAge: config.staticCacheTime, redirect: false }))
|
||||
app.use('/default.md', express.static(path.resolve(__dirname, config.defaultNotePath), { maxAge: config.staticCacheTime }))
|
||||
app.use('/', express.static(path.join(__dirname, '/public'), {
|
||||
maxAge: config.staticCacheTime,
|
||||
index: false,
|
||||
redirect: false
|
||||
}))
|
||||
app.use('/docs', express.static(path.resolve(__dirname, config.docsPath), {
|
||||
maxAge: config.staticCacheTime,
|
||||
redirect: false
|
||||
}))
|
||||
// This is done by an additional middleware, instead of setHeaders of express.static, because for what ever reason
|
||||
// the latter did not work
|
||||
app.use('/uploads', (req, res, next) => {
|
||||
res.set('Content-Disposition', 'attachment')
|
||||
res.set('Content-Security-Policy', "default-src 'none'")
|
||||
next()
|
||||
})
|
||||
app.use('/uploads', express.static(path.resolve(__dirname, config.uploadsPath), {
|
||||
maxAge: config.staticCacheTime,
|
||||
redirect: false
|
||||
}))
|
||||
app.use('/default.md', express.static(path.resolve(__dirname, config.defaultNotePath), {
|
||||
maxAge: config.staticCacheTime
|
||||
}))
|
||||
|
||||
// session
|
||||
app.use(useUnless(['/status', '/metrics', '/_health'], session({
|
||||
@@ -253,9 +282,6 @@ io.use(passportSocketIo.authorize({
|
||||
success: realtime.onAuthorizeSuccess,
|
||||
fail: realtime.onAuthorizeFail
|
||||
}))
|
||||
// socket.io heartbeat
|
||||
io.set('heartbeat interval', config.heartbeatInterval)
|
||||
io.set('heartbeat timeout', config.heartbeatTimeout)
|
||||
// socket.io connection
|
||||
io.sockets.on('connection', realtime.connection)
|
||||
|
||||
@@ -330,8 +356,9 @@ function handleTermSignals () {
|
||||
alreadyHandlingTermSignals = true
|
||||
realtime.maintenance = true
|
||||
// disconnect all socket.io clients
|
||||
Object.keys(io.sockets.sockets).forEach(function (key) {
|
||||
const socket = io.sockets.sockets[key]
|
||||
Array.from(io.sockets.sockets.keys()).forEach(function (key) {
|
||||
const socket = io.sockets.sockets.get(key)
|
||||
if (!socket) return
|
||||
// notify client server going into maintenance status
|
||||
socket.emit('maintenance')
|
||||
setTimeout(function () {
|
||||
|
||||
@@ -95,6 +95,8 @@
|
||||
"issuer": "change or delete: identity of the service provider (default: config.serverURL)",
|
||||
"identifierFormat": "change or delete: name identifier format (default: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress')",
|
||||
"disableRequestedAuthnContext": "change or delete: true to allow any authentication method, false restricts to password authentication method (default: false)",
|
||||
"wantAssertionsSigned": "change or delete: true to enforce signed assertions, false allows unsigned assertions (default: true)",
|
||||
"wantAuthnResponseSigned": "change or delete: true to enforce signed response, false allows unsigned response (default: true)",
|
||||
"groupAttribute": "change or delete: attribute name for group list (ex: memberOf)",
|
||||
"requiredGroups": [ "change or delete: group names that allowed" ],
|
||||
"externalGroups": [ "change or delete: group names that not allowed" ],
|
||||
|
||||
@@ -2,19 +2,30 @@
|
||||
|
||||
You can choose to configure HedgeDoc with either a config file or with environment variables.
|
||||
|
||||
Environment variables take precedence over configurations from the config files. They generally start with `CMD_` for our own options, but we also list node-specific options you can configure this way.
|
||||
Environment variables take precedence over configurations from the config files. They generally start with `CMD_` for
|
||||
our own options, but we also list node-specific options you can configure this way.
|
||||
|
||||
- Environment variables are processed in [`lib/config/environment.js`](https://github.com/hedgedoc/hedgedoc/tree/master/lib/config/environment.js) - so this is the first place to look if anything is missing not obvious from this document. The default values are defined in [`lib/config/default.js`](https://github.com/hedgedoc/hedgedoc/tree/master/lib/config/default.js), in case you wonder if you even need to override it.
|
||||
- Environment variables are processed in [
|
||||
`lib/config/environment.js`](https://github.com/hedgedoc/hedgedoc/tree/master/lib/config/environment.js) - so this is
|
||||
the first place to look if anything is missing not obvious from this document. The default values are defined in [
|
||||
`lib/config/default.js`](https://github.com/hedgedoc/hedgedoc/tree/master/lib/config/default.js), in case you wonder
|
||||
if you even need to override it.
|
||||
|
||||
- The config file is processed in [`lib/config/index.js`](https://github.com/hedgedoc/hedgedoc/tree/master/lib/config/index.js) - so this is the first place to look if anything is missing not obvious from this document. The default values are defined in [`lib/config/default.js`](https://github.com/hedgedoc/hedgedoc/tree/master/lib/config/default.js), in case you wonder if you even need to override it. To get started, it is a good idea to take the [`config.json.example`](https://github.com/hedgedoc/hedgedoc/tree/master/config.json.example) and copy it
|
||||
to `config.json` before filling in your own details.
|
||||
- The config file is processed in [
|
||||
`lib/config/index.js`](https://github.com/hedgedoc/hedgedoc/tree/master/lib/config/index.js) - so this is the first
|
||||
place to look if anything is missing not obvious from this document. The default values are defined in [
|
||||
`lib/config/default.js`](https://github.com/hedgedoc/hedgedoc/tree/master/lib/config/default.js), in case you wonder
|
||||
if you even need to override it. To get started, it is a good idea to take the [
|
||||
`config.json.example`](https://github.com/hedgedoc/hedgedoc/tree/master/config.json.example) and copy it
|
||||
to `config.json` before filling in your own details.
|
||||
|
||||
**Note:** *Due to the rename process we renamed all `HMD_`-prefix variables to be `CMD_`-prefixed. The old ones continue to work.*
|
||||
**Note:** *Due to the rename process we renamed all `HMD_`-prefix variables to be `CMD_`-prefixed. The old ones continue
|
||||
to work.*
|
||||
|
||||
## Node.JS
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | ----------- | ----------------------------- | -------------------------------------------------------------------------------- |
|
||||
|-------------|-------------|-------------------------------|----------------------------------------------------------------------------------|
|
||||
| | `NODE_ENV` | `production` or `development` | set current environment (will apply corresponding settings in the `config.json`) |
|
||||
| `debug` | `DEBUG` | `true` or `false` | set debug mode, show more logs |
|
||||
|
||||
@@ -30,7 +41,7 @@ to `config.json` before filling in your own details.
|
||||
| `db.host` | `CMD_DB_HOST` | **`undefined`**, `db-host.example.com` | Hostname used to connect the database server. |
|
||||
| `db.post` | `CMD_DB_PORT` | **`undefined`**, `5432` | Port used to connect the database server. |
|
||||
| `db.dialect` | `CMD_DB_DIALECT` | **`undefined`**, `postgres`, `mariadb` | [Dialect](https://sequelize.org/v5/manual/dialects.html) / protocol used to connect to the database. |
|
||||
| `dbURL` | `CMD_DB_URL` | **`undefined`**, `postgres://username:password@localhost:5432/hedgedoc` or `mysql://username:password@localhost:3306/hedgedoc` | Set the db in URL style. If set, then the relevant `db` config entries will be overridden. |
|
||||
| `dbURL` | `CMD_DB_URL` | **`undefined`**, `postgres://username:password@localhost:5432/hedgedoc`, `mysql://username:password@localhost:3306/hedgedoc`, or `sqlite:///path/to/hedgedoc.db` | Set the db in URL style. If set, then the relevant `db` config entries will be overridden. |
|
||||
| `loglevel` | `CMD_LOGLEVEL` | **`info`**, `debug` ... | Defines what kind of logs are provided to stdout. Available options: `debug`, `verbose`, `info`, `warn`, `error` |
|
||||
| `forbiddenNoteIDs` | `CMD_FORBIDDEN_NOTE_IDS` | **`['robots.txt', 'favicon.ico', 'api', 'build', 'css', 'docs', 'fonts', 'js', 'uploads', 'vendor', 'views']`**, `['robots.txt']` or `'robots.txt'` | disallow creation of notes, even if `allowFreeUrl` or `CMD_ALLOW_FREEURL` is `true` |
|
||||
| `imageUploadType` | `CMD_IMAGE_UPLOAD_TYPE` | **`filesystem`**, `imgur`, `s3`, `minio`, `azure`, `lutim` | Where to upload images. For S3, see our Image Upload Guides for [S3](guides/s3-image-upload.md) or [Minio](guides/minio-image-upload.md), also there's a whole section on their respective env vars below. |
|
||||
@@ -48,11 +59,11 @@ to `config.json` before filling in your own details.
|
||||
these are rarely used for various reasons.
|
||||
|
||||
| config file | environment | **default** and example values | description |
|
||||
| ----------------- | ----------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
|-------------------|-------------|-------------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| `defaultNotePath` | | **`./public/default.md`** | default note file path<sup>1</sup>, empty notes will be created with this template. |
|
||||
| `dhParamPath` | | **`undefined`**, `./cert/dhparam.pem` | SSL dhparam path<sup>1</sup> (only need when you set `useSSL`) |
|
||||
| `sslCAPath` | | **`undefined`**, `['./cert/COMODORSAAddTrustCA.crt']` | SSL ca chain<sup>1</sup> (only need when you set `useSSL`) |
|
||||
| `sslCertPath` | | **`undefined`**, `./cert/hedgedoc_io.crt` | SSL cert path<sup>1</sup> (only need when you set `useSSL`) |
|
||||
| `sslCertPath` | | **`undefined`**, `./cert/hedgedoc_io.crt` | SSL cert path<sup>1</sup> (only need when you set `useSSL`) |
|
||||
| `sslKeyPath` | | **`undefined`**, `./cert/client.key` | SSL key path<sup>1</sup> (only need when you set `useSSL`) |
|
||||
| `tmpPath` | | **`os.tmpdir()`**, `./tmp/` | temp directory path<sup>1</sup> |
|
||||
| `docsPath` | | **`./public/docs`** | docs directory path<sup>1</sup> |
|
||||
@@ -63,16 +74,16 @@ these are rarely used for various reasons.
|
||||
|
||||
## HedgeDoc Location
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ---------------- | --------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| `domain` | `CMD_DOMAIN` | **`null`**, `localhost`, `hedgedoc.org` | domain name |
|
||||
| `urlPath` | `CMD_URL_PATH` | **`null`**, `hedgedoc` | If HedgeDoc is run from a subdirectory like `www.example.com/<urlpath>` |
|
||||
| `host` | `CMD_HOST` | **`0.0.0.0`**, `localhost` | interface/ip to listen on |
|
||||
| `port` | `CMD_PORT` | **`3000`**, `80` | port to listen on |
|
||||
| `path` | `CMD_PATH` | **no default**, `/var/run/hedgedoc.sock` | path to UNIX domain socket to listen on (if specified, `host` or `CMD_HOST` and `port` or `CMD_PORT` are ignored) |
|
||||
| `protocolUseSSL` | `CMD_PROTOCOL_USESSL` | **`false`** or `true` | set to use SSL protocol for resources path (only applied when domain is set) |
|
||||
| `useSSL` | | **`false`** or `true` | set to use SSL server (if `true`, will auto turn on `protocolUseSSL`) |
|
||||
| `urlAddPort` | `CMD_URL_ADDPORT` | **`false`** or `true` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) |
|
||||
| config file | environment | **default** and example value | description |
|
||||
|------------------|-----------------------|--------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|
|
||||
| `domain` | `CMD_DOMAIN` | **`null`**, `localhost`, `hedgedoc.org` | domain name |
|
||||
| `urlPath` | `CMD_URL_PATH` | **`null`**, `hedgedoc` | If HedgeDoc is run from a subdirectory like `www.example.com/<urlpath>` |
|
||||
| `host` | `CMD_HOST` | **`0.0.0.0`**, `localhost` | interface/ip to listen on |
|
||||
| `port` | `CMD_PORT` | **`3000`**, `80` | port to listen on |
|
||||
| `path` | `CMD_PATH` | **no default**, `/var/run/hedgedoc.sock` | path to UNIX domain socket to listen on (if specified, `host` or `CMD_HOST` and `port` or `CMD_PORT` are ignored) |
|
||||
| `protocolUseSSL` | `CMD_PROTOCOL_USESSL` | **`false`** or `true` | set to use SSL protocol for resources path (only applied when domain is set) |
|
||||
| `useSSL` | | **`false`** or `true` | set to use SSL server (if `true`, will auto turn on `protocolUseSSL`) |
|
||||
| `urlAddPort` | `CMD_URL_ADDPORT` | **`false`** or `true` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) |
|
||||
| `allowOrigin` | `CMD_ALLOW_ORIGIN` | **`['localhost']`**, `['hedgedoc.org']`, `['localhost', 'hedgedoc.org']` | domain name whitelist (use comma to separate) |
|
||||
|
||||
## Web security aspects
|
||||
@@ -100,36 +111,37 @@ these are rarely used for various reasons.
|
||||
## Privacy and External Requests
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| --------------- | -------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
|-----------------|----------------------|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `allowGravatar` | `CMD_ALLOW_GRAVATAR` | **`true`** or `false` | set to `false` to disable [Libravatar](https://www.libravatar.org/) as profile picture source on your instance. Libravatar is a federated open-source alternative to Gravatar. |
|
||||
|
||||
## Users and Privileges
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
|--------------------------------|--------------------------------------|-------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `allowAnonymous` | `CMD_ALLOW_ANONYMOUS` | **`true`** or `false` | Set to allow anonymous usage (default is `true`). |
|
||||
| `allowAnonymousEdits` | `CMD_ALLOW_ANONYMOUS_EDITS` | **`false`** or `true` | If `allowAnonymous` is `false`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`). |
|
||||
| `allowFreeURL` | `CMD_ALLOW_FREEURL` | **`false`** or `true` | Set to allow new note creation by accessing a nonexistent note URL. This is the behavior familiar from [Etherpad](https://github.com/ether/etherpad-lite). |
|
||||
| `requireFreeURLAuthentication` | `CMD_REQUIRE_FREEURL_AUTHENTICATION` | **`false`** or `true` | Set to require authentication for FreeURL mode style note creation. |
|
||||
| `disableNoteCreation` | `CMD_DISABLE_NOTE_CREATION` | **`false`** or `true` | Set to `true` to disallow any person to create notes. |
|
||||
| `defaultPermission` | `CMD_DEFAULT_PERMISSION` | **`editable`**, `freely`, `limited`, `locked`, `protected` or `private` | Set notes default permission (only applied on signed-in users). |
|
||||
| `sessionName` | | **`connect.sid`** | Cookie session name. |
|
||||
| `sessionLife` | `CMD_SESSION_LIFE` | **`14 * 24 * 60 * 60 * 1000`**, `1209600000` (14 days) | Cookie session life time in milliseconds. |
|
||||
| `sessionSecret` | `CMD_SESSION_SECRET` | **`secret`** | Cookie session secret used to sign the session cookie. If none is set, one will randomly generated on each startup, meaning all your users will be logged out. Can be generated with e.g. `pwgen -s 64 1`. |
|
||||
| config file | environment | **default** and example value | description |
|
||||
|--------------------------------|--------------------------------------|-------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `allowAnonymous` | `CMD_ALLOW_ANONYMOUS` | **`true`** or `false` | Set to allow anonymous usage (default is `true`). |
|
||||
| `allowAnonymousEdits` | `CMD_ALLOW_ANONYMOUS_EDITS` | **`false`** or `true` | If `allowAnonymous` is `false`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`). |
|
||||
| `allowFreeURL` | `CMD_ALLOW_FREEURL` | **`false`** or `true` | Set to allow new note creation by accessing a nonexistent note URL. This is the behavior familiar from [Etherpad](https://github.com/ether/etherpad-lite). |
|
||||
| `requireFreeURLAuthentication` | `CMD_REQUIRE_FREEURL_AUTHENTICATION` | **`false`** or `true` | Set to require authentication for FreeURL mode style note creation. |
|
||||
| `disableNoteCreation` | `CMD_DISABLE_NOTE_CREATION` | **`false`** or `true` | Set to `true` to disallow any person to create notes. |
|
||||
| `defaultPermission` | `CMD_DEFAULT_PERMISSION` | **`editable`**, `freely`, `limited`, `locked`, `protected` or `private` | Set notes default permission (only applied on signed-in users). |
|
||||
| `enableUploads` | `CMD_ENABLE_UPLOADS` | **`all`** , `registered`, `none` | Set to specify who can upload images (`all` includes guest users, `registered` restricts to registered users, `none` disables uploads completely). If not set, but both `allowAnonymous` and `allowAnonymousEdits` are `false`, this will be set to `registered`. |
|
||||
| `sessionName` | | **`connect.sid`** | Cookie session name. |
|
||||
| `sessionLife` | `CMD_SESSION_LIFE` | **`14 * 24 * 60 * 60 * 1000`**, `1209600000` (14 days) | Cookie session life time in milliseconds. |
|
||||
| `sessionSecret` | `CMD_SESSION_SECRET` | **`secret`** | Cookie session secret used to sign the session cookie. If none is set, one will randomly generated on each startup, meaning all your users will be logged out. Can be generated with e.g. `pwgen -s 64 1`. |
|
||||
|
||||
## Login methods
|
||||
|
||||
### Email (local account)
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| -------------------- | -------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|----------------------|----------------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `email` | `CMD_EMAIL` | **`true`** or `false` | Set to allow email sign-in. The default is `true`. |
|
||||
| `allowEmailRegister` | `CMD_ALLOW_EMAIL_REGISTER` | **`true`** or `false` | Set to allow registration of new accounts using an email address. If set to `false`, you can still create accounts using the command line - see `bin/manage_users` for details (In production mode, remember to run it with `NODE_ENV` set as `production` in the enviroment). This setting has no effect if `email` or `CMD_EMAIL` is `false`. The default for `allowEmailRegister` or `CMD_ALLOW_EMAIL_REGISTER` is `true`. |
|
||||
|
||||
### Dropbox Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | -------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|-------------|----------------------------|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `dropbox` | | `{clientID: ..., clientSecret: ...}` | An object containing the client ID and the client secret obtained by the [Dropbox developer tools](https://www.dropbox.com/developers/apps) |
|
||||
| | `CMD_DROPBOX_CLIENTID` | **no default** | Dropbox API client id |
|
||||
| | `CMD_DROPBOX_CLIENTSECRET` | **no default** | Dropbox API client secret |
|
||||
@@ -137,7 +149,7 @@ these are rarely used for various reasons.
|
||||
### Facebook Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | --------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|-------------|-----------------------------|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `facebook` | | `{clientID: ..., clientSecret: ...}` | An object containing the client ID and the client secret obtained by the [Facebook app console](https://developers.facebook.com/apps) |
|
||||
| | `CMD_FACEBOOK_CLIENTID` | **no default** | Facebook API client id |
|
||||
| | `CMD_FACEBOOK_CLIENTSECRET` | **no default** | Facebook API client secret |
|
||||
@@ -145,7 +157,7 @@ these are rarely used for various reasons.
|
||||
### GitHub Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | ------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|-------------|---------------------------|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `github` | | `{clientID: ..., clientSecret: ...}` | An object containing the client ID and the client secret obtained by the GitHub developer page. For more details have a look at the [GitHub auth guide](guides/auth/github.md). |
|
||||
| | `CMD_GITHUB_CLIENTID` | **no default** | GitHub API client id |
|
||||
| | `CMD_GITHUB_CLIENTSECRET` | **no default** | GitHub API client secret |
|
||||
@@ -153,7 +165,7 @@ these are rarely used for various reasons.
|
||||
### GitLab Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | ------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|-------------|---------------------------|------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `gitlab` | | `{baseURL: ..., scope: ..., version: ..., clientID: ..., clientSecret: ...}` | An object containing your GitLab application data. Refer to the [GitLab guide](guides/auth/gitlab-self-hosted.md) for more details! |
|
||||
| | `CMD_GITLAB_SCOPE` | **no default**, `read_user` or `api` | GitLab API requested scope (default is `api`) (GitLab snippet import/export need `api` scope) |
|
||||
| | `CMD_GITLAB_BASEURL` | **no default** | GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional) |
|
||||
@@ -164,7 +176,7 @@ these are rarely used for various reasons.
|
||||
### Google Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | ------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
|-------------|---------------------------|---------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `google` | | `{clientID: ..., clientSecret: ..., hostedDomain: ...}` | An object containing the client ID and the client secret obtained by the [Google API console](https://console.cloud.google.com/apis) |
|
||||
| | `CMD_GOOGLE_CLIENTID` | **no default** | Google API client id |
|
||||
| | `CMD_GOOGLE_CLIENTSECRET` | **no default** | Google API client secret |
|
||||
@@ -173,7 +185,7 @@ these are rarely used for various reasons.
|
||||
### LDAP Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
|-------------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
|
||||
| `ldap` | | `{providerName: ..., url: ..., bindDn: ..., bindCredentials: ..., searchBase: ..., searchFilter: ..., searchAttributes: ..., usernameField: ..., useridField: ..., tlsca: ...}` | An object detailing the LDAP connection. Refer to the [LDAP-AD guide](guides/auth/ldap-ad.md) for more details! |
|
||||
| | `CMD_LDAP_URL` | **no default**, `ldap://example.com` | URL of LDAP server |
|
||||
| | `CMD_LDAP_BINDDN` | **no default** | bindDn for LDAP access |
|
||||
@@ -181,15 +193,15 @@ these are rarely used for various reasons.
|
||||
| | `CMD_LDAP_SEARCHBASE` | **no default**, `o=users,dc=example,dc=com` | LDAP directory to begin search from |
|
||||
| | `CMD_LDAP_SEARCHFILTER` | **no default**, `(uid={{username}})` | LDAP filter to search with |
|
||||
| | `CMD_LDAP_SEARCHATTRIBUTES` | **no default**, `displayName, mail` | LDAP attributes to search with (use comma to separate) |
|
||||
| | `CMD_LDAP_USERIDFIELD` | **no default**, `uidNumber` or `uid` or `sAMAccountName` | The LDAP field which is used uniquely identify a user on HedgeDoc |
|
||||
| | `CMD_LDAP_USERNAMEFIELD` | **no default**, fallback to userid | The LDAP field which is used as the username on HedgeDoc |
|
||||
| | `CMD_LDAP_USERIDFIELD` | **no default**, `uidNumber` or `uid` or `sAMAccountName` | The LDAP field which is used uniquely identify a user on HedgeDoc |
|
||||
| | `CMD_LDAP_USERNAMEFIELD` | **no default**, fallback to userid | The LDAP field which is used as the username on HedgeDoc |
|
||||
| | `CMD_LDAP_TLS_CA` | **no default**, `server-cert.pem, root.pem` | Root CA for LDAP TLS in PEM format (use comma to separate) |
|
||||
| | `CMD_LDAP_PROVIDERNAME` | **no default**, `My institution` | Optional name to be displayed at login form indicating the LDAP provider |
|
||||
|
||||
### Mattermost Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ------------ | ----------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|--------------|-------------------------------|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `mattermost` | | `{baseURL: ..., clientID: ..., clientSecret: ...}` | An object containing the base URL of your Mattermost application data. Refer to the [Mattermost guide](guides/auth/mattermost-self-hosted.md) for more details! |
|
||||
| | `CMD_MATTERMOST_BASEURL` | **no default** | Mattermost authentication endpoint for versions below 5.0. For Mattermost version 5.0 and above, see [guide](guides/auth/mattermost-self-hosted.md). |
|
||||
| | `CMD_MATTERMOST_CLIENTID` | **no default** | Mattermost API client id |
|
||||
@@ -198,7 +210,7 @@ these are rarely used for various reasons.
|
||||
### OAuth2 Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|-------------|---------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `oauth2` | | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ..., scope: ...}` | An object detailing your OAuth2 provider. Refer to the [Mattermost](guides/auth/mattermost-self-hosted.md) or [Nextcloud](guides/auth/nextcloud.md) examples for more details! |
|
||||
| | `CMD_OAUTH2_USER_PROFILE_URL` | **no default**, `https://example.com` | Where to retrieve information about a user after successful login. Needs to output JSON. (no default value) Refer to the [Mattermost](guides/auth/mattermost-self-hosted.md) or [Nextcloud](guides/auth/nextcloud.md) examples for more details on all of the `CMD_OAUTH2...` options. |
|
||||
| | `CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR` | **no default**, `name` | where to find the username in the JSON from the user profile URL. (no default value) |
|
||||
@@ -215,33 +227,37 @@ these are rarely used for various reasons.
|
||||
| | `CMD_OAUTH2_ACCESS_ROLE` | **no default**, `role/hedgedoc` | The role which should be included in the ID token roles claim to grant access |
|
||||
|
||||
!!! info
|
||||
If you are using a [CA not trusted by Node.js](https://github.com/nodejs/node/issues/4175) (like Let's Encrypt e.g) for your OAuth2 provider you can set the [`NODE_EXTRA_CA_CERTS`](https://nodejs.org/api/cli.html#cli_node_extra_ca_certs_file) environment variable to the CA certificate file path of your CA.
|
||||
Remember to also make the file available inside the Docker container, if you're running HedgeDoc in Docker container.
|
||||
|
||||
If you are using a [CA not trusted by Node.js](https://github.com/nodejs/node/issues/4175) (like Let's Encrypt e.g) for
|
||||
your OAuth2 provider you can set the [
|
||||
`NODE_EXTRA_CA_CERTS`](https://nodejs.org/api/cli.html#cli_node_extra_ca_certs_file) environment variable to the CA
|
||||
certificate file path of your CA.
|
||||
Remember to also make the file available inside the Docker container, if you're running HedgeDoc in Docker container.
|
||||
|
||||
### SAML Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `saml` | | `{idpSsoUrl: ..., idpCert: ..., clientCert: ..., issuer: ..., identifierFormat: ..., disableRequestedAuthnContext: ..., groupAttribute: ..., externalGroups: [], requiredGroups: [], attribute: {id: ..., username: ..., email: ...}}` | An object detailing your SAML provider. Refer to the [OneLogin](guides/auth/saml-onelogin.md) and [SAML](guides/auth/saml.md) guides for more details! |
|
||||
| | `CMD_SAML_IDPSSOURL` | **no default**, `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](guides/auth/saml-onelogin.md). |
|
||||
| | `CMD_SAML_IDPCERT` | **no default**, `/path/to/cert.pem` | certificate file path of IdP in PEM format |
|
||||
| | `CMD_SAML_CLIENTCERT` | **no default**, `/path/to/privatecert.pem` | certificate file path for the client in PEM format (optional) |
|
||||
| | `CMD_SAML_ISSUER` | **no default** | Issuer to supply to identity provider (optional, default: `serverURL` config)" |
|
||||
| | `CMD_SAML_DISABLEREQUESTEDAUTHNCONTEXT` | **no default**, `true` or `false` | true to allow any authentication method, false restricts to password authentication (PasswordProtectedTransport) method (default: false) |
|
||||
| | `CMD_SAML_IDENTIFIERFORMAT` | **`urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`** | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) |
|
||||
| | `CMD_SAML_GROUPATTRIBUTE` | **no default**, `memberOf` | attribute name for group list (optional) |
|
||||
| | `CMD_SAML_REQUIREDGROUPS` | **no default**, `hedgedoc-users` | group names that allowed (use vertical bar to separate) (optional) |
|
||||
| | `CMD_SAML_EXTERNALGROUPS` | **no default**, `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) |
|
||||
| | `CMD_SAML_ATTRIBUTE_ID` | **no default**, `sAMAccountName` | attribute map for `id` (optional, default: NameID of SAML response) |
|
||||
| | `CMD_SAML_ATTRIBUTE_USERNAME` | **no default**, `mailNickname` | attribute map for `username` (optional, default: NameID of SAML response) |
|
||||
| | `CMD_SAML_ATTRIBUTE_EMAIL` | **no default**, `mail` | attribute map for `email` (optional, default: NameID of SAML response if `CMD_SAML_IDENTIFIERFORMAT` is default) |
|
||||
| | `CMD_SAML_PROVIDERNAME` | **no default**, `My institution` | Optional name to be displayed at login form indicating the SAML provider |
|
||||
| config file | environment | **default** and example value | description |
|
||||
|-------------|-----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `saml` | | `{idpSsoUrl: ..., idpCert: ..., clientCert: ..., issuer: ..., identifierFormat: ..., disableRequestedAuthnContext: ..., groupAttribute: ..., externalGroups: [], requiredGroups: [], attribute: {id: ..., username: ..., email: ...}}` | An object detailing your SAML provider. Refer to the [OneLogin](guides/auth/saml-onelogin.md) and [SAML](guides/auth/saml.md) guides for more details! |
|
||||
| | `CMD_SAML_IDPSSOURL` | **no default**, `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](guides/auth/saml-onelogin.md). |
|
||||
| | `CMD_SAML_IDPCERT` | **no default**, `/path/to/cert.pem` | certificate file path of IdP in PEM format |
|
||||
| | `CMD_SAML_CLIENTCERT` | **no default**, `/path/to/privatecert.pem` | certificate file path for the client in PEM format (optional) |
|
||||
| | `CMD_SAML_ISSUER` | **no default** | Issuer to supply to identity provider (optional, default: `serverURL` config)" |
|
||||
| | `CMD_SAML_DISABLEREQUESTEDAUTHNCONTEXT` | **no default**, `true` or `false` | true to allow any authentication method, false restricts to password authentication (PasswordProtectedTransport) method (default: false) |
|
||||
| | `CMD_SAML_WANT_ASSERTIONS_SIGNED` | **`true`** or `false` | true to enforce signed assertions, false allows unsigned assertions |
|
||||
| | `CMD_SAML_WANT_AUTHN_RESPONSE_SIGNED` | **`true`** or `false` | true to enforce signed response, false allows unsigned response |
|
||||
| | `CMD_SAML_IDENTIFIERFORMAT` | **`urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`** | name identifier format (optional, if using `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`, ensure that the IdP returns an unique email address for each user) |
|
||||
| | `CMD_SAML_GROUPATTRIBUTE` | **no default**, `http://schemas.xmlsoap.org/claims/Group` | attribute name for group list (optional) |
|
||||
| | `CMD_SAML_REQUIREDGROUPS` | **no default**, `hedgedoc-users` | group names that allowed (use vertical bar to separate) (optional) |
|
||||
| | `CMD_SAML_EXTERNALGROUPS` | **no default**, `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) |
|
||||
| | `CMD_SAML_ATTRIBUTE_ID` | *defaults to NameID as configured per requested NameID-Format*, `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn` | attribute map for `id` (optional, default: NameID of SAML response) |
|
||||
| | `CMD_SAML_ATTRIBUTE_USERNAME` | **`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name`** | attribute map for `username` (optional) |
|
||||
| | `CMD_SAML_ATTRIBUTE_EMAIL` | **`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`** | attribute map for `email` (optional) |
|
||||
| | `CMD_SAML_PROVIDERNAME` | **no default**, `My institution` | Optional name to be displayed at login form indicating the SAML provider |
|
||||
|
||||
### Twitter Login
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | ---------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|-------------|------------------------------|-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `twitter` | | `{consumerKey: ..., consumerSecret: ...}` | An object containing the consumer key and secret obtained by the [Twitter developer tools](https://developer.twitter.com/apps). For more details have a look at the [Twitter auth guide](guides/auth/twitter.md) |
|
||||
| | `CMD_TWITTER_CONSUMERKEY` | **no default** | Twitter API consumer key |
|
||||
| | `CMD_TWITTER_CONSUMERSECRET` | **no default** | Twitter API consumer secret |
|
||||
@@ -255,7 +271,7 @@ you don't have to use either of these.
|
||||
### Amazon S3
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| --------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
|-----------------|----------------------------|-------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `s3` | | `{ "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "secretAccessKey": "YOUR_S3_ACCESS_KEY", "region": "YOUR_S3_REGION" }` | When `imageuploadtype` be set to `s3`, you would also need to setup this key, check our [S3 Image Upload Guide](guides/s3-image-upload.md) |
|
||||
| | `CMD_S3_ACCESS_KEY_ID` | **no default** | AWS access key id |
|
||||
| | `CMD_S3_SECRET_ACCESS_KEY` | **no default** | AWS secret key |
|
||||
@@ -268,20 +284,20 @@ you don't have to use either of these.
|
||||
### Azure Blob Storage
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | ----------------------------- | ----------------------------- | ------------------------------------------------------------------------- |
|
||||
|-------------|-------------------------------|-------------------------------|---------------------------------------------------------------------------|
|
||||
| | `CMD_AZURE_CONNECTION_STRING` | **no default** | Azure Blob Storage connection string |
|
||||
| | `CMD_AZURE_CONTAINER` | **no default** | Azure Blob Storage container name (automatically created if non existent) |
|
||||
|
||||
### imgur
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | -------------------- | ------------------------------ | ------------------- |
|
||||
| | `CMD_IMGUR_CLIENTID` | **no default** | Imgur API client id |
|
||||
| config file | environment | **default** and example value | description |
|
||||
|-------------|----------------------|-------------------------------|---------------------|
|
||||
| | `CMD_IMGUR_CLIENTID` | **no default** | Imgur API client id |
|
||||
|
||||
### Minio
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|-------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `minio` | | `{ "accessKey": "YOUR_MINIO_ACCESS_KEY", "secretKey": "YOUR_MINIO_SECRET_KEY", "endpoint": "YOUR_MINIO_HOST", port: 9000, secure: true }` | When `imageUploadType` is set to `minio`, you need to set this key. Also check out our [Minio Image Upload Guide](guides/minio-image-upload.md) |
|
||||
| | `CMD_MINIO_ACCESS_KEY` | **no default** | Minio access key |
|
||||
| | `CMD_MINIO_SECRET_KEY` | **no default** | Minio secret key |
|
||||
@@ -292,6 +308,6 @@ you don't have to use either of these.
|
||||
### Lutim
|
||||
|
||||
| config file | environment | **default** and example value | description |
|
||||
| ----------- | --------------- | ----------------------------- | --------------------------------------------------------------------------- |
|
||||
|-------------|-----------------|-------------------------------|-----------------------------------------------------------------------------|
|
||||
| `lutim` | | `{"url": "YOUR_LUTIM_URL"}` | When `imageUploadType` is set to `lutim`, you can setup the lutim url |
|
||||
| | `CMD_LUTIM_URL` | **`https://framapic.org/`** | When `CMD_IMAGE_UPLOAD_TYPE` is set to `lutim`, you can setup the lutim url |
|
||||
|
||||
@@ -3,7 +3,7 @@ openapi: 3.0.1
|
||||
info:
|
||||
title: HedgeDoc
|
||||
description: HedgeDoc is an open source collaborative note editor. Several tasks of HedgeDoc can be automated through this API.
|
||||
version: 1.10.1
|
||||
version: 1.10.3
|
||||
contact:
|
||||
name: HedgeDoc on GitHub
|
||||
url: https://github.com/hedgedoc/hedgedoc
|
||||
|
||||
217
docs/content/dev/release_checklist.md
Normal file
217
docs/content/dev/release_checklist.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Release Checklist:
|
||||
|
||||
## Preparations:
|
||||
|
||||
- [ ] Create release PR(s)
|
||||
- [ ] In the main repo (actually only create a local branch to include all the fixes, additions and so on and only push and create a PR after testing)
|
||||
- [ ] Release PR in <https://github.com/hedgedoc/container>
|
||||
- [ ] Release PR in <https://github.com/hedgedoc/social-media>
|
||||
- [ ] Security fixes (make sure all available undisclosed security fixes are merged)
|
||||
- [ ] Bump the version
|
||||
- [ ] docs/content/dev/openapi.yml
|
||||
- version number image
|
||||
- [ ] docs/content/setup/manual-setup.md
|
||||
- `git clone` branch
|
||||
- `git checkout` branch
|
||||
- [ ] package.json
|
||||
- `version`
|
||||
- [ ] docs/content/setup/docker.md
|
||||
- Update docker-compose in the docs
|
||||
- [ ] Make sure `yarn.lock` is up to date
|
||||
- [ ] Make sure translations are up to date
|
||||
- We use [poeditor_pull](https://github.com/costales/poeditor_pull) to download all language files from POEditor.
|
||||
|
||||
1. change the following line in the script
|
||||
```python
|
||||
- r_lang = requests.post('https://api.poeditor.com/v2/projects/export', dict(api_token=project_api, id=project_id, language=lang['code'], type="po"))
|
||||
+ r_lang = requests.post('https://api.poeditor.com/v2/projects/export', dict(api_token=project_api, id=project_id, language=lang['code'], type="key_value_json"))
|
||||
```
|
||||
2. run the script.
|
||||
3. update the json files in the `locales` directory.
|
||||
- any languages with 0% translations should not be included
|
||||
4. If any languages are new, they need to be added to `locales/_supported.json`
|
||||
- [ ] Write release notes (`public/docs/release-notes.md`)
|
||||
- [ ] Update date
|
||||
- [ ] General description
|
||||
- [ ] Things requiring special action beside updating the software
|
||||
- [ ] New features
|
||||
- [ ] Bug fixes
|
||||
- [ ] Enhancements
|
||||
- [ ] Add all contributors
|
||||
- sort alphabetically
|
||||
- [ ] Update `AUTHORS` file
|
||||
|
||||
## Final Testing:
|
||||
|
||||
- [ ] Create release tar ball
|
||||
```bash
|
||||
mkdir /tmp/hedgedoc && cd /tmp/hedgedoc
|
||||
git clone -b master https://github.com/hedgedoc/hedgedoc.git .
|
||||
yarn install
|
||||
yarn build
|
||||
cd ..
|
||||
tar cvzf hedgedoc-x.y.z.tar.gz --sort=name --exclude hedgedoc/node_modules --exclude hedgedoc/.git --exclude hedgedoc/.github --exclude hedgedoc/.yarn/cache hedgedoc
|
||||
```
|
||||
|
||||
Use this tar ball to test the following things:
|
||||
|
||||
### Account system
|
||||
|
||||
- [ ] User registration works
|
||||
- [ ] User login works
|
||||
- [ ] User self-deletion works
|
||||
|
||||
### Notes
|
||||
|
||||
- [ ] Create new note works
|
||||
- [ ] Create new note with custom alias works when FreeURL-mode is enabled
|
||||
- [ ] Create new note with custom alias fails when FreeURL-mode is disabled
|
||||
- [ ] New note keeps content (visit, write something, leave, visit again after a minute)
|
||||
- [ ] API `POST /new` works
|
||||
- `curl -i -d '# hello world' -H "Content-Type: text/markdown" http://localhost:3000/new`
|
||||
- [ ] API `POST /new/some-test-note` works when FreeURL-mode is enabled
|
||||
- [ ] API `POST /new/some-test-note` fails when FreeURL-mode is disabled
|
||||
- [ ] Working with 2 (or more) devices on a page works and results in the same document
|
||||
- [ ] Uploads work for images
|
||||
- [ ] Uploads fail for other data (e.g. binaries)
|
||||
|
||||
### Database
|
||||
|
||||
#### Sqlite
|
||||
|
||||
- [ ] Sqlite works
|
||||
- [ ] Keeps content of already existing SQLite file from older version
|
||||
|
||||
#### Postgres
|
||||
|
||||
- [ ] Postgres works
|
||||
|
||||
Run `docker run -d --name=hd1-pg -p 5432:5432 -e POSTGRES_USER=hd1db -e POSTGRES_PASSWORD=hd1db -e POSTGRES_DB=hd1db postgres:latest`
|
||||
and put this into your config:
|
||||
```
|
||||
"db": {
|
||||
"username": "hd1db",
|
||||
"password": "hd1db",
|
||||
"database": "hd1db",
|
||||
"host": "localhost",
|
||||
"port": "5432",
|
||||
"dialect": "postgres"
|
||||
},
|
||||
```
|
||||
#### MariaDB
|
||||
|
||||
- [ ] MariaDB works
|
||||
|
||||
Run `docker run --name=hd1-mysql -p 3306:3306 -e MARIADB_USER=hd1db --env MARIADB_PASSWORD=hd1db --env MARIADB_DATABASE=hd1db -e MARIADB_RANDOM_ROOT_PASSWORD=true --rm -d mariadb:latest`
|
||||
and put this into your config:
|
||||
```
|
||||
"db": {
|
||||
"username": "hd1db",
|
||||
"password": "hd1db",
|
||||
"database": "hd1db",
|
||||
"host": "localhost",
|
||||
"port": "5432",
|
||||
"dialect": "mariadb"
|
||||
},
|
||||
```
|
||||
|
||||
### Features page
|
||||
|
||||
- [ ] Loading `/features` results in no browser console errors (they may appear for iframed code)
|
||||
- [ ] Diagrams render without error
|
||||
- [ ] MathJAX rendering works for inline `$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$`
|
||||
- [ ] MathJAX rendering works for multi-line (see features page)
|
||||
- [ ] Codeblocks areas are highlighted and have line numbers in front.
|
||||
|
||||
### Table of content (TOC) tests
|
||||
|
||||
- [ ] TOC renders in the document as content
|
||||
- [ ] TOC renders in the lower right corner of the document in `both`-view
|
||||
- [ ] TOC renders besides the document in the `view`-view
|
||||
- [ ] TOC renders besides the document in `published`-view
|
||||
- [ ] Interactive TOC follows the header while scrolling in `both`-view
|
||||
- [ ] Interactive TOC follows the header while scrolling in `view`-view
|
||||
- [ ] Interactive TOC follows the header while scrolling in `published`-view
|
||||
|
||||
### Embeddings
|
||||
|
||||
Click in them an try to play around with them. Don't just check they exist and show up.
|
||||
|
||||
- [ ] Youtube embedding works
|
||||
- [ ] Vimeo embedding works
|
||||
- [ ] Gist embedding works
|
||||
|
||||
### Working YAML-Meta
|
||||
|
||||
- [ ] Testing each option if it works
|
||||
|
||||
### GDPR features
|
||||
|
||||
- [ ] Delete account works
|
||||
- [ ] When account is deleted, verify notes are gone as well
|
||||
- [ ] Data export works
|
||||
|
||||
### Auth
|
||||
|
||||
- [ ] SAML
|
||||
- config
|
||||
```
|
||||
"saml": {
|
||||
"idpSsoUrl": "https://auth.hedgedoc.cloud/application/saml/test-hd1/sso/binding/redirect/",
|
||||
"idpCert": "/tmp/auth.hedgedoc.cloud.pem",
|
||||
"identifierFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
|
||||
"attribute": {
|
||||
"id": "http://schemas.goauthentik.io/2021/02/saml/uid",
|
||||
"username": "http://schemas.goauthentik.io/2021/02/saml/username",
|
||||
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
|
||||
}
|
||||
}
|
||||
```
|
||||
- [ ] LDAP
|
||||
- docker image: `docker run --rm -p 10389:10389 -p 10636:10636 -d ghcr.io/rroemhild/docker-test-openldap:master`
|
||||
- config
|
||||
```
|
||||
"ldap": {
|
||||
"url": "ldap://localhost:10389",
|
||||
"bindDn": "cn=admin,dc=planetexpress,dc=com",
|
||||
"bindCredentials": "GoodNewsEveryone",
|
||||
"searchBase": "ou=people,dc=planetexpress,dc=com",
|
||||
"searchFilter": "(&(uid={{username}})(objectClass=inetOrgPerson))",
|
||||
"searchAttributes": ["uid", "cn"],
|
||||
"usernameField": "cn",
|
||||
"useridField": "uid",
|
||||
"tlsOptions": {}
|
||||
}
|
||||
```
|
||||
- [ ] OAuth2
|
||||
```
|
||||
"oauth2": {
|
||||
"baseURL": "https://auth.hedgedoc.cloud/application/o/test-hedgedoc/",
|
||||
"userProfileURL": "https://auth.hedgedoc.cloud/application/o/userinfo/",
|
||||
"tokenURL": "https://auth.hedgedoc.cloud/application/o/token/",
|
||||
"authorizationURL": "https://auth.hedgedoc.cloud/application/o/authorize/",
|
||||
"clientID": "REDACTED",
|
||||
"clientSecret": "REDACTED",
|
||||
"scope": "openid profile user",
|
||||
"userProfileUsernameAttr": "preferred_username",
|
||||
"userProfileEmailAttr": "email",
|
||||
"userProfileDisplayNameAttr": "name"
|
||||
}
|
||||
```
|
||||
- [ ] GitHub
|
||||
- [ ] Rate-limiting for basic user/password
|
||||
|
||||
## Release:
|
||||
|
||||
- [ ] Merge Release PR in main repo
|
||||
- [ ] Tag commit with `git tag 1.x.y` and push it
|
||||
- [ ] Create release in GitHub and upload tar ball to GitHub
|
||||
- [ ] Publish Security Advisories (if they exist)
|
||||
- [ ] Merge Release PR in <https://github.com/hedgedoc/container>
|
||||
- Wait for the images to be available at <https://quay.io/repository/hedgedoc/hedgedoc?tab=tags>
|
||||
- [ ] Update website by running the ["deploy" workflow](https://github.com/hedgedoc/hedgedoc.github.io/actions?query=workflow%3A%22Deploy+to+github+actions+branch%22) in hedgedoc/hedgedoc.github.io
|
||||
- [ ] Update docs.hedgedoc.org by running the ["build" workflow](https://github.com/hedgedoc/docs.hedgedoc.org/actions/workflows/build.yml)
|
||||
- [ ] Merge Release PR in <https://github.com/hedgedoc/social-media>
|
||||
- (optional) All people doing the release boost the post
|
||||
- [ ] Share the good news in the Matrix-Chatroom
|
||||
- [ ] Change this release checklist if necessary
|
||||
@@ -2,22 +2,22 @@
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This guide assumes you have run and configured Keycloak. If you'd like to meet this prerequisite quickly, it can be achieved by running a `jboss/keycloak` container and attaching it to your network. Set the environment variables KEYCLOAK_USER and `KEYCLOAK_PASSWORD`, and expose port 8080.
|
||||
|
||||
This guide assumes you have run and configured Keycloak.
|
||||
If you'd like to meet this prerequisite quickly, it can be achieved by running a `quay.io/keycloak/keycloak` container and attaching it to your network.
|
||||
For details and quick-start command, take a look at [the Keycloak Docker documentation](https://www.keycloak.org/getting-started/getting-started-docker).
|
||||
Where HTTPS is specified throughout, use HTTP instead. You may also have to specify the exposed port, 8080.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Sign in to the administration portal for your Keycloak instance at <https://keycloak.example.com/auth/admin/master/console>
|
||||
1. Sign in to the administration portal for your Keycloak instance at <https://keycloak.example.com/admin/master/console>.
|
||||
|
||||
You may note that a separate realm is specified throughout this tutorial. It is best practice not to use the master realm, as it normally contains the realm-management client that federates access using the policies and permissions you can create.
|
||||
> You may note that a separate realm is specified throughout this tutorial. It is best practice not to use the `master` realm, as it normally contains the realm-management client that federates access using the policies and permissions you can create.
|
||||
|
||||
2. Navigate to the client management page at `https://keycloak.example.com/auth/admin/master/console/#/realms/your-realm/clients` (admin permissions required)
|
||||
3. Click **Create** to create a new client and fill out the registration form. You should set the Root URL to the fully qualified public URL of your HedgeDoc instance.
|
||||
2. Navigate to the client management page at <https://keycloak.example.com/admin/master/console/#/your-realm/clients> (admin permissions required)
|
||||
3. Click **Create** to create a new client and fill out the registration form. You should set the “Root URL” to the fully qualified public URL of your HedgeDoc instance.
|
||||
4. Click **Save**
|
||||
5. Set the **Access Type** of the client to `confidential`. This will make your client require a client secret upon authentication.
|
||||
|
||||
---
|
||||
|
||||
### Additional steps to circumvent generic OAuth2 issue
|
||||
|
||||
@@ -26,25 +26,27 @@ You may note that a separate realm is specified throughout this tutorial. It is
|
||||
3. Create a new mapper under the Mappers tab. This should reference the User Property `id`. `Claim JSON Type` should be String and all switches below should be enabled. Save the mapper.
|
||||
4. Go to the client you set up in the previous steps using the Clients page, then choose the Client Scopes tab. Apply the scope you've created. This should mitigate errors as seen in [hedgedoc/hedgedoc#56](https://github.com/hedgedoc/hedgedoc/issues/56), as the `/userinfo` endpoint should now bring back the user's ID under the `id` key as well as `sub`.
|
||||
|
||||
---
|
||||
## Container Configuration
|
||||
|
||||
5. In the `docker-compose.yml` add the following environment variables to `app:` `environment:`
|
||||
In the `docker-compose.yml` add the following environment variables to `app.environment:`
|
||||
|
||||
```yaml
|
||||
CMD_OAUTH2_USER_PROFILE_URL=https://keycloak.example.com/auth/realms/your-realm/protocol/openid-connect/userinfo
|
||||
CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR=preferred_username
|
||||
CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR=name
|
||||
CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR=email
|
||||
CMD_OAUTH2_TOKEN_URL=https://keycloak.example.com/auth/realms/your-realm/protocol/openid-connect/token
|
||||
CMD_OAUTH2_AUTHORIZATION_URL=https://keycloak.example.com/auth/realms/your-realm/protocol/openid-connect/auth
|
||||
CMD_OAUTH2_CLIENT_ID=<your client ID>
|
||||
CMD_OAUTH2_CLIENT_SECRET=<your client secret, which you can find under the Credentials tab for your client>
|
||||
CMD_OAUTH2_PROVIDERNAME=Keycloak
|
||||
CMD_OAUTH2_SCOPE=openid email profile
|
||||
CMD_DOMAIN=<hedgedoc.example.com>
|
||||
CMD_PROTOCOL_USESSL=true
|
||||
CMD_URL_ADDPORT=false
|
||||
app:
|
||||
environment:
|
||||
- CMD_OAUTH2_USER_PROFILE_URL=https://keycloak.example.com/realms/your-realm/protocol/openid-connect/userinfo
|
||||
- CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR=preferred_username
|
||||
- CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR=name
|
||||
- CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR=email
|
||||
- CMD_OAUTH2_TOKEN_URL=https://keycloak.example.com/realms/your-realm/protocol/openid-connect/token
|
||||
- CMD_OAUTH2_AUTHORIZATION_URL=https://keycloak.example.com/realms/your-realm/protocol/openid-connect/auth
|
||||
- CMD_OAUTH2_CLIENT_ID=<your client ID>
|
||||
- CMD_OAUTH2_CLIENT_SECRET=<your client secret, which you can find under the Credentials tab for your client>
|
||||
- CMD_OAUTH2_PROVIDERNAME=Keycloak
|
||||
- CMD_OAUTH2_SCOPE=openid email profile
|
||||
- CMD_DOMAIN=<hedgedoc.example.com>
|
||||
- CMD_PROTOCOL_USESSL=true
|
||||
- CMD_URL_ADDPORT=false
|
||||
```
|
||||
|
||||
6. Run `docker-compose up -d` to apply your settings.
|
||||
7. Sign in to your HedgeDoc using your Keycloak ID
|
||||
After running `docker-compose up -d` to apply your settings,
|
||||
you should now be able to sign in to your HedgeDoc using your Keycloak.
|
||||
|
||||
@@ -7,8 +7,8 @@ This documentation will cover HTTPS setup, with comments for HTTP setup.
|
||||
|
||||
## Cloudflare
|
||||
!!! warning
|
||||
If you use Cloudflare as reverse proxy then you **MUST** disable the minify features for HTML, CSS and JS, or your HedgeDoc instance may be broken.
|
||||
For more information please read the [Cloudflare documentation](https://support.cloudflare.com/hc/en-us/articles/200168196-How-do-I-minify-HTML-CSS-and-JavaScript-to-optimize-my-site-).
|
||||
If you use Cloudflare as reverse proxy, then you **MUST** disable Rocket Loader, or your HedgeDoc instance may be broken.
|
||||
For more information please read the [Cloudflare documentation](https://developers.cloudflare.com/speed/optimization/content/rocket-loader).
|
||||
|
||||
## HedgeDoc config
|
||||
|
||||
@@ -81,8 +81,9 @@ server {
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
|
||||
listen [::]:443 ssl http2;
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl;
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
ssl_certificate fullchain.pem;
|
||||
ssl_certificate_key privkey.pem;
|
||||
include options-ssl-nginx.conf;
|
||||
@@ -97,6 +98,66 @@ server {
|
||||
connection to the server, and the editor interface will display an endless loading
|
||||
animation.
|
||||
|
||||
|
||||
|
||||
!!! warning
|
||||
|
||||
Starting with NGINX Version [1.25.1](https://nginx.org/en/CHANGES) (released on 13
|
||||
Jun 2023) the `http2`-**parameter** for the `listen`-directive has been deprecated!
|
||||
|
||||
NGINX Version 1.25.1 introduces [`http2` as a standalone directive](https://nginx.org/en/docs/http/ngx_http_v2_module.html)
|
||||
which can be enabled as can be seen in the example above.
|
||||
|
||||
If you are running on an older NGINX version you can delete the `http2 on;`-line and
|
||||
add the `http2`-parameter to both `listen`-directive lines.
|
||||
|
||||
```
|
||||
listen [::]:443 ssl http2;
|
||||
listen 443 ssl http2;
|
||||
```
|
||||
|
||||
!!! information
|
||||
|
||||
If you do not want to expose the `/metrics` and `/status` HTTP-endpoints to the whole
|
||||
internet but you need to (for example) monitor `/metrics` using your Prometheus
|
||||
installation (so disabling `enableStatsApi` in the HedgeDoc config is not a viable
|
||||
option) you can add the following location blocks to your NGINX-server-block to limit
|
||||
access to trusted (monitoring) networks / ip-literals.
|
||||
|
||||
```
|
||||
location /metrics {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
allow 2001:db8::/64;
|
||||
allow 192.0.2.0/24;
|
||||
[...]
|
||||
deny all;
|
||||
}
|
||||
|
||||
location /status {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
allow 2001:db8::/64;
|
||||
allow 192.0.2.0/24;
|
||||
[...]
|
||||
deny all;
|
||||
}
|
||||
```
|
||||
|
||||
While it is certainly not a security issue itself to keep these public to the internet
|
||||
it could give attackers additional information and help them exploit your HedgeDoc installation.
|
||||
|
||||
Therefore if you do not have a monitoring setup (like Prometheus) it's likely you do not
|
||||
need to expose this information at all and can simply set `enableStatsApi` to false (default
|
||||
is true) in your HedgeDoc `config.json`.
|
||||
|
||||
|
||||
### Apache
|
||||
You will need these modules enabled: `proxy`, `proxy_http` and `proxy_wstunnel`.
|
||||
Here is an example config snippet:
|
||||
|
||||
@@ -65,6 +65,14 @@ using the button below. This will run the official Docker image from [quay.io](h
|
||||
|
||||
[](https://www.pikapods.com/pods?run=hedgedoc)
|
||||
|
||||
### SelfPrivacy
|
||||
|
||||
HedgeDoc is available as a 1-click install on [SelfPrivacy](https://selfprivacy.org/). SelfPrivacy app allows you to set up self-hosted services on your automatically configured and managed NixOS-based server. Because HedgeDoc is officialy supported by SelfPrivacy, all you need to do is add it from the Services catalog on the Services page.
|
||||
|
||||
The source code of the module can be found [here](https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config/src/branch/flakes/sp-modules/hedgedoc).
|
||||
|
||||
[Install SelfPrivacy](https://selfprivacy.org/download/)
|
||||
|
||||
## Distribution Packages
|
||||
|
||||
### Arch Linux
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
The official docker images are [available on quay.io](https://quay.io/repository/hedgedoc/hedgedoc).
|
||||
We currently only support the `amd64` architecture.
|
||||
We currently support the `amd64` and `arm64` architectures.
|
||||
|
||||
|
||||
The easiest way to get started with HedgeDoc and Docker is to use the following `docker-compose.yml`:
|
||||
@@ -28,7 +28,7 @@ services:
|
||||
restart: always
|
||||
app:
|
||||
# Make sure to use the latest release from https://hedgedoc.org/latest-release
|
||||
image: quay.io/hedgedoc/hedgedoc:1.10.1
|
||||
image: quay.io/hedgedoc/hedgedoc:1.10.3
|
||||
environment:
|
||||
- CMD_DB_URL=postgres://hedgedoc:password@database:5432/hedgedoc
|
||||
- CMD_DOMAIN=localhost
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
1. Check if you meet the [requirements at the top of this document](#manual-installation).
|
||||
2. Download the [latest release](https://hedgedoc.org/latest-release/) and extract it.
|
||||
<small>Alternatively, you can use Git to clone the repository and checkout a release, e.g. with `git clone -b 1.10.1 https://github.com/hedgedoc/hedgedoc.git`.</small>
|
||||
<small>Alternatively, you can use Git to clone the repository and checkout a release, e.g. with `git clone -b 1.10.3 https://github.com/hedgedoc/hedgedoc.git`.</small>
|
||||
3. Enter the directory and execute `bin/setup`, which will install the dependencies and create example configs.
|
||||
4. Configure HedgeDoc: To get started, you can use this minimal `config.json`:
|
||||
```json
|
||||
@@ -61,7 +61,7 @@ If you want to upgrade HedgeDoc from an older version, follow these steps:
|
||||
and the latest release.
|
||||
2. Fully stop your old HedgeDoc server.
|
||||
3. [Download](https://hedgedoc.org/latest-release/) the new release and extract it over the old directory.
|
||||
<small>If you use Git, you can check out the new tag with e.g. `git fetch origin && git checkout 1.10.1`</small>
|
||||
<small>If you use Git, you can check out the new tag with e.g. `git fetch origin && git checkout 1.10.3`</small>
|
||||
5. Run `bin/setup`. This will take care of installing dependencies. It is safe to run on an existing installation.
|
||||
6. *:octicons-light-bulb-16: If you used the release tarball for 1.7.0 or newer, this step can be skipped.*
|
||||
Build the frontend bundle by running `yarn install --immutable` and `yarn build`. The extra `yarn install --immutable` is necessary as `bin/setup` does not install the build dependencies.
|
||||
@@ -88,6 +88,14 @@ Using the unit file below, you can run HedgeDoc as a systemd service.
|
||||
file in the root directory of the HedgeDoc installation**, but create a subfolder like `db`!
|
||||
- If you use an external database like PostgreSQL or MariaDB, make sure to add a corresponding
|
||||
`After` statement.
|
||||
- `SystemCallFilter=`
|
||||
- More about filtering system calls can be read in the [systemd.exec documentation](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#System%20Call%20Filtering).
|
||||
- If the service does not start please have a look at your systemd-journal (`journalctl -f`) and then try to `systemctl start hedgedoc.service`.
|
||||
- In the systemd-journal you will then see a line with `... kernel: audit: ...`. The important part of this line is `syscall=` (example `syscall=330`).
|
||||
- You can lookup the name of the syscall for the numer on a website like <https://filippo.io/linux-syscall-table/>. Example: 330 is `pkey_alloc`.
|
||||
- Add the name of the syscall at the end of the line of `SystemCallFilter=` (separated by spaces), `systemctl daemon-reload` and then `systemctl restart hedgedoc.service`.
|
||||
- If it does not work have another look at the systemd-journal and repeat the previous steps (add/allow additional needed syscalls).
|
||||
- You can also use groups of syscalls (starting with `@`). See the systemd.exec documentation as it contains a table of `Currently predefined system call sets` you can use. Of course as HedgeDoc is usually exposed to the internet it might be wise to only allow syscalls HedgeDoc really needs depending on your own paranoia. ;-)
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
@@ -125,7 +133,7 @@ ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service
|
||||
SystemCallFilter=@system-service pkey_alloc pkey_mprotect
|
||||
|
||||
# You may have to adjust these settings
|
||||
User=hedgedoc
|
||||
|
||||
@@ -48,6 +48,7 @@ nav:
|
||||
- 'Operational Transformation': dev/ot.md
|
||||
- Webpack: dev/webpack.md
|
||||
- 'Documentation': dev/documentation.md
|
||||
- 'Release Checklist': dev/release_checklist.md
|
||||
- FAQ: faq.md
|
||||
markdown_extensions:
|
||||
- toc:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material==9.6.1
|
||||
pymdown-extensions==10.14.3
|
||||
mkdocs-material==9.7.0
|
||||
pymdown-extensions==10.17.1
|
||||
mdx_truly_sane_lists==1.3
|
||||
mkdocs-redirects==1.2.2
|
||||
|
||||
@@ -32,6 +32,7 @@ module.exports = {
|
||||
rateLimitNewNotes: 20,
|
||||
cookiePolicy: 'lax',
|
||||
protocolUseSSL: false,
|
||||
// permissions
|
||||
allowAnonymous: true,
|
||||
allowAnonymousEdits: false,
|
||||
allowFreeURL: false,
|
||||
@@ -39,6 +40,10 @@ module.exports = {
|
||||
disableNoteCreation: false,
|
||||
forbiddenNoteIDs: ['robots.txt', 'favicon.ico', 'api', 'build', 'css', 'docs', 'fonts', 'js', 'uploads', 'vendor', 'views'],
|
||||
defaultPermission: 'editable',
|
||||
|
||||
enableUploads: undefined, // 'all', 'registered', 'none' are valid options.
|
||||
// This is undefined by default and set during runtime based on allowAnonymous and allowAnonymousEdits for backwards-compatibility unless explicitly set.
|
||||
|
||||
dbURL: '',
|
||||
db: {},
|
||||
// ssl path
|
||||
@@ -160,9 +165,11 @@ module.exports = {
|
||||
requiredGroups: [],
|
||||
attribute: {
|
||||
id: undefined,
|
||||
username: undefined,
|
||||
email: undefined
|
||||
}
|
||||
username: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name',
|
||||
email: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'
|
||||
},
|
||||
wantAssertionsSigned: true,
|
||||
wantAuthnResponseSigned: true
|
||||
},
|
||||
email: true,
|
||||
allowEmailRegister: true,
|
||||
|
||||
@@ -35,6 +35,7 @@ module.exports = {
|
||||
allowFreeURL: toBooleanConfig(process.env.CMD_ALLOW_FREEURL),
|
||||
requireFreeURLAuthentication: toBooleanConfig(process.env.CMD_REQUIRE_FREEURL_AUTHENTICATION),
|
||||
disableNoteCreation: toBooleanConfig(process.env.CMD_DISABLE_NOTE_CREATION),
|
||||
enableUploads: process.env.CMD_ENABLE_UPLOADS,
|
||||
forbiddenNoteIDs: toArrayConfig(process.env.CMD_FORBIDDEN_NOTE_IDS),
|
||||
defaultPermission: process.env.CMD_DEFAULT_PERMISSION,
|
||||
dbURL: process.env.CMD_DB_URL,
|
||||
@@ -146,6 +147,8 @@ module.exports = {
|
||||
issuer: process.env.CMD_SAML_ISSUER,
|
||||
identifierFormat: process.env.CMD_SAML_IDENTIFIERFORMAT,
|
||||
disableRequestedAuthnContext: toBooleanConfig(process.env.CMD_SAML_DISABLEREQUESTEDAUTHNCONTEXT),
|
||||
wantAssertionsSigned: toBooleanConfig(process.env.CMD_SAML_WANT_ASSERTIONS_SIGNED),
|
||||
wantAuthnResponseSigned: toBooleanConfig(process.env.CMD_SAML_WANT_AUTHN_RESPONSE_SIGNED),
|
||||
groupAttribute: process.env.CMD_SAML_GROUPATTRIBUTE,
|
||||
externalGroups: toArrayConfig(process.env.CMD_SAML_EXTERNALGROUPS, '|', []),
|
||||
requiredGroups: toArrayConfig(process.env.CMD_SAML_REQUIREDGROUPS, '|', []),
|
||||
|
||||
@@ -79,6 +79,17 @@ if (!config.allowAnonymous && !config.allowAnonymousEdits) {
|
||||
if (!(config.defaultPermission in config.permission)) {
|
||||
config.defaultPermission = config.permission.editable
|
||||
}
|
||||
if (config.enableUploads === undefined) {
|
||||
if (!config.allowAnonymousEdits && !config.allowAnonymous) {
|
||||
config.enableUploads = 'registered'
|
||||
} else {
|
||||
config.enableUploads = 'all'
|
||||
}
|
||||
}
|
||||
if (!['all', 'registered', 'none'].includes(config.enableUploads)) {
|
||||
logger.error('Config option "enableUploads"/CMD_ENABLE_UPLOADS is not correctly set. Please use "all", "registered" or "none". Defaulting to "all"')
|
||||
config.enableUploads = 'all'
|
||||
}
|
||||
|
||||
// Use HTTPS protocol if the internal TLS server is enabled
|
||||
if (config.useSSL === true) {
|
||||
@@ -164,6 +175,10 @@ if (['filesystem', 's3', 'minio', 'imgur', 'azure', 'lutim'].indexOf(config.imag
|
||||
config.imageUploadType = 'filesystem'
|
||||
}
|
||||
|
||||
if (config.isSAMLEnable && !config.saml.wantAssertionsSigned && !config.saml.wantAuthnResponseSigned) {
|
||||
logger.error('You can only deactivate one of "saml.wantAssertionsSigned" and "saml.wantAuthnResponseSigned"')
|
||||
}
|
||||
|
||||
// figure out mime types for image uploads
|
||||
switch (config.imageUploadType) {
|
||||
case 'imgur':
|
||||
|
||||
@@ -7,21 +7,18 @@ const CspStrategy = {}
|
||||
const defaultDirectives = {
|
||||
defaultSrc: ['\'none\''],
|
||||
baseUri: ['\'self\''],
|
||||
connectSrc: ['\'self\'', buildDomainOriginWithProtocol(config, 'ws')],
|
||||
connectSrc: ['\'self\'', buildDomainOriginWithProtocol(config, 'ws'), 'https://vimeo.com/api/v2/video/'],
|
||||
fontSrc: ['\'self\''],
|
||||
manifestSrc: ['\'self\''],
|
||||
frameSrc: ['\'self\'', 'https://player.vimeo.com', 'https://www.slideshare.net/slideshow/embed_code/key/', 'https://www.youtube.com'],
|
||||
frameSrc: ['\'self\'', 'https://player.vimeo.com', 'https://www.youtube.com', 'https://gist.github.com'],
|
||||
imgSrc: ['*', 'data:'], // we allow using arbitrary images & explicit data for mermaid
|
||||
scriptSrc: [
|
||||
config.serverURL + '/build/',
|
||||
config.serverURL + '/js/',
|
||||
config.serverURL + '/config',
|
||||
'https://gist.github.com/',
|
||||
'https://vimeo.com/api/oembed.json',
|
||||
'https://www.slideshare.net/api/oembed/2',
|
||||
'\'unsafe-inline\'' // this is ignored by browsers supporting nonces/hashes
|
||||
],
|
||||
styleSrc: [config.serverURL + '/build/', config.serverURL + '/css/', '\'unsafe-inline\'', 'https://github.githubassets.com'], // unsafe-inline is required for some libs, plus used in views
|
||||
styleSrc: [config.serverURL + '/build/', config.serverURL + '/css/', '\'unsafe-inline\''], // unsafe-inline is required for some libs, plus used in views
|
||||
objectSrc: ['*'], // Chrome PDF viewer treats PDFs as objects :/
|
||||
formAction: ['\'self\''],
|
||||
mediaSrc: ['*']
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
'use strict'
|
||||
// external modules
|
||||
const crypto = require('crypto')
|
||||
const randomcolor = require('randomcolor')
|
||||
const Chance = require('chance')
|
||||
const config = require('./config')
|
||||
|
||||
// core
|
||||
exports.generateAvatar = function (name) {
|
||||
const color = randomcolor({
|
||||
seed: name,
|
||||
luminosity: 'dark'
|
||||
// use darker colors for better contrast
|
||||
const color = new Chance(name).color({
|
||||
max_red: 150,
|
||||
max_green: 150,
|
||||
max_blue: 150
|
||||
})
|
||||
const letter = name.substring(0, 1).toUpperCase()
|
||||
|
||||
|
||||
@@ -7,13 +7,12 @@ const base64url = require('base64url')
|
||||
const md = require('markdown-it')()
|
||||
const metaMarked = require('@hedgedoc/meta-marked')
|
||||
const cheerio = require('cheerio')
|
||||
const shortId = require('shortid')
|
||||
const nanoid = require('nanoid')
|
||||
const Sequelize = require('sequelize')
|
||||
const async = require('async')
|
||||
const moment = require('moment')
|
||||
const DiffMatchPatch = require('diff-match-patch')
|
||||
const dmp = new DiffMatchPatch()
|
||||
const S = require('string')
|
||||
|
||||
// core
|
||||
const config = require('../config')
|
||||
@@ -37,7 +36,7 @@ module.exports = function (sequelize, DataTypes) {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
defaultValue: shortId.generate
|
||||
defaultValue: () => nanoid.nanoid(10)
|
||||
},
|
||||
alias: {
|
||||
type: DataTypes.STRING,
|
||||
@@ -297,8 +296,12 @@ module.exports = function (sequelize, DataTypes) {
|
||||
parseNoteIdByShortId: function (_callback) {
|
||||
// try to parse note id by shortId
|
||||
try {
|
||||
if (shortId.isValid(noteId)) {
|
||||
// old short ids generated by the `shortid` package could be from 7 to 14 characters long
|
||||
// new ones generated by the `nanoid` package are always 10 characters long
|
||||
if (noteId && noteId.length >= 7 && noteId.length <= 14) {
|
||||
Note.findOne({
|
||||
// MariaDB and MySQL do case-insensitive comparison by default (unless a collation charset like utf8mb4 is used)
|
||||
// The binary conversion ensures, case-sensitive comparison.
|
||||
where: utils.isMySQL(sequelize)
|
||||
? sequelize.where(sequelize.fn('BINARY', sequelize.col('shortid')), noteId)
|
||||
: {
|
||||
@@ -344,7 +347,9 @@ module.exports = function (sequelize, DataTypes) {
|
||||
title = meta.title
|
||||
} else {
|
||||
const h1s = $('h1')
|
||||
if (h1s.length > 0 && h1s.first().text().split('\n').length === 1) { title = S(h1s.first().text()).stripTags().s }
|
||||
if (h1s.length > 0 && h1s.first().text().split('\n').length === 1) {
|
||||
title = h1s.first().text().trim()
|
||||
}
|
||||
}
|
||||
if (!title) title = 'Untitled'
|
||||
return title
|
||||
@@ -374,7 +379,7 @@ module.exports = function (sequelize, DataTypes) {
|
||||
if (/^tags/gmi.test($(value).text())) {
|
||||
const codes = $(value).find('code')
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const text = S($(codes[i]).text().trim()).stripTags().s
|
||||
const text = $(codes[i]).text().trim()
|
||||
if (text) rawtags.push(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ const Sequelize = require('sequelize')
|
||||
const async = require('async')
|
||||
const moment = require('moment')
|
||||
const childProcess = require('child_process')
|
||||
const shortId = require('shortid')
|
||||
const nanoid = require('nanoid')
|
||||
const path = require('path')
|
||||
|
||||
const Op = Sequelize.Op
|
||||
@@ -44,7 +44,7 @@ function createDmpWorker () {
|
||||
|
||||
function sendDmpWorker (data, callback) {
|
||||
if (!dmpWorker) dmpWorker = createDmpWorker()
|
||||
const cacheKey = Date.now() + '_' + shortId.generate()
|
||||
const cacheKey = Date.now() + '_' + nanoid.nanoid()
|
||||
dmpCallbackCache[cacheKey] = callback
|
||||
data = Object.assign(data, {
|
||||
cacheKey
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
'use strict'
|
||||
// external modules
|
||||
const shortId = require('shortid')
|
||||
const nanoid = require('nanoid')
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const Temp = sequelize.define('Temp', {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
defaultValue: shortId.generate
|
||||
defaultValue: nanoid.nanoid
|
||||
},
|
||||
data: {
|
||||
type: DataTypes.TEXT
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
const cookie = require('cookie')
|
||||
const cookieParser = require('cookie-parser')
|
||||
const async = require('async')
|
||||
const randomcolor = require('randomcolor')
|
||||
const Chance = require('chance')
|
||||
const chance = new Chance()
|
||||
const moment = require('moment')
|
||||
@@ -178,9 +177,9 @@ function finishUpdateNote (note, _note, callback) {
|
||||
// clean when user not in any rooms or user not in connected list
|
||||
setInterval(function () {
|
||||
async.each(Object.keys(users), function (key, callback) {
|
||||
let socket = realtime.io.sockets.connected[key]
|
||||
let socket = realtime.io.sockets.sockets.get(key)
|
||||
if ((!socket && users[key]) ||
|
||||
(socket && (!socket.rooms || socket.rooms.length <= 0))) {
|
||||
(socket && (!socket.rooms || socket.rooms.size <= 0))) {
|
||||
logger.debug(`cleaner found redundant user: ${key}`)
|
||||
if (!socket) {
|
||||
socket = {
|
||||
@@ -711,7 +710,7 @@ function connection (socket) {
|
||||
|
||||
// initialize user data
|
||||
// random color
|
||||
let color = randomcolor()
|
||||
let color = chance.color()
|
||||
// make sure color not duplicated or reach max random count
|
||||
if (notes[noteId]) {
|
||||
let randomcount = 0
|
||||
@@ -724,7 +723,7 @@ function connection (socket) {
|
||||
}
|
||||
})
|
||||
if (found) {
|
||||
color = randomcolor()
|
||||
color = chance.color()
|
||||
randomcount++
|
||||
}
|
||||
} while (found && randomcount < maxrandomcount)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// external modules
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const fetch = require('node-fetch')
|
||||
// core
|
||||
const config = require('./config')
|
||||
const logger = require('./logger')
|
||||
|
||||
@@ -62,6 +62,11 @@ function parseProfile (data) {
|
||||
const displayName = extractProfileAttribute(data, config.oauth2.userProfileDisplayNameAttr)
|
||||
const email = extractProfileAttribute(data, config.oauth2.userProfileEmailAttr)
|
||||
|
||||
if (id === undefined && username === undefined) {
|
||||
logger.error('oauth2 auth failed: id and username are undefined')
|
||||
throw new Error('User ID or Username required')
|
||||
}
|
||||
|
||||
return {
|
||||
id: id || username,
|
||||
username,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const Router = require('express').Router
|
||||
const passport = require('passport')
|
||||
const SamlStrategy = require('passport-saml').Strategy
|
||||
const SamlStrategy = require('@node-saml/passport-saml').Strategy
|
||||
const config = require('../../../config')
|
||||
const models = require('../../../models')
|
||||
const logger = require('../../../logger')
|
||||
@@ -12,98 +12,123 @@ const intersection = function (array1, array2) { return array1.filter((n) => arr
|
||||
|
||||
const samlAuth = module.exports = Router()
|
||||
|
||||
passport.use(new SamlStrategy({
|
||||
callbackUrl: config.serverURL + '/auth/saml/callback',
|
||||
entryPoint: config.saml.idpSsoUrl,
|
||||
issuer: config.saml.issuer || config.serverURL,
|
||||
privateKey: config.saml.clientCert === undefined
|
||||
? undefined
|
||||
: (function () {
|
||||
passport.use(
|
||||
new SamlStrategy(
|
||||
{
|
||||
callbackUrl: config.serverURL + '/auth/saml/callback',
|
||||
entryPoint: config.saml.idpSsoUrl,
|
||||
issuer: config.saml.issuer || config.serverURL,
|
||||
privateKey: config.saml.clientCert === undefined
|
||||
? undefined
|
||||
: (function () {
|
||||
try {
|
||||
return fs.readFileSync(config.saml.clientCert, 'utf-8')
|
||||
} catch (e) {
|
||||
logger.error(`SAML client certificate: ${e.message}`)
|
||||
}
|
||||
}()),
|
||||
idpCert: (function () {
|
||||
try {
|
||||
return fs.readFileSync(config.saml.clientCert, 'utf-8')
|
||||
return fs.readFileSync(config.saml.idpCert, 'utf-8')
|
||||
} catch (e) {
|
||||
logger.error(`SAML client certificate: ${e.message}`)
|
||||
logger.error(`SAML idp certificate: ${e.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}()),
|
||||
cert: (function () {
|
||||
try {
|
||||
return fs.readFileSync(config.saml.idpCert, 'utf-8')
|
||||
} catch (e) {
|
||||
logger.error(`SAML idp certificate: ${e.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}()),
|
||||
identifierFormat: config.saml.identifierFormat,
|
||||
disableRequestedAuthnContext: config.saml.disableRequestedAuthnContext
|
||||
}, function (user, done) {
|
||||
// check authorization if needed
|
||||
if (config.saml.externalGroups && config.saml.groupAttribute) {
|
||||
const externalGroups = intersection(config.saml.externalGroups, user[config.saml.groupAttribute])
|
||||
if (externalGroups.length > 0) {
|
||||
logger.error('saml permission denied: ' + externalGroups.join(', '))
|
||||
return done('Permission denied', null)
|
||||
}
|
||||
}
|
||||
if (config.saml.requiredGroups && config.saml.groupAttribute) {
|
||||
if (intersection(config.saml.requiredGroups, user[config.saml.groupAttribute]).length === 0) {
|
||||
logger.error('saml permission denied')
|
||||
return done('Permission denied', null)
|
||||
}
|
||||
}
|
||||
// user creation
|
||||
const uuid = user[config.saml.attribute.id] || user.nameID
|
||||
const profile = {
|
||||
provider: 'saml',
|
||||
id: 'SAML-' + uuid,
|
||||
username: user[config.saml.attribute.username] || user.nameID,
|
||||
emails: user[config.saml.attribute.email] ? [user[config.saml.attribute.email]] : []
|
||||
}
|
||||
if (profile.emails.length === 0 && config.saml.identifierFormat === 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress') {
|
||||
profile.emails.push(user.nameID)
|
||||
}
|
||||
const stringifiedProfile = JSON.stringify(profile)
|
||||
models.User.findOrCreate({
|
||||
where: {
|
||||
profileid: profile.id.toString()
|
||||
identifierFormat: config.saml.identifierFormat,
|
||||
disableRequestedAuthnContext: config.saml.disableRequestedAuthnContext,
|
||||
wantAssertionsSigned: config.saml.wantAssertionsSigned,
|
||||
wantAuthnResponseSigned: config.saml.wantAuthnResponseSigned
|
||||
},
|
||||
defaults: {
|
||||
profile: stringifiedProfile
|
||||
}
|
||||
}).spread(function (user, created) {
|
||||
if (user) {
|
||||
let needSave = false
|
||||
if (user.profile !== stringifiedProfile) {
|
||||
user.profile = stringifiedProfile
|
||||
needSave = true
|
||||
// sign-in
|
||||
function (user, done) {
|
||||
// check authorization if needed
|
||||
if (config.saml.externalGroups && config.saml.groupAttribute) {
|
||||
const externalGroups = intersection(config.saml.externalGroups, user[config.saml.groupAttribute])
|
||||
if (externalGroups.length > 0) {
|
||||
logger.error('saml permission denied: ' + externalGroups.join(', '))
|
||||
return done('Permission denied', null)
|
||||
}
|
||||
}
|
||||
if (needSave) {
|
||||
user.save().then(function () {
|
||||
logger.debug(`user login: ${user.id}`)
|
||||
return done(null, user)
|
||||
})
|
||||
} else {
|
||||
logger.debug(`user login: ${user.id}`)
|
||||
return done(null, user)
|
||||
if (config.saml.requiredGroups && config.saml.groupAttribute) {
|
||||
if (intersection(config.saml.requiredGroups, user[config.saml.groupAttribute]).length === 0) {
|
||||
logger.error('saml permission denied')
|
||||
return done('Permission denied', null)
|
||||
}
|
||||
}
|
||||
// user creation
|
||||
const uuid = user[config.saml.attribute.id] || user.nameID
|
||||
if (!uuid) {
|
||||
logger.error('saml auth failed: id not found')
|
||||
return done('Permission denied', null)
|
||||
}
|
||||
const profile = {
|
||||
provider: 'saml',
|
||||
id: 'SAML-' + uuid,
|
||||
username: user[config.saml.attribute.username] || user.nameID,
|
||||
emails: user[config.saml.attribute.email] ? [user[config.saml.attribute.email]] : config.saml.identifierFormat === 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' ? [user.nameID] : []
|
||||
}
|
||||
const stringifiedProfile = JSON.stringify(profile)
|
||||
models.User.findOrCreate({
|
||||
where: {
|
||||
profileid: profile.id.toString()
|
||||
},
|
||||
defaults: {
|
||||
profile: stringifiedProfile
|
||||
}
|
||||
}).spread(function (user, created) {
|
||||
if (user) {
|
||||
let needSave = false
|
||||
if (user.profile !== stringifiedProfile) {
|
||||
user.profile = stringifiedProfile
|
||||
needSave = true
|
||||
}
|
||||
if (needSave) {
|
||||
user.save().then(function () {
|
||||
logger.debug(`user login: ${user.id}`)
|
||||
return done(null, user)
|
||||
})
|
||||
} else {
|
||||
logger.debug(`user login: ${user.id}`)
|
||||
return done(null, user)
|
||||
}
|
||||
}
|
||||
}).catch(function (err) {
|
||||
logger.error('saml auth failed: ' + err.message)
|
||||
return done(err, null)
|
||||
})
|
||||
},
|
||||
// logout
|
||||
function (profile, done) {
|
||||
return done(null, profile)
|
||||
}
|
||||
}).catch(function (err) {
|
||||
logger.error('saml auth failed: ' + err.message)
|
||||
return done(err, null)
|
||||
})
|
||||
}))
|
||||
)
|
||||
)
|
||||
|
||||
samlAuth.get('/auth/saml',
|
||||
passport.authenticate('saml', {
|
||||
successReturnToOrRedirect: config.serverURL + '/',
|
||||
failureRedirect: config.serverURL + '/'
|
||||
})
|
||||
failureRedirect: config.serverURL + '/',
|
||||
failureFlash: true
|
||||
}),
|
||||
function (req, res) {
|
||||
res.redirect('/')
|
||||
}
|
||||
)
|
||||
|
||||
samlAuth.post('/auth/saml/callback', urlencodedParser,
|
||||
samlAuth.use('/auth/saml/callback', urlencodedParser,
|
||||
function (req, res, next) {
|
||||
if (req.method !== 'GET' && req.method !== 'POST') {
|
||||
return res.status(405).end()
|
||||
}
|
||||
return next()
|
||||
},
|
||||
passport.authenticate('saml', {
|
||||
successReturnToOrRedirect: config.serverURL + '/',
|
||||
failureRedirect: config.serverURL + '/'
|
||||
})
|
||||
}),
|
||||
function (req, res) {
|
||||
res.redirect('/')
|
||||
}
|
||||
)
|
||||
|
||||
samlAuth.get('/auth/saml/metadata', function (req, res) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
const config = require('../../config')
|
||||
const logger = require('../../logger')
|
||||
const fs = require('fs')
|
||||
const fetch = require('node-fetch')
|
||||
|
||||
exports.uploadImage = function (imagePath, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
|
||||
@@ -57,13 +57,17 @@ async function checkUploadType (filePath) {
|
||||
|
||||
// upload image
|
||||
imageRouter.post('/uploadimage', function (req, res) {
|
||||
const uploadsEnabled = config.enableUploads
|
||||
if (uploadsEnabled === 'none') {
|
||||
logger.error('Image upload error: Uploads are disabled')
|
||||
return errors.errorForbidden(res)
|
||||
}
|
||||
if (
|
||||
!req.isAuthenticated() &&
|
||||
!config.allowAnonymous &&
|
||||
!config.allowAnonymousEdits
|
||||
uploadsEnabled === 'registered' &&
|
||||
!req.isAuthenticated()
|
||||
) {
|
||||
logger.error(
|
||||
'Image upload error: Anonymous edits and therefore uploads are not allowed'
|
||||
'Image upload error: Anonymous uploads are not allowed'
|
||||
)
|
||||
return errors.errorForbidden(res)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const { rateLimit } = require('express-rate-limit')
|
||||
const { rateLimit, ipKeyGenerator } = require('express-rate-limit')
|
||||
const errors = require('../../errors')
|
||||
const config = require('../../config')
|
||||
|
||||
@@ -8,7 +8,7 @@ const determineKey = (req) => {
|
||||
if (req.user) {
|
||||
return req.user.id
|
||||
}
|
||||
return req.header('cf-connecting-ip') || req.ip
|
||||
return ipKeyGenerator(req.header('cf-connecting-ip') || req.ip)
|
||||
}
|
||||
|
||||
// limits requests to user endpoints (login, signup) to 10 requests per 5 minutes
|
||||
|
||||
@@ -8,6 +8,11 @@ const config = require('../../config')
|
||||
toobusy.maxLag(config.tooBusyLag)
|
||||
|
||||
module.exports = function (req, res, next) {
|
||||
// We dont want to return "toobusy" errors for healthchecks, as that
|
||||
// will cause the process to be restarted
|
||||
if (req.baseUrl === '/_health') {
|
||||
next()
|
||||
}
|
||||
if (toobusy()) {
|
||||
errors.errorServiceUnavailable(res)
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,7 @@ const models = require('../../models')
|
||||
const logger = require('../../logger')
|
||||
const config = require('../../config')
|
||||
const errors = require('../../errors')
|
||||
const shortId = require('shortid')
|
||||
const nanoid = require('nanoid')
|
||||
const moment = require('moment')
|
||||
const querystring = require('querystring')
|
||||
|
||||
@@ -36,7 +36,7 @@ exports.createGist = function createGist (req, res, note) {
|
||||
client_id: config.github.clientID,
|
||||
redirect_uri: config.serverURL + '/auth/github/callback/' + models.Note.encodeNoteId(note.id) + '/gist',
|
||||
scope: 'gist',
|
||||
state: shortId.generate()
|
||||
state: nanoid.nanoid()
|
||||
}
|
||||
const query = querystring.stringify(data)
|
||||
res.redirect('https://github.com/login/oauth/authorize?' + query)
|
||||
|
||||
@@ -111,7 +111,8 @@ statusRouter.get('/config', function (req, res) {
|
||||
DROPBOX_APP_KEY: config.dropbox.appKey,
|
||||
allowedUploadMimeTypes: config.allowedUploadMimeTypes,
|
||||
linkifyHeaderStyle: config.linkifyHeaderStyle,
|
||||
cookiePolicy: config.cookiePolicy
|
||||
cookiePolicy: config.cookiePolicy,
|
||||
enableUploads: config.enableUploads
|
||||
}
|
||||
res.set({
|
||||
'Cache-Control': 'private', // only cache by client
|
||||
|
||||
101
package.json
101
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "HedgeDoc",
|
||||
"version": "1.10.1",
|
||||
"name": "the_hedgedoc_elf",
|
||||
"version": "1.10.3_LailaTheElf",
|
||||
"description": "The best platform to write and share markdown.",
|
||||
"main": "app.js",
|
||||
"license": "AGPL-3.0",
|
||||
@@ -18,40 +18,40 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hedgedoc/meta-marked": "14.1.0",
|
||||
"@node-saml/passport-saml": "5.0.0",
|
||||
"@node-saml/passport-saml": "5.1.0",
|
||||
"@passport-next/passport-openid": "1.0.0",
|
||||
"Idle.Js": "git+https://github.com/shawnmclean/Idle.js#commit=2b57cc6e49d177b7ddce0cca00ef5cbe07453541",
|
||||
"archiver": "6.0.2",
|
||||
"archiver": "7.0.1",
|
||||
"async": "3.2.6",
|
||||
"aws-sdk": "2.1692.0",
|
||||
"azure-storage": "2.10.7",
|
||||
"base64url": "3.0.1",
|
||||
"body-parser": "1.20.3",
|
||||
"chance": "1.1.12",
|
||||
"body-parser": "2.2.1",
|
||||
"chance": "1.1.13",
|
||||
"cheerio": "0.22.0",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"compression": "1.7.5",
|
||||
"compression": "1.8.1",
|
||||
"connect-flash": "0.1.1",
|
||||
"connect-session-sequelize": "7.1.7",
|
||||
"connect-session-sequelize": "8.0.2",
|
||||
"cookie": "1.0.2",
|
||||
"cookie-parser": "1.4.7",
|
||||
"deep-freeze": "0.0.1",
|
||||
"diff-match-patch": "git+https://github.com/hackmdio/diff-match-patch.git#commit=59a9395ad9fe143e601e7ae5765ed943bdd2b11e",
|
||||
"ejs": "3.1.10",
|
||||
"express": "4.21.2",
|
||||
"express-rate-limit": "7.5.0",
|
||||
"express-session": "1.18.1",
|
||||
"file-type": "20.0.1",
|
||||
"formidable": "2.1.2",
|
||||
"express-rate-limit": "8.2.1",
|
||||
"express-session": "1.18.2",
|
||||
"file-type": "21.1.1",
|
||||
"formidable": "3.5.4",
|
||||
"graceful-fs": "4.2.11",
|
||||
"helmet": "8.0.0",
|
||||
"i18n": "0.15.1",
|
||||
"helmet": "8.1.0",
|
||||
"i18n": "0.15.3",
|
||||
"is-svg": "4.4.0",
|
||||
"jsdom-nogyp": "0.8.3",
|
||||
"lodash": "4.17.21",
|
||||
"lutim": "1.0.3",
|
||||
"lz-string": "git+https://github.com/hackmdio/lz-string.git#commit=6edfccb79cd8c210f03fd3bf18e41ca144fbeefb",
|
||||
"mariadb": "3.4.0",
|
||||
"mariadb": "3.4.5",
|
||||
"markdown-it": "13.0.2",
|
||||
"markdown-it-abbr": "1.0.4",
|
||||
"markdown-it-container": "3.0.0",
|
||||
@@ -68,11 +68,11 @@
|
||||
"mattermost": "3.4.0",
|
||||
"method-override": "3.0.0",
|
||||
"minimist": "1.2.8",
|
||||
"minio": "7.1.3",
|
||||
"minio": "8.0.6",
|
||||
"moment": "2.30.1",
|
||||
"morgan": "1.10.0",
|
||||
"mysql2": "3.12.0",
|
||||
"node-fetch": "2.7.0",
|
||||
"morgan": "1.10.1",
|
||||
"mysql2": "3.15.3",
|
||||
"nanoid": "3.3.11",
|
||||
"passport": "patch:passport@npm%3A0.7.0#~/.yarn/patches/passport-npm-0.7.0-df02531736.patch",
|
||||
"passport-dropbox-oauth2": "1.1.0",
|
||||
"passport-facebook": "3.0.0",
|
||||
@@ -84,33 +84,30 @@
|
||||
"passport-oauth2": "1.8.0",
|
||||
"passport-twitter": "1.0.4",
|
||||
"passport.socketio": "3.7.0",
|
||||
"pdfobject": "2.3.0",
|
||||
"pg": "8.13.1",
|
||||
"pdfobject": "2.3.1",
|
||||
"pg": "8.16.3",
|
||||
"pg-hstore": "2.3.4",
|
||||
"prom-client": "15.1.3",
|
||||
"prometheus-api-metrics": "3.2.2",
|
||||
"randomcolor": "0.6.2",
|
||||
"prometheus-api-metrics": "4.0.0",
|
||||
"readline-sync": "1.4.10",
|
||||
"rimraf": "5.0.10",
|
||||
"rimraf": "6.1.2",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"scrypt-kdf": "2.0.1",
|
||||
"sequelize": "5.22.5",
|
||||
"shortid": "2.2.17",
|
||||
"socket.io": "2.5.1",
|
||||
"socket.io": "4.8.1",
|
||||
"sqlite3": "5.1.7",
|
||||
"store": "2.0.12",
|
||||
"string": "3.3.3",
|
||||
"toobusy-js": "0.5.1",
|
||||
"umzug": "2.3.0",
|
||||
"uuid": "11.0.5",
|
||||
"validator": "13.12.0",
|
||||
"winston": "3.17.0",
|
||||
"uuid": "11.1.0",
|
||||
"validator": "13.15.23",
|
||||
"winston": "3.18.3",
|
||||
"xss": "1.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"bugs": "https://github.com/hedgedoc/hedgedoc/issues",
|
||||
"bugs": "https://gitea.finnvanreenen.nl/LailaTheElf/hedgedoc/issues",
|
||||
"keywords": [
|
||||
"Collaborative",
|
||||
"Markdown",
|
||||
@@ -118,6 +115,10 @@
|
||||
],
|
||||
"homepage": "https://hedgedoc.org",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "LailaTheElf",
|
||||
"email": "mail@lailatheelf.nl"
|
||||
},
|
||||
{
|
||||
"name": "Claudius Coenen",
|
||||
"url": "https://www.claudiuscoenen.de/"
|
||||
@@ -134,13 +135,13 @@
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hedgedoc/hedgedoc.git"
|
||||
"url": "https://gitea.finnvanreenen.nl/LailaTheElf/hedgedoc.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "3.2.0",
|
||||
"@eslint/js": "9.19.0",
|
||||
"@eslint/eslintrc": "3.3.1",
|
||||
"@eslint/js": "9.39.1",
|
||||
"@hedgedoc/codemirror-5": "5.65.12",
|
||||
"abcjs": "6.4.4",
|
||||
"abcjs": "6.5.2",
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-core": "6.26.3",
|
||||
"babel-loader": "7.1.5",
|
||||
@@ -153,12 +154,11 @@
|
||||
"copy-webpack-plugin": "6.4.1",
|
||||
"css-loader": "5.2.7",
|
||||
"emojify.js": "1.1.0",
|
||||
"esbuild-loader": "4.2.2",
|
||||
"escape-html": "1.0.3",
|
||||
"eslint": "9.19.0",
|
||||
"esbuild-loader": "4.4.0",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-standard": "17.1.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-n": "17.15.1",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-n": "17.23.1",
|
||||
"eslint-plugin-promise": "7.2.1",
|
||||
"eslint-plugin-standard": "5.0.0",
|
||||
"exports-loader": "1.1.1",
|
||||
@@ -167,39 +167,38 @@
|
||||
"file-saver": "2.0.5",
|
||||
"flowchart.js": "1.18.0",
|
||||
"fork-awesome": "1.2.0",
|
||||
"gist-embed": "2.6.0",
|
||||
"globals": "15.14.0",
|
||||
"globals": "16.5.0",
|
||||
"highlight.js": "10.7.3",
|
||||
"html-webpack-plugin": "4.5.2",
|
||||
"imports-loader": "1.2.0",
|
||||
"ionicons": "2.0.1",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-mousewheel": "3.1.13",
|
||||
"jquery-mousewheel": "3.2.2",
|
||||
"jquery-ui": "1.14.1",
|
||||
"js-cookie": "3.0.5",
|
||||
"js-sequence-diagrams": "git+https://github.com/hedgedoc/js-sequence-diagrams.git",
|
||||
"js-yaml": "3.14.1",
|
||||
"js-yaml": "3.14.2",
|
||||
"jsonlint": "1.6.3",
|
||||
"keymaster": "1.6.2",
|
||||
"less": "4.2.2",
|
||||
"less": "4.4.2",
|
||||
"less-loader": "7.3.0",
|
||||
"list.js": "2.3.1",
|
||||
"mathjax": "2.7.9",
|
||||
"mermaid": "9.1.7",
|
||||
"mermaid": "11.4.1",
|
||||
"mini-css-extract-plugin": "1.6.2",
|
||||
"mocha": "11.1.0",
|
||||
"mocha": "11.7.5",
|
||||
"mock-require": "3.0.3",
|
||||
"optimize-css-assets-webpack-plugin": "6.0.1",
|
||||
"prismjs": "1.29.0",
|
||||
"prismjs": "1.30.0",
|
||||
"raphael": "2.3.0",
|
||||
"remark-cli": "12.0.1",
|
||||
"remark-preset-lint-markdown-style-guide": "5.1.3",
|
||||
"reveal.js": "3.9.2",
|
||||
"select2": "3.5.2-browserify",
|
||||
"socket.io-client": "2.5.0",
|
||||
"socket.io-client": "4.8.1",
|
||||
"spin.js": "4.1.2",
|
||||
"string-loader": "0.0.1",
|
||||
"turndown": "7.2.0",
|
||||
"turndown": "7.2.2",
|
||||
"url-loader": "4.1.1",
|
||||
"velocity-animate": "1.5.2",
|
||||
"visibilityjs": "2.0.2",
|
||||
@@ -213,5 +212,5 @@
|
||||
"bufferutil": "4.0.9",
|
||||
"utf-8-validate": "6.0.5"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0"
|
||||
"packageManager": "yarn@4.12.0"
|
||||
}
|
||||
|
||||
@@ -63,6 +63,11 @@
|
||||
-webkit-transition: opacity 0.2s; /* Safari */
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
iframe.github-gist-frame {
|
||||
width: 100%;
|
||||
border: none;
|
||||
height: 32rem;
|
||||
}
|
||||
|
||||
.slideshare .inner,
|
||||
.speakerdeck .inner {
|
||||
|
||||
@@ -238,10 +238,6 @@ When you’re a carpenter making a beautiful chest of drawers, you’re not goin
|
||||
|
||||
{%gist schacon/4277%}
|
||||
|
||||
#### SlideShare
|
||||
|
||||
{%slideshare briansolis/26-disruptive-technology-trends-2016-2018-56796196 %}
|
||||
|
||||
#### PDF
|
||||
|
||||
**Caution: this might be blocked by your browser if not using an `https` URL.**
|
||||
|
||||
@@ -2,7 +2,41 @@
|
||||
|
||||
## <i class="fa fa-tag"></i> 1.x.x <i class="fa fa-calendar-o"></i> UNRELEASED
|
||||
|
||||
## <i class="fa fa-tag"></i> 1.10.1 <i class="fa fa-calendar-o"></i> 2024-02-02
|
||||
### Enhancements
|
||||
- Add `enableUploads` (`CMD_ENABLE_UPLOADS`) config option to restrict uploads to `registered` users, `all` users or
|
||||
`none` to completely disable uploads.
|
||||
- Allow links to protocols such as xmpp, webcal or geo
|
||||
- Switch from deprecated shortid to nanoid module, with 10 character long aliases in "public" links
|
||||
|
||||
### Bugfixes
|
||||
- Ignore the healthcheck endpoint in the "too busy" limiter
|
||||
|
||||
## <i class="fa fa-tag"></i> 1.10.3 <i class="fa fa-calendar-o"></i> 2025-04-09
|
||||
|
||||
### Security fixes
|
||||
|
||||
This release fixes a security issue of a possible XSS exploit which can be planted via a malicous SVG file upload.
|
||||
|
||||
See [GHSA-3983-rrqh-mvx5](https://github.com/hedgedoc/hedgedoc/security/advisories/GHSA-3983-rrqh-mvx5) for more details
|
||||
|
||||
### Enhancements
|
||||
- Add config options `CMD_SAML_WANT_ASSERTIONS_SIGNED` and `CMD_SAML_WANT_AUTHN_RESPONSE_SIGNED` for SAML auth, since
|
||||
some instances didn't comply with the new defaults of `@node-saml/passport-saml`
|
||||
|
||||
## <i class="fa fa-tag"></i> 1.10.2 <i class="fa fa-calendar-o"></i> 2025-02-14
|
||||
|
||||
**PLEASE CHECK THIS IF YOU USE SAML AUTHENTICATION:**
|
||||
This release had to set default values for the username and email address attribute mapping for SAML authentication for
|
||||
security reasons.
|
||||
If you use SAML authentication, please make sure to update your SAML configuration accordingly.
|
||||
See: https://docs.hedgedoc.org/configuration/#saml-login `CMD_SAML_ATTRIBUTE_USERNAME` or `CMD_SAML_ATTRIBUTE_EMAIL`
|
||||
|
||||
### Bugfixes
|
||||
- Check if a valid user id is present when using OAuth2
|
||||
- Abort SAML login if NameID is undefined instead of logging in with a user named "undefined" (Thanks [@Haanifee](https://github.com/Haanifee))
|
||||
- Set default values for username and email attribute mapping in SAML configuration
|
||||
|
||||
## <i class="fa fa-tag"></i> 1.10.1 <i class="fa fa-calendar-o"></i> 2025-02-02
|
||||
|
||||
This release fixes a security issue where brute-forcing local email/passwords is possible because of missing rate-limits.
|
||||
We recommend upgrading as soon as possible, if you use local logins.
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
|
||||
import { saveAs } from 'file-saver'
|
||||
import List from 'list.js'
|
||||
import S from 'string'
|
||||
import { unescapeHtml } from './utils'
|
||||
|
||||
require('./locale')
|
||||
|
||||
@@ -398,7 +398,7 @@ function buildTagsFilter (tags) {
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
tags[i] = {
|
||||
id: i,
|
||||
text: S(tags[i]).unescapeHTML().s
|
||||
text: unescapeHtml(tags[i])
|
||||
}
|
||||
}
|
||||
filtertags = tags
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
import Prism from 'prismjs'
|
||||
import PDFObject from 'pdfobject'
|
||||
import S from 'string'
|
||||
import { saveAs } from 'file-saver'
|
||||
import escapeHTML from 'escape-html'
|
||||
import filterXSS from 'xss'
|
||||
|
||||
import getUIElements from './lib/editor/ui-elements'
|
||||
import { escapeHtml, unescapeHtml } from './utils'
|
||||
|
||||
import markdownit from 'markdown-it'
|
||||
import markdownitContainer from 'markdown-it-container'
|
||||
@@ -15,8 +15,6 @@ import markdownitContainer from 'markdown-it-container'
|
||||
/* Defined regex markdown it plugins */
|
||||
import Plugin from 'markdown-it-regexp'
|
||||
|
||||
import 'gist-embed'
|
||||
|
||||
require('prismjs/themes/prism.css')
|
||||
require('prismjs/components/prism-wiki')
|
||||
require('prismjs/components/prism-haskell')
|
||||
@@ -168,7 +166,11 @@ export function renderTags (view) {
|
||||
|
||||
function slugifyWithUTF8 (text) {
|
||||
// remove HTML tags and trim spaces
|
||||
let newText = S(text).trim().stripTags().s
|
||||
let newText = filterXSS(text.trim(), {
|
||||
whiteList: {},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: ['script', 'style']
|
||||
})
|
||||
// replace space between words with dashes
|
||||
newText = newText.replace(/\s+/g, '-')
|
||||
// slugify string to make it valid as an attribute
|
||||
@@ -291,23 +293,15 @@ export function finishView (view) {
|
||||
imgPlayiframe(this, 'https://player.vimeo.com/video/')
|
||||
})
|
||||
.each((key, value) => {
|
||||
const vimeoLink = `https://vimeo.com/${$(value).attr('data-videoid')}`
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: `https://vimeo.com/api/oembed.json?url=${encodeURIComponent(vimeoLink)}`,
|
||||
jsonp: 'callback',
|
||||
dataType: 'jsonp',
|
||||
success (data) {
|
||||
const image = `<img src="${data.thumbnail_url}" />`
|
||||
fetch(`https://vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const image = `<img src="${data[0].thumbnail_large}" />`
|
||||
$(value).prepend(image)
|
||||
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(console.error)
|
||||
})
|
||||
// gist
|
||||
view.find('code[data-gist-id]').each((key, value) => {
|
||||
if ($(value).children().length === 0) { $(value).gist(window.viewAjaxCallback) }
|
||||
})
|
||||
// sequence diagram
|
||||
const sequences = view.find('div.sequence-diagram.raw').removeClass('raw')
|
||||
sequences.each((key, value) => {
|
||||
@@ -328,7 +322,7 @@ export function finishView (view) {
|
||||
svg[0].setAttribute('preserveAspectRatio', 'xMidYMid meet')
|
||||
} catch (err) {
|
||||
$value.unwrap()
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(err)}</div>`)
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(err)}</div>`)
|
||||
console.warn(err)
|
||||
}
|
||||
})
|
||||
@@ -353,7 +347,7 @@ export function finishView (view) {
|
||||
$value.children().unwrap().unwrap()
|
||||
} catch (err) {
|
||||
$value.unwrap()
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(err)}</div>`)
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(err)}</div>`)
|
||||
console.warn(err)
|
||||
}
|
||||
})
|
||||
@@ -375,7 +369,7 @@ export function finishView (view) {
|
||||
})
|
||||
} catch (err) {
|
||||
$value.unwrap()
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(err)}</div>`)
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(err)}</div>`)
|
||||
console.warn(err)
|
||||
}
|
||||
})
|
||||
@@ -398,7 +392,7 @@ export function finishView (view) {
|
||||
errormessage = err.str
|
||||
}
|
||||
$value.unwrap()
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(errormessage)}</div>`)
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(errormessage)}</div>`)
|
||||
console.warn(errormessage)
|
||||
}
|
||||
})
|
||||
@@ -421,7 +415,7 @@ export function finishView (view) {
|
||||
})
|
||||
} catch (err) {
|
||||
$value.unwrap()
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHTML(err)}</div>`)
|
||||
$value.parent().append(`<div class="alert alert-warning">${escapeHtml(err)}</div>`)
|
||||
console.warn(err)
|
||||
}
|
||||
})
|
||||
@@ -450,26 +444,14 @@ export function finishView (view) {
|
||||
// slideshare
|
||||
view.find('div.slideshare.raw').removeClass('raw')
|
||||
.each((key, value) => {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: `https://www.slideshare.net/api/oembed/2?url=https://www.slideshare.net/${$(value).attr('data-slideshareid')}&format=json`,
|
||||
jsonp: 'callback',
|
||||
dataType: 'jsonp',
|
||||
success (data) {
|
||||
const $html = $(data.html)
|
||||
const iframe = $html.closest('iframe')
|
||||
const caption = $html.closest('div')
|
||||
const inner = $('<div class="inner"></div>').append(iframe)
|
||||
const height = iframe.attr('height')
|
||||
const width = iframe.attr('width')
|
||||
const ratio = (height / width) * 100
|
||||
inner.css('padding-bottom', `${ratio}%`)
|
||||
$(value).html(inner).append(caption)
|
||||
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
||||
}
|
||||
})
|
||||
const url = `https://slideshare.com/${$(value).attr('data-slideshareid')}`
|
||||
const inner = $('<a>Slideshare</a>')
|
||||
inner.attr('href', url)
|
||||
inner.attr('rel', 'noopener noreferrer')
|
||||
inner.attr('target', '_blank')
|
||||
$(value).append(inner)
|
||||
})
|
||||
// speakerdeck
|
||||
// speakerdeck
|
||||
view.find('div.speakerdeck.raw').removeClass('raw')
|
||||
.each((key, value) => {
|
||||
const url = `https://speakerdeck.com/${$(value).attr('data-speakerdeckid')}`
|
||||
@@ -508,24 +490,24 @@ export function finishView (view) {
|
||||
value: code
|
||||
}
|
||||
} else if (reallang === 'haskell' || reallang === 'go' || reallang === 'typescript' || reallang === 'jsx' || reallang === 'gherkin') {
|
||||
code = S(code).unescapeHTML().s
|
||||
code = unescapeHtml(code)
|
||||
result = {
|
||||
value: Prism.highlight(code, Prism.languages[reallang])
|
||||
}
|
||||
} else if (reallang === 'tiddlywiki' || reallang === 'mediawiki') {
|
||||
code = S(code).unescapeHTML().s
|
||||
code = unescapeHtml(code)
|
||||
result = {
|
||||
value: Prism.highlight(code, Prism.languages.wiki)
|
||||
}
|
||||
} else if (reallang === 'cmake') {
|
||||
code = S(code).unescapeHTML().s
|
||||
code = unescapeHtml(code)
|
||||
result = {
|
||||
value: Prism.highlight(code, Prism.languages.makefile)
|
||||
}
|
||||
} else {
|
||||
require.ensure([], function (require) {
|
||||
const hljs = require('highlight.js')
|
||||
code = S(code).unescapeHTML().s
|
||||
code = unescapeHtml(code)
|
||||
const languages = hljs.listLanguages()
|
||||
if (!languages.includes(reallang)) {
|
||||
result = hljs.highlightAuto(code)
|
||||
@@ -598,7 +580,7 @@ export function postProcess (code) {
|
||||
if (warning && warning.length > 0) {
|
||||
warning.text(md.metaError)
|
||||
} else {
|
||||
warning = $(`<div id="meta-error" class="alert alert-warning">${escapeHTML(md.metaError)}</div>`)
|
||||
warning = $(`<div id="meta-error" class="alert alert-warning">${escapeHtml(md.metaError)}</div>`)
|
||||
result.prepend(warning)
|
||||
}
|
||||
}
|
||||
@@ -639,8 +621,6 @@ function generateCleanHTML (view) {
|
||||
src.find('*[class=""]').removeAttr('class')
|
||||
eles.removeAttr('data-startline data-endline')
|
||||
src.find("a[href^='#'][smoothhashscroll]").removeAttr('smoothhashscroll')
|
||||
// remove gist content
|
||||
src.find('code[data-gist-id]').children().remove()
|
||||
// disable todo list
|
||||
src.find('input.task-list-item-checkbox').attr('disabled', '')
|
||||
// replace emoji image path
|
||||
@@ -836,7 +816,7 @@ export function smoothHashScroll () {
|
||||
|
||||
function imgPlayiframe (element, src) {
|
||||
if (!$(element).attr('data-videoid')) return
|
||||
const iframe = $("<iframe frameborder='0' webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>")
|
||||
const iframe = $("<iframe style='border: none' allowfullscreen></iframe>")
|
||||
$(iframe).attr('src', `${src + $(element).attr('data-videoid')}?autoplay=1`)
|
||||
$(element).find('img').css('visibility', 'hidden')
|
||||
$(element).append(iframe)
|
||||
@@ -987,7 +967,7 @@ export function scrollToHash () {
|
||||
|
||||
function highlightRender (code, lang) {
|
||||
if (!lang || /no(-?)highlight|plain|text/.test(lang)) { return }
|
||||
code = S(code).escapeHTML().s
|
||||
code = escapeHtml(code)
|
||||
if (lang === 'sequence') {
|
||||
return `<div class="sequence-diagram raw">${code}</div>`
|
||||
} else if (lang === 'flow') {
|
||||
@@ -1156,8 +1136,7 @@ const gistPlugin = new Plugin(
|
||||
|
||||
(match, utils) => {
|
||||
const gistid = match[1]
|
||||
const code = `<code data-gist-id="${gistid}"></code>`
|
||||
return code
|
||||
return `<iframe sandbox class="github-gist-frame" src="https://gist.github.com/${gistid}.pibb"></iframe>`
|
||||
}
|
||||
)
|
||||
// TOC
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
/* global serverurl, moment */
|
||||
|
||||
import store from 'store'
|
||||
import S from 'string'
|
||||
import LZString from 'lz-string'
|
||||
import url from 'wurl'
|
||||
|
||||
import {
|
||||
checkNoteIdValid,
|
||||
encodeNoteId
|
||||
encodeNoteId,
|
||||
escapeHtml
|
||||
} from './utils'
|
||||
|
||||
import {
|
||||
@@ -275,8 +275,8 @@ function parseToHistory (list, notehistory, callback) {
|
||||
notehistory[i].fromNow = timestamp.fromNow()
|
||||
notehistory[i].time = timestamp.format('llll')
|
||||
// prevent XSS
|
||||
notehistory[i].text = S(notehistory[i].text).escapeHTML().s
|
||||
notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? S(notehistory[i].tags).escapeHTML().s.split(',') : []
|
||||
notehistory[i].text = escapeHtml(notehistory[i].text)
|
||||
notehistory[i].tags = (notehistory[i].tags && notehistory[i].tags.length > 0) ? escapeHtml(notehistory[i].tags).split(',') : []
|
||||
// add to list
|
||||
if (notehistory[i].id && list.get('id', notehistory[i].id).length === 0) { list.add(notehistory[i]) }
|
||||
}
|
||||
|
||||
@@ -22,4 +22,3 @@ const $ = require('jquery')
|
||||
window.jQuery = $
|
||||
window.$ = $
|
||||
require('bootstrap')
|
||||
require('gist-embed/gist-embed.min')
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ot } from '../vendor/ot/ot.min.js'
|
||||
import hex2rgb from '../vendor/ot/hex2rgb'
|
||||
|
||||
import { saveAs } from 'file-saver'
|
||||
import randomColor from 'randomcolor'
|
||||
import chance from 'chance'
|
||||
import store from 'store'
|
||||
import url from 'wurl'
|
||||
import { Spinner } from 'spin.js'
|
||||
@@ -427,7 +427,7 @@ const supportExtraTags = [
|
||||
text: '[random color tag]',
|
||||
search: '[]',
|
||||
command: function () {
|
||||
const color = randomColor()
|
||||
const color = chance().color()
|
||||
return '[color=' + color + ']'
|
||||
}
|
||||
}
|
||||
@@ -1079,6 +1079,10 @@ function changeMode (type) {
|
||||
// add and update tool bar
|
||||
if (!editorInstance.toolBar) {
|
||||
editorInstance.addToolBar()
|
||||
const uploadButtonVisible = window.enableUploads === 'all' || (window.enableUploads === 'registered' && personalInfo.login)
|
||||
if (!uploadButtonVisible) {
|
||||
$('#uploadImage').remove()
|
||||
}
|
||||
}
|
||||
// work around foldGutter might not init properly
|
||||
editor.setOption('foldGutter', false)
|
||||
@@ -2111,11 +2115,11 @@ function updatePermission (newPermission) {
|
||||
break
|
||||
case 'editable':
|
||||
label = '<i class="fa fa-shield"></i> Editable'
|
||||
title = 'Signed people can edit'
|
||||
title = 'Signed-in people can edit'
|
||||
break
|
||||
case 'limited':
|
||||
label = '<i class="fa fa-id-card"></i> Limited'
|
||||
title = 'Signed people can edit (forbid guest)'
|
||||
title = 'Signed-in people can edit (forbid guests)'
|
||||
break
|
||||
case 'locked':
|
||||
label = '<i class="fa fa-lock"></i> Locked'
|
||||
@@ -2123,7 +2127,7 @@ function updatePermission (newPermission) {
|
||||
break
|
||||
case 'protected':
|
||||
label = '<i class="fa fa-umbrella"></i> Protected'
|
||||
title = 'Only owner can edit (forbid guest)'
|
||||
title = 'Only owner can edit (forbid guests)'
|
||||
break
|
||||
case 'private':
|
||||
label = '<i class="fa fa-hand-stop-o"></i> Private'
|
||||
|
||||
@@ -3,6 +3,7 @@ window.urlpath = '<%- urlpath %>'
|
||||
window.debug = <%- debug %>
|
||||
window.version = '<%- version %>'
|
||||
|
||||
window.enableUploads = '<%- enableUploads %>'
|
||||
window.allowedUploadMimeTypes = <%- JSON.stringify(allowedUploadMimeTypes) %>
|
||||
|
||||
window.linkifyHeaderStyle = '<%- linkifyHeaderStyle %>'
|
||||
|
||||
@@ -4,8 +4,10 @@ const filterXSS = require('xss')
|
||||
|
||||
const whiteListAttr = ['id', 'class', 'style']
|
||||
window.whiteListAttr = whiteListAttr
|
||||
// allow link starts with '.', '/' and custom protocol with '://', exclude link starts with javascript://
|
||||
const linkRegex = /^(?!javascript:\/\/)([\w|-]+:\/\/)|^([.|/])+/i
|
||||
// allow links starting with '.', '/', '#', '?', 'http://', 'https://' and protocols supported by the navigator.registerProtocolHandler API
|
||||
// These schemes can be considered safe-enough for linking to since these are the ones that can be opened using a browser.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler
|
||||
const linkRegex = /^(?:\?|#|\.|\/|https?:\/\/|(?:web\+[a-z]+|bitcoin|ftp|ftps|geo|im|irc|ircs|magnet|mailto|matrix|mms|news|nntp|openpgp4fpr|sftp|sip|sms|smsto|ssh|tel|urn|webcal|wtai|xmpp):)/i
|
||||
// allow data uri, from https://gist.github.com/bgrins/6194623
|
||||
const dataUriRegex = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*)\s*$/i
|
||||
// custom white list
|
||||
|
||||
@@ -30,3 +30,21 @@ export function decodeNoteId (encodedId) {
|
||||
idParts.push(id.substr(20, 12))
|
||||
return idParts.join('-')
|
||||
}
|
||||
|
||||
// use browser's DOM APIs for escaping and unescaping HTML
|
||||
export function escapeHtml (unsafe) {
|
||||
if (!unsafe) {
|
||||
return ''
|
||||
}
|
||||
const tempDiv = document.createElement('div')
|
||||
tempDiv.appendChild(document.createTextNode(String(unsafe)))
|
||||
return tempDiv.innerHTML
|
||||
}
|
||||
|
||||
export function unescapeHtml (escapedHtml) {
|
||||
if (!escapedHtml) {
|
||||
return ''
|
||||
}
|
||||
const doc = new DOMParser().parseFromString(escapedHtml, 'text/html')
|
||||
return doc.documentElement.textContent || ''
|
||||
}
|
||||
|
||||
1
resources/config.json
Normal file
1
resources/config.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
58
resources/docker-entrypoint.sh
Executable file
58
resources/docker-entrypoint.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Use gosu if the container started with root privileges
|
||||
UID="$(id -u)"
|
||||
[ "$UID" -eq 0 ] && GOSU="gosu hedgedoc" || GOSU=""
|
||||
|
||||
if [ "$HMD_IMAGE_UPLOAD_TYPE" != "" ] && [ "$CMD_IMAGE_UPLOAD_TYPE" = "" ]; then
|
||||
CMD_IMAGE_UPLOAD_TYPE="$HMD_IMAGE_UPLOAD_TYPE"
|
||||
fi
|
||||
|
||||
# Print warning if local data storage is used but no volume is mounted
|
||||
[ "$CMD_IMAGE_UPLOAD_TYPE" = "filesystem" ] && { mountpoint -q ./public/uploads || {
|
||||
echo "
|
||||
#################################################################
|
||||
### ###
|
||||
### !!!WARNING!!! ###
|
||||
### ###
|
||||
### Using local uploads without persistence is ###
|
||||
### dangerous. You'll loose your data on ###
|
||||
### container removal. Check out: ###
|
||||
### https://docs.docker.com/engine/tutorials/dockervolumes/ ###
|
||||
### ###
|
||||
### !!!WARNING!!! ###
|
||||
### ###
|
||||
#################################################################
|
||||
";
|
||||
} ; }
|
||||
|
||||
# Change owner and permission if filesystem backend is used and user has root permissions
|
||||
if [ "$UID" -eq 0 ] && [ "$CMD_IMAGE_UPLOAD_TYPE" = "filesystem" ]; then
|
||||
if [ "$UID" -eq 0 ]; then
|
||||
echo "Updating uploads directory permissions ($UPLOADS_MODE)"
|
||||
chown -R hedgedoc ./public/uploads
|
||||
chmod $UPLOADS_MODE ./public/uploads
|
||||
find ./public/uploads -type f -executable -exec chmod a-x {} \;
|
||||
else
|
||||
echo "
|
||||
#################################################################
|
||||
### ###
|
||||
### !!!WARNING!!! ###
|
||||
### ###
|
||||
### Container was started without root permissions ###
|
||||
### and filesystem storage is being used. ###
|
||||
### In case of filesystem errors these need to be ###
|
||||
### changed manually ###
|
||||
### ###
|
||||
### !!!WARNING!!! ###
|
||||
### ###
|
||||
#################################################################
|
||||
";
|
||||
fi
|
||||
fi
|
||||
|
||||
# Sleep to make sure everything is fine...
|
||||
sleep 3
|
||||
|
||||
# run
|
||||
exec $GOSU "$@"
|
||||
20
resources/healthcheck.mjs
Normal file
20
resources/healthcheck.mjs
Normal file
@@ -0,0 +1,20 @@
|
||||
import fetch from 'node-fetch'
|
||||
|
||||
// Kill myself after 5 second timeout
|
||||
setTimeout(() => {
|
||||
process.exit(1)
|
||||
}, 5000)
|
||||
|
||||
fetch(`http://localhost:${process.env.CMD_PORT || '3000' }/_health`, {headers: { "user-agent": "hedgedoc-container-healthcheck/1.1"}}).then((response) => {
|
||||
if (!response.ok) {
|
||||
process.exit(1)
|
||||
}
|
||||
return response.json()
|
||||
}).then((data) => {
|
||||
if (!data.ready) {
|
||||
process.exit(1)
|
||||
}
|
||||
process.exit(0)
|
||||
}).catch(() => {
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user