diff options
31 files changed, 816 insertions, 265 deletions
@@ -212,6 +212,8 @@ There are some config settings you need to change in the files below. | `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_AZURE_CONNECTION_STRING` | no example | Azure Blob Storage connection string | +| `HMD_AZURE_CONTAINER` | no example | Azure Blob Storage container name (automatically created if non existent) | | `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) | @@ -261,7 +263,7 @@ There are some config settings you need to change in the files below. | `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`. Note `bin/manage_users` might help you if registration is `false`.) | -| `imageUploadType` | `imgur`(default), `s3`, `minio` or `filesystem` | Where to upload image +| `imageUploadType` | `imgur`(default), `s3`, `minio`, `azure` 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` | @@ -271,7 +273,7 @@ There are some config settings you need to change in the files below. | 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 | +| imgur, s3, minio, azure | environment variables or `config.json` | for image upload | | dropbox(`dropbox/appKey`) | `config.json` | for export and import | ## Third-party integration OAuth callback URLs @@ -129,9 +129,10 @@ if (config.csp.enable) { } i18n.configure({ - 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'], + 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', 'ko'], cookie: 'locale', - directory: path.join(__dirname, '/locales') + directory: path.join(__dirname, '/locales'), + updateFiles: config.updateI18nFiles }) app.use(cookieParser()) diff --git a/config.json.example b/config.json.example index 6cd55efb..e07052bd 100644 --- a/config.json.example +++ b/config.json.example @@ -114,6 +114,11 @@ "secretAccessKey": "change this", "region": "change this" }, - "s3bucket": "change this" + "s3bucket": "change this", + "azure": + { + "connectionString": "change this", + "container": "change this" + } } } diff --git a/docs/guides/auth.md b/docs/guides/auth.md deleted file mode 100644 index e4261724..00000000 --- a/docs/guides/auth.md +++ /dev/null @@ -1,241 +0,0 @@ -# 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 `identifierFormat` 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 - ```` - - -### GitLab (self-hosted) - -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.hackmd.domain -- HMD_URL_ADDPORT=443 -- HMD_PROTOCOL_USESSL=true -- HMD_GITLAB_BASEURL=https://your.gitlab.domain -- HMD_GITLAB_CLIENTID=23462a34example99fid0943c3fde97310fb7db47fab1112 -- HMD_GITLAB_CLIENTSECRET=5532e9dexample70432secret0c37dd20ce077e6073ea9f1d6 -``` - -7. Run `docker-compose up -d` to apply your settings. -8. Sign in to your HackMD using your GitLab ID: - -![Sign in via GitLab](images/auth/gitlab-sign-in.png) diff --git a/docs/guides/auth/github.md b/docs/guides/auth/github.md new file mode 100644 index 00000000..62910cb2 --- /dev/null +++ b/docs/guides/auth/github.md @@ -0,0 +1,36 @@ +Authentication guide - 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 + ```` diff --git a/docs/guides/auth/gitlab-self-hosted.md b/docs/guides/auth/gitlab-self-hosted.md new file mode 100644 index 00000000..361ee958 --- /dev/null +++ b/docs/guides/auth/gitlab-self-hosted.md @@ -0,0 +1,30 @@ +# GitLab (self-hosted) +=== + +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.hackmd.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 HackMD using your GitLab ID: + +![Sign in via GitLab](images/auth/gitlab-sign-in.png) diff --git a/docs/guides/auth/saml-onelogin.md b/docs/guides/auth/saml-onelogin.md new file mode 100644 index 00000000..245876c5 --- /dev/null +++ b/docs/guides/auth/saml-onelogin.md @@ -0,0 +1,52 @@ +Authentication guide - 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) diff --git a/docs/guides/auth/saml.md b/docs/guides/auth/saml.md new file mode 100644 index 00000000..9a516675 --- /dev/null +++ b/docs/guides/auth/saml.md @@ -0,0 +1,83 @@ +Authentication guide - SAML +=== + +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 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 `identifierFormat` 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 + ```` diff --git a/docs/guides/auth/twitter.md b/docs/guides/auth/twitter.md new file mode 100644 index 00000000..02309ca5 --- /dev/null +++ b/docs/guides/auth/twitter.md @@ -0,0 +1,42 @@ +Authentication guide - 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 + ```` diff --git a/lib/config/default.js b/lib/config/default.js index 68849d36..1b124b3e 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -56,7 +56,7 @@ module.exports = { heartbeatTimeout: 10000, // document documentMaxLength: 100000, - // image upload setting, available options are imgur/s3/filesystem + // image upload setting, available options are imgur/s3/filesystem/azure imageUploadType: 'filesystem', imgur: { clientID: undefined @@ -74,7 +74,17 @@ module.exports = { port: 9000 }, s3bucket: undefined, + azure: { + connectionString: undefined, + container: undefined + }, // authentication + oauth2: { + authorizationURL: undefined, + tokenURL: undefined, + clientID: undefined, + clientSecret: undefined + }, facebook: { clientID: undefined, clientSecret: undefined diff --git a/lib/config/dockerSecret.js b/lib/config/dockerSecret.js index b9116cd3..fd66ddfe 100644 --- a/lib/config/dockerSecret.js +++ b/lib/config/dockerSecret.js @@ -22,6 +22,9 @@ if (fs.existsSync(basePath)) { accessKeyId: getSecret('s3_acccessKeyId'), secretAccessKey: getSecret('s3_secretAccessKey') }, + azure: { + connectionString: getSecret('azure_connectionString') + }, facebook: { clientID: getSecret('facebook_clientID'), clientSecret: getSecret('facebook_clientSecret') diff --git a/lib/config/environment.js b/lib/config/environment.js index 3dde4786..e1c11569 100644 --- a/lib/config/environment.js +++ b/lib/config/environment.js @@ -45,6 +45,10 @@ module.exports = { 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 @@ -68,6 +72,17 @@ module.exports = { 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, diff --git a/lib/config/index.js b/lib/config/index.js index bdba5e0e..7853dbad 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -99,8 +99,12 @@ config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret config.isLDAPEnable = config.ldap.url config.isSAMLEnable = config.saml.idpSsoUrl +config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret config.isPDFExportEnable = config.allowPDFExport +// Only update i18n files in development setups +config.updateI18nFiles = (env === Environment.development) + // merge legacy values let keys = Object.keys(config) const uppercase = /[A-Z]/ @@ -127,8 +131,8 @@ if (config.sessionSecret === 'secret') { } // Validate upload upload providers -if (['filesystem', 's3', 'minio', 'imgur'].indexOf(config.imageUploadType) === -1) { - logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio" or "imgur". Defaulting to "imgur"') +if (['filesystem', 's3', 'minio', 'imgur', 'azure'].indexOf(config.imageUploadType) === -1) { + logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio", "azure" or "imgur". Defaulting to "imgur"') config.imageUploadType = 'imgur' } diff --git a/lib/migrations/20180326103000-use-text-in-tokens.js b/lib/migrations/20180326103000-use-text-in-tokens.js new file mode 100644 index 00000000..f9507747 --- /dev/null +++ b/lib/migrations/20180326103000-use-text-in-tokens.js @@ -0,0 +1,23 @@ +'use strict' + +module.exports = { + up: function (queryInterface, Sequelize) { + return queryInterface.changeColumn('Users', 'accessToken', { + type: Sequelize.TEXT + }).then(function () { + return queryInterface.changeColumn('Users', 'refreshToken', { + type: Sequelize.TEXT + }) + }) + }, + + down: function (queryInterface, Sequelize) { + return queryInterface.changeColumn('Users', 'accessToken', { + type: Sequelize.STRING + }).then(function () { + return queryInterface.changeColumn('Users', 'refreshToken', { + type: Sequelize.STRING + }) + }) + } +} diff --git a/lib/models/user.js b/lib/models/user.js index 019aab7e..5dd13869 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -26,10 +26,10 @@ module.exports = function (sequelize, DataTypes) { type: DataTypes.TEXT }, accessToken: { - type: DataTypes.STRING + type: DataTypes.TEXT }, refreshToken: { - type: DataTypes.STRING + type: DataTypes.TEXT }, deleteToken: { type: DataTypes.UUID, diff --git a/lib/response.js b/lib/response.js index b1b89c78..4cfa9a74 100644 --- a/lib/response.js +++ b/lib/response.js @@ -18,7 +18,13 @@ var utils = require('./utils') // public var response = { errorForbidden: function (res) { - responseError(res, '403', 'Forbidden', 'oh no.') + const {req} = res + if (req.user) { + responseError(res, '403', 'Forbidden', 'oh no.') + } else { + req.flash('error', 'You are not allowed to access this page. Maybe try logging in?') + res.redirect(config.serverURL) + } }, errorNotFound: function (res) { responseError(res, '404', 'Not Found', 'oops.') @@ -74,6 +80,8 @@ function showIndex (req, res, next) { ldap: config.isLDAPEnable, ldapProviderName: config.ldap.providerName, saml: config.isSAMLEnable, + oauth2: config.isOAuth2Enable, + oauth2ProviderName: config.oauth2.providerName, email: config.isEmailEnable, allowEmailRegister: config.allowEmailRegister, allowPDFExport: config.allowPDFExport, @@ -126,7 +134,9 @@ function responseHackMD (res, note) { google: config.isGoogleEnable, ldap: config.isLDAPEnable, ldapProviderName: config.ldap.providerName, + oauth2ProviderName: config.oauth2.providerName, saml: config.isSAMLEnable, + oauth2: config.isOAuth2Enable, email: config.isEmailEnable, allowEmailRegister: config.allowEmailRegister, allowPDFExport: config.allowPDFExport diff --git a/lib/web/auth/index.js b/lib/web/auth/index.js index eb42fb36..61e7c3f9 100644 --- a/lib/web/auth/index.js +++ b/lib/web/auth/index.js @@ -43,6 +43,7 @@ if (config.isDropboxEnable) authRouter.use(require('./dropbox')) if (config.isGoogleEnable) authRouter.use(require('./google')) if (config.isLDAPEnable) authRouter.use(require('./ldap')) if (config.isSAMLEnable) authRouter.use(require('./saml')) +if (config.isOAuth2Enable) authRouter.use(require('./oauth2')) if (config.isEmailEnable) authRouter.use(require('./email')) // logout diff --git a/lib/web/auth/oauth2/index.js b/lib/web/auth/oauth2/index.js new file mode 100644 index 00000000..f2a3132d --- /dev/null +++ b/lib/web/auth/oauth2/index.js @@ -0,0 +1,106 @@ +'use strict' + +const Router = require('express').Router +const passport = require('passport') +const OAuth2Strategy = require('passport-oauth2').Strategy +const config = require('../../../config') +const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') + +let oauth2Auth = module.exports = Router() + +class OAuth2CustomStrategy extends OAuth2Strategy { + constructor (options, verify) { + options.customHeaders = options.customHeaders || {} + super(options, verify) + this.name = 'oauth2' + this._userProfileURL = options.userProfileURL + this._oauth2.useAuthorizationHeaderforGET(true) + } + + userProfile (accessToken, done) { + this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) { + var json + + if (err) { + return done(new passport.InternalOAuthError('Failed to fetch user profile', err)) + } + + try { + json = JSON.parse(body) + } catch (ex) { + return done(new Error('Failed to parse user profile')) + } + + let profile = parseProfile(json) + profile.provider = 'oauth2' + + done(null, profile) + }) + } +} + +function extractProfileAttribute (data, path) { + // can handle stuff like `attrs[0].name` + path = path.split('.') + for (const segment of path) { + const m = segment.match(/([\d\w]+)\[(.*)\]/) + data = m ? data[m[1]][m[2]] : data[segment] + } + return data +} + +function parseProfile (data) { + const username = extractProfileAttribute(data, config.oauth2.userProfileUsernameAttr) + const displayName = extractProfileAttribute(data, config.oauth2.userProfileDisplayNameAttr) + const email = extractProfileAttribute(data, config.oauth2.userProfileEmailAttr) + + return { + id: username, + username: username, + displayName: displayName, + email: email + } +} + +OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) { + this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) { + var json + + if (err) { + return done(new passport.InternalOAuthError('Failed to fetch user profile', err)) + } + + try { + json = JSON.parse(body) + } catch (ex) { + return done(new Error('Failed to parse user profile')) + } + + let profile = parseProfile(json) + profile.provider = 'oauth2' + + done(null, profile) + }) +} + +passport.use(new OAuth2CustomStrategy({ + authorizationURL: config.oauth2.authorizationURL, + tokenURL: config.oauth2.tokenURL, + clientID: config.oauth2.clientID, + clientSecret: config.oauth2.clientSecret, + callbackURL: config.serverURL + '/auth/oauth2/callback', + userProfileURL: config.oauth2.userProfileURL +}, passportGeneralCallback)) + +oauth2Auth.get('/auth/oauth2', function (req, res, next) { + setReturnToFromReferer(req) + passport.authenticate('oauth2')(req, res, next) +}) + +// github auth callback +oauth2Auth.get('/auth/oauth2/callback', + passport.authenticate('oauth2', { + successReturnToOrRedirect: config.serverurl + '/', + failureRedirect: config.serverurl + '/' + }) +) diff --git a/lib/web/imageRouter/azure.js b/lib/web/imageRouter/azure.js new file mode 100644 index 00000000..cc98e5fc --- /dev/null +++ b/lib/web/imageRouter/azure.js @@ -0,0 +1,35 @@ +'use strict' +const path = require('path') + +const config = require('../../config') +const logger = require('../../logger') + +const azure = require('azure-storage') + +exports.uploadImage = function (imagePath, callback) { + if (!imagePath || typeof imagePath !== 'string') { + callback(new Error('Image path is missing or wrong'), null) + return + } + + if (!callback || typeof callback !== 'function') { + logger.error('Callback has to be a function') + return + } + + var azureBlobService = azure.createBlobService(config.azure.connectionString) + + azureBlobService.createContainerIfNotExists(config.azure.container, { publicAccessLevel: 'blob' }, function (err, result, response) { + if (err) { + callback(new Error(err.message), null) + } else { + azureBlobService.createBlockBlobFromLocalFile(config.azure.container, path.basename(imagePath), imagePath, function (err, result, response) { + if (err) { + callback(new Error(err.message), null) + } else { + callback(null, azureBlobService.getUrl(config.azure.container, result.name)) + } + }) + } + }) +} diff --git a/lib/web/imageRouter/filesystem.js b/lib/web/imageRouter/filesystem.js index da76ba5e..145876a9 100644 --- a/lib/web/imageRouter/filesystem.js +++ b/lib/web/imageRouter/filesystem.js @@ -2,6 +2,7 @@ const url = require('url') const config = require('../../config') +const logger = require('../../logger') exports.uploadImage = function (imagePath, callback) { if (!imagePath || typeof imagePath !== 'string') { @@ -10,7 +11,7 @@ exports.uploadImage = function (imagePath, callback) { } if (!callback || typeof callback !== 'function') { - callback(new Error('Callback has to be a function'), null) + logger.error('Callback has to be a function') return } diff --git a/lib/web/imageRouter/imgur.js b/lib/web/imageRouter/imgur.js index 31d5f55c..2a20002c 100644 --- a/lib/web/imageRouter/imgur.js +++ b/lib/web/imageRouter/imgur.js @@ -11,7 +11,7 @@ exports.uploadImage = function (imagePath, callback) { } if (!callback || typeof callback !== 'function') { - callback(new Error('Callback has to be a function'), null) + logger.error('Callback has to be a function') return } diff --git a/lib/web/imageRouter/minio.js b/lib/web/imageRouter/minio.js index 099cb926..b921c2d2 100644 --- a/lib/web/imageRouter/minio.js +++ b/lib/web/imageRouter/minio.js @@ -4,6 +4,7 @@ const path = require('path') const config = require('../../config') const {getImageMimeType} = require('../../utils') +const logger = require('../../logger') const Minio = require('minio') const minioClient = new Minio.Client({ @@ -21,7 +22,7 @@ exports.uploadImage = function (imagePath, callback) { } if (!callback || typeof callback !== 'function') { - callback(new Error('Callback has to be a function'), null) + logger.error('Callback has to be a function') return } diff --git a/lib/web/imageRouter/s3.js b/lib/web/imageRouter/s3.js index bcd3ea60..626fe148 100644 --- a/lib/web/imageRouter/s3.js +++ b/lib/web/imageRouter/s3.js @@ -4,6 +4,7 @@ const path = require('path') const config = require('../../config') const {getImageMimeType} = require('../../utils') +const logger = require('../../logger') const AWS = require('aws-sdk') const awsConfig = new AWS.Config(config.s3) @@ -16,7 +17,7 @@ exports.uploadImage = function (imagePath, callback) { } if (!callback || typeof callback !== 'function') { - callback(new Error('Callback has to be a function'), null) + logger.error('Callback has to be a function') return } diff --git a/locales/ko.json b/locales/ko.json new file mode 100644 index 00000000..84dda60b --- /dev/null +++ b/locales/ko.json @@ -0,0 +1,109 @@ +{ + "Collaborative markdown notes": "협동 마크다운 노트", + "Realtime collaborative markdown notes on all platforms.": "실시간으로 모든 플랫폼에서 마크다운 노트를 함께 작성해보세요.", + "Best way to write and share your knowledge in markdown.": "마크다운을 쓰고 공유하는 최고의 플랫폼입니다.", + "Intro": "소개", + "History": "기록", + "New guest note": "새 손님 노트", + "Collaborate with URL": "URL을 통한 실시간 협업", + "Support charts and MathJax": "차트와 MathJax 지원", + "Support slide mode": "슬라이드 모드 지원", + "Sign In": "로그인", + "Below is the history from browser": "아래는 이 브라우저에서 찾은 기록입니다.", + "Welcome!": "환영합니다!", + "New note": "새 노트", + "or": "또는", + "Sign Out": "로그아웃", + "Explore all features": "모든 기능 둘러보기", + "Select tags...": "태그 선택하기", + "Search keyword...": "키워드 검색하기", + "Sort by title": "제목 기준 정렬", + "Title": "제목", + "Sort by time": "시간 기준 정렬", + "Time": "시간", + "Export history": "기록 내보내기", + "Import history": "기록 불러오기", + "Clear history": "기록 초기화", + "Refresh history": "기록 새로고침", + "No history": "기록 없음", + "Import from browser": "브라우저에서 불러오기", + "Releases": "릴리즈", + "Are you sure?": "확실합니까?", + "Do you really want to delete this note?": "정말로 이 노트를 삭제하시겠습니까?", + "All users will lose their connection.": "모든 사용자의 연결이 끊어집니다.", + "Cancel": "취소", + "Yes, do it!": "네", + "Choose method": "방법 선택", + "Sign in via %s": "%s으로 로그인", + "New": "새", + "Publish": "공개하기", + "Extra": "추가", + "Revision": "기록", + "Slide Mode": "슬라이드 모드", + "Export": "내보내기", + "Import": "들여오기", + "Clipboard": "클립보드", + "Download": "다운로드", + "Raw HTML": "순수 HTML", + "Edit": "수정", + "View": "보기", + "Both": "한번에", + "Help": "도움말", + "Upload Image": "이미지 업로드", + "Menu": "메뉴", + "This page need refresh": "새로고침이 필요합니다", + "You have an incompatible client version.": "호환되지 않는 클라이언트입니다.", + "Refresh to update.": "새로고침하기", + "New version available!": "새로운 버전이 있습니다!", + "See releases notes here": "릴리즈 노트를 읽어보세요", + "Refresh to enjoy new features.": "새로운 기능을 즐기려면 새로고침하십시오", + "Your user state has changed.": "유저 상태가 변경되었습니다.", + "Refresh to load new user state.": "새로고침하여 새로운 유저 상태를 적용합니다.", + "Refresh": "새로고침", + "Contacts": "연락처", + "Report an issue": "이슈 보고하기", + "Meet us on %s": "%s에서 만나보세요", + "Send us email": "이메일 보내기", + "Documents": "문서", + "Features": "기능", + "YAML Metadata": "YAML 속성", + "Slide Example": "슬라이드 예제", + "Cheatsheet": "치트시트", + "Example": "예시", + "Syntax": "문법", + "Header": "머리글", + "Unordered List": "순서 없는 목록", + "Ordered List": "순서 있는 목록", + "Todo List": "체크리스트", + "Blockquote": "인용문", + "Bold font": "굵게", + "Italics font": "기울임", + "Strikethrough": "취소선", + "Inserted text": "밑줄", + "Marked text": "강조", + "Link": "링크", + "Image": "이미지", + "Code": "코드", + "Externals": "Externals", + "This is a alert area.": "여기는 알림 공간입니다.", + "Revert": "되돌리기", + "Import from clipboard": "클립보드에서 불러오기", + "Paste your markdown or webpage here...": "마크다운이나 웹페이지 붙여넣기", + "Clear": "Clear", + "This note is locked": "이 노트는 잠겨있습니다.", + "Sorry, only owner can edit this note.": "죄송하지만 소유자만 이 노트를 수정할 수 있습니다.", + "OK": "확인", + "Reach the limit": "한계에 도달", + "Sorry, you've reached the max length this note can be.": "죄송합니다. 노트 최대 길이를 초과하였습니다.", + "Please reduce the content or divide it to more notes, thank you!": "노트 길이를 줄여주십시오.", + "Import from Gist": "Gist에서 불러오기", + "Paste your gist url here...": "Gist URL을 입력하세요", + "Import from Snippet": "Import from Snippet", + "Select From Available Projects": "가능한 프로젝트 중 선택", + "Select From Available Snippets": "Select From Available Snippets", + "OR": "또는", + "Export to Snippet": "Export to Snippet", + "Select Visibility Level": "Select Visibility Level", + "Night Theme": "다크 테마", + "Follow us on %s and %s.": "%s과 %s에서 저희를 팔로우해보세요" +} diff --git a/package.json b/package.json index f676a4cf..20920e21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hackmd", - "version": "1.1.0-ce", + "version": "1.1.1-ce", "description": "Realtime collaborative markdown notes on all platforms.", "main": "app.js", "license": "AGPL-3.0", @@ -20,6 +20,7 @@ "async": "^2.1.4", "aws-sdk": "^2.7.20", "base64url": "^3.0.0", + "azure-storage": "^2.7.0", "blueimp-md5": "^2.6.0", "body-parser": "^1.15.2", "bootstrap": "^3.3.7", diff --git a/public/css/slide.css b/public/css/slide.css index a8591108..f8f9c717 100644 --- a/public/css/slide.css +++ b/public/css/slide.css @@ -81,7 +81,8 @@ .task-list-item-checkbox { font-size: inherit; height: 1em; - margin: 0.2em 0 0.2em -0.65em !important; + transform: scale(2); + margin: 0.15em 0 0.15em -0.84em !important; } pre code .wrapper { diff --git a/public/docs/release-notes.md b/public/docs/release-notes.md index a6428eed..891c506a 100644 --- a/public/docs/release-notes.md +++ b/public/docs/release-notes.md @@ -1,6 +1,27 @@ Release Notes === +<i class="fa fa-tag"></i> 1.1.1-ce <i class="fa fa-clock-o"></i> 2018-05-23 12:00 +--- + +### Security +* Fix Google Drive integration leaked `clientSecret` for Google integration +* Update base64url package + +### Fixes +* Fix typos in integrations +* Fix high need of file descriptors during build +* Fix heroku deployment by limiting node version to <10.x + +### Refactors +* Refactor letterAvatars to be compliant with CSP + +### Removes +* Google Drive integration + +### Honorable mentions +* [Max Wu (jackycute)](https://github.com/jackycute) + <i class="fa fa-tag"></i> 1.1.0-ce <i class="fa fa-clock-o"></i> 2018-04-06 12:00 --- diff --git a/public/vendor/md-toc.js b/public/vendor/md-toc.js index f93f7921..3457d465 100644 --- a/public/vendor/md-toc.js +++ b/public/vendor/md-toc.js @@ -54,6 +54,7 @@ var j = i + 1 this._elTitleElement = this.elTitleElements[i] this._elTitleElementName = this._elTitleElement.tagName + this._elTitleElementTitle = this._elTitleElement.textContent.replace(/"/g, '"') this._elTitleElementText = (typeof this.process === 'function' ? this.process(this._elTitleElement) : this._elTitleElement.innerHTML).replace(/<(?:.|\n)*?>/gm, '') var id = this._elTitleElement.getAttribute('id') if (!id) { @@ -63,7 +64,7 @@ id = '#' + id } - this.tocContent += '<li><a href="' + id + '">' + this._elTitleElementText + '</a>' + this.tocContent += '<li><a href="' + id + '" title="'+ this._elTitleElementTitle +'">' + this._elTitleElementText + '</a>' if (j !== this._elTitleElementsLen) { this._elNextTitleElementName = this.elTitleElements[j].tagName diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs index a7c5a0b8..220e0dae 100644 --- a/public/views/index/body.ejs +++ b/public/views/index/body.ejs @@ -15,7 +15,7 @@ <% if(allowAnonymous) { %> <a type="button" href="<%- url %>/new" class="btn btn-sm btn-primary"><i class="fa fa-plus"></i> <%= __('New guest note') %></a> <% } %> - <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || email) { %> + <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || oauth2 || email) { %> <button class="btn btn-sm btn-success ui-signin" data-toggle="modal" data-target=".signin-modal"><%= __('Sign In') %></button> <% } %> </div> @@ -51,7 +51,7 @@ <% if (errorMessage && errorMessage.length > 0) { %> <div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div> <% } %> - <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || email) { %> + <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || oauth2 || email) { %> <span class="ui-signin"> <br> <a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="min-width: 200px;"><%= __('Sign In') %></a> @@ -147,6 +147,7 @@ <option value="sv">svenska</option> <option value="eo">Esperanto</option> <option value="da">dansk</option> + <option value="ko">한국어</option> </select> <p> © 2018 <a href="https://hackmd.io">HackMD</a> | <a href="<%- url %>/s/release-notes" target="_blank"><%= __('Releases') %></a><% if(privacyStatement) { %> | <a href="<%- url %>/s/privacy" target="_blank"><%= __('Privacy') %></a><% } %><% if(termsOfUse) { %> | <a href="<%- url %>/s/terms-of-use" target="_blank"><%= __('Terms of Use') %></a><% } %> diff --git a/public/views/shared/signin-modal.ejs b/public/views/shared/signin-modal.ejs index 82b5cf1f..f0cffad9 100644 --- a/public/views/shared/signin-modal.ejs +++ b/public/views/shared/signin-modal.ejs @@ -48,7 +48,12 @@ <i class="fa fa-users"></i> <%= __('Sign in via %s', 'SAML') %> </a> <% } %> - <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || saml) && ldap) { %> + <% if(oauth2) { %> + <a href="<%- url %>/auth/oauth2" class="btn btn-lg btn-block btn-social btn-soundcloud"> + <i class="fa fa-mail-forward"></i> <%= __('Sign in via %s', oauth2ProviderName || 'OAuth2') %> + </a> + <% } %> + <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || saml || oauth2) && ldap) { %> <hr> <% }%> <% if(ldap) { %> @@ -73,7 +78,7 @@ </div> </form> <% } %> - <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap) && email) { %> + <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || oauth2) && email) { %> <hr> <% }%> <% if(email) { %> @@ -85,6 +85,15 @@ ajv@^4.7.0, ajv@^4.9.1: co "^4.6.0" json-stable-stringify "^1.0.1" +ajv@^5.1.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -338,10 +347,34 @@ aws-sign2@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + aws4@^1.2.1: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +aws4@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" + +azure-storage@^2.7.0: + version "2.8.3" + resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.8.3.tgz#6ee1539188ee42b91c62dad691b39ac5452e142b" + dependencies: + browserify-mime "~1.2.9" + extend "~1.2.1" + json-edm-parser "0.1.2" + md5.js "1.3.4" + readable-stream "~2.0.0" + request "^2.86.0" + underscore "~1.8.3" + uuid "^3.0.0" + validator "~9.4.1" + xml2js "0.2.8" + xmlbuilder "0.4.3" + babel-cli@^6.18.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.26.0.tgz#502ab54874d7db88ad00b887a06383ce03d002f1" @@ -969,6 +1002,10 @@ browserify-aes@0.4.0: dependencies: inherits "^2.0.1" +browserify-mime@~1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f" + browserify-zlib@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" @@ -1342,6 +1379,12 @@ colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +combined-stream@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + dependencies: + delayed-stream "~1.0.0" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" @@ -2432,10 +2475,14 @@ extend@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" -extend@^3.0.0, extend@~3.0.0: +extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extend@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-1.2.1.tgz#a0f5fd6cfc83a5fe49ef698d60ec8a624dd4576c" + extendr@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/extendr/-/extendr-2.1.0.tgz#301aa0bbea565f4d2dc8f570f2a22611a8527b56" @@ -2490,6 +2537,14 @@ fancy-log@^1.1.0: chalk "^1.1.1" time-stamp "^1.0.0" +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -2694,6 +2749,14 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" +form-data@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + formidable@^1.0.17: version "1.1.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" @@ -3096,6 +3159,10 @@ har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + har-validator@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" @@ -3103,6 +3170,13 @@ har-validator@~4.2.1: ajv "^4.9.1" har-schema "^1.0.5" +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -3147,6 +3221,13 @@ has@^1.0.1: dependencies: function-bind "^1.0.2" +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + hasha@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/hasha/-/hasha-2.2.0.tgz#78d7cbfc1e6d66303fe79837365984517b2f6ee1" @@ -3317,6 +3398,14 @@ http-signature@~1.1.0: jsprim "^1.2.2" sshpk "^1.7.0" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + https-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" @@ -3864,10 +3953,20 @@ jshint@~2.9.4: shelljs "0.3.x" strip-json-comments "1.0.x" +json-edm-parser@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" + dependencies: + jsonparse "~1.2.0" + json-loader@^0.5.4: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -3913,6 +4012,10 @@ jsonlint@^1.6.2: JSV ">= 4.0.x" nomnom ">= 1.5.x" +jsonparse@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd" + jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" @@ -4460,6 +4563,13 @@ maxmin@^1.1.0: gzip-size "^1.0.0" pretty-bytes "^1.0.0" +md5.js@1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -4580,12 +4690,22 @@ micromatch@^2.1.5, micromatch@^2.3.7: version "1.30.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + mime-types@^2.1.12, mime-types@^2.1.14, mime-types@^2.1.3, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: mime-db "~1.30.0" +mime-types@~2.1.17: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + dependencies: + mime-db "~1.33.0" + mime@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" @@ -4905,7 +5025,7 @@ number-is-nan@^1.0.0: version "1.3.9" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.3.9.tgz#8bab486ff7fa3dfd086656bbe8b17116d3692d2a" -oauth-sign@~0.8.1: +oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" @@ -5326,6 +5446,10 @@ performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + pg-connection-string@0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" @@ -5809,6 +5933,10 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +qs@~6.5.1: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -5946,6 +6074,17 @@ readable-stream@2.3.3, readable-stream@^2.0.1, readable-stream@^2.0.2, readable- string_decoder "~1.0.3" util-deprecate "~1.0.1" +readable-stream@~2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -6169,6 +6308,31 @@ request@2.81.0, request@2.x, request@^2.40.0, request@^2.72.0, request@^2.79.0, tunnel-agent "^0.6.0" uuid "^3.0.0" +request@^2.86.0: + version "2.87.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -6289,6 +6453,10 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@^5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + safe-json-stringify@~1: version "1.0.4" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911" @@ -6299,6 +6467,10 @@ safefs@^3.1.2: dependencies: graceful-fs "*" +sax@0.5.x: + version "0.5.8" + resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" + sax@1.2.1, sax@>=0.6.0, sax@^1.2.1, sax@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" @@ -7020,6 +7192,12 @@ tough-cookie@^2.3.2, tough-cookie@~2.3.0: dependencies: punycode "^1.4.1" +tough-cookie@~2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + dependencies: + punycode "^1.4.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -7345,6 +7523,10 @@ validator@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/validator/-/validator-6.3.0.tgz#47ce23ed8d4eaddfa9d4b8ef0071b6cf1078d7c8" +validator@~9.4.1: + version "9.4.1" + resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" + vary@~1.1.1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -7626,6 +7808,12 @@ xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" +xml2js@0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2" + dependencies: + sax "0.5.x" + xml2js@0.4.17: version "0.4.17" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" @@ -7644,6 +7832,10 @@ xml@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" +xmlbuilder@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-0.4.3.tgz#c4614ba74e0ad196e609c9272cd9e1ddb28a8a58" + xmlbuilder@4.2.1, xmlbuilder@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" |