Compare commits

..

4 commits

Author SHA1 Message Date
Wu Cheng-Han
e939581e86 Fix for code style 2018-01-29 10:48:49 +08:00
Wu Cheng-Han
53e08c20f1 Fix for code style 2018-01-29 10:43:00 +08:00
Wu Cheng-Han
7df03b42c8 Add config for AsciiMath preprocessor 2018-01-29 10:38:33 +08:00
Wu Cheng-Han
cae65192c8 Upgrade MathJax to 2.7.2 and support AsciiMath 2018-01-28 23:36:29 +08:00
237 changed files with 10215 additions and 18077 deletions

View file

@ -1,11 +1,6 @@
{ {
"presets": [ "presets": [
["env", { "es2015"
"targets": {
"node": "8",
"uglify": true
}
}]
], ],
"plugins": [ "plugins": [
"transform-runtime" "transform-runtime"

View file

@ -17,9 +17,3 @@ trim_trailing_whitespace = false
[{.travis.yml,npm-shrinkwrap.json,package.json}] [{.travis.yml,npm-shrinkwrap.json,package.json}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
[locales/*.json]
# this is the exact style poeditor.com exports, so this should prevent churn.
insert_final_newline = false
indent_style = space
indent_size = 4

View file

@ -1,3 +0,0 @@
lib/ot
public/vendor
public/build

View file

@ -1,22 +0,0 @@
module.exports = {
"root": true,
"extends": "standard",
"env": {
"node": true
},
"rules": {
// at some point all of these should return to their default "error" state
// but right now, this is not a good choice, because too many places are
// wrong.
"import/first": ["warn"],
"indent": ["warn"],
"no-console": ["warn"],
"no-multiple-empty-lines": ["warn"],
"no-multi-spaces": ["warn"],
"object-curly-spacing": ["warn"],
"one-var": ["warn"],
"quotes": ["warn"],
"semi": ["warn"],
"space-infix-ops": ["warn"]
}
};

1
.gitignore vendored
View file

@ -8,6 +8,7 @@ composer.lock
.idea/ .idea/
Thumbs.db Thumbs.db
npm-debug.log npm-debug.log
hackmd_io
newrelic_agent.log newrelic_agent.log
logs/ logs/
tmp/ tmp/

View file

@ -1,14 +0,0 @@
Max Wu <jackymaxj@gmail.com> Wu Cheng-Han <jacky_cute0808@hotmail.com>
Max Wu <jackymaxj@gmail.com> Cheng-Han, Wu <jackymaxj@gmail.com>
Max Wu <jackymaxj@gmail.com> jackycute <jackymaxj@gmail.com>
Max Wu <jackymaxj@gmail.com> Wu, Cheng-Han <jackymaxj@gmail.com>
Max Wu <jackymaxj@gmail.com> jackycute <jacky_cute0808@hotmail.com>
Sheogorath <sheogorath@shivering-isles.com> Christoph (Sheogorath) Kern <sheogorath@shivering-isles.com>
Raccoon <raccoon@hackmd.io> Raccoon Li <a60814billy@gmail.com>
Raccoon <raccoon@hackmd.io> Raccoon <a60814billy@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org> Peter Dave Hello <PeterDaveHello@users.noreply.github.com>
Claudius Coenen <github@amenthes.de> Claudius Coenen <opensource@amenthes.de>

View file

@ -1,40 +1,38 @@
language: node_js language: node_js
dist: xenial dist: trusty
cache: yarn cache: yarn
env:
global:
- CXX=g++-4.8
- YARN_VERSION=1.3.2
jobs: jobs:
include: include:
- stage: Static Tests - env: task=npm-test
name: eslint
node_js: node_js:
- 10 - 6
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
- export PATH="$HOME/.yarn/bin:$PATH"
- env: task=npm-test
node_js:
- 7
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version "$YARN_VERSION"
- export PATH="$HOME/.yarn/bin:$PATH"
- env: task=ShellCheck
script: script:
- yarn run eslint - shellcheck bin/*
- name: ShellCheck
script:
- shellcheck bin/heroku bin/setup
language: generic language: generic
- name: json-lint - env: task=doctoc
addons: install: npm install doctoc
apt:
packages:
- jq
script: script:
- yarn run jsonlint - cp README.md README.md.orig
- npm run doctoc
- diff -q README.md README.md.orig
language: generic language: generic
- stage: Dynamic Tests - env: task=json-lint
name: Node.js 8 install: npm install jsonlint
node_js:
- 8
script: script:
- yarn run mocha-suite - npm run jsonlint
- name: Node.js 10 language: generic
node_js:
- 10
script:
- yarn run mocha-suite
- name: Node.js 12
node_js:
- 12
script:
- yarn run mocha-suite

View file

@ -1,7 +0,0 @@
# CHANGELOG
Please refer to the release notes published under
[`public/docs/release-notes.md`](public/docs/release-notes.md).
These are also available on each CodiMD instance under
https://[domain-name]/release-notes

View file

@ -1,37 +0,0 @@
Contributor Code of Conduct
===
As contributors and maintainers of this project, and in the interest of fostering an open and
welcoming community, we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation, submitting pull requests or patches, and other
activities.
We are committed to making participation in this project a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression, sexual orientation,
disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses, without explicit
permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits,
code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By
adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project. Project maintainers who do not
follow or enforce the Code of Conduct may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces when an individual is
representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an
issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org),
version 1.2.0, available at
[http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)

View file

@ -3,8 +3,7 @@
When contributing to this repository, please first discuss the change you wish to make via issue, When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change. email, or any other method with the owners of this repository before making a change.
Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in all your Please note we have a code of conduct, please follow it in all your interactions with the project.
interactions with the project.
## Pull Request Process ## Pull Request Process
1. Ensure you signed all your commits with Developer Certificate of Origin (DCO). 1. Ensure you signed all your commits with Developer Certificate of Origin (DCO).
@ -17,13 +16,50 @@ interactions with the project.
5. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 5. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer to merge it for you. do not have permission to do that, you may request the second reviewer to merge it for you.
## Sign your work ## Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering an open and
welcoming community, we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation, submitting pull requests or patches, and other
activities.
We are committed to making participation in this project a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression, sexual orientation,
disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses, without explicit
permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits,
code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By
adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project. Project maintainers who do not
follow or enforce the Code of Conduct may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces when an individual is
representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an
issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org),
version 1.2.0, available at
[http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
### Sign your work
We use the Developer Certificate of Origin (DCO) as a additional safeguard We use the Developer Certificate of Origin (DCO) as a additional safeguard
for the CodiMD project. This is a well established and widely used for the HackMD project. This is a well established and widely used
mechanism to assure contributors have confirmed their right to license mechanism to assure contributors have confirmed their right to license
their contribution under the project's license. their contribution under the project's license.
Please read [docs/legal/developer-certificate-of-origin.txt][dcofile]. Please read [contribute/developer-certificate-of-origin][dcofile].
If you can certify it, then just add a line to every git commit message: If you can certify it, then just add a line to every git commit message:
```` ````

902
CONTRIBUTORS Normal file
View file

@ -0,0 +1,902 @@
=== .babelrc
Yukai Huang <yukaihuangtw@gmail.com>
=== .editorconfig
bananaappletw <bananaappletw@gmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== .gitignore
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== .sequelizerc.example
Yukai Huang <yukaihuangtw@gmail.com>
=== .travis.yml
bananaappletw <bananaappletw@gmail.com>
BoHong Li <a60814billy@gmail.com>
Max Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
=== AUTHORS
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== CONTRIBUTING.md
Max Wu <jackymaxj@gmail.com>
=== LICENSE
Cheng-Han, Wu <jackymaxj@gmail.com>
jackycute <jacky_cute0808@hotmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== README.md
alecdwm <alec@owls.io>
bananaappletw <bananaappletw@gmail.com>
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Florian Rhiem <florian.rhiem@gmail.com>
jackycute <jackymaxj@gmail.com>
Jannik Lorenz <dev@janniklorenz.de>
Jason Croft <jcroft@velocity.org>
Johannes Weißl <jargon@molb.org>
Jun SAKATA <jun.bj141400@gmail.com>
Laura Kyle <laura.kyle91@gmail.com>
Max Wu <jackymaxj@gmail.com>
neopostmodern <clemens@neopostmodern.com>
NV <nvsofts@gmail.com>
Sheogorath <sheogorath@shivering-isles.com>
The Gitter Badger <badger@gitter.im>
Wonder Chang <iwonder.tw@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
蒼時弦也 <elct9620@frost.tw>
=== app.js
alecdwm <alec@owls.io>
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
James Stephenson <c4p7.fl1n7@gmail.com>
Jan Kunzmann <jan-github@phobia.de>
Jason Croft <jcroft@velocity.org>
Jordan Matelsky <j6k4m8@gmail.com>
knjcode <knjcode@gmail.com>
LluisArevalo <thorin119@gmail.com>
Max Wu <jackymaxj@gmail.com>
NV <nvsofts@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Raccoon Li <a60814billy@gmail.com>
robert <ahmerov.rt@molodost.bz>
Sheogorath <sheogorath@shivering-isles.com>
S.Noda <noda@fenrir.co.jp>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
xnum <s000032001@gmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== app.json
bananaappletw <bananaappletw@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== bin/heroku
bananaappletw <bananaappletw@gmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== bin/setup
Sheogorath <sheogorath@shivering-isles.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== config.json.example
alecdwm <alec@owls.io>
bananaappletw <bananaappletw@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== docs/guides/images/s3-image-upload/bucket-policy-editor.png
Yukai Huang <yukaihuangtw@gmail.com>
=== docs/guides/images/s3-image-upload/bucket-property.png
Yukai Huang <yukaihuangtw@gmail.com>
=== docs/guides/images/s3-image-upload/create-bucket.png
Yukai Huang <yukaihuangtw@gmail.com>
=== docs/guides/images/s3-image-upload/custom-policy.png
Yukai Huang <yukaihuangtw@gmail.com>
=== docs/guides/images/s3-image-upload/iam-user.png
Yukai Huang <yukaihuangtw@gmail.com>
=== docs/guides/images/s3-image-upload/review-policy.png
Yukai Huang <yukaihuangtw@gmail.com>
=== docs/guides/s3-image-upload.md
Johannes Weißl <jargon@molb.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== lib/config/default.js
BoHong Li <a60814billy@gmail.com>
=== lib/config/defaultSSL.js
BoHong Li <a60814billy@gmail.com>
=== lib/config/dockerSecret.js
BoHong Li <a60814billy@gmail.com>
=== lib/config/enum.js
BoHong Li <a60814billy@gmail.com>
=== lib/config/environment.js
BoHong Li <a60814billy@gmail.com>
Raccoon Li <a60814billy@gmail.com>
=== lib/config/index.js
BoHong Li <a60814billy@gmail.com>
tkykm <tkykm@users.noreply.github.com>
=== lib/config/oldEnvironment.js
BoHong Li <a60814billy@gmail.com>
Raccoon Li <a60814billy@gmail.com>
=== lib/config/utils.js
Raccoon Li <a60814billy@gmail.com>
=== lib/history.js
BoHong Li <a60814billy@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/letter-avatars.js
alecdwm <alec@owls.io>
BoHong Li <a60814billy@gmail.com>
=== lib/logger.js
BoHong Li <a60814billy@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/migrations/20150504155329-create-users.js
BoHong Li <a60814billy@gmail.com>
=== lib/migrations/20150508114741-create-notes.js
BoHong Li <a60814billy@gmail.com>
=== lib/migrations/20150515125813-create-temp.js
BoHong Li <a60814billy@gmail.com>
=== lib/migrations/20150702001020-update-to-0_3_1.js
BoHong Li <a60814billy@gmail.com>
=== lib/migrations/20150915153700-change-notes-title-to-text.js
BoHong Li <a60814billy@gmail.com>
=== lib/migrations/20160112220142-note-add-lastchange.js
BoHong Li <a60814billy@gmail.com>
=== lib/migrations/20160420180355-note-add-alias.js
BoHong Li <a60814billy@gmail.com>
=== lib/migrations/20160515114000-user-add-tokens.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
=== lib/migrations/20160607060246-support-revision.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/migrations/20160703062241-support-authorship.js
BoHong Li <a60814billy@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/migrations/20161009040430-support-delete-note.js
BoHong Li <a60814billy@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/migrations/20161201050312-support-email-signin.js
BoHong Li <a60814billy@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/models/author.js
BoHong Li <a60814billy@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/models/index.js
bananaappletw <bananaappletw@gmail.com>
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== lib/models/note.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
NV <nvsofts@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
蒼時弦也 <elct9620@frost.tw>
=== lib/models/revision.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/models/temp.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
=== lib/models/user.js
alecdwm <alec@owls.io>
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Jason Croft <jcroft@velocity.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/ot/client.js
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/ot/editor-socketio-server.js
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/ot/index.js
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/ot/selection.js
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/ot/server.js
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/ot/simple-text-operation.js
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/ot/text-operation.js
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/ot/wrapped-operation.js
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== lib/realtime.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Florian Rhiem <florian.rhiem@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
蒼時弦也 <elct9620@frost.tw>
=== lib/response.js
alecdwm <alec@owls.io>
BoHong Li <a60814billy@gmail.com>
butlerx <butlerx@notthe.cloud>
Cheng-Han, Wu <jackymaxj@gmail.com>
Florian Rhiem <florian.rhiem@gmail.com>
Ikumi Shimizu <193s@users.noreply.github.com>
Jannik Lorenz <dev@janniklorenz.de>
Jason Croft <jcroft@velocity.org>
Sheogorath <sheogorath@shivering-isles.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
xnum <s000032001@gmail.com>
蒼時弦也 <elct9620@frost.tw>
=== lib/utils.js
BoHong Li <a60814billy@gmail.com>
butlerx <butlerx@notthe.cloud>
LluisArevalo <thorin119@gmail.com>
=== lib/web/auth/dropbox/index.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/auth/email/index.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/auth/facebook/index.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/auth/github/index.js
BoHong Li <a60814billy@gmail.com>
Max Wu <jackymaxj@gmail.com>
=== lib/web/auth/gitlab/index.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/auth/google/index.js
BoHong Li <a60814billy@gmail.com>
Kaiyu Shi <skyisno.1@gmail.com>
=== lib/web/auth/index.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/auth/ldap/index.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/auth/twitter/index.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/auth/utils.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/baseRouter.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/historyRouter.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/imageRouter.js
BoHong Li <a60814billy@gmail.com>
Kotaro Yamamoto <kota.crk@gmail.com>
Raccoon Li <a60814billy@gmail.com>
=== lib/web/middleware/checkURIValid.js
BoHong Li <a60814billy@gmail.com>
Max Wu <jackymaxj@gmail.com>
=== lib/web/middleware/redirectWithoutTrailingSlashes.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/middleware/tooBusy.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/noteRouter.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/statusRouter.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/userRouter.js
BoHong Li <a60814billy@gmail.com>
=== lib/web/utils.js
BoHong Li <a60814billy@gmail.com>
=== lib/workers/dmpWorker.js
BoHong Li <a60814billy@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== locales/ca.json
Xavier Marques <xaviermarques4f@gmail.com>
=== locales/da.json
Patrick Andersen <patrick@bacha.dk>
=== locales/de.json
Jannik Lorenz <dev@janniklorenz.de>
Philipp Zumstein <zuphilip@users.noreply.github.com>
Simon Joda Stößer <SimJoSt@users.noreply.github.com>
=== locales/el.json
Stratos Gerakakis <stratosgear@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
zachariast <zachariastraianos@gmail.com>
=== locales/en.json
alecdwm <alec@owls.io>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== locales/eo.json
James Stephenson <c4p7.fl1n7@gmail.com>
=== locales/es.json
Pablo Guerrero <pablo.guerrero@sap.com>
Sergio Valverde <svg153@users.noreply.github.com>
=== locales/fr.json
Colin Maudry <colin@maudry.com>
Ho33e5 <ho33e5@gmail.com>
=== locales/hi.json
Paras <paraschadha2052@gmail.com>
=== locales/hr.json
ivanorsolic <ivanorsolic@users.noreply.github.com>
=== locales/it.json
GhiMax <ghina8@gmail.com>
=== locales/ja.json
tkqubo <tk.qubo@gmail.com>
=== locales/nl.json
Martijnpold <martijntje7@gmail.com>
Tom Wyckhuys <tomwyckhuys@gmail.com>
=== locales/pl.json
Bartlomiej Szala <fenix440@gmail.com>
Jakub Sygnowski <sygnowski@gmail.com>
=== locales/pt.json
Marcelo Alencar <marceloalves@ufpa.br>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== locales/ru.json
Himura Kazuto <Himura2la@users.noreply.github.com>
p0v1n0m <p0v1n0m@gmail.com>
=== locales/sv.json
Lars Karlsson <lars@kajes.se>
Patrick Andersen <patrick@bacha.dk>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== locales/tr.json
Ömer Erdinç Yağmurlu <omeryagmurlu@gmail.com>
=== locales/uk.json
Dmytro Kytsmen <dmitrokytsmen@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== locales/zh.json
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== package.json
alecdwm <alec@owls.io>
bananaappletw <bananaappletw@gmail.com>
BoHong Li <a60814billy@gmail.com>
Bryan Davis <bd808@wikimedia.org>
Cheng-Han, Wu <jackymaxj@gmail.com>
Fabien Meghazi <agr@amigrave.com>
greenkeeperio-bot <support@greenkeeper.io>
Jason Croft <jcroft@velocity.org>
Max Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Peter Dave Hello <PeterDaveHello@users.noreply.github.com>
Sheogorath <sheogorath@shivering-isles.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
xnum <s000032001@gmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/apple-touch-icon.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/css/bootstrap-social.css
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/css/center.css
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/css/cover.css
Cheng-Han, Wu <jackymaxj@gmail.com>
Jason Croft <jcroft@velocity.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/css/extra.css
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/css/font.css
Yukai Huang <yukaihuangtw@gmail.com>
=== public/css/github-extract.css
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/css/google-font.css
Yukai Huang <yukaihuangtw@gmail.com>
=== public/css/index.css
Cheng-Han, Wu <jackymaxj@gmail.com>
Jason Croft <jcroft@velocity.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/css/markdown.css
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/css/mermaid.css
Cheng-Han, Wu <jackymaxj@gmail.com>
=== public/css/site.css
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/css/slide-preview.css
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/css/slide.css
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/default.md
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/docs/features.md
Cheng-Han, Wu <jackymaxj@gmail.com>
Max Wu <jackymaxj@gmail.com>
Pablo Guerrero <pablo.guerrero@gmail.com>
Sheogorath <sheogorath@shivering-isles.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/docs/release-notes.md
Cheng-Han, Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/docs/slide-example.md
butlerx <butlerx@notthe.cloud>
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/docs/yaml-metadata.md
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/favicon.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/fonts/SourceCodePro-Black.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Black.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Black.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Bold.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Bold.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Bold.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-ExtraLight.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-ExtraLight.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-ExtraLight.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Light.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Light.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Light.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Medium.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Medium.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Medium.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Regular.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Regular.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Regular.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Semibold.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Semibold.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceCodePro-Semibold.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Black.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Black.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Black.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-BlackItalic.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-BlackItalic.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-BlackItalic.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Bold.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Bold.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Bold.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-BoldItalic.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-BoldItalic.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-BoldItalic.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-ExtraLight.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-ExtraLight.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-ExtraLight.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-ExtraLightItalic.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-ExtraLightItalic.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-ExtraLightItalic.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Italic.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Italic.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Italic.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Light.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Light.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Light.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-LightItalic.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-LightItalic.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-LightItalic.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Regular.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Regular.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Regular.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Semibold.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Semibold.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-Semibold.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-SemiboldItalic.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-SemiboldItalic.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSansPro-SemiboldItalic.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSerifPro-Bold.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSerifPro-Bold.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSerifPro-Bold.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSerifPro-Regular.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSerifPro-Regular.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSerifPro-Regular.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSerifPro-Semibold.eot
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSerifPro-Semibold.ttf
Peter Dave Hello <hsu@peterdavehello.org>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/fonts/SourceSerifPro-Semibold.woff
Yukai Huang <yukaihuangtw@gmail.com>
=== public/hackmd-icon-1024.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/js/cover.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Jason Croft <jcroft@velocity.org>
NV <nvsofts@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/extra.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
NV <nvsofts@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/google-drive-picker.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Max Wu <jackymaxj@gmail.com>
=== public/js/google-drive-upload.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/js/history.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/htmlExport.js
BoHong Li <a60814billy@gmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/index.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Jason Croft <jcroft@velocity.org>
Laura Kyle <laura.kyle91@gmail.com>
NV <nvsofts@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
xnum <s000032001@gmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
Zankio <xxoojoeooxx1@gmail.com>
蒼時弦也 <elct9620@frost.tw>
=== public/js/lib/appState.js
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/lib/common/constant.ejs
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/js/lib/common/login.js
BoHong Li <a60814billy@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/lib/config/index.js
BoHong Li <a60814billy@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/lib/editor/config.js
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/lib/editor/index.js
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/lib/editor/statusbar.html
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/lib/editor/ui-elements.js
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/lib/editor/utils.js
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/lib/modeType.js
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/lib/syncscroll.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/locale.js
BoHong Li <a60814billy@gmail.com>
Peter Dave Hello <PeterDaveHello@users.noreply.github.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/pretty.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/render.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/js/reveal-markdown.js
BoHong Li <a60814billy@gmail.com>
Cheng-Han, Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/js/slide.js
BoHong Li <a60814billy@gmail.com>
Max Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/screenshot.png
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/uploads/.gitkeep
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/vendor/abcjs_basic_3.1.1-min.js
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/bootstrap/tooltip.min.css
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/bootstrap/tooltip.min.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/codemirror-spell-checker/en_US.aff
Cheng-Han, Wu <jackymaxj@gmail.com>
=== public/vendor/codemirror-spell-checker/en_US.dic
Cheng-Han, Wu <jackymaxj@gmail.com>
=== public/vendor/codemirror-spell-checker/spell-checker.min.css
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/codemirror-spell-checker/spell-checker.min.js
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/vendor/inlineAttachment/codemirror.inline-attachment.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/inlineAttachment/inline-attachment.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-textcomplete/jquery.textcomplete.js
Cheng-Han, Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-icons_222222_256x240.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-icons_2e83ff_256x240.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-icons_454545_256x240.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-icons_888888_256x240.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/images/ui-icons_cd0a0a_256x240.png
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/jquery-ui.min.css
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/jquery-ui/jquery-ui.min.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/md-toc.js
BoHong Li <a60814billy@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/ajax-adapter.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/client.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/codemirror-adapter.js
Cheng-Han, Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/compress.sh
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/editor-client.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/ot.min.js
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/selection.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/socketio-adapter.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/text-operation.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/undo-manager.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/ot/wrapped-operation.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/showup/showup.css
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/vendor/showup/showup.js
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/error.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/hackmd.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/hackmd/body.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Florian Rhiem <florian.rhiem@gmail.com>
Ian Dees <ian.dees@gmail.com>
Jason Croft <jcroft@velocity.org>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
蒼時弦也 <elct9620@frost.tw>
=== public/views/hackmd/foot.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Jannik Lorenz <dev@janniklorenz.de>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/views/hackmd/footer.ejs
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/hackmd/head.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
xnum <s000032001@gmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/views/hackmd/header.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Jannik Lorenz <dev@janniklorenz.de>
Jason Croft <jcroft@velocity.org>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
xnum <s000032001@gmail.com>
=== public/views/html.hbs
Cheng-Han, Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/includes/header.ejs
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/views/includes/scripts.ejs
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/views/index.ejs
alecdwm <alec@owls.io>
Cheng-Han, Wu <jackymaxj@gmail.com>
Florian Rhiem <florian.rhiem@gmail.com>
James Stephenson <c4p7.fl1n7@gmail.com>
Jannik Lorenz <dev@janniklorenz.de>
Jason Croft <jcroft@velocity.org>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/views/index/body.ejs
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/index/foot.ejs
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/index/footer.ejs
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/index/head.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
xnum <s000032001@gmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/views/index/header.ejs
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/pretty.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== public/views/shared/disqus.ejs
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/shared/ga.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/shared/help-modal.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/shared/polyfill.ejs
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/shared/refresh-modal.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/shared/revision-modal.ejs
Cheng-Han, Wu <jackymaxj@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/shared/signin-modal.ejs
alecdwm <alec@owls.io>
Cheng-Han, Wu <jackymaxj@gmail.com>
Jason Croft <jcroft@velocity.org>
neopostmodern <clemens@neopostmodern.com>
Sheogorath <sheogorath@shivering-isles.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== public/views/slide.ejs
butlerx <butlerx@notthe.cloud>
Cheng-Han, Wu <jackymaxj@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== tmp/.keep
Wu Cheng-Han <jacky_cute0808@hotmail.com>
=== webpack.config.js
BoHong Li <a60814billy@gmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== webpack.production.js
BoHong Li <a60814billy@gmail.com>
geekyd <singhsince94@gmail.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== webpackBaseConfig.js
BoHong Li <a60814billy@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>
=== yarn.lock
BoHong Li <a60814billy@gmail.com>
Christian Schuhmann <madebyherzblut@users.noreply.github.com>
Wu Cheng-Han <jacky_cute0808@hotmail.com>
Yukai Huang <yukaihuangtw@gmail.com>

11
LICENSE
View file

@ -629,15 +629,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
CodiMD - Realtime collaborative markdown notes on all platforms. <one line to give the program's name and a brief idea of what it does.>
Copyright (C) 2019 Christoph (Sheogorath) Kern Copyright (C) <year> <name of author>
Copyright (C) 2019 Claudius Coenen
Copyright (C) 2019 Max Wu
Copyright (C) 2017 Yukai Huang
And more can be found on https://github.com/codimd/server/graphs/contributors
Or in the local AUTHORS file
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU Affero General Public License as published by

368
README.md
View file

@ -1,72 +1,45 @@
CodiMD HackMD Community Edition
=== ===
[![#CodiMD on matrix.org][matrix.org-image]][matrix.org-url] [![Standard - JavaScript Style Guide][standardjs-image]][standardjs-url]
[![Join the chat at https://gitter.im/hackmdio/hackmd][gitter-image]][gitter-url]
[![build status][travis-image]][travis-url] [![build status][travis-image]][travis-url]
[![version][github-version-badge]][github-release-page] [![version][github-version-badge]][github-release-page]
[![POEditor][poeditor-image]][poeditor-url] [![Help Contribute to Open Source][codetriage-image]][codetriage-url]
[![Mastodon][social-mastodon-image]][social-mastodon]
CodiMD lets you create real-time collaborative markdown notes. You can test-drive HackMD lets you create realtime collaborative markdown notes on all platforms.
it by visiting our [CodiMD demo server][codimd-demo]. Inspired by Hackpad, with more focus on speed and flexibility.
Still in the early stage, feel free to fork or contribute to HackMD.
It is inspired by Hackpad, Etherpad and similar collaborative editors. This Thanks for using! :smile:
project originated with the team at [HackMD](https://hackmd.io) and now forked
into its own organisation. [A longer writeup can be read in the history doc](docs/history.md).
[![CodiMD 1.3.2 with its feature demonstration page open](docs/images/CodiMD-1.3.2-features.png)][codimd-demo-features] <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
# Table of Contents
- [Browsers Requirement](#browsers-requirement)
- [Installation](#installation)
- [Getting started (Native install)](#getting-started-native-install)
- [Prerequisite](#prerequisite)
- [Instructions](#instructions)
- [Heroku Deployment](#heroku-deployment)
- [HackMD by docker container](#hackmd-by-docker-container)
- [Upgrade](#upgrade)
- [Native setup](#native-setup)
- [Configuration](#configuration)
- [Environment variables (will overwrite other server configs)](#environment-variables-will-overwrite-other-server-configs)
- [Application settings `config.json`](#application-settings-configjson)
- [Third-party integration api key settings](#third-party-integration-api-key-settings)
- [Third-party integration oauth callback urls](#third-party-integration-oauth-callback-urls)
- [Developer Notes](#developer-notes)
- [Structure](#structure)
- [Operational Transformation](#operational-transformation)
- [License](#license)
## Community and Contributions <!-- END doctoc generated TOC please keep comment here to allow auto update -->
We welcome contributions! There's a lot to do: If you would like to report bugs, # Browsers Requirement
the [issue tracker][github-issue-tracker] is the right place. If you can help
translating, find us on [POEditor][poeditor-url]. To get started developing,
take a look at the [docs/dev](docs/dev) directory. In any case: come talk to us,
we'll be delighted to help you with the first steps.
To stay up to date with our work or get support it's recommended to join our
[Matrix channel][matrix.org-url], stop by our [community forums][codimd-community]
or subscribe to the [release feed][github-release-feed]. We also engage in
regular [community calls][codimd-community-calls] ([RSS](https://community.codimd.org/t/codimd-community-call/19.rss)) which you are very welcome to join.
## Installation / Upgrading
You can run CodiMD in a number of ways, and we created setup instructions for
all of these:
* [Docker](docs/setup/docker.md)
* [Kubernetes](docs/setup/kubernetes.md)
* [Cloudron](docs/setup/cloudron.md)
* [LinuxServer.io (multi-arch docker)](docs/setup/docker-linuxserver.md)
* [Heroku](docs/setup/heroku.md)
* [Manual setup](docs/setup/manual-setup.md)
If you do not wish to run your own setup, you can find a commercial offering at
https://hackmd.io. This is not the same codebase as this one, but it is a very
similar project.
## Configuration
Theres two main ways to configure your CodiMD instance:
[Config file](docs/configuration-config-file.md) or
[environment variables](docs/configuration-env-vars.md). You can choose what
works best for you.
CodiMD can integrate with
* facebook, twitter, github, gitlab, mattermost, dropbox, google, ldap, saml and [oauth2](docs/guides/auth/oauth.md) **for login**
* imgur, s3, minio, azure **for image/attachment storage** (files can also be local!)
* dropbox **for export and import**
More info about that can be found in the configuration docs above.
## Browser support
To use CodiMD, your browser should match or exceed these versions:
- ![Chrome](http://browserbadge.com/chrome/47/18px) Chrome >= 47, Chrome for Android >= 47 - ![Chrome](http://browserbadge.com/chrome/47/18px) Chrome >= 47, Chrome for Android >= 47
- ![Safari](http://browserbadge.com/safari/9/18px) Safari >= 9, iOS Safari >= 8.4 - ![Safari](http://browserbadge.com/safari/9/18px) Safari >= 9, iOS Safari >= 8.4
@ -75,30 +48,267 @@ To use CodiMD, your browser should match or exceed these versions:
- ![Opera](http://browserbadge.com/opera/34/18px) Opera >= 34, Opera Mini not supported - ![Opera](http://browserbadge.com/opera/34/18px) Opera >= 34, Opera Mini not supported
- Android Browser >= 4.4 - Android Browser >= 4.4
# Installation
## Related Tools ## Getting started (Native install)
### Prerequisite
- Node.js 6.x or up (test up to 7.5.0)
- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8`
- npm (and its dependencies, especially [uWebSockets](https://github.com/uWebSockets/uWebSockets#nodejs-developers), [node-gyp](https://github.com/nodejs/node-gyp#installation))
- For **building** HackMD we recommend to use a machine with at least **2GB** RAM
### Instructions
1. Download a release and unzip or clone into a directory
2. Enter the directory and type `bin/setup`, which will install npm dependencies and create configs. The setup script is written in Bash, you would need bash as a prerequisite.
3. Setup the configs, see more below
4. Setup environment variables which will overwrite the configs
5. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development)
6. Run the server as you like (node, forever, pm2)
## Heroku Deployment
You can quickly setup a sample heroku hackmd application by clicking the button below.
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
## HackMD by docker container
[![Try in PWD](https://cdn.rawgit.com/play-with-docker/stacks/cff22438/assets/images/button.png)](http://play-with-docker.com?stack=https://github.com/hackmdio/docker-hackmd/raw/master/docker-compose.yml&stack_name=hackmd)
**Debian-based version:**
[![latest](https://images.microbadger.com/badges/version/hackmdio/hackmd.svg)](https://microbadger.com/images/hackmdio/hackmd "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/hackmdio/hackmd.svg)](https://microbadger.com/images/hackmdio/hackmd "Get your own image badge on microbadger.com")
**Alpine-based version:**
[![latest-alpine](https://images.microbadger.com/badges/version/hackmdio/hackmd:latest-alpine.svg)](https://microbadger.com/images/hackmdio/hackmd:latest-alpine "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/hackmdio/hackmd:latest-alpine.svg)](https://microbadger.com/images/hackmdio/hackmd:latest-alpine "Get your own image badge on microbadger.com")
The easiest way to setup HackMD using docker are using the following three commands:
```console
git clone https://github.com/hackmdio/docker-hackmd.git
cd docker-hackmd
docker-compose up
```
Read more about it in the [docker repository…](https://github.com/hackmdio/docker-hackmd)
# Upgrade
## Native setup
If you are upgrading HackMD from an older version, follow these steps:
1. Fully stop your old server first (important)
2. `git pull` or do whatever that updates the files
3. `npm install` to update dependencies
4. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development)
5. Modify the file named `.sequelizerc`, change the value of the variable `url` with your db connection string
For example: `postgres://username:password@localhost:5432/hackmd`
6. Run `node_modules/.bin/sequelize db:migrate`, this step will migrate your db to the latest schema
7. Start your whole new server!
* [migration-to-0.5.0](https://github.com/hackmdio/migration-to-0.5.0)
We don't use LZString to compress socket.io data and DB data after version 0.5.0.
Please run the migration tool if you're upgrading from the old version.
* [migration-to-0.4.0](https://github.com/hackmdio/migration-to-0.4.0)
We've dropped MongoDB after version 0.4.0.
So here is the migration tool for you to transfer the old DB data to the new DB.
This tool is also used for official service.
# Configuration
There are some configs you need to change in the files below
```
./config.json ----application settings
```
## Environment variables (will overwrite other server configs)
| variables | example values | description |
| --------- | ------ | ----------- |
| NODE_ENV | `production` or `development` | set current environment (will apply corresponding settings in the `config.json`) |
| DEBUG | `true` or `false` | set debug mode, show more logs |
| HMD_DOMAIN | `hackmd.io` | domain name |
| HMD_URL_PATH | `hackmd` | sub url path, like `www.example.com/<URL_PATH>` |
| HMD_PORT | `80` | web app port |
| HMD_ALLOW_ORIGIN | `localhost, hackmd.io` | domain name whitelist (use comma to separate) |
| HMD_PROTOCOL_USESSL | `true` or `false` | set to use ssl protocol for resources path (only applied when domain is set) |
| HMD_URL_ADDPORT | `true` or `false` | set to add port on callback url (port 80 or 443 won't applied) (only applied when domain is set) |
| HMD_USECDN | `true` or `false` | set to use CDN resources or not (default is `true`) |
| HMD_ALLOW_ANONYMOUS | `true` or `false` | set to allow anonymous usage (default is `true`) |
| HMD_ALLOW_ANONYMOUS_EDITS | `true` or `false` | if `allowanonymous` is `true`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) |
| HMD_ALLOW_FREEURL | `true` or `false` | set to allow new note by accessing not exist note url |
| HMD_DEFAULT_PERMISSION | `freely`, `editable`, `limited`, `locked` or `private` | set notes default permission (only applied on signed users) |
| HMD_DB_URL | `mysql://localhost:3306/database` | set the db url |
| HMD_FACEBOOK_CLIENTID | no example | Facebook API client id |
| HMD_FACEBOOK_CLIENTSECRET | no example | Facebook API client secret |
| HMD_TWITTER_CONSUMERKEY | no example | Twitter API consumer key |
| HMD_TWITTER_CONSUMERSECRET | no example | Twitter API consumer secret |
| HMD_GITHUB_CLIENTID | no example | GitHub API client id |
| HMD_GITHUB_CLIENTSECRET | no example | GitHub API client secret |
| HMD_GITLAB_SCOPE | `read_user` or `api` | GitLab API requested scope (default is `api`) (gitlab snippet import/export need `api` scope) |
| HMD_GITLAB_BASEURL | no example | GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional) |
| HMD_GITLAB_CLIENTID | no example | GitLab API client id |
| HMD_GITLAB_CLIENTSECRET | no example | GitLab API client secret |
| HMD_MATTERMOST_BASEURL | no example | Mattermost authentication endpoint |
| HMD_MATTERMOST_CLIENTID | no example | Mattermost API client id |
| HMD_MATTERMOST_CLIENTSECRET | no example | Mattermost API client secret |
| HMD_DROPBOX_CLIENTID | no example | Dropbox API client id |
| HMD_DROPBOX_CLIENTSECRET | no example | Dropbox API client secret |
| HMD_GOOGLE_CLIENTID | no example | Google API client id |
| HMD_GOOGLE_CLIENTSECRET | no example | Google API client secret |
| HMD_LDAP_URL | `ldap://example.com` | url of LDAP server |
| HMD_LDAP_BINDDN | no example | bindDn for LDAP access |
| HMD_LDAP_BINDCREDENTIALS | no example | bindCredentials for LDAP access |
| HMD_LDAP_TOKENSECRET | `supersecretkey` | secret used for generating access/refresh tokens |
| HMD_LDAP_SEARCHBASE | `o=users,dc=example,dc=com` | LDAP directory to begin search from |
| HMD_LDAP_SEARCHFILTER | `(uid={{username}})` | LDAP filter to search with |
| HMD_LDAP_SEARCHATTRIBUTES | `displayName, mail` | LDAP attributes to search with (use comma to separate) |
| HMD_LDAP_USERNAMEFIELD | `uid` | The LDAP field which is used as the username on HackMD |
| HMD_LDAP_TLS_CA | `server-cert.pem, root.pem` | Root CA for LDAP TLS in PEM format (use comma to separate) |
| HMD_LDAP_PROVIDERNAME | `My institution` | Optional name to be displayed at login form indicating the LDAP provider |
| HMD_SAML_IDPSSOURL | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](docs/guides/auth.md#saml-onelogin). |
| HMD_SAML_IDPCERT | `/path/to/cert.pem` | certificate file path of IdP in PEM format |
| HMD_SAML_ISSUER | no example | identity of the service provider (optional, default: serverurl)" |
| HMD_SAML_IDENTIFIERFORMAT | no example | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) |
| HMD_SAML_GROUPATTRIBUTE | `memberOf` | attribute name for group list (optional) |
| HMD_SAML_REQUIREDGROUPS | `Hackmd-users` | group names that allowed (use vertical bar to separate) (optional) |
| HMD_SAML_EXTERNALGROUPS | `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) |
| HMD_SAML_ATTRIBUTE_ID | `sAMAccountName` | attribute map for `id` (optional, default: NameID of SAML response) |
| HMD_SAML_ATTRIBUTE_USERNAME | `mailNickname` | attribute map for `username` (optional, default: NameID of SAML response) |
| HMD_SAML_ATTRIBUTE_EMAIL | `mail` | attribute map for `email` (optional, default: NameID of SAML response if `HMD_SAML_IDENTIFIERFORMAT` is default) |
| HMD_IMGUR_CLIENTID | no example | Imgur API client id |
| HMD_EMAIL | `true` or `false` | set to allow email signin |
| HMD_ALLOW_PDF_EXPORT | `true` or `false` | Enable or disable PDF exports |
| HMD_ALLOW_EMAIL_REGISTER | `true` or `false` | set to allow email register (only applied when email is set, default is `true`) |
| HMD_IMAGE_UPLOAD_TYPE | `imgur`, `s3`, `minio` or `filesystem` | Where to upload image. For S3, see our Image Upload Guides for [S3](docs/guides/s3-image-upload.md) or [Minio](docs/guides/minio-image-upload.md) |
| HMD_S3_ACCESS_KEY_ID | no example | AWS access key id |
| HMD_S3_SECRET_ACCESS_KEY | no example | AWS secret key |
| HMD_S3_REGION | `ap-northeast-1` | AWS S3 region |
| HMD_S3_BUCKET | no example | AWS S3 bucket name |
| HMD_MINIO_ACCESS_KEY | no example | Minio access key |
| HMD_MINIO_SECRET_KEY | no example | Minio secret key |
| HMD_MINIO_ENDPOINT | `minio.example.org` | Address of your Minio endpoint/instance |
| HMD_MINIO_PORT | `9000` | Port that is used for your minio instance |
| HMD_MINIO_SECURE | `true` | If set to true HTTPS is used for minio |
| HMD_HSTS_ENABLE | ` true` | set to enable [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) if HTTPS is also enabled (default is ` true`) |
| HMD_HSTS_INCLUDE_SUBDOMAINS | `true` | set to include subdomains in HSTS (default is `true`) |
| HMD_HSTS_MAX_AGE | `31536000` | max duration in seconds to tell clients to keep HSTS status (default is a year) |
| HMD_HSTS_PRELOAD | `true` | whether to allow preloading of the site's HSTS status (e.g. into browsers) |
| HMD_CSP_ENABLE | `true` | whether to enable Content Security Policy (directives cannot be configured with environment variables) |
## Application settings `config.json`
| variables | example values | description |
| --------- | ------ | ----------- |
| debug | `true` or `false` | set debug mode, show more logs |
| domain | `localhost` | domain name |
| urlpath | `hackmd` | sub url path, like `www.example.com/<urlpath>` |
| port | `80` | web app port |
| alloworigin | `['localhost']` | domain name whitelist |
| usessl | `true` or `false` | set to use ssl server (if true will auto turn on `protocolusessl`) |
| hsts | `{"enable": true, "maxAgeSeconds": 31536000, "includeSubdomains": true, "preload": true}` | [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) options to use with HTTPS (default is the example value, max age is a year) |
| csp | `{"enable": true, "directives": {"scriptSrc": "trustworthy-scripts.example.com"}, "upgradeInsecureRequests": "auto", "addDefaults": true}` | Configures [Content Security Policy](https://helmetjs.github.io/docs/csp/). Directives are passed to Helmet - see [their documentation](https://helmetjs.github.io/docs/csp/) for more information on the format. Some defaults are added to the configured values so that the application doesn't break. To disable this behaviour, set `addDefaults` to `false`. Further, if `usecdn` is on, some CDN locations are allowed too. By default (`auto`), insecure (HTTP) requests are upgraded to HTTPS via CSP if `usessl` is on. To change this behaviour, set `upgradeInsecureRequests` to either `true` or `false`. |
| protocolusessl | `true` or `false` | set to use ssl protocol for resources path (only applied when domain is set) |
| urladdport | `true` or `false` | set to add port on callback url (port 80 or 443 won't applied) (only applied when domain is set) |
| usecdn | `true` or `false` | set to use CDN resources or not (default is `true`) |
| allowanonymous | `true` or `false` | set to allow anonymous usage (default is `true`) |
| allowanonymousedits | `true` or `false` | if `allowanonymous` is `true`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) |
| allowfreeurl | `true` or `false` | set to allow new note by accessing not exist note url |
| defaultpermission | `freely`, `editable`, `limited`, `locked`, `protected` or `private` | set notes default permission (only applied on signed users) |
| dburl | `mysql://localhost:3306/database` | set the db url, if set this variable then below db config won't be applied |
| db | `{ "dialect": "sqlite", "storage": "./db.hackmd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) |
| sslkeypath | `./cert/client.key` | ssl key path (only need when you set usessl) |
| sslcertpath | `./cert/hackmd_io.crt` | ssl cert path (only need when you set usessl) |
| sslcapath | `['./cert/COMODORSAAddTrustCA.crt']` | ssl ca chain (only need when you set usessl) |
| dhparampath | `./cert/dhparam.pem` | ssl dhparam path (only need when you set usessl) |
| tmppath | `./tmp/` | temp directory path |
| defaultnotepath | `./public/default.md` | default note file path |
| docspath | `./public/docs` | docs directory path |
| indexpath | `./public/views/index.ejs` | index template file path |
| hackmdpath | `./public/views/hackmd.ejs` | hackmd template file path |
| errorpath | `./public/views/error.ejs` | error template file path |
| prettypath | `./public/views/pretty.ejs` | pretty template file path |
| slidepath | `./public/views/slide.hbs` | slide template file path |
| sessionname | `connect.sid` | cookie session name |
| sessionsecret | `secret` | cookie session secret |
| sessionlife | `14 * 24 * 60 * 60 * 1000` | cookie session life |
| staticcachetime | `1 * 24 * 60 * 60 * 1000` | static file cache time |
| heartbeatinterval | `5000` | socket.io heartbeat interval |
| heartbeattimeout | `10000` | socket.io heartbeat timeout |
| documentmaxlength | `100000` | note max length |
| email | `true` or `false` | set to allow email signin |
| allowemailregister | `true` or `false` | set to allow email register (only applied when email is set, default is `true`) |
| imageUploadType | `imgur`(default), `s3`, `minio` or `filesystem` | Where to upload image
| 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 checkout our [Minio Image Upload Guide](docs/guides/minio-image-upload.md) |
| 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](docs/guides/s3-image-upload.md) |
| s3bucket | `YOUR_S3_BUCKET_NAME` | bucket name when `imageUploadType` is set to `s3` or `minio` |
## Third-party integration api key settings
| service | settings location | description |
| ------- | --------- | ----------- |
| facebook, twitter, github, gitlab, mattermost, dropbox, google, ldap, saml | environment variables or `config.json` | for signin |
| imgur, s3, minio | environment variables or `config.json` | for image upload |
| google drive(`google/apiKey`, `google/clientID`), dropbox(`dropbox/appKey`) | `config.json` | for export and import |
## Third-party integration oauth callback urls
| service | callback url (after the server url) |
| ------- | --------- |
| facebook | `/auth/facebook/callback` |
| twitter | `/auth/twitter/callback` |
| github | `/auth/github/callback` |
| gitlab | `/auth/gitlab/callback` |
| mattermost | `/auth/mattermost/callback` |
| dropbox | `/auth/dropbox/callback` |
| google | `/auth/google/callback` |
| saml | `/auth/saml/callback` |
# Developer Notes
## Structure
```text
hackmd/
├── tmp/ --- temporary files
├── docs/ --- document files
├── lib/ --- server libraries
└── public/ --- client files
├── css/ --- css styles
├── js/ --- js scripts
├── vendor/ --- vendor includes
└── views/ --- view templates
```
## Operational Transformation
From 0.3.2, we started supporting operational transformation.
It makes concurrent editing safe and will not break up other users' operations.
Additionally, now can show other clients' selections.
See more at [http://operational-transformation.github.io/](http://operational-transformation.github.io/)
Our community has created related tools, we'd like to highlight [codimd-cli](https://github.com/codimd/cli)
which lets you use CodiMD from the comfort of your command line.
# License # License
Licensed under AGPLv3. For our list of contributors, see [AUTHORS](AUTHORS). **License under AGPL.**
[matrix.org-image]: https://img.shields.io/badge/Matrix.org-%23CodiMD@matrix.org-green.svg [gitter-image]: https://badges.gitter.im/Join%20Chat.svg
[matrix.org-url]: https://riot.im/app/#/room/#codimd:matrix.org [gitter-url]: https://gitter.im/hackmdio/hackmd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[travis-image]: https://travis-ci.org/codimd/server.svg?branch=master [travis-image]: https://travis-ci.org/hackmdio/hackmd.svg?branch=master
[travis-url]: https://travis-ci.org/codimd/server [travis-url]: https://travis-ci.org/hackmdio/hackmd
[github-version-badge]: https://img.shields.io/github/release/codimd/server.svg [github-version-badge]: https://img.shields.io/github/release/hackmdio/hackmd.svg
[github-release-page]: https://github.com/codimd/server/releases [github-release-page]: https://github.com/hackmdio/hackmd/releases
[github-release-feed]: https://github.com/codimd/server/releases.atom [standardjs-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg
[github-issue-tracker]: https://github.com/codimd/server/issues/ [standardjs-url]: https://github.com/feross/standard
[poeditor-image]: https://img.shields.io/badge/POEditor-translate-blue.svg [codetriage-image]: https://www.codetriage.com/hackmdio/hackmd/badges/users.svg
[poeditor-url]: https://poeditor.com/join/project/1OpGjF2Jir [codetriage-url]: https://www.codetriage.com/hackmdio/hackmd
[codimd-demo]: https://demo.codimd.org
[codimd-demo-features]: https://demo.codimd.org/features
[codimd-community]: https://community.codimd.org
[codimd-community-calls]: https://community.codimd.org/t/codimd-community-call/19
[social-mastodon]: https://social.codimd.org/mastodon
[social-mastodon-image]: https://img.shields.io/badge/social-mastodon-3c99dc.svg

125
app.js
View file

@ -26,23 +26,41 @@ var response = require('./lib/response')
var models = require('./lib/models') var models = require('./lib/models')
var csp = require('./lib/csp') var csp = require('./lib/csp')
// generate front-end constants by template
var constpath = path.join(__dirname, './public/js/lib/common/constant.ejs')
var data = {
domain: config.domain,
urlpath: config.urlpath,
debug: config.debug,
version: config.version,
GOOGLE_API_KEY: config.google.clientSecret,
GOOGLE_CLIENT_ID: config.google.clientID,
DROPBOX_APP_KEY: config.dropbox.appKey,
allowedUploadMimeTypes: config.allowedUploadMimeTypes
}
ejs.renderFile(constpath, data, {}, function (err, str) {
if (err) throw new Error(err)
fs.writeFileSync(path.join(__dirname, './public/build/constant.js'), str)
})
// server setup // server setup
var app = express() var app = express()
var server = null var server = null
if (config.useSSL) { if (config.usessl) {
var ca = (function () { var ca = (function () {
var i, len, results var i, len, results
results = [] results = []
for (i = 0, len = config.sslCAPath.length; i < len; i++) { for (i = 0, len = config.sslcapath.length; i < len; i++) {
results.push(fs.readFileSync(config.sslCAPath[i], 'utf8')) results.push(fs.readFileSync(config.sslcapath[i], 'utf8'))
} }
return results return results
})() })()
var options = { var options = {
key: fs.readFileSync(config.sslKeyPath, 'utf8'), key: fs.readFileSync(config.sslkeypath, 'utf8'),
cert: fs.readFileSync(config.sslCertPath, 'utf8'), cert: fs.readFileSync(config.sslcertpath, 'utf8'),
ca: ca, ca: ca,
dhparam: fs.readFileSync(config.dhParamPath, 'utf8'), dhparam: fs.readFileSync(config.dhparampath, 'utf8'),
requestCert: false, requestCert: false,
rejectUnauthorized: false rejectUnauthorized: false
} }
@ -53,12 +71,12 @@ if (config.useSSL) {
// logger // logger
app.use(morgan('combined', { app.use(morgan('combined', {
'stream': logger.stream 'stream': logger
})) }))
// socket io // socket io
var io = require('socket.io')(server) var io = require('socket.io')(server)
io.engine.ws = new (require('ws').Server)({ io.engine.ws = new (require('uws').Server)({
noServer: true, noServer: true,
perMessageDeflate: false perMessageDeflate: false
}) })
@ -83,22 +101,15 @@ app.use(compression())
// use hsts to tell https users stick to this // use hsts to tell https users stick to this
if (config.hsts.enable) { if (config.hsts.enable) {
app.use(helmet.hsts({ app.use(helmet.hsts({
maxAge: config.hsts.maxAgeSeconds, maxAge: config.hsts.maxAgeSeconds * 1000,
includeSubdomains: config.hsts.includeSubdomains, includeSubdomains: config.hsts.includeSubdomains,
preload: config.hsts.preload preload: config.hsts.preload
})) }))
} else if (config.useSSL) { } else if (config.usessl) {
logger.info('Consider enabling HSTS for extra security:') logger.info('Consider enabling HSTS for extra security:')
logger.info('https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security') logger.info('https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security')
} }
// Add referrer policy to improve privacy
app.use(
helmet.referrerPolicy({
policy: 'same-origin'
})
)
// Generate a random nonce per request, for CSP with inline scripts // Generate a random nonce per request, for CSP with inline scripts
app.use(csp.addNonceToLocals) app.use(csp.addNonceToLocals)
@ -113,11 +124,9 @@ if (config.csp.enable) {
} }
i18n.configure({ i18n.configure({
locales: ['en', 'zh-CN', 'zh-TW', 'fr', 'de', 'ja', 'es', 'ca', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo', 'da', 'ko', 'id', 'sr', 'vi'], locales: ['en', 'zh', 'zh-CN', 'zh-TW', 'fr', 'de', 'ja', 'es', 'ca', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo', 'da'],
cookie: 'locale', cookie: 'locale',
indent: ' ', // this is the style poeditor.com exports it, this creates less churn directory: path.join(__dirname, '/locales')
directory: path.join(__dirname, '/locales'),
updateFiles: config.updateI18nFiles
}) })
app.use(cookieParser()) app.use(cookieParser())
@ -126,20 +135,17 @@ app.use(i18n.init)
// routes without sessions // routes without sessions
// static files // static files
app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.staticCacheTime, index: false })) app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.staticcachetime }))
app.use('/docs', express.static(path.resolve(__dirname, config.docsPath), { maxAge: config.staticCacheTime }))
app.use('/uploads', express.static(path.resolve(__dirname, config.uploadsPath), { maxAge: config.staticCacheTime }))
app.use('/default.md', express.static(path.resolve(__dirname, config.defaultNotePath), { maxAge: config.staticCacheTime }))
// session // session
app.use(session({ app.use(session({
name: config.sessionName, name: config.sessionname,
secret: config.sessionSecret, secret: config.sessionsecret,
resave: false, // don't save session if unmodified resave: false, // don't save session if unmodified
saveUninitialized: true, // always create session to ensure the origin saveUninitialized: true, // always create session to ensure the origin
rolling: true, // reset maxAge on every response rolling: true, // reset maxAge on every response
cookie: { cookie: {
maxAge: config.sessionLife maxAge: config.sessionlife
}, },
store: sessionStore store: sessionStore
})) }))
@ -167,44 +173,14 @@ app.use(passport.session())
app.use(require('./lib/web/middleware/checkURIValid')) app.use(require('./lib/web/middleware/checkURIValid'))
// redirect url without trailing slashes // redirect url without trailing slashes
app.use(require('./lib/web/middleware/redirectWithoutTrailingSlashes')) app.use(require('./lib/web/middleware/redirectWithoutTrailingSlashes'))
app.use(require('./lib/web/middleware/codiMDVersion'))
// routes need sessions // routes need sessions
// template files // template files
app.set('views', config.viewPath) app.set('views', path.join(__dirname, '/public/views'))
// set render engine // set render engine
app.engine('ejs', ejs.renderFile) app.engine('ejs', ejs.renderFile)
// set view engine // set view engine
app.set('view engine', 'ejs') app.set('view engine', 'ejs')
// set generally available variables for all views
app.locals.useCDN = config.useCDN
app.locals.serverURL = config.serverURL
app.locals.sourceURL = config.sourceURL
app.locals.allowAnonymous = config.allowAnonymous
app.locals.allowAnonymousEdits = config.allowAnonymousEdits
app.locals.allowPDFExport = config.allowPDFExport
app.locals.authProviders = {
facebook: config.isFacebookEnable,
twitter: config.isTwitterEnable,
github: config.isGitHubEnable,
gitlab: config.isGitLabEnable,
mattermost: config.isMattermostEnable,
dropbox: config.isDropboxEnable,
google: config.isGoogleEnable,
ldap: config.isLDAPEnable,
ldapProviderName: config.ldap.providerName,
saml: config.isSAMLEnable,
oauth2: config.isOAuth2Enable,
oauth2ProviderName: config.oauth2.providerName,
openID: config.isOpenIDEnable,
email: config.isEmailEnable,
allowEmailRegister: config.allowEmailRegister
}
// Export/Import menu items
app.locals.enableDropBoxSave = config.isDropboxEnable
app.locals.enableGitHubGist = config.isGitHubEnable
app.locals.enableGitlabSnippets = config.isGitlabSnippetsEnable
app.use(require('./lib/web/baseRouter')) app.use(require('./lib/web/baseRouter'))
app.use(require('./lib/web/statusRouter')) app.use(require('./lib/web/statusRouter'))
@ -224,35 +200,25 @@ io.use(realtime.secure)
// socket.io auth // socket.io auth
io.use(passportSocketIo.authorize({ io.use(passportSocketIo.authorize({
cookieParser: cookieParser, cookieParser: cookieParser,
key: config.sessionName, key: config.sessionname,
secret: config.sessionSecret, secret: config.sessionsecret,
store: sessionStore, store: sessionStore,
success: realtime.onAuthorizeSuccess, success: realtime.onAuthorizeSuccess,
fail: realtime.onAuthorizeFail fail: realtime.onAuthorizeFail
})) }))
// socket.io heartbeat // socket.io heartbeat
io.set('heartbeat interval', config.heartbeatInterval) io.set('heartbeat interval', config.heartbeatinterval)
io.set('heartbeat timeout', config.heartbeatTimeout) io.set('heartbeat timeout', config.heartbeattimeout)
// socket.io connection // socket.io connection
io.sockets.on('connection', realtime.connection) io.sockets.on('connection', realtime.connection)
// listen // listen
function startListen () { function startListen () {
var address server.listen(config.port, function () {
var listenCallback = function () { var schema = config.usessl ? 'HTTPS' : 'HTTP'
var schema = config.useSSL ? 'HTTPS' : 'HTTP' logger.info('%s Server listening at port %d', schema, config.port)
logger.info('%s Server listening at %s', schema, address)
realtime.maintenance = false realtime.maintenance = false
} })
// use unix domain socket if 'path' is specified
if (config.path) {
address = config.path
server.listen(config.path, listenCallback)
} else {
address = config.host + ':' + config.port
server.listen(config.port, config.host, listenCallback)
}
} }
// sync db then start listen // sync db then start listen
@ -278,7 +244,7 @@ process.on('uncaughtException', function (err) {
// install exit handler // install exit handler
function handleTermSignals () { function handleTermSignals () {
logger.info('CodiMD has been killed by signal, try to exit gracefully...') logger.info('hackmd has been killed by signal, try to exit gracefully...')
realtime.maintenance = true realtime.maintenance = true
// disconnect all socket.io clients // disconnect all socket.io clients
Object.keys(io.sockets.sockets).forEach(function (key) { Object.keys(io.sockets.sockets).forEach(function (key) {
@ -289,9 +255,6 @@ function handleTermSignals () {
socket.disconnect(true) socket.disconnect(true)
}, 0) }, 0)
}) })
if (config.path) {
fs.unlink(config.path)
}
var checkCleanTimer = setInterval(function () { var checkCleanTimer = setInterval(function () {
if (realtime.isReady()) { if (realtime.isReady()) {
models.Revision.checkAllNotesRevision(function (err, notes) { models.Revision.checkAllNotesRevision(function (err, notes) {

View file

@ -1,16 +1,20 @@
{ {
"name": "CodiMD", "name": "HackMD",
"description": "Realtime collaborative markdown notes on all platforms", "description": "Realtime collaborative markdown notes on all platforms",
"keywords": [ "keywords": [
"Collaborative", "Collaborative",
"Markdown", "Markdown",
"Notes" "Notes"
], ],
"website": "https://codimd.org", "website": "https://hackmd.io",
"repository": "https://github.com/codimd/server", "repository": "https://github.com/hackmdio/hackmd",
"logo": "https://github.com/codimd/server/raw/master/public/codimd-icon-1024.png", "logo": "https://github.com/hackmdio/hackmd/raw/master/public/hackmd-icon-1024.png",
"success_url": "/", "success_url": "/",
"env": { "env": {
"BUILD_ASSETS": {
"description": "Our build script variable",
"value": "true"
},
"NPM_CONFIG_PRODUCTION": { "NPM_CONFIG_PRODUCTION": {
"description": "Let npm also install development build tool", "description": "Let npm also install development build tool",
"value": "false" "value": "false"
@ -19,129 +23,137 @@
"description": "Specify database type. See sequelize available databases. Default using postgres", "description": "Specify database type. See sequelize available databases. Default using postgres",
"value": "postgres" "value": "postgres"
}, },
"CMD_SESSION_SECRET": { "HMD_HSTS_ENABLE": {
"description": "Secret used to secure session cookies.",
"required": false
},
"CMD_HSTS_ENABLE": {
"description": "whether to also use HSTS if HTTPS is enabled", "description": "whether to also use HSTS if HTTPS is enabled",
"required": false "required": false
}, },
"CMD_HSTS_MAX_AGE": { "HMD_HSTS_MAX_AGE": {
"description": "max duration, in seconds, to tell clients to keep HSTS status", "description": "max duration, in seconds, to tell clients to keep HSTS status",
"required": false "required": false
}, },
"CMD_HSTS_INCLUDE_SUBDOMAINS": { "HMD_HSTS_INCLUDE_SUBDOMAINS": {
"description": "whether to tell clients to also regard subdomains as HSTS hosts", "description": "whether to tell clients to also regard subdomains as HSTS hosts",
"required": false "required": false
}, },
"CMD_HSTS_PRELOAD": { "HMD_HSTS_PRELOAD": {
"description": "whether to allow at all adding of the site to HSTS preloads (e.g. in browsers)", "description": "whether to allow at all adding of the site to HSTS preloads (e.g. in browsers)",
"required": false "required": false
}, },
"CMD_DOMAIN": { "HMD_DOMAIN": {
"description": "domain name", "description": "domain name",
"required": false "required": false
}, },
"CMD_URL_PATH": { "HMD_URL_PATH": {
"description": "sub url path, like `www.example.com/<URL_PATH>`", "description": "sub url path, like `www.example.com/<URL_PATH>`",
"required": false "required": false
}, },
"CMD_ALLOW_ORIGIN": { "HMD_ALLOW_ORIGIN": {
"description": "domain name whitelist (use comma to separate)", "description": "domain name whitelist (use comma to separate)",
"required": false, "required": false,
"value": "localhost" "value": "localhost"
}, },
"CMD_PROTOCOL_USESSL": { "HMD_PROTOCOL_USESSL": {
"description": "set to use ssl protocol for resources path (only applied when domain is set)", "description": "set to use ssl protocol for resources path (only applied when domain is set)",
"required": false "required": false
}, },
"CMD_URL_ADDPORT": { "HMD_URL_ADDPORT": {
"description": "set to add port on callback url (port 80 or 443 won't applied) (only applied when domain is set)", "description": "set to add port on callback url (port 80 or 443 won't applied) (only applied when domain is set)",
"required": false "required": false
}, },
"CMD_FACEBOOK_CLIENTID": { "HMD_FACEBOOK_CLIENTID": {
"description": "Facebook API client id", "description": "Facebook API client id",
"required": false "required": false
}, },
"CMD_FACEBOOK_CLIENTSECRET": { "HMD_FACEBOOK_CLIENTSECRET": {
"description": "Facebook API client secret", "description": "Facebook API client secret",
"required": false "required": false
}, },
"CMD_TWITTER_CONSUMERKEY": { "HMD_TWITTER_CONSUMERKEY": {
"description": "Twitter API consumer key", "description": "Twitter API consumer key",
"required": false "required": false
}, },
"CMD_TWITTER_CONSUMERSECRET": { "HMD_TWITTER_CONSUMERSECRET": {
"description": "Twitter API consumer secret", "description": "Twitter API consumer secret",
"required": false "required": false
}, },
"CMD_GITHUB_CLIENTID": { "HMD_GITHUB_CLIENTID": {
"description": "GitHub API client id", "description": "GitHub API client id",
"required": false "required": false
}, },
"CMD_GITHUB_CLIENTSECRET": { "HMD_GITHUB_CLIENTSECRET": {
"description": "GitHub API client secret", "description": "GitHub API client secret",
"required": false "required": false
}, },
"CMD_GITLAB_BASEURL": { "HMD_GITLAB_BASEURL": {
"description": "GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional)", "description": "GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional)",
"required": false "required": false
}, },
"CMD_GITLAB_CLIENTID": { "HMD_GITLAB_CLIENTID": {
"description": "GitLab API client id", "description": "GitLab API client id",
"required": false "required": false
}, },
"CMD_GITLAB_CLIENTSECRET": { "HMD_GITLAB_CLIENTSECRET": {
"description": "GitLab API client secret", "description": "GitLab API client secret",
"required": false "required": false
}, },
"CMD_GITLAB_SCOPE": { "HMD_GITLAB_SCOPE": {
"description": "GitLab API client scope (optional)", "description": "GitLab API client scope (optional)",
"required": false "required": false
}, },
"CMD_MATTERMOST_BASEURL": { "HMD_MATTERMOST_BASEURL": {
"description": "Mattermost authentication endpoint", "description": "Mattermost authentication endpoint",
"required": false "required": false
}, },
"CMD_MATTERMOST_CLIENTID": { "HMD_MATTERMOST_CLIENTID": {
"description": "Mattermost API client id", "description": "Mattermost API client id",
"required": false "required": false
}, },
"CMD_MATTERMOST_CLIENTSECRET": { "HMD_MATTERMOST_CLIENTSECRET": {
"description": "Mattermost API client secret", "description": "Mattermost API client secret",
"required": false "required": false
}, },
"CMD_DROPBOX_CLIENTID": { "HMD_DROPBOX_CLIENTID": {
"description": "Dropbox API client id", "description": "Dropbox API client id",
"required": false "required": false
}, },
"CMD_DROPBOX_CLIENTSECRET": { "HMD_DROPBOX_CLIENTSECRET": {
"description": "Dropbox API client secret", "description": "Dropbox API client secret",
"required": false "required": false
}, },
"CMD_DROPBOX_APP_KEY": { "HMD_DROPBOX_APP_KEY": {
"description": "Dropbox app key (for import/export)", "description": "Dropbox app key (for import/export)",
"required": false "required": false
}, },
"CMD_GOOGLE_CLIENTID": { "HMD_GOOGLE_CLIENTID": {
"description": "Google API client id", "description": "Google API client id",
"required": false "required": false
}, },
"CMD_GOOGLE_CLIENTSECRET": { "HMD_GOOGLE_CLIENTSECRET": {
"description": "Google API client secret", "description": "Google API client secret",
"required": false "required": false
}, },
"CMD_IMGUR_CLIENTID": { "HMD_GOOGLE_API_KEY": {
"description": "Google API key (for import/export)",
"required": false
},
"HMD_IMGUR_CLIENTID": {
"description": "Imgur API client id", "description": "Imgur API client id",
"required": false "required": false
}, },
"CMD_ALLOW_PDF_EXPORT": { "HMD_ALLOW_PDF_EXPORT": {
"description": "Enable or disable PDF exports", "description": "Enable or disable PDF exports",
"required": false "required": false
} }
}, },
"addons": [ "addons": [
"heroku-postgresql" "heroku-postgresql"
],
"buildpacks": [
{
"url": "https://github.com/alex88/heroku-buildpack-vips"
},
{
"url": "https://github.com/heroku/heroku-buildpack-nodejs"
}
] ]
} }

View file

@ -2,7 +2,9 @@
set -e set -e
cat << EOF > .sequelizerc if [ "$BUILD_ASSETS" = true ]; then
# setup config files
cat << EOF > .sequelizerc
var path = require('path'); var path = require('path');
module.exports = { module.exports = {
@ -15,7 +17,7 @@ module.exports = {
EOF EOF
cat << EOF > config.json cat << EOF > config.json
{ {
"production": { "production": {
@ -23,3 +25,7 @@ cat << EOF > config.json
} }
EOF EOF
# build app
npm run build
fi

View file

@ -1,119 +0,0 @@
#!/usr/bin/env node
// First configure the logger so it does not spam the console
const logger = require("../lib/logger");
logger.transports.forEach((transport) => transport.level = "warning")
const models = require("../lib/models/");
const readline = require("readline-sync");
const minimist = require("minimist");
function showUsage(tips) {
console.log(`${tips}
Command-line utility to create users for email-signin.
Usage: bin/manage_users [--pass password] (--add | --del) user-email
Options:
--add Add user with the specified user-email
--del Delete user with specified user-email
--reset Reset user password with specified user-email
--pass Use password from cmdline rather than prompting
`);
process.exit(1);
}
function getPass(argv, action) {
// Find whether we use cmdline or prompt password
if(typeof argv["pass"] !== 'string') {
return readline.question(`Password for ${argv[action]}:`, {hideEchoBack: true});
}
console.log("Using password from commandline...");
return argv["pass"];
}
// Using an async function to be able to use await inside
async function createUser(argv) {
const existing_user = await models.User.findOne({where: {email: argv["add"]}});
// Cannot create already-existing users
if(existing_user != undefined) {
console.log(`User with e-mail ${existing_user.email} already exists! Aborting ...`);
process.exit(1);
}
const pass = getPass(argv, "add");
// Lets try to create, and check success
const ref = await models.User.create({email: argv["add"], password: pass});
if(ref == undefined) {
console.log(`Could not create user with email ${argv["add"]}`);
process.exit(1);
} else
console.log(`Created user with email ${argv["add"]}`);
}
// Using an async function to be able to use await inside
async function deleteUser(argv) {
// Cannot delete non-existing users
const existing_user = await models.User.findOne({where: {email: argv["del"]}});
if(existing_user === undefined) {
console.log(`User with e-mail ${argv["del"]} does not exist, cannot delete`);
process.exit(1);
}
// Sadly .destroy() does not return any success value with all
// backends. See sequelize #4124
await existing_user.destroy();
console.log(`Deleted user ${argv["del"]} ...`);
}
// Using an async function to be able to use await inside
async function resetUser(argv) {
const existing_user = await models.User.findOne({where: {email: argv["reset"]}});
// Cannot reset non-existing users
if(existing_user == undefined) {
console.log(`User with e-mail ${argv["reset"]} does not exist, cannot reset`);
process.exit(1);
}
const pass = getPass(argv, "reset");
// set password and save
existing_user.password = pass;
await existing_user.save();
console.log(`User with email ${argv["reset"]} password has been reset`);
}
const options = {
add: createUser,
del: deleteUser,
reset: resetUser,
};
// Perform commandline-parsing
const argv = minimist(process.argv.slice(2));
const keys = Object.keys(options);
const opts = keys.filter((key) => argv[key] !== undefined);
const action = opts[0];
// Check for options missing
if (opts.length === 0) {
showUsage(`You did not specify either ${keys.map((key) => `--${key}`).join(' or ')}!`);
}
// Check if both are specified
if (opts.length > 1) {
showUsage(`You cannot ${action.join(' and ')} at the same time!`);
}
// Check if not string
if (typeof argv[action] !== 'string') {
showUsage(`You must follow an email after --${action}`);
}
// Call respective processing functions
options[action](argv).then(function() {
process.exit(0);
});

View file

@ -8,12 +8,11 @@ if [ -d .git ]; then
cd "$(git rev-parse --show-toplevel)" cd "$(git rev-parse --show-toplevel)"
fi fi
if ! type yarn > /dev/null if ! type npm > /dev/null
then then
cat << EOF cat << EOF
yarn is not installed, please install Node.js, npm and yarn. npm is not installed, please install Node.js and npm.
Read more on Node.js official website: https://nodejs.org Read more on Node.js official website: https://nodejs.org
And for yarn package manager at: https://yarnpkg.com/en/
Setup will not be run Setup will not be run
EOF EOF
exit 0 exit 0
@ -28,17 +27,17 @@ if [ ! -f .sequelizerc ]; then
cp .sequelizerc.example .sequelizerc cp .sequelizerc.example .sequelizerc
fi fi
echo "install packages" echo "install npm packages"
yarn install --pure-lockfile BUILD_ASSETS=false npm install
yarn install --production=false --pure-lockfile
cat << EOF cat << EOF
Edit the following config file to setup CodiMD server and client. Edit the following config file to setup hackmd server and client.
Read more info at https://github.com/codimd/server#configuration-files Read more info at https://github.com/hackmdio/hackmd#configuration-files
* config.json -- CodiMD config * config.json -- server config
* public/js/config.js -- client config
* .sequelizerc -- db config * .sequelizerc -- db config
EOF EOF

View file

@ -6,37 +6,33 @@
} }
}, },
"development": { "development": {
"loglevel": "debug",
"hsts": { "hsts": {
"enable": false "enable": false
}, },
"db": { "db": {
"dialect": "sqlite", "dialect": "sqlite",
"storage": "./db.codimd.sqlite" "storage": "./db.hackmd.sqlite"
} }
}, },
"production": { "production": {
"domain": "localhost", "domain": "localhost",
"loglevel": "info",
"hsts": { "hsts": {
"enable": true, "enable": true,
"maxAgeSeconds": 31536000, "maxAgeSeconds": "31536000",
"includeSubdomains": true, "includeSubdomains": true,
"preload": true "preload": true
}, },
"csp": { csp: {
"enable": true, "enable": true,
"directives": { "directives": {
}, },
"upgradeInsecureRequests": "auto", "upgradeInsecureRequests": "auto"
"addDefaults": true, "addDefaults": true
"addDisqus": true,
"addGoogleAnalytics": true
}, },
"db": { "db": {
"username": "", "username": "",
"password": "", "password": "",
"database": "codimd", "database": "hackmd",
"host": "localhost", "host": "localhost",
"port": "5432", "port": "5432",
"dialect": "postgres" "dialect": "postgres"
@ -57,8 +53,7 @@
"baseURL": "change this", "baseURL": "change this",
"clientID": "change this", "clientID": "change this",
"clientSecret": "change this", "clientSecret": "change this",
"scope": "use 'read_user' scope for auth user only or remove this property if you need gitlab snippet import/export support (will result to be default scope 'api')", "scope": "use 'read_user' scope for auth user only or remove this property if you need gitlab snippet import/export support (will result to be default scope 'api')"
"version": "use 'v4' if gitlab version > 11, 'v3' otherwise. Default to 'v4'"
}, },
"mattermost": { "mattermost": {
"baseURL": "change this", "baseURL": "change this",
@ -79,11 +74,11 @@
"url": "ldap://change_this", "url": "ldap://change_this",
"bindDn": null, "bindDn": null,
"bindCredentials": null, "bindCredentials": null,
"tokenSecret": "change this",
"searchBase": "change this", "searchBase": "change this",
"searchFilter": "change this", "searchFilter": "change this",
"searchAttributes": ["change this"], "searchAttributes": ["change this"],
"usernameField": "change this e.g. cn", "usernameField": "change this e.g. uid",
"useridField": "change this e.g. uid",
"tlsOptions": { "tlsOptions": {
"changeme": "See https://nodejs.org/api/tls.html#tls_tls_connect_options_callback" "changeme": "See https://nodejs.org/api/tls.html#tls_tls_connect_options_callback"
} }
@ -93,7 +88,6 @@
"idpCert": "change: certificate file path of IdP in PEM format", "idpCert": "change: certificate file path of IdP in PEM format",
"issuer": "change or delete: identity of the service provider (default: serverurl)", "issuer": "change or delete: identity of the service provider (default: serverurl)",
"identifierFormat": "change or delete: name identifier format (default: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress')", "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)",
"groupAttribute": "change or delete: attribute name for group list (ex: memberOf)", "groupAttribute": "change or delete: attribute name for group list (ex: memberOf)",
"requiredGroups": [ "change or delete: group names that allowed" ], "requiredGroups": [ "change or delete: group names that allowed" ],
"externalGroups": [ "change or delete: group names that not allowed" ], "externalGroups": [ "change or delete: group names that not allowed" ],
@ -118,11 +112,6 @@
"secretAccessKey": "change this", "secretAccessKey": "change this",
"region": "change this" "region": "change this"
}, },
"s3bucket": "change this", "s3bucket": "change this"
"azure":
{
"connectionString": "change this",
"container": "change this"
}
} }
} }

View file

@ -1,154 +0,0 @@
Configuration Using Config file
===
You can choose to configure CodiMD with either a config file or with
[environment variables](configuration-env-vars.md). The config file is processed
in [`lib/config/index.js`](../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`](../lib/config/default.js),
in case you wonder if you even need to override it.
Environment variables take precedence over configurations from the config files.
To get started, it is a good idea to take the `config.json.example` and copy it
to `config.json` before filling in your own details.
## Node.JS
| variables | example values | description |
| --------- | ------ | ----------- |
| `debug` | `true` or `false` | set debug mode, show more logs |
## CodiMD basics
| variables | example values | description |
| --------- | ------ | ----------- |
| `allowPDFExport` | `true` | Whether or not PDF export is offered. |
| `db` | `{ "dialect": "sqlite", "storage": "./db.codimd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) |
| `dbURL` | `mysql://localhost:3306/database` | set the db URL; if set, then db config (below) won't be applied |
| `forbiddenNoteIDs` | `['robots.txt']` | disallow creation of notes, even if `allowFreeUrl` is `true` |
| `loglevel` | `info` | Defines what kind of logs are provided to stdout. |
| `imageUploadType` | `imgur`, `s3`, `minio`, `azure`, `lutim` or `filesystem`(default) | 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)|
| `sourceURL` | `https://github.com/codimd/server/tree/<current commit>` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) |
| `staticCacheTime` | `1 * 24 * 60 * 60 * 1000` | static file cache time |
| `tooBusyLag` | `70` | CPU time for one eventloop tick until node throttles connections. (milliseconds) |
| `heartbeatInterval` | `5000` | socket.io heartbeat interval |
| `heartbeatTimeout` | `10000` | socket.io heartbeat timeout |
| `documentMaxLength` | `100000` | note max length |
## CodiMD paths stuff
these are rarely used for various reasons.
| variables | example values | description |
| --------- | ------ | ----------- |
| `defaultNotePath` | `./public/default.md` | default note file path<sup>1</sup>, empty notes will be created with this template. |
| `dhParamPath` | `./cert/dhparam.pem` | SSL dhparam path<sup>1</sup> (only need when you set `useSSL`) |
| `sslCAPath` | `['./cert/COMODORSAAddTrustCA.crt']` | SSL ca chain<sup>1</sup> (only need when you set `useSSL`) |
| `sslCertPath` | `./cert/codimd_io.crt` | SSL cert path<sup>1</sup> (only need when you set `useSSL`) |
| `sslKeyPath` | `./cert/client.key` | SSL key path<sup>1</sup> (only need when you set `useSSL`) |
| `tmpPath` | `./tmp/` | temp directory path<sup>1</sup> |
| `docsPath` | `./public/docs` | docs directory path<sup>1</sup> |
| `viewPath` | `./public/views` | template directory path<sup>1</sup> |
| `uploadsPath` | `./public/uploads` | uploads directory<sup>1</sup> - needs to be persistent when you use imageUploadType `filesystem` |
## CodiMD Location
| variables | example values | description |
| --------- | ------ | ----------- |
| `domain` | `localhost` | domain name |
| `urlPath` | `codimd` | sub URL path, like `www.example.com/<urlpath>` |
| `host` | `localhost` | interface/ip to listen on |
| `port` | `80` | port to listen on |
| `path` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `host` and `port` are ignored) |
| `protocolUseSSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) |
| `useSSL` | `true` or `false` | set to use SSL server (if `true`, will auto turn on `protocolUseSSL`) |
| `urlAddPort` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) |
| `allowOrigin` | `['localhost']` | domain name whitelist |
## CSP and HSTS
| variables | example values | description |
| --------- | ------ | ----------- |
| `hsts` | `{"enable": true, "maxAgeSeconds": 31536000, "includeSubdomains": true, "preload": true}` | [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) options to use with HTTPS (default is the example value, max age is a year) |
| `csp` | `{"enable": true, "directives": {"scriptSrc": "trustworthy-scripts.example.com"}, "upgradeInsecureRequests": "auto", "addDefaults": true}` | Configures [Content Security Policy](https://helmetjs.github.io/docs/csp/). Directives are passed to Helmet - see [their documentation](https://helmetjs.github.io/docs/csp/) for more information on the format. Some defaults are added to the configured values so that the application doesn't break. To disable this behaviour, set `addDefaults` to `false`. Further, if `usecdn` is on, some CDN locations are allowed too. By default (`auto`), insecure (HTTP) requests are upgraded to HTTPS via CSP if `useSSL` is on. To change this behaviour, set `upgradeInsecureRequests` to either `true` or `false`. |
## Privacy and External Requests
| variables | example values | description |
| --------- | ------ | ----------- |
| `allowGravatar` | `true` or `false` | set to `false` to disable gravatar as profile picture source on your instance |
| `useCDN` | `true` or `false` | set to use CDN resources or not (default is `true`) |
## Users and Privileges
| variables | example values | description |
| --------- | ------ | ----------- |
| `allowAnonymous` | `true` or `false` | set to allow anonymous usage (default is `true`) |
| `allowAnonymousEdits` | `true` or `false` | if `allowAnonymous` is `true`: allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) |
| `allowFreeURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL |
| `defaultPermission` | `freely`, `editable`, `limited`, `locked`, `protected` or `private` | set notes default permission (only applied on signed users) |
| `sessionName` | `connect.sid` | cookie session name |
| `sessionLife` | `14 * 24 * 60 * 60 * 1000` | cookie session life |
| `sessionSecret` | `secret` | cookie session secret | If none is set, one will randomly generated on each startup, meaning all your users will be logged out. |
## Login methods
Most of these have never been documented for the config.json, feel free to expand these
### Email (local account)
| variables | example values | description |
| --------- | ------ | ----------- |
| `email` | `true` or `false` | set to allow email signin |
| `allowEmailRegister` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) |
### Dropbox Login
### Facebook Login
### GitHub Login
### GitLab Login
### Google Login
### LDAP Login
### Mattermost Login
### OAuth2 Login
| variables | example values | description |
| --------- | ------ | ----------- |
| `oauth2` | `{baseURL: ..., userProfileURL: ..., userProfileUsernameAttr: ..., userProfileDisplayNameAttr: ..., userProfileEmailAttr: ..., tokenURL: ..., authorizationURL: ..., clientID: ..., clientSecret: ...}` | 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!|
### SAML Login
### Twitter Login
## Upload Storage
Most of these have never been documented for the config.json, feel free to expand these
### Amazon S3
| variables | example values | 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) |
| `s3bucket` | `YOUR_S3_BUCKET_NAME` | bucket name when `imageUploadType` is set to `s3` or `minio` |
### Azure Blob Storage
### imgur
### Minio
| variables | example values | 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) |
### Lutim
| variables | example values | description |
| --------- | ------ | ----------- |
|`lutim`| `{"url": "YOUR_LUTIM_URL"}`| When `imageUploadType` is set to `lutim`, you can setup the lutim url|
<sup>1</sup>: relative paths are based on CodiMD's base directory

View file

@ -1,256 +0,0 @@
Configuration Using Environment variables
===
You can choose to configure CodiMD with either a
[config file](configuration-config-file.md) or with environment variables.
Environment variables are processed in
[`lib/config/environment.js`](../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`](../lib/config/default.js),
in case you wonder if you even need to override it.
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.
## Node.JS
| variable | example value | description |
| -------- | ------------- | ----------- |
| `NODE_ENV` | `production` or `development` | set current environment (will apply corresponding settings in the `config.json`) |
| `DEBUG` | `true` or `false` | set debug mode; show more logs |
## CodiMD basics
defaultNotePath can't be set from env-vars
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_ALLOW_PDF_EXPORT` | `true` or `false` | Enable or disable PDF exports |
| `CMD_CONFIG_FILE` | `/path/to/config.json` | optional override for the path to CodiMD's config file |
| `CMD_DB_URL` | `mysql://localhost:3306/database` | set the database URL |
| `CMD_LOGLEVEL` | `info`, `debug` ... | Defines what kind of logs are provided to stdout. |
| `CMD_FORBIDDEN_NOTE_IDS` | `'robots.txt'` | disallow creation of notes, even if `CMD_ALLOW_FREEURL` is `true` |
| `CMD_IMAGE_UPLOAD_TYPE` | `imgur`, `s3`, `minio`, `lutim` or `filesystem` | 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. |
| `CMD_SOURCE_URL` | `https://github.com/codimd/server/tree/<current commit>` | Provides the link to the source code of CodiMD on the entry page (Please, make sure you change this when you run a modified version) |
| `CMD_TOOBUSY_LAG` | `70` | CPU time for one eventloop tick until node throttles connections. (milliseconds) |
## CodiMD Location
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_DOMAIN` | `codimd.org` | domain name |
| `CMD_URL_PATH` | `codimd` | If CodiMD is run from a subdirectory like `www.example.com/<urlpath>` |
| `CMD_HOST` | `localhost` | interface/ip to listen on |
| `CMD_PORT` | `80` | port to listen on |
| `CMD_PATH` | `/var/run/codimd.sock` | path to UNIX domain socket to listen on (if specified, `CMD_HOST` and `CMD_PORT` are ignored) |
| `CMD_PROTOCOL_USESSL` | `true` or `false` | set to use SSL protocol for resources path (only applied when domain is set) |
| `CMD_URL_ADDPORT` | `true` or `false` | set to add port on callback URL (ports `80` or `443` won't be applied) (only applied when domain is set) |
| `CMD_ALLOW_ORIGIN` | `localhost, codimd.org` | domain name whitelist (use comma to separate) |
## CSP and HSTS
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_CSP_ENABLE` | `true` | whether to enable Content Security Policy (directives cannot be configured with environment variables) |
| `CMD_CSP_REPORTURI` | `https://<someid>.report-uri.com/r/d/csp/enforce` | Allows to add a URL for CSP reports in case of violations |
| `CMD_HSTS_ENABLE` | ` true` | set to enable [HSTS](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) if HTTPS is also enabled (default is ` true`) |
| `CMD_HSTS_INCLUDE_SUBDOMAINS` | `true` | set to include subdomains in HSTS (default is `true`) |
| `CMD_HSTS_MAX_AGE` | `31536000` | max duration in seconds to tell clients to keep HSTS status (default is a year) |
| `CMD_HSTS_PRELOAD` | `true` | whether to allow preloading of the site's HSTS status (e.g. into browsers) |
## Privacy and External Requests
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_ALLOW_GRAVATAR` | `true` or `false` | set to `false` to disable gravatar as profile picture source on your instance |
| `CMD_USECDN` | `true` or `false` | set to use CDN resources or not|
## Users and Privileges
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_ALLOW_ANONYMOUS` | `true` or `false` | set to allow anonymous usage (default is `true`) |
| `CMD_ALLOW_ANONYMOUS_EDITS` | `true` or `false` | if `allowAnonymous` is `true`, allow users to select `freely` permission, allowing guests to edit existing notes (default is `false`) |
| `CMD_ALLOW_FREEURL` | `true` or `false` | set to allow new note creation by accessing a nonexistent note URL |
| `CMD_DEFAULT_PERMISSION` | `freely`, `editable`, `limited`, `locked` or `private` | set notes default permission (only applied on signed users) |
| `CMD_SESSION_LIFE` | `1209600000` | Session life time. (milliseconds) |
| `CMD_SESSION_SECRET` | no example | 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. |
## Login methods
### Email (local account)
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_EMAIL` | `true` or `false` | set to allow email signin |
| `CMD_ALLOW_EMAIL_REGISTER` | `true` or `false` | set to allow email register (only applied when email is set, default is `true`. Note `bin/manage_users` might help you if registration is `false`.) |
### Dropbox Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_DROPBOX_CLIENTID` | no example | Dropbox API client id |
| `CMD_DROPBOX_CLIENTSECRET` | no example | Dropbox API client secret |
### Facebook Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_FACEBOOK_CLIENTID` | no example | Facebook API client id |
| `CMD_FACEBOOK_CLIENTSECRET` | no example | Facebook API client secret |
### GitHub Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_GITHUB_CLIENTID` | no example | GitHub API client id |
| `CMD_GITHUB_CLIENTSECRET` | no example | GitHub API client secret |
### GitLab Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_GITLAB_SCOPE` | `read_user` or `api` | GitLab API requested scope (default is `api`) (GitLab snippet import/export need `api` scope) |
| `CMD_GITLAB_BASEURL` | no example | GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional) |
| `CMD_GITLAB_CLIENTID` | no example | GitLab API client id |
| `CMD_GITLAB_CLIENTSECRET` | no example | GitLab API client secret |
| `CMD_GITLAB_VERSION` | no example | GitLab API version (v3 or v4) |
### Google Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_GOOGLE_CLIENTID` | no example | Google API client id |
| `CMD_GOOGLE_CLIENTSECRET` | no example | Google API client secret |
### LDAP Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_LDAP_URL` | `ldap://example.com` | URL of LDAP server |
| `CMD_LDAP_BINDDN` | no example | bindDn for LDAP access |
| `CMD_LDAP_BINDCREDENTIALS` | no example | bindCredentials for LDAP access |
| `CMD_LDAP_SEARCHBASE` | `o=users,dc=example,dc=com` | LDAP directory to begin search from |
| `CMD_LDAP_SEARCHFILTER` | `(uid={{username}})` | LDAP filter to search with |
| `CMD_LDAP_SEARCHATTRIBUTES` | `displayName, mail` | LDAP attributes to search with (use comma to separate) |
| `CMD_LDAP_USERIDFIELD` | `uidNumber` or `uid` or `sAMAccountName` | The LDAP field which is used uniquely identify a user on CodiMD |
| `CMD_LDAP_USERNAMEFIELD` | Fallback to userid | The LDAP field which is used as the username on CodiMD |
| `CMD_LDAP_TLS_CA` | `server-cert.pem, root.pem` | Root CA for LDAP TLS in PEM format (use comma to separate) |
| `CMD_LDAP_PROVIDERNAME` | `My institution` | Optional name to be displayed at login form indicating the LDAP provider |
### Mattermost Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_MATTERMOST_BASEURL` | no example | 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 example | Mattermost API client id |
| `CMD_MATTERMOST_CLIENTSECRET` | no example | Mattermost API client secret |
### OAuth2 Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_OAUTH2_USER_PROFILE_URL` | `https://example.com` | where retrieve information about a user after succesful 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` | `name` | where to find the username in the JSON from the user profile URL. (no default value)|
| `CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR` | `display-name` | where to find the display-name in the JSON from the user profile URL. (no default value) |
| `CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR` | `email` | where to find the email address in the JSON from the user profile URL. (no default value) |
| `CMD_OAUTH2_TOKEN_URL` | `https://example.com` | sometimes called token endpoint, please refer to the documentation of your OAuth2 provider (no default value) |
| `CMD_OAUTH2_AUTHORIZATION_URL` | `https://example.com` | authorization URL of your provider, please refer to the documentation of your OAuth2 provider (no default value) |
| `CMD_OAUTH2_CLIENT_ID` | `afae02fckafd...` | you will get this from your OAuth2 provider when you register CodiMD as OAuth2-client, (no default value) |
| `CMD_OAUTH2_CLIENT_SECRET` | `afae02fckafd...` | you will get this from your OAuth2 provider when you register CodiMD as OAuth2-client, (no default value) |
| `CMD_OAUTH2_PROVIDERNAME` | `My institution` | Optional name to be displayed at login form indicating the oAuth2 provider |
### SAML Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_SAML_IDPSSOURL` | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](guides/auth/saml-onelogin.md). |
| `CMD_SAML_IDPCERT` | `/path/to/cert.pem` | certificate file path of IdP in PEM format |
| `CMD_SAML_ISSUER` | no example | identity of the service provider (optional, default: serverurl)" |
| `CMD_SAML_DISABLEREQUESTEDAUTHNCONTEXT` | `true` or `false` | true to allow any authentication method, false restricts to password authentication (PasswordProtectedTransport) method (default: false) |
| `CMD_SAML_IDENTIFIERFORMAT` | no example | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) |
| `CMD_SAML_GROUPATTRIBUTE` | `memberOf` | attribute name for group list (optional) |
| `CMD_SAML_REQUIREDGROUPS` | `codimd-users` | group names that allowed (use vertical bar to separate) (optional) |
| `CMD_SAML_EXTERNALGROUPS` | `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) |
| `CMD_SAML_ATTRIBUTE_ID` | `sAMAccountName` | attribute map for `id` (optional, default: NameID of SAML response) |
| `CMD_SAML_ATTRIBUTE_USERNAME` | `mailNickname` | attribute map for `username` (optional, default: NameID of SAML response) |
| `CMD_SAML_ATTRIBUTE_EMAIL` | `mail` | attribute map for `email` (optional, default: NameID of SAML response if `CMD_SAML_IDENTIFIERFORMAT` is default) |
### Twitter Login
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_TWITTER_CONSUMERKEY` | no example | Twitter API consumer key |
| `CMD_TWITTER_CONSUMERSECRET` | no example | Twitter API consumer secret |
## Upload Storage
These are only relevant when they are also configured in sync with their
`CMD_IMAGE_UPLOAD_TYPE`. Also keep in mind, that `filesystem` is available, so
you don't have to use either of these.
### Amazon S3
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_S3_ACCESS_KEY_ID` | no example | AWS access key id |
| `CMD_S3_SECRET_ACCESS_KEY` | no example | AWS secret key |
| `CMD_S3_REGION` | `ap-northeast-1` | AWS S3 region |
| `CMD_S3_BUCKET` | no example | AWS S3 bucket name |
### Azure Blob Storage
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_AZURE_CONNECTION_STRING` | no example | Azure Blob Storage connection string |
| `CMD_AZURE_CONTAINER` | no example | Azure Blob Storage container name (automatically created if non existent) |
### imgur
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_IMGUR_CLIENTID` | no example | Imgur API client id |
### Minio
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_MINIO_ACCESS_KEY` | no example | Minio access key |
| `CMD_MINIO_SECRET_KEY` | no example | Minio secret key |
| `CMD_MINIO_ENDPOINT` | `minio.example.org` | Address of your Minio endpoint/instance |
| `CMD_MINIO_PORT` | `9000` | Port that is used for your Minio instance |
| `CMD_MINIO_SECURE` | `true` | If set to `true` HTTPS is used for Minio |
### Lutim
| variable | example value | description |
| -------- | ------------- | ----------- |
| `CMD_LUTIM_URL` | `https://framapic.org/` | When `CMD_IMAGE_UPLOAD_TYPE` is set to `lutim`, you can setup the lutim url |
**Note:** *Due to the rename process we renamed all `HMD_`-prefix variables to be `CMD_`-prefixed. The old ones continue to work.*
**Note:** *relative paths are based on CodiMD's base directory*

View file

@ -1,57 +0,0 @@
Developer Notes
===
## Preparing for running the code
**Notice:** *There's [specialised instructions for docker](../setup/docker.md) or [heroku](../setup/heroku.md), if you prefer running code this way!*
1. Clone the repository with `git clone https://github.com/codimd/server.git codimd-server`
(cloning is the preferred way, but you can also download and unzip a release)
2. Enter the directory and run `bin/setup`, which will install npm dependencies
and create configs. The setup script is written in Bash, you would need bash
as a prerequisite.
3. Setup the [config file](../configuration-config-file.md) or set up
[environment variables](../configuration-env-vars.md).
## Running the Code
Now that everything is in place, we can start CodiMD:
4. `npm run build` will build the frontend bundle. It uses webpack to do that.
5. Run the server with `node app.js`
## Running the Code with Auto-Reload
The commands above are fine for production, but you're a developer and surely
you want to change things. You would need to restart both commands whenever you
change something. Luckily, you can run these commands that will automatically
rebuild the frontend or restart the server if necessary.
The commands will stay active in your terminal, so you will need multiple tabs
to run both at the same time.
4. Use `npm run dev` if you want webpack to continuously rebuild the frontend
code.
5. To auto-reload the server, the easiest method is to install [nodemon](https://www.npmjs.com/package/nodemon)
and run `nodemon --watch app.js --watch lib --watch locales app.js`.
## Structure
The repository contains two parts: a server (backend) and a client (frontend).
most of the server code is in `/lib` and most of the client code is in `public`.
```text
codimd-server/
├── docs/ --- documentation
├── lib/ --- server code
├── test/ --- test suite
└── public/ --- client code
├── css/ --- css styles
├── docs/ --- default documents
├── js/ --- js scripts
├── vendor/ --- vendor includes
└── views/ --- view templates
```

View file

@ -1,14 +0,0 @@
Operational Transformation
===
From 0.3.2, we started supporting operational transformation.
It makes concurrent editing safe and will not break up other users' operations.
Additionally, now can show other clients' selections.
See more at [https://operational-transformation.github.io/](https://operational-transformation.github.io/)
And even more in this 2010 article series:
* https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs_21.html
* https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs_22.html
* https://drive.googleblog.com/2010/09/whats-different-about-new-google-docs.html

View file

@ -1,33 +0,0 @@
Webpack
===
Webpack is a JavaScript build system for frontend code. You can find out all
about it on [the webpack website](https://webpack.js.org/).
Here's how we're using it:
## `webpack.common.js`
This file contains all common definition for chunks and plugins, that are needed by the whole app.
**TODO:** Document which entry points are used for what.
## `webpack.htmlexport.js`
Separate config for the "save as html" feature.
Packs all CSS from `public/js/htmlExport.js` to `build/html.min.css`.
This file is then downloaded by client-side JS and used to create the HTML.
See `exportToHTML()` in `public/js/extra.js`.
## `webpack.dev.js`
The development config uses both common configs, enables development mode and enables "cheap" source maps (lines only).
If you need more detailed source maps while developing, you might want to use the `source-maps` option.
See https://webpack.js.org/configuration/devtool/ for details.
## `webpack.prod.js`
The production config uses both common configs and enables production mode.
This automatically enables various optimizations (e.g. UglifyJS). See https://webpack.js.org/concepts/mode/ for details.
For the global app config, the name of the emitted chunks is changed to include the content hash.
See https://webpack.js.org/guides/caching/ on why this is a good idea.
For the HTML export config, CSS minification is enabled.

212
docs/guides/auth.md Normal file
View file

@ -0,0 +1,212 @@
# Guide - Authentication
### Twitter
1. Sign-in or sign-up for a Twitter account
2. Go to the Twitter Application management page [here](https://apps.twitter.com/)
3. Click on the **Create New App** button to create a new Twitter app:
![create-twitter-app](images/auth/create-twitter-app.png)
4. Fill out the create application form, check the developer agreement box, and click **Create Your Twitter Application**
![register-twitter-application](images/auth/register-twitter-application.png)
*Note: you may have to register your phone number with Twitter to create a Twitter application*
To do this Click your profile icon --> Settings and privacy --> Mobile --> Select Country/region --> Enter phone number --> Click Continue
5. After you receive confirmation that the Twitter application was created, click **Keys and Access Tokens**
![twitter-app-confirmation](images/auth/twitter-app-confirmation.png)
6. Obtain your Twitter Consumer Key and Consumer Secret
![twitter-app-keys](images/auth/twitter-app-keys.png)
7. Add your Consumer Key and Consumer Secret to your config.json file or pass them as environment variables:
* config.json:
````javascript
{
"production": {
"twitter": {
"consumerKey": "esTCJFXXXXXXXXXXXXXXXXXXX",
"consumerSecret": "zpCs4tU86pRVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
````
* environment variables:
````
HMD_TWITTER_CONSUMERKEY=esTCJFXXXXXXXXXXXXXXXXXXX
HMD_TWITTER_CONSUMERSECRET=zpCs4tU86pRVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
````
### GitHub
1. Sign-in or sign-up for a GitHub account
2. Navigate to developer settings in your GitHub account [here](https://github.com/settings/developers) and select the "OAuth Apps" tab
3. Click on the **New OAuth App** button, to create a new OAuth App:
![create-oauth-app](images/auth/create-oauth-app.png)
4. Fill out the new OAuth application registration form, and click **Register Application**
![register-oauth-application-form](images/auth/register-oauth-application-form.png)
*Note: The callback URL is <your-hackmd-url>/auth/github/callback*
5. After successfully registering the application, you'll receive the Client ID and Client Secret for the application
![application-page](images/auth/application-page.png)
6. Add the Client ID and Client Secret to your config.json file or pass them as environment variables
* config.json:
````javascript
{
"production": {
"github": {
"clientID": "3747d30eaccXXXXXXXXX",
"clientSecret": "2a8e682948eee0c580XXXXXXXXXXXXXXXXXXXXXX"
}
}
}
````
* environment variables:
````
HMD_GITHUB_CLIENTID=3747d30eaccXXXXXXXXX
HMD_GITHUB_CLIENTSECRET=2a8e682948eee0c580XXXXXXXXXXXXXXXXXXXXXX
````
### SAML (OneLogin)
1. Sign-in or sign-up for an OneLogin account. (available free trial for 2 weeks)
2. Go to the administration page.
3. Select the **APPS** menu and click on the **Add Apps**.
![onelogin-add-app](images/auth/onelogin-add-app.png)
4. Find "SAML Test Connector (SP)" for template of settings and select it.
![onelogin-select-template](images/auth/onelogin-select-template.png)
5. Edit display name and icons for OneLogin dashboard as you want, and click **SAVE**.
![onelogin-edit-app-name](images/auth/onelogin-edit-app-name.png)
6. After that other tabs will appear, click the **Configuration**, and fill out the below items, and click **SAVE**.
* RelayState: The base URL of your hackmd, which is issuer. (last slash is not needed)
* ACS (Consumer) URL Validator: The callback URL of your hackmd. (serverurl + /auth/saml/callback)
* ACS (Consumer) URL: same as above.
* Login URL: login URL(SAML requester) of your hackmd. (serverurl + /auth/saml)
![onelogin-edit-sp-metadata](images/auth/onelogin-edit-sp-metadata.png)
7. The registration is completed. Next, click **SSO** and copy or download the items below.
* X.509 Certificate: Click **View Details** and **DOWNLOAD** or copy the content of certificate ....(A)
* SAML 2.0 Endpoint (HTTP): Copy the URL ....(B)
![onelogin-copy-idp-metadata](images/auth/onelogin-copy-idp-metadata.png)
8. In your hackmd server, create IdP certificate file from (A)
9. Add the IdP URL (B) and the Idp certificate file path to your config.json file or pass them as environment variables.
* config.json:
````javascript
{
"production": {
"saml": {
"idpSsoUrl": "https://*******.onelogin.com/trust/saml2/http-post/sso/******",
"idpCert": "/path/to/idp_cert.pem"
}
}
}
````
* environment variables
````
HMD_SAML_IDPSSOURL=https://*******.onelogin.com/trust/saml2/http-post/sso/******
HMD_SAML_IDPCERT=/path/to/idp_cert.pem
````
10. Try sign-in with SAML from your hackmd sign-in button or OneLogin dashboard (like the screenshot below).
![onelogin-use-dashboard](images/auth/onelogin-use-dashboard.png)
### SAML (Other cases)
The basic procedure is the same as the case of OneLogin which is mentioned above. If you want to match your IdP, you can use more configurations as below.
* If your IdP accepts metadata XML of the service provider to ease configuraion, use this url to download metadata XML.
* {{your-serverurl}}/auth/saml/metadata
* _Note: If not accessable from IdP, download to local once and upload to IdP._
* Change the value of `issuer`, `identifierFormat` to match your IdP.
* `issuer`: A unique id to identify the application to the IdP, which is the base URL of your HackMD as default
* `identifierFormat`: A format of unique id to identify the user of IdP, which is the format based on email address as default. It is recommend that you use as below.
* urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress (default)
* urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
* config.json:
````javascript
{
"production": {
"saml": {
/* omitted */
"issuer": "myhackmd"
"identifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
}
}
}
````
* environment variables
````
HMD_SAML_ISSUER=myhackmd
HMD_SAML_IDENTIFIERFORMAT=urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
````
* Change mapping of attribute names to customize the displaying user name and email address to match your IdP.
* `attribute`: A dictionary to map attribute names
* `attribute.id`: A primary key of user table for your HackMD
* `attribute.username`: Attribute name of displaying user name on HackMD
* `attribute.email`: Attribute name of email address, which will be also used for Gravatar
* _Note: Default value of all attributes is NameID of SAML response, which is email address if `idfentifierFormat` is default._
* config.json:
````javascript
{
"production": {
"saml": {
/* omitted */
"attribute": {
"id": "sAMAccountName",
"username": "displayName",
"email": "mail"
}
}
}
}
````
* environment variables
````
HMD_SAML_ATTRIBUTE_ID=sAMAccountName
HMD_SAML_ATTRIBUTE_USERNAME=nickName
HMD_SAML_ATTRIBUTE_EMAIL=mail
````
* If you want to controll permission by group membership, add group attribute name and required group (allowed) or external group (not allowed).
* `groupAttribute`: An attribute name of group membership
* `requiredGroups`: Group names array for allowed access to HackMD. Use vertical bar to separate for environment variables.
* `externalGroups`: Group names array for not allowed access to HackMD. Use vertical bar to separate for environment variables.
* _Note: Evaluates `externalGroups` first_
* config.json:
````javascript
{
"production": {
"saml": {
/* omitted */
"groupAttribute": "memberOf",
"requiredGroups": [ "hackmd-users", "board-members" ],
"externalGroups": [ "temporary-staff" ]
}
}
}
````
* environment variables
````
HMD_SAML_GROUPATTRIBUTE=memberOf
HMD_SAML_REQUIREDGROUPS=hackmd-users|board-members
HMD_SAML_EXTERNALGROUPS=temporary-staff
````

View file

@ -1,35 +0,0 @@
Authentication guide - GitHub
===
**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!*
1. Sign-in or sign-up for a GitHub account
2. Navigate to developer settings in your GitHub account [here](https://github.com/settings/developers) and select the "OAuth Apps" tab
3. Click on the **New OAuth App** button, to create a new OAuth App:
![create-oauth-app](../../images/auth/create-oauth-app.png)
4. Fill out the new OAuth application registration form, and click **Register Application**
![register-oauth-application-form](../../images/auth/register-oauth-application-form.png)
**Note:** *The callback URL is <your-codimd-url>/auth/github/callback*
5. After successfully registering the application, you'll receive the Client ID and Client Secret for the application
![application-page](../../images/auth/application-page.png)
6. Add the Client ID and Client Secret to your config.json file or pass them as environment variables
* `config.json`:
```js
{
"production": {
"github": {
"clientID": "3747d30eaccXXXXXXXXX",
"clientSecret": "2a8e682948eee0c580XXXXXXXXXXXXXXXXXXXXXX"
}
}
}
```
* environment variables:
```sh
CMD_GITHUB_CLIENTID=3747d30eaccXXXXXXXXX
CMD_GITHUB_CLIENTSECRET=2a8e682948eee0c580XXXXXXXXXXXXXXXXXXXXXX
````

View file

@ -1,32 +0,0 @@
GitLab (self-hosted)
===
**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!*
1. Sign in to your GitLab
2. Navigate to the application management page at `https://your.gitlab.domain/admin/applications` (admin permissions required)
3. Click **New application** to create a new application and fill out the registration form:
![New GitLab application](../../images/auth/gitlab-new-application.png)
4. Click **Submit**
5. In the list of applications select **HackMD**. Leave that site open to copy the application ID and secret in the next step.
![Application: HackMD](../../images/auth/gitlab-application-details.png)
6. In the `docker-compose.yml` add the following environment variables to `app:` `environment:`
```
- HMD_DOMAIN=your.codimd.domain
- HMD_URL_ADDPORT=443
- HMD_PROTOCOL_USESSL=true
- HMD_GITLAB_BASEURL=https://your.gitlab.domain
- HMD_GITLAB_CLIENTID=23462a34example99XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- HMD_GITLAB_CLIENTSECRET=5532e9dexamplXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```
7. Run `docker-compose up -d` to apply your settings.
8. Sign in to your CodiMD using your GitLab ID:
![Sign in via GitLab](../../images/auth/gitlab-sign-in.png)

View file

@ -1,50 +0,0 @@
Keycloak/Red Hat SSO (self-hosted)
===
## 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.
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
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 CodiMD 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:
1. Select Client Scopes from the sidebar, and begin to create a new client scope using the Create button.
2. Ensure that the **Name** field is set to `id`.
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 [codimd/server#56](https://github.com/codimd/server/issues/56), as the `/userinfo` endpoint should now bring back the user's ID under the `id` key as well as `sub`.
---
6. In the `docker-compose.yml` add the following environment variables to `app:` `environment:`
```
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_DOMAIN=<codimd.example.com>
CMD_PROTOCOL_USESSL=true
CMD_URL_ADDPORT=false
```
7. Run `docker-compose up -d` to apply your settings.
8. Sign in to your CodiMD using your Keycloak ID

View file

@ -1,41 +0,0 @@
AD LDAP auth
===
To setup your CodiMD instance with Active Directory you need the following configs:
```
CMD_LDAP_URL=ldap://internal.example.com
CMD_LDAP_BINDDN=cn=binduser,cn=Users,dc=internal,dc=example,dc=com
CMD_LDAP_BINDCREDENTIALS=<super secret password>
CMD_LDAP_SEARCHBASE=dc=internal,dc=example,dc=com
CMD_LDAP_SEARCHFILTER=(&(objectcategory=person)(objectclass=user)(|(sAMAccountName={{username}})(mail={{username}})))
CMD_LDAP_USERIDFIELD=sAMAccountName
CMD_LDAP_PROVIDERNAME=Example Inc AD
```
`CMD_LDAP_BINDDN` is either the `distinguishedName` or the `userPrincipalName`. *This can cause "username/password is invalid" when either this value or the password from `CMD_LDAP_BINDCREDENTIALS` are incorrect.*
`CMD_LDAP_SEARCHFILTER` matches on all users and uses either the email address or the `sAMAccountName` (usually the login name you also use to login to Windows).
*Only using `sAMAccountName` looks like this:* `(&(objectcategory=person)(objectclass=user)(sAMAccountName={{username}}))`
`CMD_LDAP_USERIDFIELD` says we want to use `sAMAccountName` as unique identifier for the account itself.
`CMD_LDAP_PROVIDERNAME` just the name written above the username and password field on the login page.
Same in json:
```json
"ldap": {
"url": "ldap://internal.example.com",
"bindDn": "cn=binduser,cn=Users,dc=internal,dc=example,dc=com",
"bindCredentials": "<super secret password>",
"searchBase": "dc=internal,dc=example,dc=com",
"searchFilter": "(&(objectcategory=person)(objectclass=user)(|(sAMAccountName={{username}})(mail={{username}})))",
"useridField": "sAMAccountName",
},
```
More details and example: https://www.npmjs.com/package/passport-ldapauth

View file

@ -1,54 +0,0 @@
Authentication guide - Mattermost (self-hosted)
===
**Note:** *The Mattermost setup portion of this document is just a quick guide. See the [official documentation](https://docs.mattermost.com/developer/oauth-2-0-applications.html) for more details.*
This guide uses the generic OAuth2 module for compatibility with Mattermost version 5.0 and above.
1. Sign-in with an administrator account to your Mattermost instance
2. Make sure **OAuth 2.0 Service Provider** is enabled in the Main Menu (menu button next to your username in the top left corner) --> System Console --> Custom Integrations menu, which you can find at `https://your.mattermost.domain/admin_console/integrations/custom`
![mattermost-enable-oauth2](../../images/auth/mattermost-enable-oauth2.png)
3. Navigate to the OAuth integration settings through Main Menu --> Integrations --> OAuth 2.0 Applications, at `https://your.mattermost.domain/yourteam/integrations/oauth2-apps`
4. Click on the **Add OAuth 2.0 Application** button to add a new OAuth application
![mattermost-oauth-app-add](../../images/auth/mattermost-oauth-app-add.png)
5. Fill out the form and click **Save**
![mattermost-oauth-app-form](../../images/auth/mattermost-oauth-app-form.png)
*Note: The callback URL is \<your-codimd-url\>/auth/oauth2/callback*
6. After saving the application, you'll receive the Client ID and Client Secret
![mattermost-oauth-app-done](../../images/auth/mattermost-oauth-app-done.png)
7. Add the Client ID and Client Secret to your config.json file or pass them as environment variables
* `config.json`:
```javascript
{
"production": {
"oauth2": {
"baseURL": "https://your.mattermost.domain",
"userProfileURL": "https://your.mattermost.domain/api/v4/users/me",
"userProfileUsernameAttr": "id",
"userProfileDisplayNameAttr": "username",
"userProfileEmailAttr": "email",
"tokenURL": "https://your.mattermost.domain/oauth/access_token",
"authorizationURL": "https://your.mattermost.domain/oauth/authorize",
"clientID": "ii4p1u3jz7dXXXXXXXXXXXXXXX",
"clientSecret": "mqzzx6fydbXXXXXXXXXXXXXXXX"
}
}
}
```
* environment variables:
```sh
CMD_OAUTH2_BASEURL=https://your.mattermost.domain
CMD_OAUTH2_USER_PROFILE_URL=https://your.mattermost.domain/api/v4/users/me
CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR=id
CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR=username
CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR=email
CMD_OAUTH2_TOKEN_URL=https://your.mattermost.domain/oauth/access_token
CMD_OAUTH2_AUTHORIZATION_URL=https://your.mattermost.domain/oauth/authorize
CMD_OAUTH2_CLIENT_ID=ii4p1u3jz7dXXXXXXXXXXXXXXX
CMD_OAUTH2_CLIENT_SECRET=mqzzx6fydbXXXXXXXXXXXXXXXX
```

View file

@ -1,52 +0,0 @@
Authentication guide - Nextcloud (self-hosted)
===
*This has been constructed using the [Nextcloud OAuth2 Documentation](https://docs.nextcloud.com/server/14/admin_manual/configuration_server/oauth2.html?highlight=oauth2) combined with [this issue comment on the nextcloud bugtracker](https://github.com/nextcloud/server/issues/5694#issuecomment-314761326).*
This guide uses the generic OAuth2 module for compatibility with Nextcloud 13 and above (this guide has been tested successfully with Nextcloud 14).
1. Sign-in with an administrator account to your Nextcloud server
2. Navigate to the OAuth integration settings: Profile Icon (top right) --> Settings
Then choose Security Settings from the *Administration* part of the list - Don't confuse this with Personal Security Settings, where you would change your personal password!
At the top there's OAuth 2.0-Clients.
![Where to find OAuth2 in Nextcloud](../../images/auth/nextcloud-oauth2-1-settings.png)
3. Add your CodiMD instance by giving it a *name* (perhaps CodiMD, but could be anything) and a *Redirection-URI*. The Redirection-URI will be `\<your-codimd-url\>/auth/oauth2/callback`. Click <kbd>Add</kbd>.
![Adding a client to Nextcloud](../../images/auth/nextcloud-oauth2-2-client-add.png)
4. You'll now see a line containing a *client identifier* and a *Secret*.
![Successfully added OAuth2-client](../../images/auth/nextcloud-oauth2-3-clientid-secret.png)
5. That's it for Nextcloud, the rest is configured in your CodiMD `config.json` or via the `CMD_` environment variables!
6. Add the Client ID and Client Secret to your `config.json` file or pass them as environment variables. Make sure you also replace `<your-nextcloud-domain>` with the right domain name.
* `config.json`:
```javascript
{
"production": {
"oauth2": {
"clientID": "ii4p1u3jz7dXXXXXXXXXXXXXXX",
"clientSecret": "mqzzx6fydbXXXXXXXXXXXXXXXX",
"authorizationURL": "https://<your-nextcloud-domain>/apps/oauth2/authorize",
"tokenURL": "https://<your-nextcloud-domain>/apps/oauth2/api/v1/token",
"userProfileURL": "https://<your-nextcloud-domain>/ocs/v2.php/cloud/user?format=json",
"userProfileUsernameAttr": "ocs.data.id",
"userProfileDisplayNameAttr": "ocs.data.display-name",
"userProfileEmailAttr": "ocs.data.email"
}
}
}
```
* environment variables:
```sh
CMD_OAUTH2_CLIENT_ID=ii4p1u3jz7dXXXXXXXXXXXXXXX
CMD_OAUTH2_CLIENT_SECRET=mqzzx6fydbXXXXXXXXXXXXXXXX
CMD_OAUTH2_AUTHORIZATION_URL=https://<your-nextcloud-domain>/apps/oauth2/authorize
CMD_OAUTH2_TOKEN_URL=https://<your-nextcloud-domain>/apps/oauth2/api/v1/token
CMD_OAUTH2_USER_PROFILE_URL=https://<your-nextcloud-domain>/ocs/v2.php/cloud/user?format=json
CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR=ocs.data.id
CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR=ocs.data.display-name
CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR=ocs.data.email
```

View file

@ -1,12 +0,0 @@
# OAuth general information
| service | callback URL (after the server URL) |
| ------- | --------- |
| facebook | `/auth/facebook/callback` |
| twitter | `/auth/twitter/callback` |
| github | `/auth/github/callback` |
| gitlab | `/auth/gitlab/callback` |
| mattermost | `/auth/mattermost/callback` |
| dropbox | `/auth/dropbox/callback` |
| google | `/auth/google/callback` |
| saml | `/auth/saml/callback` |

View file

@ -1,48 +0,0 @@
Authentication guide - SAML (OneLogin)
===
**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!*
1. Sign-in or sign-up for an OneLogin account. (available free trial for 2 weeks)
2. Go to the administration page.
3. Select the **APPS** menu and click on the **Add Apps**.
![onelogin-add-app](../../images/auth/onelogin-add-app.png)
4. Find "SAML Test Connector (SP)" for template of settings and select it.
![onelogin-select-template](../../images/auth/onelogin-select-template.png)
5. Edit display name and icons for OneLogin dashboard as you want, and click **SAVE**.
![onelogin-edit-app-name](../../images/auth/onelogin-edit-app-name.png)
6. After that other tabs will appear, click the **Configuration**, and fill out the below items, and click **SAVE**.
* RelayState: The base URL of your CodiMD, which is issuer. (last slash is not needed)
* ACS (Consumer) URL Validator: The callback URL of your CodiMD. (serverurl + /auth/saml/callback)
* ACS (Consumer) URL: same as above.
* Login URL: login URL(SAML requester) of your CopiMD. (serverurl + /auth/saml)
![onelogin-edit-sp-metadata](../../images/auth/onelogin-edit-sp-metadata.png)
7. The registration is completed. Next, click **SSO** and copy or download the items below.
* X.509 Certificate: Click **View Details** and **DOWNLOAD** or copy the content of certificate ....(A)
* SAML 2.0 Endpoint (HTTP): Copy the URL ....(B)
![onelogin-copy-idp-metadata](../../images/auth/onelogin-copy-idp-metadata.png)
8. In your CodiMD server, create IdP certificate file from (A)
9. Add the IdP URL (B) and the Idp certificate file path to your config.json file or pass them as environment variables.
* `config.json`:
```javascript
{
"production": {
"saml": {
"idpSsoUrl": "https://*******.onelogin.com/trust/saml2/http-post/sso/******",
"idpCert": "/path/to/idp_cert.pem"
}
}
}
```
* environment variables
```sh
CMD_SAML_IDPSSOURL=https://*******.onelogin.com/trust/saml2/http-post/sso/******
CMD_SAML_IDPCERT=/path/to/idp_cert.pem
```
10. Try sign-in with SAML from your CodiMD sign-in button or OneLogin dashboard (like the screenshot below).
![onelogin-use-dashboard](../../images/auth/onelogin-use-dashboard.png)

View file

@ -1,85 +0,0 @@
Authentication guide - SAML
===
**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!*
The basic procedure is the same as the case of OneLogin which is mentioned in [OneLogin-Guide](./saml-onelogin.md). If you want to match your IdP, you can use more configurations as below.
* If your IdP accepts metadata XML of the service provider to ease configuration, use this url to download metadata XML.
* {{your-serverurl}}/auth/saml/metadata
* _Note: If not accessible from IdP, download to local once and upload to IdP._
* Change the value of `issuer`, `identifierFormat` to match your IdP.
* `issuer`: A unique id to identify the application to the IdP, which is the base URL of your CodiMD as default
* `identifierFormat`: A format of unique id to identify the user of IdP, which is the format based on email address as default. It is recommend that you use as below.
* urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress (default)
* urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
* `config.json`:
```javascript
{
"production": {
"saml": {
/* omitted */
"issuer": "mycodimd"
"identifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
}
}
}
```
* environment variables
```
CMD_SAML_ISSUER=mycodimd
CMD_SAML_IDENTIFIERFORMAT=urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
```
* Change mapping of attribute names to customize the displaying user name and email address to match your IdP.
* `attribute`: A dictionary to map attribute names
* `attribute.id`: A primary key of user table for your CodiMD
* `attribute.username`: Attribute name of displaying user name on CodiMD
* `attribute.email`: Attribute name of email address, which will be also used for Gravatar
* _Note: Default value of all attributes is NameID of SAML response, which is email address if `identifierFormat` is default._
* `config.json`:
```javascript
{
"production": {
"saml": {
/* omitted */
"attribute": {
"id": "sAMAccountName",
"username": "displayName",
"email": "mail"
}
}
}
}
```
* environment variables
```sh
CMD_SAML_ATTRIBUTE_ID=sAMAccountName
CMD_SAML_ATTRIBUTE_USERNAME=nickName
CMD_SAML_ATTRIBUTE_EMAIL=mail
```
* If you want to control permission by group membership, add group attribute name and required group (allowed) or external group (not allowed).
* `groupAttribute`: An attribute name of group membership
* `requiredGroups`: Group names array for allowed access to CodiMD. Use vertical bar to separate for environment variables.
* `externalGroups`: Group names array for not allowed access to CodiMD. Use vertical bar to separate for environment variables.
* _Note: Evaluates `externalGroups` first_
* `config.json`:
```javascript
{
"production": {
"saml": {
/* omitted */
"groupAttribute": "memberOf",
"requiredGroups": [ "codimd-users", "board-members" ],
"externalGroups": [ "temporary-staff" ]
}
}
}
```
* environment variables
```sh
CMD_SAML_GROUPATTRIBUTE=memberOf
CMD_SAML_REQUIREDGROUPS=codimd-users|board-members
CMD_SAML_EXTERNALGROUPS=temporary-staff
```

View file

@ -1,40 +0,0 @@
Authentication guide - Twitter
===
**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!*
1. Sign-in or sign-up for a Twitter account
2. Go to the Twitter Application management page [here](https://apps.twitter.com/)
3. Click on the **Create New App** button to create a new Twitter app:
![create-twitter-app](../../images/auth/create-twitter-app.png)
4. Fill out the create application form, check the developer agreement box, and click **Create Your Twitter Application**
![register-twitter-application](../../images/auth/register-twitter-application.png)
*Note: you may have to register your phone number with Twitter to create a Twitter application*
To do this Click your profile icon --> Settings and privacy --> Mobile --> Select Country/region --> Enter phone number --> Click Continue
5. After you receive confirmation that the Twitter application was created, click **Keys and Access Tokens**
![twitter-app-confirmation](../../images/auth/twitter-app-confirmation.png)
6. Obtain your Twitter Consumer Key and Consumer Secret
![twitter-app-keys](../../images/auth/twitter-app-keys.png)
7. Add your Consumer Key and Consumer Secret to your `config.json` file or pass them as environment variables:
* `config.json`:
```javascript
{
"production": {
"twitter": {
"consumerKey": "esTCJFXXXXXXXXXXXXXXXXXXX",
"consumerSecret": "zpCs4tU86pRVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
```
* environment variables:
```sh
CMD_TWITTER_CONSUMERKEY=esTCJFXXXXXXXXXXXXXXXXXXX
CMD_TWITTER_CONSUMERSECRET=zpCs4tU86pRVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

View file

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View file

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View file

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View file

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 234 KiB

View file

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View file

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

View file

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View file

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View file

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 198 KiB

View file

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

View file

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View file

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View file

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View file

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View file

@ -1,131 +0,0 @@
Pad migration guide from etherpad-lite
===
The goal of this migration is to do a "dumb" import from all the pads in Etherpad, to notes in
CodiMD. In particular, the url locations of the pads in Etherpad will be lost. Furthermore, any
metadata in Etherpad, such as revisions, author data and also formatted text will not be migrated
to CodiMD (only the plain text contents).
Note that this guide is not really meant as a support guide. I migrated my own Etherpad to CodiMD,
and it turned out to be quite easy in my opinion. In this guide I share my experience. Stuff may
require some creativity to work properly in your case. When I wrote this guide, I was using
[Etherpad 1.7.0] and [CodiMD 1.2.1]. Good luck!
[Etherpad 1.7.0]: https://github.com/ether/etherpad-lite/tree/1.7.0
[CodiMD 1.2.1]: https://github.com/codimd/server/tree/1.2.1
## 0. Requirements
- `curl`
- running Etherpad server
- running CodiMD server
- [codimd-cli]
[codimd-cli]: https://github.com/codimd/cli/blob/master/bin/codimd
## 1. Retrieve the list of pads
First, compose a list of all the pads that you want to have migrated from your Etherpad. Other than
the admin interface, Etherpad does not have a dedicated function to dump a list of all the pads.
However, the Etherpad wiki explains how to list all the pads by [talking directly to the
database][howtolistallpads].
You will end up with a file containing a pad name on each line:
```
date-ideas
groceries
london
weddingchecklist
(...)
```
[howtolistallpads]: https://github.com/ether/etherpad-lite/wiki/How-to-list-all-pads/49701ecdcbe07aea7ad27ffa23aed0d99c2e17db
## 2. Run the migration
Download [codimd-cli] and put the script in the same directory as the file containing the pad names.
Add to this directory the file listed below, I called it `migrate-etherpad.sh`. Modify at least the
configuration settings `ETHERPAD_SERVER` and `CODIMD_SERVER`.
```shell
#!/bin/sh
# migrate-etherpad.sh
#
# Description: Migrate pads from etherpad to codimd
# Author: Daan Sprenkels <hello@dsprenkels.com>
# This script uses the codimd command line script[1] to import a list of pads from
# [1]: https://github.com/codimd/cli/blob/master/bin/codimd
# The base url to where etherpad is hosted
ETHERPAD_SERVER="https://etherpad.example.com"
# The base url where codimd is hosted
CODIMD_SERVER="https://codimd.example.com"
# Write a list of pads and the urls which they were migrated to
REDIRECTS_FILE="redirects.txt"
# Fail if not called correctly
if (( $# != 1 )); then
echo "Usage: $0 PAD_NAMES_FILE"
exit 2
fi
# Do the migration
for PAD_NAME in $1; do
# Download the pad
PAD_FILE="$(mktemp)"
curl "$ETHERPAD_SERVER/p/$PAD_NAME/export/txt" >"$PAD_FILE"
# Import the pad into codimd
OUTPUT="$(./codimd import "$PAD_FILE")"
echo "$PAD_NAME -> $OUTPUT" >>"$REDIRECTS_FILE"
done
```
Call this file like this:
```shell
./migrate-etherpad.sh pad_names.txt
```
This will download all the pads in `pad_names.txt` and put them on CodiMD. They will get assigned
random ids, so you won't be able to find them. The script will save the mappings to a file though
(in my case `redirects.txt`). You can use this file to redirect your users when they visit your
etherpad using a `301 Permanent Redirect` status code (see the next section).
## 3. Setup redirects (optional)
I got a `redirects.txt` file that looked a bit like this:
```
date-ideas -> Found. Redirecting to https://codimd.example.com/mPt0KfiKSBOTQ3mNcdfn
groceries -> Found. Redirecting to https://codimd.example.com/UukqgwLfhYyUUtARlcJ2_y
london -> Found. Redirecting to https://codimd.example.com/_d3wa-BE8t4Swv5w7O2_9R
weddingchecklist -> Found. Redirecting to https://codimd.example.com/XcQGqlBjl0u40wfT0N8TzQ
(...)
```
Using some `sed` magic, I changed it to an nginx config snippet:
```
location = /p/date-ideas {
return 301 https://codimd.example.com/mPt0M1KfiKSBOTQ3mNcdfn;
}
location = /p/groceries {
return 301 https://codimd.example.com/UukqgwLfhYyUUtARlcJ2_y;
}
location = /p/london {
return 301 https://codimd.example.com/_d3wa-BE8t4Swv5w7O2_9R;
}
location = /p/weddingchecklist {
return 301 https://codimd.example.com/XcQGqlBjl0u40wfT0N8TzQ;
}
```
I put this file into my `etherpad.example.com` nginx config, such that all the users would be
redirected accordingly.

View file

@ -1,56 +0,0 @@
Migrations and Notable Changes
===
## Migrating to 1.4.0
We dropped support for node 6 with this version. If you have any trouble running this version, please double check that you are running at least node 8!
## Migrating to 1.3.2
This is not a breaking change, but to stay up to date with the community
repository, you may need to update a few urls. This is not a breaking change.
See more at [issue #10](https://github.com/codimd/server/issues/10)
**Native setup using git:**
Change the upstream remote using `git remote set-url origin https://github.com/codimd/server.git`.
**Docker:**
When you use our [container repository](https://github.com/codimd/container)
(which was previously `codimd-container`) all you can simply run `git pull` and
your `docker-compose.yml` will be updated.
When you setup things yourself, make sure you use the new image:
[`quay.io/codimd/server`](https://quay.io/repository/codimd/server?tab=tags).
**Heroku:**
All you need to do is [disconnect GitHub](https://devcenter.heroku.com/articles/github-integration#disconnecting-from-github)
and [reconnect it](https://devcenter.heroku.com/articles/github-integration#enabling-github-integration)
with this new repository.
Or you can use our Heroku button and redeploy your instance and link the old
database again.
## Migrating to 1.1.0
We deprecated the older lower case config style and moved on to camel case style. Please have a look at the current `config.json.example` and check the warnings on startup.
*Notice: This is not a breaking change right now but will be in the future*
## Migrating to 0.5.0
[migration-to-0.5.0 migration tool](https://github.com/hackmdio/migration-to-0.5.0)
We don't use LZString to compress socket.io data and DB data after version 0.5.0.
Please run the migration tool if you're upgrading from the old version.
## Migrating to 0.4.0
[migration-to-0.4.0 migration tool](https://github.com/hackmdio/migration-to-0.4.0)
We've dropped MongoDB after version 0.4.0.
So here is the migration tool for you to transfer the old DB data to the new DB.
This tool is also used for official service.

View file

@ -1,15 +1,13 @@
Minio Guide for CodiMD Minio Guide for HackMD
=== ===
**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!*
1. First of all you need to setup Minio itself. 1. First of all you need to setup Minio itself.
Please refer to the [official Minio docs](https://docs.minio.io/) for an Please refer to the [official Minio docs](https://docs.minio.io/) for an
production setup. production setup.
For checking it out and development purposes a non-persistent setup is enough: For checking it out and development purposes a non-persistent setup is enough:
```sh ```console
docker run --name test-minio --rm -d -p 9000:9000 minio/minio server /data docker run --name test-minio --rm -d -p 9000:9000 minio/minio server /data
``` ```
@ -18,29 +16,29 @@ Minio Guide for CodiMD
2. Next step is to get the credentials form the container: 2. Next step is to get the credentials form the container:
```sh ```
docker logs test-minio docker logs test-minio
``` ```
![docker logs](../images/minio-image-upload/docker-logs.png) ![docker logs](images/minio-image-upload/docker-logs.png)
3. Open http://localhost:9000 and login with the shown credentials. 3. Open http://localhost:9000 and login with the shown credentials.
![minio default view](../images/minio-image-upload/default-view.png) ![minio default view](images/minio-image-upload/default-view.png)
4. Create a bucket for CodiMD 4. Create a bucket for HackMD
![minio create bucket](../images/minio-image-upload/create-bucket.png) ![minio create bucket](images/minio-image-upload/create-bucket.png)
5. Add a policy for the prefix `uploads` and make it read-only. 5. Add a policy for the prefix `uploads` and make it read-only.
![minio edit policy](../images/minio-image-upload/open-edit-policy.png) ![minio edit policy](images/minio-image-upload/open-edit-policy.png)
*Open policy editor* *Open policy editor*
![minio policy adding](../images/minio-image-upload/create-policy.png) ![minio policy adding](images/minio-image-upload/create-policy.png)
*Add policy for uploads* *Add policy for uploads*
6. Set credentials and configs for Minio in CodiMD's `config.json` 6. Set credentials and configs for Minio in HackMD's `config.json`
```JSON ```JSON
"minio": { "minio": {
@ -58,13 +56,13 @@ Minio Guide for CodiMD
7. Set bucket name 7. Set bucket name
```JSON ```JSON
"s3bucket": "codimd" "s3bucket": "hackmd"
``` ```
8. Set upload type. 8. Set upload type.
```JSON ```JSON
"imageuploadtype": "minio" "imageUploadType": "minio"
``` ```
9. Review your config. 9. Review your config.
@ -79,7 +77,7 @@ Minio Guide for CodiMD
"port": 9000, "port": 9000,
"secure": false "secure": false
}, },
"s3bucket": "codimd", "s3bucket": "hackmd",
"imageuploadtype": "minio" "imageUploadType": "minio"
} }
``` ```

View file

@ -1,17 +0,0 @@
Setup your terms of use
===
To setup your terms of use, you need to provide a document called `terms-of-use.md` which contains them. Of course written in Markdown.
It has to be provided under `./public/docs/` and will be automatically turned into a CodiMD document. It will also automatically updated as soon as you change the document on disk.
As soon as the file exists a link will show up in the bottom part along with the release notes and link to them.
Setup your privacy policy
===
To add a privacy policy you can use the same technique as for the terms of use. The main difference is that the document is called `privacy.md`.
See our example file `./public/docs/privacy.md.example` container some useful hints for writing your own privacy policy.
As with the terms of use, a link to the privacy notices will show up in the area where the release notes are provided on the index page.

View file

@ -1,19 +1,16 @@
Guide - Setup CodiMD S3 image upload # Guide - Setup HackMD S3 image upload
===
**Note:** *This guide was written before the renaming. Just replace `HackMD` with `CodiMD` in your mind :smile: thanks!*
1. Go to [AWS S3 console](https://console.aws.amazon.com/s3/home) and create a new bucket. 1. Go to [AWS S3 console](https://console.aws.amazon.com/s3/home) and create a new bucket.
![create-bucket](../images/s3-image-upload/create-bucket.png) ![create-bucket](images/s3-image-upload/create-bucket.png)
2. Click on bucket, select **Properties** on the side panel, and find **Permission** section. Click **Edit bucket policy**. 2. Click on bucket, select **Properties** on the side panel, and find **Permission** section. Click **Edit bucket policy**.
![bucket-property](../images/s3-image-upload/bucket-property.png) ![bucket-property](images/s3-image-upload/bucket-property.png)
3. Enter the following policy, replace `bucket_name` with your bucket name: 3. Enter the following policy, replace `bucket_name` with your bucket name:
![bucket-policy-editor](../images/s3-image-upload/bucket-policy-editor.png) ![bucket-policy-editor](images/s3-image-upload/bucket-policy-editor.png)
```json ```json
{ {
@ -33,15 +30,15 @@ Guide - Setup CodiMD S3 image upload
5. Enter user page, select **Permission** tab, look at **Inline Policies** section, and click **Create User Policy** 5. Enter user page, select **Permission** tab, look at **Inline Policies** section, and click **Create User Policy**
![iam-user](../images/s3-image-upload/iam-user.png) ![iam-user](images/s3-image-upload/iam-user.png)
6. Select **Custom Policy** 6. Select **Custom Policy**
![custom-policy](../images/s3-image-upload/custom-policy.png) ![custom-policy](images/s3-image-upload/custom-policy.png)
7. Enter the following policy, replace `bucket_name` with your bucket name: 7. Enter the following policy, replace `bucket_name` with your bucket name:
![review-policy](../images/s3-image-upload/review-policy.png) ![review-policy](images/s3-image-upload/review-policy.png)
```json ```json
{ {
@ -66,7 +63,7 @@ Guide - Setup CodiMD S3 image upload
{ {
"production": { "production": {
... ...
"imageuploadtype": "s3", "imageUploadType": "s3",
"s3": { "s3": {
"accessKeyId": "YOUR_S3_ACCESS_KEY_ID", "accessKeyId": "YOUR_S3_ACCESS_KEY_ID",
"secretAccessKey": "YOUR_S3_ACCESS_KEY", "secretAccessKey": "YOUR_S3_ACCESS_KEY",
@ -77,7 +74,7 @@ Guide - Setup CodiMD S3 image upload
} }
``` ```
9. In additional to edit `config.json` directly, you could also try [environment variables](../configuration-env-vars.md). 9. In additional to edit `config.json` directly, you could also try [environment variable](https://github.com/hackmdio/hackmd#environment-variables-will-overwrite-other-server-configs).
## Related Tools ## Related Tools

View file

@ -1,40 +0,0 @@
History of CodiMD
===
## It started with HackMD
HackMD is the origin of this project, which was mostly developed by Max Wu and
Yukai Huang. Originally, this was open source under MIT license, but was
[relicensed in October 2017 to be AGPLv3](https://github.com/hackmdio/codimd/pull/578).
At the same time, [hackmd.io](https://hackmd.io) was founded to offer a
commercial version of HackMD.
The AGPLv3-version was developed and released by the community, this was for a
while referred to as "HackMD community edition".
*For more on the splitting of the projects, please refer to [A note to our community (2017-10-11)](https://hackmd.io/c/community-news/https%3A%2F%2Fhackmd.io%2Fs%2Fr1_4j9_hZ).*
## HackMD CE became CodiMD
In June 2018, CodiMD was renamed from its former name "HackMD" and continued to
be developed under AGPLv3 by the community. We decided to change the name to
break the confusion between HackMD (enterprise offering) and CodiMD (community
project), as people mistook it for an open core development model.
*For the whole renaming story, see the [issue where the renaming was discussed](https://github.com/hackmdio/hackmd/issues/720).*
## CodiMD went independent
In March 2019, a discussion over licensing, governance and the future of CodiMD
lead to the formation of a distinct GitHub organization. Up to that point, the
community project resided in the organization of hackmdio but was for the most
part self-organized.
During that debate, we did not reach an agreement that would have allowed us to
move the repository, so we simply forked it. We still welcome the HackMD team
as part of our community, especially since a large portion of this code base
originated with them.
*For the debate that lead to this step, please refer to the [governance debate](https://github.com/hackmdio/hackmd/issues/1170) and [the announcement of the new repository](https://github.com/codimd/server/issues/10).*

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,6 +0,0 @@
Cloudron
===
Install CodiMD on [Cloudron](https://cloudron.io):
[![Install](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=io.hackmd.cloudronapp)

View file

@ -1,14 +0,0 @@
LinuxServer.io CodiMD Image
===
[![LinuxServer.io Discord](https://img.shields.io/discord/354974912613449730.svg?logo=discord&label=LSIO%20Discord&style=flat-square)](https://discord.gg/YWrKVTn)[![container version badge](https://images.microbadger.com/badges/version/linuxserver/codimd.svg)](https://microbadger.com/images/linuxserver/codimd "Get your own version badge on microbadger.com")[![container image size badge](https://images.microbadger.com/badges/image/linuxserver/codimd.svg)](https://microbadger.com/images/linuxserver/codimd "Get your own version badge on microbadger.com")![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/codimd.svg)![Docker Stars](https://img.shields.io/docker/stars/linuxserver/codimd.svg)[![Build Status](https://ci.linuxserver.io/buildStatus/icon?job=Docker-Pipeline-Builders/docker-codimd/master)](https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-codimd/job/master/)[![LinuxServer.io CI summary](https://lsio-ci.ams3.digitaloceanspaces.com/linuxserver/codimd/latest/badge.svg)](https://lsio-ci.ams3.digitaloceanspaces.com/linuxserver/codimd/latest/index.html)
[LinuxServer.io](https://linuxserver.io) have created an Ubuntu-based multi-arch container image for x86-64, arm64 and armhf which supports PDF export from all architectures using [PhantomJS](https://phantomjs.org/).
- It supports all the environment variables detailed in the [configuration documentation](../configuration-env-vars.md) to modify it according to your needs.
- It gets rebuilt on new releases from CodiMD and also weekly if necessary to update any other package changes in the underlying container, making it easy to keep your CodiMD instance up to date.
- It also details how to easily [utilize Docker networking to reverse proxy](https://github.com/linuxserver/docker-codimd/#application-setup) CodiMD using their [LetsEncrypt docker image](https://github.com/linuxserver/docker-letsencrypt)
In order to contribute check the LinuxServer.io [GitHub repository](https://github.com/linuxserver/docker-codimd/) for CodiMD.
And to find all tags and versions of the image, check the [Docker Hub repository](https://hub.docker.com/r/linuxserver/codimd).

View file

@ -1,23 +0,0 @@
CodiMD Docker Image
===
[![Try in PWD](https://cdn.rawgit.com/play-with-docker/stacks/cff22438/assets/images/button.png)](http://play-with-docker.com?stack=https://github.com/codimd/container/raw/master/docker-compose.yml&stack_name=codimd)
**Debian-based version:**
[![Docker Repository on Quay](https://quay.io/repository/codimd/server/status "Docker Repository on Quay")](https://quay.io/repository/codimd/server)
**Alpine-based version:**
[![Docker Repository on Quay](https://quay.io/repository/codimd/server/status "Docker Repository on Quay")](https://quay.io/repository/codimd/server)
The easiest way to setup CodiMD using docker are using the following three commands:
```sh
git clone https://github.com/codimd/container.git codimd-container
cd codimd-container
docker-compose up
```
Read more about it in the [container repository](https://github.com/codimd/container).

View file

@ -1,7 +0,0 @@
Heroku Deployment
===
You can quickly setup a sample Heroku CodiMD application by clicking the button
below.
[![Deploy on Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/codimd/server/tree/master)

View file

@ -1,6 +0,0 @@
Kubernetes
===
To install use `helm install stable/hackmd`.
For all further details, please check out the offical CodiMD [K8s helm chart](https://github.com/kubernetes/charts/tree/master/stable/hackmd).

View file

@ -1,39 +0,0 @@
Manual Installation
===
## Requirements on your server
- Node.js 8.5 or up
- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) use charset `utf8`
- npm (and its dependencies, [node-gyp](https://github.com/nodejs/node-gyp#installation))
- yarn
- Bash (for the setup script)
- For **building** CodiMD we recommend to use a machine with at least **2GB** RAM
## Instructions
1. Check if you meet the [requirements at the top of this document](#requirements-on-your-server).
2. Clone this repository (preferred) or download a release and unzip it.
3. Enter the directory and type `bin/setup`, which will install npm dependencies and create configs.
4. Setup the configs, see more below
5. Setup environment variables which will overwrite the configs
6. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development)
7. Modify the file named `.sequelizerc`, change the value of the variable `url` with your db connection string
For example: `postgres://username:password@localhost:5432/codimd`
8. It is recommended to start your server manually once: `npm start --production`, this way it's easier to see warnings or errors that might occur (leave out `--production` for development).
9. Run the server as you like (node, forever, pm2, SystemD, Init-Scripts)
## How to upgrade your installation
If you are upgrading CodiMD from an older version, follow these steps:
1. Check if you meet the [requirements at the top of this document](#requirements-on-your-server).
2. Verify which version you were running before and take a look at [migrations and breaking changes](../guides/migrations-and-breaking-changes.md) to see if additional steps, or configuration changes are necessary!
3. Fully stop your old CodiMD server.
4. `git pull` or unzip a new release in the directory.
5. Run `bin/setup`. This will take care of installing dependencies. It is safe to run on an existing installation.
6. Build front-end bundle by `npm run build` (use `npm run dev` if you are in development).
7. It is recommended to start your server manually once: `npm start --production`, this way it's easier to see warnings or errors that might occur (leave out `--production` for development).
8. You can now restart the CodiMD server!

View file

@ -1,161 +0,0 @@
Slide Separators
===
If you're getting started with reveal.js slides, there are a few things you need to know.
There are two types of slides, those that transition horizontally and those that transition vertically (subslides).
The following separators are used for each in the CodiMD syntax:
```
# First Slide
---
# Next slide
----
## Subslide
```
as you can see, horizontal transitions are separated by `---` and vertical transitions by `----`
## Basic YAML header
It's possible to customise the slide options using the YAML header in the slide markdown.
eg:
```
---
title: Example Slide
tags: presentation
slideOptions:
theme: solarized
transition: 'fade'
# parallaxBackgroundImage: 'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg'
---
```
make sure to have two spaces only at the start of the listed slide options.
you can comment out options with a `#`
### Some other options
```
# Display controls in the bottom right corner
controls: true
# Display a presentation progress bar
progress: true
# Set default timing of 2 minutes per slide
defaultTiming: 120
# Display the page number of the current slide
slideNumber: false
# Push each slide change to the browser history
history: false
# Enable keyboard shortcuts for navigation
keyboard: true
# Enable the slide overview mode
overview: true
# Vertical centering of slides
center: true
# Enables touch navigation on devices with touch input
touch: true
# Loop the presentation
loop: false
# Change the presentation direction to be RTL
rtl: false
# Randomizes the order of slides each time the presentation loads
shuffle: false
# Turns fragments on and off globally
fragments: true
# Flags if the presentation is running in an embedded mode,
# i.e. contained within a limited portion of the screen
embedded: false
# Flags if we should show a help overlay when the questionmark
# key is pressed
help: true
# Flags if speaker notes should be visible to all viewers
showNotes: false
# Global override for autolaying embedded media (video/audio/iframe)
# - null: Media will only autoplay if data-autoplay is present
# - true: All media will autoplay, regardless of individual setting
# - false: No media will autoplay, regardless of individual setting
autoPlayMedia: null
# Number of milliseconds between automatically proceeding to the
# next slide, disabled when set to 0, this value can be overwritten
# by using a data-autoslide attribute on your slides
autoSlide: 0
# Stop auto-sliding after user input
autoSlideStoppable: true
# Use this method for navigation when auto-sliding
autoSlideMethod: Reveal.navigateNext
# Enable slide navigation via mouse wheel
mouseWheel: false
# Hides the address bar on mobile devices
hideAddressBar: true
# Opens links in an iframe preview overlay
previewLinks: false
# Transition style
transition: 'slide'
# none/fade/slide/convex/concave/zoom
# Transition speed
transitionSpeed: 'default'
# default/fast/slow
# Transition style for full page slide backgrounds
backgroundTransition: 'fade'
# none/fade/slide/convex/concave/zoom
# Number of slides away from the current that are visible
viewDistance: 3
# Parallax background image
parallaxBackgroundImage: ''
# e.g. "'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg'"
# Parallax background size
parallaxBackgroundSize: ''
# CSS syntax, e.g. "2100px 900px"
# Number of pixels to move the parallax background per slide
# - Calculated automatically unless specified
# - Set to 0 to disable movement along an axis
parallaxBackgroundHorizontal: null
parallaxBackgroundVertical: null
# The display mode that will be used to show slides
display: 'block'
```
## Customising individual slides
custom background image:
```
---
<!-- .slide: data-background="https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg" -->
#### testslide
---
```

View file

@ -1,19 +1,15 @@
'use strict' 'use strict'
const os = require('os')
module.exports = { module.exports = {
domain: '', domain: '',
urlPath: '', urlpath: '',
host: '0.0.0.0',
port: 3000, port: 3000,
loglevel: 'info', urladdport: false,
urlAddPort: false, alloworigin: ['localhost'],
allowOrigin: ['localhost'], usessl: false,
useSSL: false,
hsts: { hsts: {
enable: true, enable: true,
maxAgeSeconds: 60 * 60 * 24 * 365, maxAgeSeconds: 31536000,
includeSubdomains: true, includeSubdomains: true,
preload: true preload: true
}, },
@ -22,49 +18,42 @@ module.exports = {
directives: { directives: {
}, },
addDefaults: true, addDefaults: true,
addDisqus: true, upgradeInsecureRequests: 'auto'
addGoogleAnalytics: true,
upgradeInsecureRequests: 'auto',
reportURI: undefined
}, },
protocolUseSSL: false, protocolusessl: false,
useCDN: true, usecdn: true,
allowAnonymous: true, allowanonymous: true,
allowAnonymousEdits: false, allowanonymousedits: false,
allowFreeURL: false, allowfreeurl: false,
forbiddenNoteIDs: ['robots.txt', 'favicon.ico', 'api'], defaultpermission: 'editable',
defaultPermission: 'editable', dburl: '',
dbURL: '',
db: {}, db: {},
// ssl path // ssl path
sslKeyPath: '', sslkeypath: '',
sslCertPath: '', sslcertpath: '',
sslCAPath: '', sslcapath: '',
dhParamPath: '', dhparampath: '',
// other path // other path
viewPath: './public/views', tmppath: './tmp',
tmpPath: os.tmpdir(), defaultnotepath: './public/default.md',
defaultNotePath: './public/default.md', docspath: './public/docs',
docsPath: './public/docs', indexpath: './public/views/index.ejs',
uploadsPath: './public/uploads', hackmdpath: './public/views/hackmd.ejs',
errorpath: './public/views/error.ejs',
prettypath: './public/views/pretty.ejs',
slidepath: './public/views/slide.ejs',
// session // session
sessionName: 'connect.sid', sessionname: 'connect.sid',
sessionSecret: 'secret', sessionsecret: 'secret',
sessionSecretLen: 128, sessionlife: 14 * 24 * 60 * 60 * 1000, // 14 days
sessionLife: 14 * 24 * 60 * 60 * 1000, // 14 days staticcachetime: 1 * 24 * 60 * 60 * 1000, // 1 day
staticCacheTime: 1 * 24 * 60 * 60 * 1000, // 1 day
// socket.io // socket.io
heartbeatInterval: 5000, heartbeatinterval: 5000,
heartbeatTimeout: 10000, heartbeattimeout: 10000,
// too busy timeout
tooBusyLag: 70,
// document // document
documentMaxLength: 100000, documentmaxlength: 100000,
// image upload setting, available options are imgur/s3/filesystem/azure/lutim // image upload setting, available options are imgur/s3/filesystem
imageUploadType: 'filesystem', imageUploadType: 'filesystem',
lutim: {
url: 'https://framapic.org/'
},
imgur: { imgur: {
clientID: undefined clientID: undefined
}, },
@ -81,18 +70,7 @@ module.exports = {
port: 9000 port: 9000
}, },
s3bucket: undefined, s3bucket: undefined,
azure: {
connectionString: undefined,
container: undefined
},
// authentication // authentication
oauth2: {
providerName: undefined,
authorizationURL: undefined,
tokenURL: undefined,
clientID: undefined,
clientSecret: undefined
},
facebook: { facebook: {
clientID: undefined, clientID: undefined,
clientSecret: undefined clientSecret: undefined
@ -109,8 +87,7 @@ module.exports = {
baseURL: undefined, baseURL: undefined,
clientID: undefined, clientID: undefined,
clientSecret: undefined, clientSecret: undefined,
scope: undefined, scope: undefined
version: 'v4'
}, },
mattermost: { mattermost: {
baseURL: undefined, baseURL: undefined,
@ -131,11 +108,11 @@ module.exports = {
url: undefined, url: undefined,
bindDn: undefined, bindDn: undefined,
bindCredentials: undefined, bindCredentials: undefined,
tokenSecret: undefined,
searchBase: undefined, searchBase: undefined,
searchFilter: undefined, searchFilter: undefined,
searchAttributes: undefined, searchAttributes: undefined,
usernameField: undefined, usernameField: undefined,
useridField: undefined,
tlsca: undefined tlsca: undefined
}, },
saml: { saml: {
@ -143,7 +120,6 @@ module.exports = {
idpCert: undefined, idpCert: undefined,
issuer: undefined, issuer: undefined,
identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
disableRequestedAuthnContext: false,
groupAttribute: undefined, groupAttribute: undefined,
externalGroups: [], externalGroups: [],
requiredGroups: [], requiredGroups: [],
@ -154,8 +130,6 @@ module.exports = {
} }
}, },
email: true, email: true,
allowEmailRegister: true, allowemailregister: true,
allowGravatar: true, allowpdfexport: true
allowPDFExport: true,
openID: false
} }

View file

@ -10,8 +10,8 @@ function getFile (path) {
} }
module.exports = { module.exports = {
sslKeyPath: getFile('/run/secrets/key.pem'), sslkeypath: getFile('/run/secrets/key.pem'),
sslCertPath: getFile('/run/secrets/cert.pem'), sslcertpath: getFile('/run/secrets/cert.pem'),
sslCAPath: getFile('/run/secrets/ca.pem') !== undefined ? [getFile('/run/secrets/ca.pem')] : [], sslcapath: getFile('/run/secrets/ca.pem') !== undefined ? [getFile('/run/secrets/ca.pem')] : [],
dhParamPath: getFile('/run/secrets/dhparam.pem') dhparampath: getFile('/run/secrets/dhparam.pem')
} }

View file

@ -13,7 +13,6 @@ function getSecret (secret) {
if (fs.existsSync(basePath)) { if (fs.existsSync(basePath)) {
module.exports = { module.exports = {
dbURL: getSecret('dbURL'),
sessionsecret: getSecret('sessionsecret'), sessionsecret: getSecret('sessionsecret'),
sslkeypath: getSecret('sslkeypath'), sslkeypath: getSecret('sslkeypath'),
sslcertpath: getSecret('sslcertpath'), sslcertpath: getSecret('sslcertpath'),
@ -23,9 +22,6 @@ if (fs.existsSync(basePath)) {
accessKeyId: getSecret('s3_acccessKeyId'), accessKeyId: getSecret('s3_acccessKeyId'),
secretAccessKey: getSecret('s3_secretAccessKey') secretAccessKey: getSecret('s3_secretAccessKey')
}, },
azure: {
connectionString: getSecret('azure_connectionString')
},
facebook: { facebook: {
clientID: getSecret('facebook_clientID'), clientID: getSecret('facebook_clientID'),
clientSecret: getSecret('facebook_clientSecret') clientSecret: getSecret('facebook_clientSecret')

View file

@ -1,137 +1,106 @@
'use strict' 'use strict'
const { toBooleanConfig, toArrayConfig, toIntegerConfig } = require('./utils') const {toBooleanConfig, toArrayConfig} = require('./utils')
module.exports = { module.exports = {
sourceURL: process.env.CMD_SOURCE_URL, domain: process.env.HMD_DOMAIN,
domain: process.env.CMD_DOMAIN, urlpath: process.env.HMD_URL_PATH,
urlPath: process.env.CMD_URL_PATH, port: process.env.HMD_PORT,
host: process.env.CMD_HOST, urladdport: toBooleanConfig(process.env.HMD_URL_ADDPORT),
port: toIntegerConfig(process.env.CMD_PORT), usessl: toBooleanConfig(process.env.HMD_USESSL),
path: process.env.CMD_PATH,
loglevel: process.env.CMD_LOGLEVEL,
urlAddPort: toBooleanConfig(process.env.CMD_URL_ADDPORT),
useSSL: toBooleanConfig(process.env.CMD_USESSL),
hsts: { hsts: {
enable: toBooleanConfig(process.env.CMD_HSTS_ENABLE), enable: toBooleanConfig(process.env.HMD_HSTS_ENABLE),
maxAgeSeconds: toIntegerConfig(process.env.CMD_HSTS_MAX_AGE), maxAgeSeconds: process.env.HMD_HSTS_MAX_AGE,
includeSubdomains: toBooleanConfig(process.env.CMD_HSTS_INCLUDE_SUBDOMAINS), includeSubdomains: toBooleanConfig(process.env.HMD_HSTS_INCLUDE_SUBDOMAINS),
preload: toBooleanConfig(process.env.CMD_HSTS_PRELOAD) preload: toBooleanConfig(process.env.HMD_HSTS_PRELOAD)
}, },
csp: { csp: {
enable: toBooleanConfig(process.env.CMD_CSP_ENABLE), enable: toBooleanConfig(process.env.HMD_CSP_ENABLE)
reportURI: process.env.CMD_CSP_REPORTURI
}, },
protocolUseSSL: toBooleanConfig(process.env.CMD_PROTOCOL_USESSL), protocolusessl: toBooleanConfig(process.env.HMD_PROTOCOL_USESSL),
allowOrigin: toArrayConfig(process.env.CMD_ALLOW_ORIGIN), alloworigin: toArrayConfig(process.env.HMD_ALLOW_ORIGIN),
useCDN: toBooleanConfig(process.env.CMD_USECDN), usecdn: toBooleanConfig(process.env.HMD_USECDN),
allowAnonymous: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS), allowanonymous: toBooleanConfig(process.env.HMD_ALLOW_ANONYMOUS),
allowAnonymousEdits: toBooleanConfig(process.env.CMD_ALLOW_ANONYMOUS_EDITS), allowanonymousedits: toBooleanConfig(process.env.HMD_ALLOW_ANONYMOUS_EDITS),
allowFreeURL: toBooleanConfig(process.env.CMD_ALLOW_FREEURL), allowfreeurl: toBooleanConfig(process.env.HMD_ALLOW_FREEURL),
forbiddenNoteIDs: toArrayConfig(process.env.CMD_FORBIDDEN_NOTE_IDS), defaultpermission: process.env.HMD_DEFAULT_PERMISSION,
defaultPermission: process.env.CMD_DEFAULT_PERMISSION, dburl: process.env.HMD_DB_URL,
dbURL: process.env.CMD_DB_URL, imageUploadType: process.env.HMD_IMAGE_UPLOAD_TYPE,
sessionSecret: process.env.CMD_SESSION_SECRET,
sessionLife: toIntegerConfig(process.env.CMD_SESSION_LIFE),
tooBusyLag: toIntegerConfig(process.env.CMD_TOOBUSY_LAG),
imageUploadType: process.env.CMD_IMAGE_UPLOAD_TYPE,
imgur: { imgur: {
clientID: process.env.CMD_IMGUR_CLIENTID clientID: process.env.HMD_IMGUR_CLIENTID
}, },
s3: { s3: {
accessKeyId: process.env.CMD_S3_ACCESS_KEY_ID, accessKeyId: process.env.HMD_S3_ACCESS_KEY_ID,
secretAccessKey: process.env.CMD_S3_SECRET_ACCESS_KEY, secretAccessKey: process.env.HMD_S3_SECRET_ACCESS_KEY,
region: process.env.CMD_S3_REGION region: process.env.HMD_S3_REGION
}, },
minio: { minio: {
accessKey: process.env.CMD_MINIO_ACCESS_KEY, accessKey: process.env.HMD_MINIO_ACCESS_KEY,
secretKey: process.env.CMD_MINIO_SECRET_KEY, secretKey: process.env.HMD_MINIO_SECRET_KEY,
endPoint: process.env.CMD_MINIO_ENDPOINT, endPoint: process.env.HMD_MINIO_ENDPOINT,
secure: toBooleanConfig(process.env.CMD_MINIO_SECURE), secure: toBooleanConfig(process.env.HMD_MINIO_SECURE),
port: toIntegerConfig(process.env.CMD_MINIO_PORT) port: process.env.HMD_MINIO_PORT
},
lutim: {
url: process.env.CMD_LUTIM_URL
},
s3bucket: process.env.CMD_S3_BUCKET,
azure: {
connectionString: process.env.CMD_AZURE_CONNECTION_STRING,
container: process.env.CMD_AZURE_CONTAINER
}, },
s3bucket: process.env.HMD_S3_BUCKET,
facebook: { facebook: {
clientID: process.env.CMD_FACEBOOK_CLIENTID, clientID: process.env.HMD_FACEBOOK_CLIENTID,
clientSecret: process.env.CMD_FACEBOOK_CLIENTSECRET clientSecret: process.env.HMD_FACEBOOK_CLIENTSECRET
}, },
twitter: { twitter: {
consumerKey: process.env.CMD_TWITTER_CONSUMERKEY, consumerKey: process.env.HMD_TWITTER_CONSUMERKEY,
consumerSecret: process.env.CMD_TWITTER_CONSUMERSECRET consumerSecret: process.env.HMD_TWITTER_CONSUMERSECRET
}, },
github: { github: {
clientID: process.env.CMD_GITHUB_CLIENTID, clientID: process.env.HMD_GITHUB_CLIENTID,
clientSecret: process.env.CMD_GITHUB_CLIENTSECRET clientSecret: process.env.HMD_GITHUB_CLIENTSECRET
}, },
gitlab: { gitlab: {
baseURL: process.env.CMD_GITLAB_BASEURL, baseURL: process.env.HMD_GITLAB_BASEURL,
clientID: process.env.CMD_GITLAB_CLIENTID, clientID: process.env.HMD_GITLAB_CLIENTID,
clientSecret: process.env.CMD_GITLAB_CLIENTSECRET, clientSecret: process.env.HMD_GITLAB_CLIENTSECRET,
scope: process.env.CMD_GITLAB_SCOPE scope: process.env.HMD_GITLAB_SCOPE
}, },
mattermost: { mattermost: {
baseURL: process.env.CMD_MATTERMOST_BASEURL, baseURL: process.env.HMD_MATTERMOST_BASEURL,
clientID: process.env.CMD_MATTERMOST_CLIENTID, clientID: process.env.HMD_MATTERMOST_CLIENTID,
clientSecret: process.env.CMD_MATTERMOST_CLIENTSECRET clientSecret: process.env.HMD_MATTERMOST_CLIENTSECRET
},
oauth2: {
providerName: process.env.CMD_OAUTH2_PROVIDERNAME,
baseURL: process.env.CMD_OAUTH2_BASEURL,
userProfileURL: process.env.CMD_OAUTH2_USER_PROFILE_URL,
userProfileUsernameAttr: process.env.CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR,
userProfileDisplayNameAttr: process.env.CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR,
userProfileEmailAttr: process.env.CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR,
tokenURL: process.env.CMD_OAUTH2_TOKEN_URL,
authorizationURL: process.env.CMD_OAUTH2_AUTHORIZATION_URL,
clientID: process.env.CMD_OAUTH2_CLIENT_ID,
clientSecret: process.env.CMD_OAUTH2_CLIENT_SECRET
}, },
dropbox: { dropbox: {
clientID: process.env.CMD_DROPBOX_CLIENTID, clientID: process.env.HMD_DROPBOX_CLIENTID,
clientSecret: process.env.CMD_DROPBOX_CLIENTSECRET, clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET,
appKey: process.env.CMD_DROPBOX_APPKEY appKey: process.env.HMD_DROPBOX_APPKEY
}, },
google: { google: {
clientID: process.env.CMD_GOOGLE_CLIENTID, clientID: process.env.HMD_GOOGLE_CLIENTID,
clientSecret: process.env.CMD_GOOGLE_CLIENTSECRET clientSecret: process.env.HMD_GOOGLE_CLIENTSECRET
}, },
ldap: { ldap: {
providerName: process.env.CMD_LDAP_PROVIDERNAME, providerName: process.env.HMD_LDAP_PROVIDERNAME,
url: process.env.CMD_LDAP_URL, url: process.env.HMD_LDAP_URL,
bindDn: process.env.CMD_LDAP_BINDDN, bindDn: process.env.HMD_LDAP_BINDDN,
bindCredentials: process.env.CMD_LDAP_BINDCREDENTIALS, bindCredentials: process.env.HMD_LDAP_BINDCREDENTIALS,
searchBase: process.env.CMD_LDAP_SEARCHBASE, tokenSecret: process.env.HMD_LDAP_TOKENSECRET,
searchFilter: process.env.CMD_LDAP_SEARCHFILTER, searchBase: process.env.HMD_LDAP_SEARCHBASE,
searchAttributes: toArrayConfig(process.env.CMD_LDAP_SEARCHATTRIBUTES), searchFilter: process.env.HMD_LDAP_SEARCHFILTER,
usernameField: process.env.CMD_LDAP_USERNAMEFIELD, searchAttributes: toArrayConfig(process.env.HMD_LDAP_SEARCHATTRIBUTES),
useridField: process.env.CMD_LDAP_USERIDFIELD, usernameField: process.env.HMD_LDAP_USERNAMEFIELD,
tlsca: process.env.CMD_LDAP_TLS_CA tlsca: process.env.HMD_LDAP_TLS_CA
}, },
saml: { saml: {
idpSsoUrl: process.env.CMD_SAML_IDPSSOURL, idpSsoUrl: process.env.HMD_SAML_IDPSSOURL,
idpCert: process.env.CMD_SAML_IDPCERT, idpCert: process.env.HMD_SAML_IDPCERT,
issuer: process.env.CMD_SAML_ISSUER, issuer: process.env.HMD_SAML_ISSUER,
identifierFormat: process.env.CMD_SAML_IDENTIFIERFORMAT, identifierFormat: process.env.HMD_SAML_IDENTIFIERFORMAT,
disableRequestedAuthnContext: toBooleanConfig(process.env.CMD_SAML_DISABLEREQUESTEDAUTHNCONTEXT), groupAttribute: process.env.HMD_SAML_GROUPATTRIBUTE,
groupAttribute: process.env.CMD_SAML_GROUPATTRIBUTE, externalGroups: toArrayConfig(process.env.HMD_SAML_EXTERNALGROUPS, '|', []),
externalGroups: toArrayConfig(process.env.CMD_SAML_EXTERNALGROUPS, '|', []), requiredGroups: toArrayConfig(process.env.HMD_SAML_REQUIREDGROUPS, '|', []),
requiredGroups: toArrayConfig(process.env.CMD_SAML_REQUIREDGROUPS, '|', []),
attribute: { attribute: {
id: process.env.CMD_SAML_ATTRIBUTE_ID, id: process.env.HMD_SAML_ATTRIBUTE_ID,
username: process.env.CMD_SAML_ATTRIBUTE_USERNAME, username: process.env.HMD_SAML_ATTRIBUTE_USERNAME,
email: process.env.CMD_SAML_ATTRIBUTE_EMAIL email: process.env.HMD_SAML_ATTRIBUTE_EMAIL
} }
}, },
email: toBooleanConfig(process.env.CMD_EMAIL), email: toBooleanConfig(process.env.HMD_EMAIL),
allowEmailRegister: toBooleanConfig(process.env.CMD_ALLOW_EMAIL_REGISTER), allowemailregister: toBooleanConfig(process.env.HMD_ALLOW_EMAIL_REGISTER),
allowGravatar: toBooleanConfig(process.env.CMD_ALLOW_GRAVATAR), allowpdfexport: toBooleanConfig(process.env.HMD_ALLOW_PDF_EXPORT)
allowPDFExport: toBooleanConfig(process.env.CMD_ALLOW_PDF_EXPORT),
openID: toBooleanConfig(process.env.CMD_OPENID)
} }

View file

@ -1,125 +0,0 @@
'use strict'
const { toBooleanConfig, toArrayConfig, toIntegerConfig } = require('./utils')
module.exports = {
domain: process.env.HMD_DOMAIN,
urlPath: process.env.HMD_URL_PATH,
port: toIntegerConfig(process.env.HMD_PORT),
urlAddPort: toBooleanConfig(process.env.HMD_URL_ADDPORT),
useSSL: toBooleanConfig(process.env.HMD_USESSL),
hsts: {
enable: toBooleanConfig(process.env.HMD_HSTS_ENABLE),
maxAgeSeconds: toIntegerConfig(process.env.HMD_HSTS_MAX_AGE),
includeSubdomains: toBooleanConfig(process.env.HMD_HSTS_INCLUDE_SUBDOMAINS),
preload: toBooleanConfig(process.env.HMD_HSTS_PRELOAD)
},
csp: {
enable: toBooleanConfig(process.env.HMD_CSP_ENABLE),
reportURI: process.env.HMD_CSP_REPORTURI
},
protocolUseSSL: toBooleanConfig(process.env.HMD_PROTOCOL_USESSL),
allowOrigin: toArrayConfig(process.env.HMD_ALLOW_ORIGIN),
useCDN: toBooleanConfig(process.env.HMD_USECDN),
allowAnonymous: toBooleanConfig(process.env.HMD_ALLOW_ANONYMOUS),
allowAnonymousEdits: toBooleanConfig(process.env.HMD_ALLOW_ANONYMOUS_EDITS),
allowFreeURL: toBooleanConfig(process.env.HMD_ALLOW_FREEURL),
defaultPermission: process.env.HMD_DEFAULT_PERMISSION,
dbURL: process.env.HMD_DB_URL,
sessionSecret: process.env.HMD_SESSION_SECRET,
sessionLife: toIntegerConfig(process.env.HMD_SESSION_LIFE),
imageUploadType: process.env.HMD_IMAGE_UPLOAD_TYPE,
imgur: {
clientID: process.env.HMD_IMGUR_CLIENTID
},
s3: {
accessKeyId: process.env.HMD_S3_ACCESS_KEY_ID,
secretAccessKey: process.env.HMD_S3_SECRET_ACCESS_KEY,
region: process.env.HMD_S3_REGION
},
minio: {
accessKey: process.env.HMD_MINIO_ACCESS_KEY,
secretKey: process.env.HMD_MINIO_SECRET_KEY,
endPoint: process.env.HMD_MINIO_ENDPOINT,
secure: toBooleanConfig(process.env.HMD_MINIO_SECURE),
port: toIntegerConfig(process.env.HMD_MINIO_PORT)
},
s3bucket: process.env.HMD_S3_BUCKET,
azure: {
connectionString: process.env.HMD_AZURE_CONNECTION_STRING,
container: process.env.HMD_AZURE_CONTAINER
},
facebook: {
clientID: process.env.HMD_FACEBOOK_CLIENTID,
clientSecret: process.env.HMD_FACEBOOK_CLIENTSECRET
},
twitter: {
consumerKey: process.env.HMD_TWITTER_CONSUMERKEY,
consumerSecret: process.env.HMD_TWITTER_CONSUMERSECRET
},
github: {
clientID: process.env.HMD_GITHUB_CLIENTID,
clientSecret: process.env.HMD_GITHUB_CLIENTSECRET
},
gitlab: {
baseURL: process.env.HMD_GITLAB_BASEURL,
clientID: process.env.HMD_GITLAB_CLIENTID,
clientSecret: process.env.HMD_GITLAB_CLIENTSECRET,
scope: process.env.HMD_GITLAB_SCOPE
},
mattermost: {
baseURL: process.env.HMD_MATTERMOST_BASEURL,
clientID: process.env.HMD_MATTERMOST_CLIENTID,
clientSecret: process.env.HMD_MATTERMOST_CLIENTSECRET
},
oauth2: {
baseURL: process.env.HMD_OAUTH2_BASEURL,
userProfileURL: process.env.HMD_OAUTH2_USER_PROFILE_URL,
userProfileUsernameAttr: process.env.HMD_OAUTH2_USER_PROFILE_USERNAME_ATTR,
userProfileDisplayNameAttr: process.env.HMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR,
userProfileEmailAttr: process.env.HMD_OAUTH2_USER_PROFILE_EMAIL_ATTR,
tokenURL: process.env.HMD_OAUTH2_TOKEN_URL,
authorizationURL: process.env.HMD_OAUTH2_AUTHORIZATION_URL,
clientID: process.env.HMD_OAUTH2_CLIENT_ID,
clientSecret: process.env.HMD_OAUTH2_CLIENT_SECRET
},
dropbox: {
clientID: process.env.HMD_DROPBOX_CLIENTID,
clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET,
appKey: process.env.HMD_DROPBOX_APPKEY
},
google: {
clientID: process.env.HMD_GOOGLE_CLIENTID,
clientSecret: process.env.HMD_GOOGLE_CLIENTSECRET
},
ldap: {
providerName: process.env.HMD_LDAP_PROVIDERNAME,
url: process.env.HMD_LDAP_URL,
bindDn: process.env.HMD_LDAP_BINDDN,
bindCredentials: process.env.HMD_LDAP_BINDCREDENTIALS,
searchBase: process.env.HMD_LDAP_SEARCHBASE,
searchFilter: process.env.HMD_LDAP_SEARCHFILTER,
searchAttributes: toArrayConfig(process.env.HMD_LDAP_SEARCHATTRIBUTES),
usernameField: process.env.HMD_LDAP_USERNAMEFIELD,
useridField: process.env.HMD_LDAP_USERIDFIELD,
tlsca: process.env.HMD_LDAP_TLS_CA
},
saml: {
idpSsoUrl: process.env.HMD_SAML_IDPSSOURL,
idpCert: process.env.HMD_SAML_IDPCERT,
issuer: process.env.HMD_SAML_ISSUER,
identifierFormat: process.env.HMD_SAML_IDENTIFIERFORMAT,
disableRequestedAuthnContext: toBooleanConfig(process.env.HMD_SAML_DISABLEREQUESTEDAUTHNCONTEXT),
groupAttribute: process.env.HMD_SAML_GROUPATTRIBUTE,
externalGroups: toArrayConfig(process.env.HMD_SAML_EXTERNALGROUPS, '|', []),
requiredGroups: toArrayConfig(process.env.HMD_SAML_REQUIREDGROUPS, '|', []),
attribute: {
id: process.env.HMD_SAML_ATTRIBUTE_ID,
username: process.env.HMD_SAML_ATTRIBUTE_USERNAME,
email: process.env.HMD_SAML_ATTRIBUTE_EMAIL
}
},
email: toBooleanConfig(process.env.HMD_EMAIL),
allowEmailRegister: toBooleanConfig(process.env.HMD_ALLOW_EMAIL_REGISTER),
allowPDFExport: toBooleanConfig(process.env.HMD_ALLOW_PDF_EXPORT)
}

View file

@ -1,56 +1,37 @@
'use strict' 'use strict'
const crypto = require('crypto')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const { merge } = require('lodash') const {merge} = require('lodash')
const deepFreeze = require('deep-freeze') const deepFreeze = require('deep-freeze')
const { Environment, Permission } = require('./enum') const {Environment, Permission} = require('./enum')
const logger = require('../logger')
const { getGitCommit, getGitHubURL } = require('./utils')
const appRootPath = path.resolve(__dirname, '../../') const appRootPath = path.join(__dirname, '../../')
const env = process.env.NODE_ENV || Environment.development const env = process.env.NODE_ENV || Environment.development
const debugConfig = { const debugConfig = {
debug: (env === Environment.development) debug: (env === Environment.development)
} }
// Get version string from package.json const {version} = require(path.join(appRootPath, 'package.json'))
const { version, repository } = require(path.join(appRootPath, 'package.json'))
const commitID = getGitCommit(appRootPath)
const sourceURL = getGitHubURL(repository.url, commitID || version)
const fullversion = commitID ? `${version}-${commitID}` : version
const packageConfig = { const packageConfig = {
version: version, version: version,
minimumCompatibleVersion: '0.5.0', minimumCompatibleVersion: '0.5.0'
fullversion: fullversion,
sourceURL: sourceURL
} }
const configFilePath = path.resolve(appRootPath, process.env.CMD_CONFIG_FILE || const configFilePath = path.join(__dirname, '../../config.json')
'config.json')
const fileConfig = fs.existsSync(configFilePath) ? require(configFilePath)[env] : undefined const fileConfig = fs.existsSync(configFilePath) ? require(configFilePath)[env] : undefined
let config = require('./default') let config = require('./default')
merge(config, require('./defaultSSL')) merge(config, require('./defaultSSL'))
merge(config, require('./oldDefault'))
merge(config, debugConfig) merge(config, debugConfig)
merge(config, packageConfig) merge(config, packageConfig)
merge(config, fileConfig) merge(config, fileConfig)
merge(config, require('./oldEnvironment')) merge(config, require('./oldEnvironment'))
merge(config, require('./hackmdEnvironment'))
merge(config, require('./environment')) merge(config, require('./environment'))
merge(config, require('./dockerSecret')) merge(config, require('./dockerSecret'))
if (['debug', 'verbose', 'info', 'warn', 'error'].includes(config.loglevel)) {
logger.level = config.loglevel
} else {
logger.error('Selected loglevel %s doesn\'t exist, using default level \'debug\'. Available options: debug, verbose, info, warn, error', config.loglevel)
}
// load LDAP CA // load LDAP CA
if (config.ldap.tlsca) { if (config.ldap.tlsca) {
let ca = config.ldap.tlsca.split(',') let ca = config.ldap.tlsca.split(',')
@ -68,43 +49,39 @@ if (config.ldap.tlsca) {
// Permission // Permission
config.permission = Permission config.permission = Permission
if (!config.allowAnonymous && !config.allowAnonymousEdits) { if (!config.allowanonymous && !config.allowanonymousedits) {
delete config.permission.freely delete config.permission.freely
} }
if (!(config.defaultPermission in config.permission)) { if (!(config.defaultpermission in config.permission)) {
config.defaultPermission = config.permission.editable config.defaultpermission = config.permission.editable
} }
// cache result, cannot change config in runtime!!! // cache result, cannot change config in runtime!!!
config.isStandardHTTPsPort = (function isStandardHTTPsPort () { config.isStandardHTTPsPort = (function isStandardHTTPsPort () {
return config.useSSL && config.port === 443 return config.usessl && config.port === 443
})() })()
config.isStandardHTTPPort = (function isStandardHTTPPort () { config.isStandardHTTPPort = (function isStandardHTTPPort () {
return !config.useSSL && config.port === 80 return !config.usessl && config.port === 80
})() })()
// cache serverURL // cache serverURL
config.serverURL = (function getserverurl () { config.serverurl = (function getserverurl () {
var url = '' var url = ''
if (config.domain) { if (config.domain) {
var protocol = config.protocolUseSSL ? 'https://' : 'http://' var protocol = config.protocolusessl ? 'https://' : 'http://'
url = protocol + config.domain url = protocol + config.domain
if (config.urlAddPort) { if (config.urladdport) {
if (!config.isStandardHTTPPort || !config.isStandardHTTPsPort) { if (!config.isStandardHTTPPort || !config.isStandardHTTPsPort) {
url += ':' + config.port url += ':' + config.port
} }
} }
} }
if (config.urlPath) { if (config.urlpath) {
url += '/' + config.urlPath url += '/' + config.urlpath
} }
return url return url
})() })()
if (config.serverURL === '') {
logger.warn('Neither \'domain\' nor \'CMD_DOMAIN\' is configured. This can cause issues with various components.\nHint: Make sure \'protocolUseSSL\' and \'urlAddPort\' or \'CMD_PROTOCOL_USESSL\' and \'CMD_URL_ADDPORT\' are configured properly.')
}
config.Environment = Environment config.Environment = Environment
// auth method // auth method
@ -113,61 +90,12 @@ config.isGoogleEnable = config.google.clientID && config.google.clientSecret
config.isDropboxEnable = config.dropbox.clientID && config.dropbox.clientSecret config.isDropboxEnable = config.dropbox.clientID && config.dropbox.clientSecret
config.isTwitterEnable = config.twitter.consumerKey && config.twitter.consumerSecret config.isTwitterEnable = config.twitter.consumerKey && config.twitter.consumerSecret
config.isEmailEnable = config.email config.isEmailEnable = config.email
config.isOpenIDEnable = config.openID
config.isGitHubEnable = config.github.clientID && config.github.clientSecret config.isGitHubEnable = config.github.clientID && config.github.clientSecret
config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret
config.isLDAPEnable = config.ldap.url config.isLDAPEnable = config.ldap.url
config.isSAMLEnable = config.saml.idpSsoUrl config.isSAMLEnable = config.saml.idpSsoUrl
config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret config.isPDFExportEnable = config.allowpdfexport
config.isPDFExportEnable = config.allowPDFExport
// Check gitlab api version
if (config.gitlab && config.gitlab.version !== 'v4' && config.gitlab.version !== 'v3') {
logger.warn('config.js contains wrong version (' + config.gitlab.version + ') for gitlab api; it should be \'v3\' or \'v4\'. Defaulting to v4')
config.gitlab.version = 'v4'
}
// If gitlab scope is api, enable snippets Export/import
config.isGitlabSnippetsEnable = (!config.gitlab.scope || config.gitlab.scope === 'api') && config.isGitLabEnable
// Only update i18n files in development setups
config.updateI18nFiles = (env === Environment.development)
// merge legacy values
let keys = Object.keys(config)
const uppercase = /[A-Z]/
for (let i = keys.length; i--;) {
let lowercaseKey = keys[i].toLowerCase()
// if the config contains uppercase letters
// and a lowercase version of this setting exists
// and the config with uppercase is not set
// we set the new config using the old key.
if (uppercase.test(keys[i]) &&
config[lowercaseKey] !== undefined &&
fileConfig[keys[i]] === undefined) {
logger.warn('config.js contains deprecated lowercase setting for ' + keys[i] + '. Please change your config.js file to replace ' + lowercaseKey + ' with ' + keys[i])
config[keys[i]] = config[lowercaseKey]
}
}
// Notify users about the prefix change and inform them they use legacy prefix for environment variables
if (Object.keys(process.env).toString().indexOf('HMD_') !== -1) {
logger.warn('Using legacy HMD prefix for environment variables. Please change your variables in future. For details see: https://github.com/codimd/server#environment-variables-will-overwrite-other-server-configs')
}
// Generate session secret if it stays on default values
if (config.sessionSecret === 'secret') {
logger.warn('Session secret not set. Using random generated one. Please set `sessionSecret` in your config.js file. All users will be logged out.')
config.sessionSecret = crypto.randomBytes(Math.ceil(config.sessionSecretLen / 2)) // generate crypto graphic random number
.toString('hex') // convert to hexadecimal format
.slice(0, config.sessionSecretLen) // return required number of characters
}
// Validate upload upload providers
if (['filesystem', 's3', 'minio', 'imgur', 'azure', 'lutim'].indexOf(config.imageUploadType) === -1) {
logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio", "azure", "lutim" or "imgur". Defaulting to "filesystem"')
config.imageUploadType = 'filesystem'
}
// figure out mime types for image uploads // figure out mime types for image uploads
switch (config.imageUploadType) { switch (config.imageUploadType) {
@ -190,18 +118,22 @@ switch (config.imageUploadType) {
} }
// generate correct path // generate correct path
config.sslCAPath.forEach(function (capath, i, array) { config.sslcapath.forEach(function (capath, i, array) {
array[i] = path.resolve(appRootPath, capath) array[i] = path.resolve(appRootPath, capath)
}) })
config.sslCertPath = path.resolve(appRootPath, config.sslCertPath) config.sslcertpath = path.join(appRootPath, config.sslcertpath)
config.sslKeyPath = path.resolve(appRootPath, config.sslKeyPath) config.sslkeypath = path.join(appRootPath, config.sslkeypath)
config.dhParamPath = path.resolve(appRootPath, config.dhParamPath) config.dhparampath = path.join(appRootPath, config.dhparampath)
config.viewPath = path.resolve(appRootPath, config.viewPath)
config.tmpPath = path.resolve(appRootPath, config.tmpPath) config.tmppath = path.join(appRootPath, config.tmppath)
config.defaultNotePath = path.resolve(appRootPath, config.defaultNotePath) config.defaultnotepath = path.join(appRootPath, config.defaultnotepath)
config.docsPath = path.resolve(appRootPath, config.docsPath) config.docspath = path.join(appRootPath, config.docspath)
config.uploadsPath = path.resolve(appRootPath, config.uploadsPath) config.indexpath = path.join(appRootPath, config.indexpath)
config.hackmdpath = path.join(appRootPath, config.hackmdpath)
config.errorpath = path.join(appRootPath, config.errorpath)
config.prettypath = path.join(appRootPath, config.prettypath)
config.slidepath = path.join(appRootPath, config.slidepath)
// make config readonly // make config readonly
config = deepFreeze(config) config = deepFreeze(config)

View file

@ -1,42 +0,0 @@
'use strict'
module.exports = {
urlpath: undefined,
urladdport: undefined,
alloworigin: undefined,
usessl: undefined,
protocolusessl: undefined,
usecdn: undefined,
allowanonymous: undefined,
allowanonymousedits: undefined,
allowfreeurl: undefined,
defaultpermission: undefined,
dburl: undefined,
// ssl path
sslkeypath: undefined,
sslcertpath: undefined,
sslcapath: undefined,
dhparampath: undefined,
// other path
tmppath: undefined,
defaultnotepath: undefined,
docspath: undefined,
indexpath: undefined,
hackmdpath: undefined,
errorpath: undefined,
prettypath: undefined,
slidepath: undefined,
// session
sessionname: undefined,
sessionsecret: undefined,
sessionlife: undefined,
staticcachetime: undefined,
// socket.io
heartbeatinterval: undefined,
heartbeattimeout: undefined,
// document
documentmaxlength: undefined,
imageuploadtype: undefined,
allowemailregister: undefined,
allowpdfexport: undefined
}

View file

@ -1,6 +1,6 @@
'use strict' 'use strict'
const { toBooleanConfig } = require('./utils') const {toBooleanConfig} = require('./utils')
module.exports = { module.exports = {
debug: toBooleanConfig(process.env.DEBUG), debug: toBooleanConfig(process.env.DEBUG),

View file

@ -1,8 +1,5 @@
'use strict' 'use strict'
const fs = require('fs')
const path = require('path')
exports.toBooleanConfig = function toBooleanConfig (configValue) { exports.toBooleanConfig = function toBooleanConfig (configValue) {
if (configValue && typeof configValue === 'string') { if (configValue && typeof configValue === 'string') {
return (configValue === 'true') return (configValue === 'true')
@ -16,40 +13,3 @@ exports.toArrayConfig = function toArrayConfig (configValue, separator = ',', fa
} }
return fallback return fallback
} }
exports.toIntegerConfig = function toIntegerConfig (configValue) {
if (configValue && typeof configValue === 'string') {
return parseInt(configValue)
}
return configValue
}
exports.getGitCommit = function getGitCommit (repodir) {
if (!fs.existsSync(repodir + '/.git/HEAD')) {
return undefined
}
let reference = fs.readFileSync(repodir + '/.git/HEAD', 'utf8')
if (reference.startsWith('ref: ')) {
reference = reference.substr(5).replace('\n', '')
reference = fs.readFileSync(path.resolve(repodir + '/.git', reference), 'utf8')
}
reference = reference.replace('\n', '')
return reference
}
exports.getGitHubURL = function getGitHubURL (repo, reference) {
// if it's not a github reference, we handle handle that anyway
if (!repo.startsWith('https://github.com') && !repo.startsWith('git@github.com')) {
return repo
}
if (repo.startsWith('git@github.com') || repo.startsWith('ssh://git@github.com')) {
repo = repo.replace(/^(ssh:\/\/)?git@github.com:/, 'https://github.com/')
}
if (repo.endsWith('.git')) {
repo = repo.replace(/\.git$/, '/')
} else if (!repo.endsWith('/')) {
repo = repo + '/'
}
return repo + 'tree/' + reference
}

View file

@ -5,13 +5,12 @@ var CspStrategy = {}
var defaultDirectives = { var defaultDirectives = {
defaultSrc: ['\'self\''], defaultSrc: ['\'self\''],
scriptSrc: ['\'self\'', 'vimeo.com', 'https://gist.github.com', 'www.slideshare.net', 'https://query.yahooapis.com', '\'unsafe-eval\''], scriptSrc: ['\'self\'', 'vimeo.com', 'https://gist.github.com', 'www.slideshare.net', 'https://query.yahooapis.com', 'https://*.disqus.com', '\'unsafe-eval\''],
// ^ TODO: Remove unsafe-eval - webpack script-loader issues https://github.com/hackmdio/codimd/issues/594 // ^ TODO: Remove unsafe-eval - webpack script-loader issues https://github.com/hackmdio/hackmd/issues/594
imgSrc: ['*'], imgSrc: ['*'],
styleSrc: ['\'self\'', '\'unsafe-inline\'', 'https://github.githubassets.com'], // unsafe-inline is required for some libs, plus used in views styleSrc: ['\'self\'', '\'unsafe-inline\'', 'https://assets-cdn.github.com'], // unsafe-inline is required for some libs, plus used in views
fontSrc: ['\'self\'', 'data:', 'https://public.slidesharecdn.com'], fontSrc: ['\'self\'', 'https://public.slidesharecdn.com'],
objectSrc: ['*'], // Chrome PDF viewer treats PDFs as objects :/ objectSrc: ['*'], // Chrome PDF viewer treats PDFs as objects :/
mediaSrc: ['*'],
childSrc: ['*'], childSrc: ['*'],
connectSrc: ['*'] connectSrc: ['*']
} }
@ -22,28 +21,15 @@ var cdnDirectives = {
fontSrc: ['https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com'] fontSrc: ['https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com']
} }
var disqusDirectives = {
scriptSrc: ['https://disqus.com', 'https://*.disqus.com', 'https://*.disquscdn.com'],
styleSrc: ['https://*.disquscdn.com'],
fontSrc: ['https://*.disquscdn.com']
}
var googleAnalyticsDirectives = {
scriptSrc: ['https://www.google-analytics.com']
}
CspStrategy.computeDirectives = function () { CspStrategy.computeDirectives = function () {
var directives = {} var directives = {}
mergeDirectives(directives, config.csp.directives) mergeDirectives(directives, config.csp.directives)
mergeDirectivesIf(config.csp.addDefaults, directives, defaultDirectives) mergeDirectivesIf(config.csp.addDefaults, directives, defaultDirectives)
mergeDirectivesIf(config.useCDN, directives, cdnDirectives) mergeDirectivesIf(config.usecdn, directives, cdnDirectives)
mergeDirectivesIf(config.csp.addDisqus, directives, disqusDirectives)
mergeDirectivesIf(config.csp.addGoogleAnalytics, directives, googleAnalyticsDirectives)
if (!areAllInlineScriptsAllowed(directives)) { if (!areAllInlineScriptsAllowed(directives)) {
addInlineScriptExceptions(directives) addInlineScriptExceptions(directives)
} }
addUpgradeUnsafeRequestsOptionTo(directives) addUpgradeUnsafeRequestsOptionTo(directives)
addReportURI(directives)
return directives return directives
} }
@ -71,7 +57,7 @@ function addInlineScriptExceptions (directives) {
directives.scriptSrc.push(getCspNonce) directives.scriptSrc.push(getCspNonce)
// TODO: This is the SHA-256 hash of the inline script in build/reveal.js/plugins/notes/notes.html // TODO: This is the SHA-256 hash of the inline script in build/reveal.js/plugins/notes/notes.html
// Any more clean solution appreciated. // Any more clean solution appreciated.
directives.scriptSrc.push('\'sha256-Lc+VnBdinzYTTAkFrIoUqdoA9EQFeS1AF9ybmF+LLfM=\'') directives.scriptSrc.push('\'sha256-EtvSSxRwce5cLeFBZbvZvDrTiRoyoXbWWwvEVciM5Ag=\'')
} }
function getCspNonce (req, res) { function getCspNonce (req, res) {
@ -79,19 +65,13 @@ function getCspNonce (req, res) {
} }
function addUpgradeUnsafeRequestsOptionTo (directives) { function addUpgradeUnsafeRequestsOptionTo (directives) {
if (config.csp.upgradeInsecureRequests === 'auto' && config.useSSL) { if (config.csp.upgradeInsecureRequests === 'auto' && config.usessl) {
directives.upgradeInsecureRequests = true directives.upgradeInsecureRequests = true
} else if (config.csp.upgradeInsecureRequests === true) { } else if (config.csp.upgradeInsecureRequests === true) {
directives.upgradeInsecureRequests = true directives.upgradeInsecureRequests = true
} }
} }
function addReportURI (directives) {
if (config.csp.reportURI) {
directives.reportUri = config.csp.reportURI
}
}
CspStrategy.addNonceToLocals = function (req, res, next) { CspStrategy.addNonceToLocals = function (req, res, next) {
res.locals.nonce = uuid.v4() res.locals.nonce = uuid.v4()
next() next()

View file

@ -1,9 +1,9 @@
'use strict' 'use strict'
// history // history
// external modules // external modules
var LZString = require('lz-string')
// core // core
var config = require('./config')
var logger = require('./logger') var logger = require('./logger')
var response = require('./response') var response = require('./response')
var models = require('./models') var models = require('./models')
@ -27,35 +27,11 @@ function getHistory (userid, callback) {
} }
var history = {} var history = {}
if (user.history) { if (user.history) {
history = JSON.parse(user.history) history = parseHistoryToObject(JSON.parse(user.history))
// migrate LZString encoded note id to base64url encoded note id }
for (let i = 0, l = history.length; i < l; i++) { if (config.debug) {
// Calculate minimal string length for an UUID that is encoded logger.info('read history success: ' + user.id)
// base64 encoded and optimize comparsion by using -1
// this should make a lot of LZ-String parsing errors obsolete
// as we can assume that a nodeId that is 48 chars or longer is a
// noteID.
const base64UuidLength = ((4 * 36) / 3) - 1
if (!(history[i].id.length > base64UuidLength)) {
continue
}
try {
let id = LZString.decompressFromBase64(history[i].id)
if (id && models.Note.checkNoteIdValid(id)) {
history[i].id = models.Note.encodeNoteId(id)
}
} catch (err) {
// most error here comes from LZString, ignore
if (err.message === 'Cannot read property \'charAt\' of undefined') {
logger.warning('Looks like we can not decode "' + history[i].id + '" with LZString. Can be ignored.')
} else {
logger.error(err)
}
}
}
history = parseHistoryToObject(history)
} }
logger.debug(`read history success: ${user.id}`)
return callback(null, history) return callback(null, history)
}).catch(function (err) { }).catch(function (err) {
logger.error('read history failed: ' + err) logger.error('read history failed: ' + err)
@ -137,7 +113,7 @@ function historyPost (req, res) {
var noteId = req.params.noteId var noteId = req.params.noteId
if (!noteId) { if (!noteId) {
if (typeof req.body['history'] === 'undefined') return response.errorBadRequest(res) if (typeof req.body['history'] === 'undefined') return response.errorBadRequest(res)
logger.debug(`SERVER received history from [${req.user.id}]: ${req.body.history}`) if (config.debug) { logger.info('SERVER received history from [' + req.user.id + ']: ' + req.body.history) }
try { try {
var history = JSON.parse(req.body.history) var history = JSON.parse(req.body.history)
} catch (err) { } catch (err) {

View file

@ -1,18 +1,16 @@
'use strict' 'use strict'
// external modules // external modules
const crypto = require('crypto') var randomcolor = require('randomcolor')
const randomcolor = require('randomcolor')
const config = require('./config')
// core // core
exports.generateAvatar = function (name) { module.exports = function (name) {
const color = randomcolor({ var color = randomcolor({
seed: name, seed: name,
luminosity: 'dark' luminosity: 'dark'
}) })
const letter = name.substring(0, 1).toUpperCase() var letter = name.substring(0, 1).toUpperCase()
let svg = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' var svg = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
svg += '<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="96" width="96" version="1.1" viewBox="0 0 96 96">' svg += '<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="96" width="96" version="1.1" viewBox="0 0 96 96">'
svg += '<g>' svg += '<g>'
svg += '<rect width="96" height="96" fill="' + color + '" />' svg += '<rect width="96" height="96" fill="' + color + '" />'
@ -22,29 +20,5 @@ exports.generateAvatar = function (name) {
svg += '</g>' svg += '</g>'
svg += '</svg>' svg += '</svg>'
return svg return 'data:image/svg+xml;base64,' + new Buffer(svg).toString('base64')
}
exports.generateAvatarURL = function (name, email = '', big = true) {
let photo
if (typeof email !== 'string') {
email = '' + name + '@example.com'
}
name = encodeURIComponent(name)
let hash = crypto.createHash('md5')
hash.update(email.toLowerCase())
let hexDigest = hash.digest('hex')
if (email !== '' && config.allowGravatar) {
photo = 'https://cdn.libravatar.org/avatar/' + hexDigest
if (big) {
photo += '?s=400'
} else {
photo += '?s=96'
}
} else {
photo = config.serverURL + '/user/' + (name || email.substring(0, email.lastIndexOf('@')) || hexDigest) + '/avatar.svg'
}
return photo
} }

View file

@ -1,27 +1,23 @@
'use strict' 'use strict'
const { createLogger, format, transports } = require('winston') const winston = require('winston')
const logger = createLogger({ class Logger extends winston.Logger {
level: 'debug', // Implement stream.writable.write interface
format: format.combine( write (chunk) {
format.uncolorize(), this.info(chunk)
format.timestamp(),
format.align(),
format.splat(),
format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
),
transports: [
new transports.Console({
handleExceptions: true
})
],
exitOnError: false
})
logger.stream = {
write: function (message, encoding) {
logger.info(message)
} }
} }
module.exports = logger module.exports = new Logger({
transports: [
new winston.transports.Console({
level: 'debug',
handleExceptions: true,
json: false,
colorize: false,
timestamp: true
})
],
emitErrs: true,
exitOnError: false
})

View file

@ -20,13 +20,6 @@ module.exports = {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
defaultValue: 0 defaultValue: 0
}) })
}).catch(function (error) {
if (error.message === 'SQLITE_ERROR: duplicate column name: shortid' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'shortid'" || error.message === 'column "shortid" of relation "Notes" already exists') {
// eslint-disable-next-line no-console
console.log('Migration has already run… ignoring.')
} else {
throw error
}
}) })
}, },

View file

@ -7,20 +7,13 @@ module.exports = {
return queryInterface.addColumn('Notes', 'lastchangeAt', { return queryInterface.addColumn('Notes', 'lastchangeAt', {
type: Sequelize.DATE type: Sequelize.DATE
}) })
}).catch(function (error) {
if (error.message === 'SQLITE_ERROR: duplicate column name: lastchangeuserId' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'lastchangeuserId'" || error.message === 'column "lastchangeuserId" of relation "Notes" already exists') {
// eslint-disable-next-line no-console
console.log('Migration has already run… ignoring.')
} else {
throw error
}
}) })
}, },
down: function (queryInterface, Sequelize) { down: function (queryInterface, Sequelize) {
return queryInterface.removeColumn('Notes', 'lastchangeAt') return queryInterface.removeColumn('Notes', 'lastchangeAt')
.then(function () { .then(function () {
return queryInterface.removeColumn('Notes', 'lastchangeuserId') return queryInterface.removeColumn('Notes', 'lastchangeuserId')
}) })
} }
} }

View file

@ -7,13 +7,6 @@ module.exports = {
return queryInterface.addIndex('Notes', ['alias'], { return queryInterface.addIndex('Notes', ['alias'], {
indicesType: 'UNIQUE' indicesType: 'UNIQUE'
}) })
}).catch(function (error) {
if (error.message === 'SQLITE_ERROR: duplicate column name: alias' || error.message === "ER_DUP_FIELDNAME: Duplicate column name 'alias'" || error.message === 'column "alias" of relation "Notes" already exists') {
// eslint-disable-next-line no-console
console.log('Migration has already run… ignoring.')
} else {
throw error
}
}) })
}, },

Some files were not shown because too many files have changed in this diff Show more