diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 241ace475..cd57331ac 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -1,15 +1,15 @@
## Contributing
-This project uses [editor config](http://editorconfig.org/), please make sure to [download the plugin for your editor](http://editorconfig.org/#download) so that we stay consistent. Current ECMAScript 5 is implemented and supported at this time. This may change eventually to some ECMAScript 6 from the finalization that occurred in June of 2015.
+This project uses [editor config](https://editorconfig.org/), please make sure to [download the plugin for your editor](https://editorconfig.org/#download) so that we stay consistent. Current ECMAScript 5 is implemented and supported at this time. This may change eventually to some ECMAScript 6 from the finalization that occurred in June of 2015.
### Creating a Local Environment
#### Prerequisites
-* [Git](http://git-scm.com/)
-* [node.js](http://nodejs.org/) *(see [`./package.json`](https://github.com/OpenUserJs/OpenUserJS.org/blob/master/package.json) engines for specific requirements)*
-* [MongoDB](http://www.mongodb.org/) (Optional. The project is preconfigured to use a dev DB on [MongoLab](https://mongolab.com/).)
+* [Git](https://git-scm.com/)
+* [node.js](https://nodejs.org/) *(see [`./package.json`](https://github.com/OpenUserJs/OpenUserJS.org/blob/master/package.json) engines for specific requirements)*
+* [MongoDB](https://www.mongodb.org/) *(Required: See [overall instructions here](https://docs.mongodb.com/manual/installation/) or [Community Edition instructions](https://docs.mongodb.com/manual/administration/install-community/))*
#### GitHub Fork Setup
@@ -32,11 +32,19 @@ This project uses [editor config](http://editorconfig.org/), please make sure to
#### Configuration
1. Navigate to https://github.com/settings/applications and register a new OAuth application, saving the Client ID and Secret. To ensure GitHub OAuth authentication will work the "Authorization callback URL" value must exactly match `AUTH_CALLBACK_BASE_URL` (see below, e.g. http://localhost:8080).
-2. Open a [MongoDB shell](http://docs.mongodb.org/manual/tutorial/getting-started-with-the-mongo-shell/) and run the following (replacing "your_GitHub_client_ID" and "your_GitHub_secret") to create an "oujs_dev" database with a "strategies" collection containing your application instance's GitHub OAuth information.
- * `use oujs_dev`
+2. Open a [MongoDB shell](https://docs.mongodb.com/manual/mongo/) and run the following (replacing "your_GitHub_client_ID" and "your_GitHub_secret") to create an "openuserjs_devel" database with a "strategies" collection containing your application instance's GitHub OAuth information.
+ * `use openuserjs_devel`
* `db.createCollection("strategies")`
* `db.strategies.insert({id: "your_GitHub_client_ID", key: "your_GitHub_secret", name: "github", display: "GitHub"})`
-3. Edit `models/settings.json`, setting your desired session secret, [MongoDB connection string](http://docs.mongodb.org/manual/reference/connection-string/) (if using your own MongoDB instance), etc.
+3. Edit `models/settings.json`, setting your desired session secret, [MongoDB connection string](https://docs.mongodb.com/manual/reference/connection-string/) (if using your own MongoDB instance), etc.
+4. Depending on how you installed MongoDB you may need to set an environment variable in your CLI:
+
+Linux Example:
+~/.bashrc
+``` console
+export CONNECT_STRING=mongodb://127.0.0.1:27017/openuserjs_devel
+```
+5. You may import a “dirty” database using `mongorestore --gzip --db openuserjs_devel --archive=./dev/devDBdirty.gz` from the projects home.
#### Running the Application
@@ -70,6 +78,10 @@ To contribute code to OpenUserJS.org the following process should generally be u
The following is a brief list of **some** of the labels used on the project and is used to establish teamwork. Not everyone has permission to set these and usually will be set by someone, unless expressly prohibited, either when an Issue or Pull Request *(PR)* is created or after an Issue is reported:
+##### SOFT BLOCKING
+Only the establishing owner and in extreme cases the active maintainer of the project may add this. Removal is usually done by the active maintainer and above. Recommendations by other contributors and collaborators are always accepted to have this put on or removed. This label means that merging unrelated or non-bug fix PRs will be put on hold until this label has been removed. Documentation fixes are always welcome by the active maintainer.
+
+
##### BLOCKING
Only the establishing owner and in extreme cases the active maintainer of the project may add this. Removal is done by the establishing owner. Recommendations by other contributors and collaborators are always accepted to have this put on or removed. This label means that merging unrelated or non-bug fix PRs will be put on hold until this label has been removed. Documentation fixes are always welcome by the Active Maintainer.
diff --git a/.github/ISSUE_TEMPLATE/01_feature_request.yml b/.github/ISSUE_TEMPLATE/01_feature_request.yml
new file mode 100644
index 000000000..e6bef7081
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/01_feature_request.yml
@@ -0,0 +1,29 @@
+name: Feature Request
+description: Something we **DO NOT** already have implemented to the best of your knowledge but you would like to see. (Please search existing issues and milestones first.)
+labels: ["feature"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thank you for taking the time to fill this out.
+ - type: textarea
+ id: what-is-missing
+ attributes:
+ label: What is missing?
+ description: Describe your feature idea.
+ placeholder: Start typing here.
+ validations:
+ required: true
+ - type: textarea
+ id: why
+ attributes:
+ label: Why?
+ description: Describe the problem you are facing.
+ placeholder: Continue typing here.
+ validations:
+ required: true
+ - type: textarea
+ id: alt
+ attributes:
+ label: Alternatives you have tried.
+ description: Describe any workarounds you tried so far and how they worked for you.
diff --git a/.github/ISSUE_TEMPLATE/02_enhancement_request.yml b/.github/ISSUE_TEMPLATE/02_enhancement_request.yml
new file mode 100644
index 000000000..2707fc776
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/02_enhancement_request.yml
@@ -0,0 +1,29 @@
+name: Enhancement Request
+description: Something we **DO** have implemented already but needs improvement upon to the best of your knowledge. (Please search existing issues and milestones first.)
+labels: ["enhancement"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thank you for taking the time to fill this out.
+ - type: textarea
+ id: what-is-missing
+ attributes:
+ label: What is missing?
+ description: Describe your enhancement idea.
+ placeholder: Start typing here.
+ validations:
+ required: true
+ - type: textarea
+ id: why
+ attributes:
+ label: Why?
+ description: Describe the problem you are facing.
+ placeholder: Continue typing here.
+ validations:
+ required: true
+ - type: textarea
+ id: alt
+ attributes:
+ label: Alternatives you have tried.
+ description: Describe any workarounds you tried so far and how they worked for you.
diff --git a/.github/ISSUE_TEMPLATE/03_possible_bug.yml b/.github/ISSUE_TEMPLATE/03_possible_bug.yml
new file mode 100644
index 000000000..5ea8e9dbb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/03_possible_bug.yml
@@ -0,0 +1,31 @@
+name: Bug investigation
+description: |
+ If something isn't working as expected.
+ (Status codes below 500 are usually a user error and not a server error. i.e. Usually not a bug. Please search existing issues and milestones first.)
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thank you for taking the time to fill this out.
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: Describe the problem and how to reproduce it. Add screenshots or a link to your repository if helpful.
+ placeholder: Start typing here.
+ validations:
+ required: true
+ - type: textarea
+ id: what-expected
+ attributes:
+ label: What did you expect to happen?
+ description: Describe what you expected to happen instead.
+ placeholder: Continue typing here.
+ validations:
+ required: true
+ - type: textarea
+ id: solutions-tried
+ attributes:
+ label: What the problem might be?
+ description: If you have an idea where the bug might lie, please share here.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..d20b72c2c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Community-driven help
+ url: https://openuserjs.org/discuss
+ about: Do you have a question? Start here.
diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml
new file mode 100644
index 000000000..f158d7111
--- /dev/null
+++ b/.github/workflows/lock.yml
@@ -0,0 +1,24 @@
+name: 'Lock Threads'
+
+on:
+ schedule:
+ - cron: '0 0 * * *'
+ workflow_dispatch:
+
+permissions:
+ issues: write
+ pull-requests: write
+ discussions: write
+
+concurrency:
+ group: lock-threads
+
+jobs:
+ action:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: dessant/lock-threads@v5
+ with:
+ github-token: ${{ github.token }}
+ issue-inactive-days: '1'
+ process-only: 'issues'
diff --git a/.gitignore b/.gitignore
index d168ab0dd..ad219c646 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,8 @@ lib-cov
*.out
*.pid
*.gz
+!dev/devDBclean.gz
+!dev/devDBdirty.gz
pids
logs
diff --git a/README.md b/README.md
index 55857b8f4..b836806a5 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@ The home of Free and Open Source Software (FOSS) user scripts. Built using Node.
Repository | Reference | Recent Version
:--- | :---: | :---
-[nodejs][nodeGHUrl] | [Documentation][nodejsDOCUrl] | [¹][nodejsGHReleasesUrl] [CHANGELOG][nodejsReleasesUrl]
-[npm][npmGHUrl] | [Documentation][npmDOCUrl] | [![npm][npmNPMVersionImage]][npmNPMUrl] [CHANGELOG][npmGHReleasesUrl]
+[nodejs][nodeGHUrl] | [Documentation][nodejsDOCUrl] |
[¹][nodejsGHReleasesUrl] [RELEASES][nodejsReleasesUrl] [²][nodejsDownloadsUrl] [³][nodejsNvmDownload]
+[npm][npmGHUrl] | [Documentation][npmDOCUrl] | [![npm][npmNPMVersionImage]][npmNPMUrl] [RELEASES][npmGHReleasesUrl]
### Contributing
@@ -15,30 +15,33 @@ Repository | Reference | Recent Version
### Dependencies
#### Dispersed
-These also may use [shields.io][shieldsHomepage] where applicable for more explicit and up to date results.
+These also may use [a badge provider][badgeProvider] where applicable for potentialliy more up to date results.
##### Dynamic
Repository | Reference | Recent Version
--- | --- | ---
-[ace-builds][ace-buildsGHUrl] [¹][aceGHUrl] | [Documentation][ace-buildsDOCUrl] [¹][aceDOCUrl] | [1.4.9][ace-buildsGHHASHUrl] [RELEASES][ace-buildsGHRELEASESUrl]
+[ace-builds][ace-buildsGHUrl] [¹][aceGHUrl] | [Documentation][ace-buildsDOCUrl] [¹][aceDOCUrl] | [![NPM version][ace-buildsNPMVersionImage]][ace-buildsNPMUrl]
+[animate.css][animate.cssGHUrl] | [Documentation][animate.cssDOCUrl] | [![NPM version][animate.cssNPMVersionImage]][animate.cssNPMUrl]
[ansi-colors][ansi-colorsGHUrl] | [Documentation][ansi-colorsDOCUrl] | [![NPM version][ansi-colorsNPMVersionImage]][ansi-colorsNPMUrl]
[async][asyncGHUrl] | [Documentation][asyncDOCUrl] | [![NPM version][asyncNPMVersionImage]][asyncNPMUrl]
[aws-sdk][aws-sdkGHUrl] | [Documentation][aws-sdkDOCUrl] | [![NPM version][aws-sdkNPMVersionImage]][aws-sdkNPMUrl]
-[base62][base62GHUrl] | [Documentation][base62DOCUrl] | [![NPM version][base62NPMVersionImage]][base62NPMUrl]
[body-parser][body-parserGHUrl] | [Documentation][body-parserDOCUrl] | [![NPM version][body-parserNPMVersionImage]][body-parserNPMUrl]
[bootstrap][bootstrapGHUrl] | [Documentation][bootstrapDOCUrl] | [![NPM version][bootstrapNPMVersionImage]][bootstrapNPMUrl]
-[bootstrap-markdown][bootstrap-markdownGHUrl] | [Documentation][bootstrap-markdownDOCUrl] | [![NPM version][bootstrap-markdownNPMVersionImage]][bootstrap-markdownNPMUrl]
+[bootstrap-markdown][bootstrap-markdownGHUrl]
⋔ [`marked4.x`][bootstrap-markdownGHUrlForkUrl] | [Documentation][bootstrap-markdownDOCUrl] | [![NPM version][bootstrap-markdownNPMVersionImage]][bootstrap-markdownNPMUrl]
[clipboard][clipboardGHUrl] | [Documentation][clipboardDOCUrl] | [![NPM version][clipboardNPMVersionImage]][clipboardNPMUrl]
[compression][compressionGHUrl] | [Documentation][compressionDOCUrl] | [![NPM version][compressionNPMVersionImage]][compressionNPMUrl]
[connect-mongo][connect-mongoGHUrl] | [Documentation][connect-mongoDOCUrl] | [![NPM version][connect-mongoNPMVersionImage]][connect-mongoNPMUrl]
[diff][diffGHUrl] | [Documentation][diffDOCUrl] | [![NPM version][diffNPMVersionImage]][diffNPMUrl]
[express][expressGHUrl] | [Documentation][expressDOCUrl] | [![NPM version][expressNPMVersionImage]][expressNPMUrl]
+[express-hcaptcha][express-hcaptchaGHUrl]
⋔ [`forkUpdate`][express-hcaptchaGHUrlForkUrl] | [Documentation][express-hcaptchaDOCUrl] | [![NPM version][express-hcaptchaNPMVersionImage]][express-hcaptchaNPMUrl]
[express-minify][express-minifyGHUrl] | [Documentation][express-minifyDOCUrl] | [![NPM version][express-minifyNPMVersionImage]][express-minifyNPMUrl]
[express-rate-limit][express-rate-limitGHUrl] | [Documentation][express-rate-limitDOCUrl] | [![NPM version][express-rate-limitNPMVersionImage]][express-rate-limitNPMUrl]
[express-session][express-sessionGHUrl] | [Documentation][express-sessionDOCUrl] | [![NPM version][express-sessionNPMVersionImage]][express-sessionNPMUrl]
+[express-svg-captcha][express-svg-captchaGHUrl] | [Documentation][express-svg-captchaDOCUrl] | [![NPM version][express-svg-captchaNPMVersionImage]][express-svg-captchaNPMUrl]
[font-awesome][font-awesomeGHUrl] | [Documentation][font-awesomeDOCUrl] | [![NPM version][font-awesomeNPMVersionImage]][font-awesomeNPMUrl]
[formidable][formidableGHUrl] | [Documentation][formidableDOCUrl] | [![NPM version][formidableNPMVersionImage]][formidableNPMUrl]
[git-rev][git-revGHUrl] | [Documentation][git-revDOCUrl] | [![NPM version][git-revNPMVersionImage]][git-revNPMUrl]
+[git-rev-sync][git-rev-syncGHUrl] | [Documentation][git-rev-syncDOCUrl] | [![NPM version][git-rev-syncNPMVersionImage]][git-rev-syncNPMUrl]
[highlight.js][highlight.jsGHUrl] | [Documentation][highlight.jsDOCUrl][ᴸᴬᴺᴳ][highlight.jsLANGUrl] | [![NPM version][highlight.jsNPMVersionImage]][highlight.jsNPMUrl]
[image-size][image-sizeGHUrl] | [Documentation][image-sizeDOCUrl] | [![NPM version][image-sizeNPMVersionImage]][image-sizeNPMUrl]
[ip-range-check][ip-range-checkGHUrl] | [Documentation][ip-range-checkDOCUrl] | [![NPM version][ip-range-checkNPMVersionImage]][ip-range-checkNPMUrl]
@@ -48,6 +51,7 @@ Repository | Reference | Recent Version
[kerberos][kerberosGHUrl] | [Documentation][kerberosDOCUrl] | [![NPM version][kerberosNPMVersionImage]][kerberosNPMUrl]
[less-middleware][less-middlewareGHUrl] [¹][lessGHUrl] | [Documentation][less-middlewareDOCUrl] [¹][lessDOCUrl] | [![NPM version][less-middlewareNPMVersionImage]][less-middlewareNPMUrl]
[marked][markedGHUrl] | [Documentation][markedDOCUrl] | [![NPM version][markedNPMVersionImage]][markedNPMUrl]
+[marked-highlight][marked-highlightGHUrl] | [Documentation][marked-highlightDOCUrl] | [![NPM version][marked-highlightNPMVersionImage]][marked-highlightNPMUrl]
[media-type][media-typeGHUrl] | [Documentation][media-typeDOCUrl] | [![NPM version][media-typeNPMVersionImage]][media-typeNPMUrl]
[method-override][method-overrideGHUrl] | [Documentation][method-overrideDOCUrl] | [![NPM version][method-overrideNPMVersionImage]][method-overrideNPMUrl]
[mime-db][mime-dbGHUrl] | [Documentation][mime-dbDOCUrl] | [![NPM version][mime-dbNPMVersionImage]][mime-dbNPMUrl]
@@ -59,15 +63,12 @@ Repository | Reference | Recent Version
[mu2][mu2GHUrl] | [Documentation][mu2DOCUrl] | [![NPM version][mu2NPMVersionImage]][mu2NPMUrl]
[octicons][octiconsGHUrl] | [Documentation][octiconsDOCUrl] | [![NPM version][octiconsNPMVersionImage]][octiconsNPMUrl]
[passport][passportGHUrl] | [Documentation][passportDOCUrl] | [![NPM version][passportNPMVersionImage]][passportNPMUrl]
-[passport-facebook][passport-facebookGHUrl] | [Documentation][passport-facebookDOCUrl] | [![NPM version][passport-facebookNPMVersionImage]][passport-facebookNPMUrl] ![OAuth2][oauth2Logo]
[passport-github][passport-githubGHUrl] | [Documentation][passport-githubDOCUrl] | [![NPM version][passport-githubNPMVersionImage]][passport-githubNPMUrl] ![OAuth2][oauth2Logo]
[passport-gitlab2][passport-gitlab2GHUrl] | [Documentation][passport-gitlab2DOCUrl] | [![NPM version][passport-gitlab2NPMVersionImage]][passport-gitlab2NPMUrl] ![OAuth2][oauth2Logo]
[passport-google-oauth2][passport-google-oauth2GHUrl] | [Documentation][passport-google-oauth2DOCUrl] | [![NPM version][passport-google-oauth2NPMVersionImage]][passport-google-oauth2NPMUrl] ![OAuth2][oauth2Logo]
[passport-imgur][passport-imgurGHUrl] | [Documentation][passport-imgurDOCUrl] | [![NPM version][passport-imgurNPMVersionImage]][passport-imgurNPMUrl] ![OAuth2][oauth2Logo] ![oauth][oauthLogo]
-[passport-reddit][passport-redditGHUrl] | [Documentation][passport-redditDOCUrl] | [![NPM version][passport-redditNPMVersionImage]][passport-redditNPMUrl] ![OAuth2][oauth2Logo]
-[passport-steam][passport-steamGHUrl]
⋔ [`OpenID2`][passport-steamGHOpenIDUrl] | [Documentation][passport-steamDOCUrl] | [![NPM version][passport-steamNPMVersionImage]][passport-steamNPMUrl] ![OpenID][openidLogo] [⋔][passport-openid]
-[passport-twitter][passport-twitterGHUrl] | [Documentation][passport-twitterDOCUrl] | [![NPM version][passport-twitterNPMVersionImage]][passport-twitterNPMUrl] ![oauth1][oauth1Logo]
-[passport-yahoo][passport-yahooGHUrl]
⋔ [`OpenID2`][passport-yahooGHOpenIDUrl] | [Documentation][passport-yahooDOCUrl] | [![NPM version][passport-yahooNPMVersionImage]][passport-yahooNPMUrl] ![OpenID][openidLogo] [⋔][passport-openid]
+[passport-reddit][passport-redditGHUrl] [
⋔ `536b656c6c79/passport-reddit-commonJS`][passport-reddit-commonjsGHUrl] | [Documentation][passport-redditDOCUrl]
[⋔][passport-reddit-commonjsDOCUrl] | [![NPM version][passport-redditNPMVersionImage]][passport-redditNPMUrl] ![OAuth2][oauth2Logo]
[⋔ ![NPM version][passport-reddit-commonjsNPMVersionImage]][passport-reddit-commonjsNPMUrl]
+[passport-steam][passport-steamGHUrl] [
⋔ `OpenID2`][passport-steamGHOpenIDUrl] | [Documentation][passport-steamDOCUrl] | [![NPM version][passport-steamNPMVersionImage]][passport-steamNPMUrl] ![OpenID][openidLogo]
[⋔][passport-openid]
[pegjs][pegjsGHUrl] | [Documentation][pegjsDOCUrl] | [![NPM version][pegjsNPMVersionImage]][pegjsNPMUrl]
[rate-limit-mongo][rate-limit-mongoGHUrl] | [Documentation][rate-limit-mongoDOCUrl] | [![NPM version][rate-limit-mongoNPMVersionImage]][rate-limit-mongoNPMUrl]
[remark][remarkGHUrl] | [Documentation][remarkDOCUrl] | [![NPM version][remarkNPMVersionImage]][remarkNPMUrl]
@@ -83,11 +84,11 @@ Repository | Reference | Recent Version
[strip-markdown][strip-markdownGHUrl] | [Documentation][strip-markdownDOCUrl] | [![NPM version][strip-markdownNPMVersionImage]][strip-markdownNPMUrl]
[terser][terserGHUrl] | [Documentation][terserDOCUrl] | [![NPM version][terserNPMVersionImage]][terserNPMUrl]
[toobusy-js][toobusy-jsGHUrl]
⋔ [`harmony`][toobusy-jsGHUrlHarmonyUrl] | [Documentation][toobusy-jsDOCUrl] | [![NPM version][toobusy-jsNPMVersionImage]][toobusy-jsNPMUrl]
-[underscore][underscoreGHUrl] | [Documentation][underscoreDOCUrl] | [![NPM version][underscoreNPMVersionImage]][underscoreNPMUrl]
+[underscore][underscoreGHUrl] | [Documentation][underscoreDOCUrl] [Δ][underscoreDOCCLUrl] | [![NPM version][underscoreNPMVersionImage]][underscoreNPMUrl]
[useragent][useragentGHUrl] | [Documentation][useragentDOCUrl] | [![NPM version][useragentNPMVersionImage]][useragentNPMUrl]
+[@octokit/auth-oauth-app][auth-oauth-appGHUrl] | [Documentation][auth-oauth-appDOCUrl] | [![NPM version][auth-oauth-appNPMVersionImage]][auth-oauth-appNPMUrl]
[@octokit/rest ᶠᵏᵃ ᵍᶦᵗʰᵘᵇ][githubGHUrl] | [Documentation][githubDOCUrl] | [![NPM version][githubNPMVersionImage]][githubNPMUrl]
-
##### Static
Repository | Reference | Recent Version | Stored
@@ -99,401 +100,408 @@ Repository | Reference | Recent Version | Stored
Repository | Reference | Recent Version | Referenced
--- | --- | --- | ---
-[Google Analytics][gaCFGUrl] | [Documentation][gaDOCUrl] | Client-side from GA | [gaCDNUrl]
#### Aggregate
-[![Using David][davidImageViaShields]][davidReport]
-
-Outdated dependencies list can also be achieved with `$ npm outdated`
+Outdated dependencies list can be achieved with `$ npm outdated` from the terminal and alternatively `$ npm show packagename versions`.
[nodeGHUrl]: https://github.com/nodejs/node
[nodejsGHReleasesUrl]: https://github.com/nodejs/Release/#release-schedule
-[nodejsReleasesUrl]: https://nodejs.org/download/release/
-[nodejsDOCUrl]: http://nodejs.org/documentation/
+[nodejsReleasesUrl]: https://nodejs.org/blog/release
+[nodejsDownloadsUrl]: https://nodejs.org/download/release/
+[nodejsDOCUrl]: https://nodejs.org/documentation/
+[nodejsNvmDownload]: https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating
[npmNPMUrl]: https://www.npmjs.com/package/npm
-[npmNPMVersionImage]: http://img.shields.io/npm/v/npm.svg
+[npmNPMVersionImage]: https://badgen.net/npm/v/npm?cache=86400
[npmGHReleasesUrl]: https://github.com/npm/cli/releases
[npmGHUrl]: https://github.com/npm/cli
[npmDOCUrl]: https://github.com/npm/cli/blob/latest/README.md
-[davidImageViaShields]: http://img.shields.io/david/openuserjs/openuserjs.org.svg?style=flat
-[davidReport]: https://david-dm.org/OpenUserJS/OpenUserJS.org
-
-[shieldsHomepage]: http://shields.io/
+[badgeProvider]: https://badgen.net/
[ace-buildsGHUrl]: https://github.com/ajaxorg/ace-builds/tree/master/src
-[ace-buildsGHHASHUrl]: https://github.com/ajaxorg/ace-builds/tree/bd7ce25
-[ace-buildsGHRELEASESUrl]: https://github.com/ajaxorg/ace-builds/releases
[ace-buildsDOCUrl]: https://github.com/ajaxorg/ace-builds/blob/master/README.md
+[ace-buildsNPMUrl]: https://www.npmjs.com/package/ace-builds
+[ace-buildsNPMVersionImage]: https://badgen.net/npm/v/ace-builds?cache=86400
[aceGHUrl]: https://github.com/ajaxorg/ace "ace"
-[aceDOCUrl]: http://ace.c9.io/#nav=api "ace"
+[aceDOCUrl]: https://ace.c9.io/#nav=api "ace"
+
+[animate.cssGHUrl]: https://github.com/animate-css/animate.css
+[animate.cssDOCUrl]: https://animate.style/
+[animate.cssNPMUrl]: https://www.npmjs.com/package/animate.css
+[animate.cssNPMVersionImage]: https://badgen.net/npm/v/animate.css?cache=86400
[ansi-colorsGHUrl]: https://github.com/doowb/ansi-colors
[ansi-colorsDOCUrl]: https://github.com/doowb/ansi-colors/blob/master/README.md
[ansi-colorsNPMUrl]: https://www.npmjs.com/package/ansi-colors
-[ansi-colorsNPMVersionImage]: https://img.shields.io/npm/v/ansi-colors.svg?style=flat
+[ansi-colorsNPMVersionImage]: https://badgen.net/npm/v/ansi-colors?cache=86400
[asyncGHUrl]: https://github.com/caolan/async
[asyncDOCUrl]: https://github.com/caolan/async/blob/master/README.md
[asyncNPMUrl]: https://www.npmjs.com/package/async
-[asyncNPMVersionImage]: https://img.shields.io/npm/v/async.svg?style=flat
+[asyncNPMVersionImage]: https://badgen.net/npm/v/async?cache=86400
[aws-sdkGHUrl]: https://github.com/aws/aws-sdk-js
[aws-sdkDOCUrl]: https://github.com/aws/aws-sdk-js/blob/master/README.md
[aws-sdkNPMUrl]: https://www.npmjs.com/package/aws-sdk
-[aws-sdkNPMVersionImage]: https://img.shields.io/npm/v/aws-sdk.svg?style=flat
-
-[base62GHUrl]: https://github.com/base62/base62.js
-[base62DOCUrl]: https://github.com/base62/base62.js/blob/master/Readme.md
-[base62NPMUrl]: https://www.npmjs.com/package/base62
-[base62NPMVersionImage]: https://img.shields.io/npm/v/base62.svg?style=flat
+[aws-sdkNPMVersionImage]: https://badgen.net/npm/v/aws-sdk?cache=86400
[body-parserGHUrl]: https://github.com/expressjs/body-parser
[body-parserDOCUrl]: https://github.com/expressjs/body-parser/blob/master/README.md
[body-parserNPMUrl]: https://www.npmjs.com/package/body-parser
-[body-parserNPMVersionImage]: https://img.shields.io/npm/v/body-parser.svg?style=flat
+[body-parserNPMVersionImage]: https://badgen.net/npm/v/body-parser?cache=86400
-[bootstrapUrl]: http://getbootstrap.com/
+[bootstrapUrl]: https://getbootstrap.com/
[bootstrapGHUrl]: https://github.com/twbs/bootstrap
-[bootstrapDOCUrl]: http://getbootstrap.com/components/
+[bootstrapDOCUrl]: https://getbootstrap.com/components/
[bootstrapNPMUrl]: https://www.npmjs.com/package/bootstrap
-[bootstrapNPMVersionImage]: https://img.shields.io/npm/v/bootstrap.svg?style=flat
+[bootstrapNPMVersionImage]: https://badgen.net/npm/v/bootstrap?cache=86400
-[bootstrap-markdownGHUrl]: https://github.com/toopay/bootstrap-markdown
-[bootstrap-markdownDOCUrl]: http://toopay.github.io/bootstrap-markdown/
+[bootstrap-markdownGHUrl]: https://github.com/refactory-id/bootstrap-markdown
+[bootstrap-markdownGHUrlForkUrl]: https://github.com/OpenUserJS/bootstrap-markdown/tree/marked4.x
+[bootstrap-markdownDOCUrl]: https://refactory-id.github.io/bootstrap-markdown/
[bootstrap-markdownNPMUrl]: https://www.npmjs.com/package/bootstrap-markdown
-[bootstrap-markdownNPMVersionImage]: https://img.shields.io/npm/v/bootstrap-markdown.svg?style=flat
+[bootstrap-markdownNPMVersionImage]: https://badgen.net/npm/v/bootstrap-markdown?cache=86400
[clipboardGHUrl]: https://github.com/zenorocha/clipboard.js
[clipboardDOCUrl]: https://github.com/zenorocha/clipboard.js/blob/master/readme.md
[clipboardNPMUrl]: https://www.npmjs.com/package/clipboard
-[clipboardNPMVersionImage]: https://img.shields.io/npm/v/clipboard.svg?style=flat
+[clipboardNPMVersionImage]: https://badgen.net/npm/v/clipboard?cache=86400
[compressionGHUrl]: https://github.com/expressjs/compression
[compressionDOCUrl]: https://github.com/expressjs/compression/blob/master/README.md
[compressionNPMUrl]: https://www.npmjs.com/package/compression
-[compressionNPMVersionImage]: https://img.shields.io/npm/v/compression.svg?style=flat
+[compressionNPMVersionImage]: https://badgen.net/npm/v/compression?cache=86400
-[connect-mongoGHUrl]: https://github.com/kcbanner/connect-mongo
-[connect-mongoDOCUrl]: https://github.com/kcbanner/connect-mongo/blob/master/README.md
+[connect-mongoGHUrl]: https://github.com/jdesboeufs/connect-mongo
+[connect-mongoDOCUrl]: https://github.com/jdesboeufs/connect-mongo/blob/master/README.md
[connect-mongoNPMUrl]: https://www.npmjs.com/package/connect-mongo
-[connect-mongoNPMVersionImage]: https://img.shields.io/npm/v/connect-mongo.svg?style=flat
+[connect-mongoNPMVersionImage]: https://badgen.net/npm/v/connect-mongo?cache=86400
[diffGHUrl]: https://github.com/kpdecker/jsdiff
[diffDOCUrl]: https://github.com/kpdecker/jsdiff/blob/master/README.md
[diffNPMUrl]: https://www.npmjs.com/package/diff
-[diffNPMVersionImage]: https://img.shields.io/npm/v/diff.svg?style=flat
+[diffNPMVersionImage]: https://badgen.net/npm/v/diff?cache=86400
[expressGHUrl]: https://github.com/expressjs/express
-[expressDOCUrl]: http://expressjs.com/
+[expressDOCUrl]: https://expressjs.com/
[expressNPMUrl]: https://www.npmjs.com/package/express
-[expressNPMVersionImage]: https://img.shields.io/npm/v/express.svg?style=flat
+[expressNPMVersionImage]: https://badgen.net/npm/v/express?cache=86400
+
+[express-hcaptchaGHUrl]: https://github.com/vastus/express-hcaptcha
+[express-hcaptchaGHUrlForkUrl]: https://github.com/OpenUserJS/express-hcaptcha/tree/forkUpdate
+[express-hcaptchaDOCUrl]: https://github.com/vastus/express-hcaptcha/blob/master/README.md
+[express-hcaptchaNPMUrl]: https://www.npmjs.com/package/express-hcaptcha
+[express-hcaptchaNPMVersionImage]: https://badgen.net/npm/v/express-hcaptcha?cache=86400
[express-minifyGHUrl]: https://github.com/breeswish/express-minify
[express-minifyDOCUrl]: https://github.com/breeswish/express-minify/blob/master/README.md
[express-minifyNPMUrl]: https://www.npmjs.com/package/express-minify
-[express-minifyNPMVersionImage]: https://img.shields.io/npm/v/express-minify.svg?style=flat
+[express-minifyNPMVersionImage]: https://badgen.net/npm/v/express-minify?cache=86400
-[express-rate-limitGHUrl]: https://github.com/nfriedly/express-rate-limit
-[express-rate-limitDOCUrl]: https://github.com/nfriedly/express-rate-limit/blob/master/README.md
+[express-rate-limitGHUrl]: https://github.com/express-rate-limit/express-rate-limit
+[express-rate-limitDOCUrl]: https://github.com/express-rate-limit/express-rate-limit/blob/main/readme.md
[express-rate-limitNPMUrl]: https://www.npmjs.com/package/express-rate-limit
-[express-rate-limitNPMVersionImage]: https://img.shields.io/npm/v/express-rate-limit.svg?style=flat
+[express-rate-limitNPMVersionImage]: https://badgen.net/npm/v/express-rate-limit?cache=86400
[express-sessionGHUrl]: https://github.com/expressjs/session
[express-sessionDOCUrl]: https://github.com/expressjs/session/blob/master/README.md
[express-sessionNPMUrl]: https://www.npmjs.com/package/express-session
-[express-sessionNPMVersionImage]: https://img.shields.io/npm/v/express-session.svg?style=flat
+[express-sessionNPMVersionImage]: https://badgen.net/npm/v/express-session?cache=86400
+
+[express-svg-captchaGHUrl]: https://github.com/cmd430/express-svg-captcha
+[express-svg-captchaDOCUrl]: https://github.com/cmd430/express-svg-captcha/blob/master/README.md
+[express-svg-captchaNPMUrl]: https://www.npmjs.com/package/express-svg-captcha
+[express-svg-captchaNPMVersionImage]: https://badgen.net/npm/v/express-svg-captcha?cache=86400
[font-awesomeGHUrl]: https://github.com/FortAwesome/Font-Awesome
-[font-awesomeDOCUrl]: http://fontawesome.io/
+[font-awesomeDOCUrl]: https://fontawesome.com/
[font-awesomeNPMUrl]: https://www.npmjs.com/package/font-awesome
-[font-awesomeNPMVersionImage]: https://img.shields.io/npm/v/font-awesome.svg?style=flat
+[font-awesomeNPMVersionImage]: https://badgen.net/npm/v/font-awesome?cache=86400
-[formidableGHUrl]: https://github.com/felixge/node-formidable
-[formidableDOCUrl]: https://github.com/felixge/node-formidable/blob/master/Readme.md
+[formidableGHUrl]: https://github.com/node-formidable/formidable
+[formidableDOCUrl]: https://github.com/node-formidable/formidable/blob/master/README.md
[formidableNPMUrl]: https://www.npmjs.com/package/formidable
-[formidableNPMVersionImage]: https://img.shields.io/npm/v/formidable.svg?style=flat
-
-[githubGHUrl]: https://github.com/octokit/rest.js
-[githubDOCUrl]: https://github.com/octokit/rest.js/blob/master/README.md
-[githubNPMUrl]: https://www.npmjs.com/package/@octokit/rest
-[githubNPMVersionImage]: https://img.shields.io/npm/v/@octokit/rest.svg?style=flat
+[formidableNPMVersionImage]: https://badgen.net/npm/v/formidable?cache=86400
[git-revGHUrl]: https://github.com/tblobaum/git-rev
[git-revDOCUrl]: https://github.com/tblobaum/git-rev/blob/master/README.md
[git-revNPMUrl]: https://www.npmjs.com/package/git-rev
-[git-revNPMVersionImage]: https://img.shields.io/npm/v/git-rev.svg?style=flat
+[git-revNPMVersionImage]: https://badgen.net/npm/v/git-rev?cache=86400
-[highlight.jsGHUrl]: https://github.com/isagalaev/highlight.js
-[highlight.jsDOCUrl]: http://highlightjs.readthedocs.org
+[git-rev-syncGHUrl]: https://github.com/kurttheviking/git-rev-sync-js
+[git-rev-syncDOCUrl]: https://github.com/kurttheviking/git-rev-sync-js/blob/master/README.md
+[git-rev-syncNPMUrl]: https://www.npmjs.com/package/git-rev-sync
+[git-rev-syncNPMVersionImage]: https://badgen.net/npm/v/git-rev-sync?cache=86400
+
+[highlight.jsGHUrl]: https://github.com/highlightjs/highlight.js
+[highlight.jsDOCUrl]: https://highlightjs.readthedocs.io
[highlight.jsNPMUrl]: https://www.npmjs.com/package/highlight.js
-[highlight.jsNPMVersionImage]: https://img.shields.io/npm/v/highlight.js.svg?style=flat
-[highlight.jsLANGUrl]: https://github.com/highlightjs/highlight.js#getting-started
+[highlight.jsNPMVersionImage]: https://badgen.net/npm/v/highlight.js?cache=86400
+[highlight.jsLANGUrl]: https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md
[image-sizeNPMUrl]: https://www.npmjs.com/package/image-size
-[image-sizeNPMVersionImage]: https://img.shields.io/npm/v/image-size.svg?style=flat
+[image-sizeNPMVersionImage]: https://badgen.net/npm/v/image-size?cache=86400
[image-sizeGHUrl]: https://github.com/image-size/image-size
[image-sizeDOCUrl]: https://github.com/image-size/image-size/blob/master/Readme.md
[ip-range-checkGHUrl]: https://github.com/danielcompton/ip-range-check
[ip-range-checkDOCUrl]: https://github.com/danielcompton/ip-range-check/blob/master/README.md
[ip-range-checkNPMUrl]: https://www.npmjs.com/package/ip-range-check
-[ip-range-checkNPMVersionImage]: https://img.shields.io/npm/v/ip-range-check.svg?style=flat
+[ip-range-checkNPMVersionImage]: https://badgen.net/npm/v/ip-range-check?cache=86400
[jQueryNPMUrl]: https://www.npmjs.com/package/jquery
-[jQueryNPMVersionImage]: https://img.shields.io/npm/v/jquery.svg?style=flat
+[jQueryNPMVersionImage]: https://badgen.net/npm/v/jquery?cache=86400
[jQueryGHUrl]: https://github.com/jquery/jquery
-[jQueryUrl]: http://jquery.com/
-[jQueryDOCUrl]: http://api.jquery.com/
+[jQueryUrl]: https://jquery.com/
+[jQueryDOCUrl]: https://api.jquery.com/
[js-beautifyNPMUrl]: https://www.npmjs.com/package/js-beautify
-[js-beautifyNPMVersionImage]: https://img.shields.io/npm/v/js-beautify.svg?style=flat
-[js-beautifyGHUrl]: https://github.com/beautify-web/js-beautify
-[js-beautifyDOCUrl]: https://github.com/beautify-web/js-beautify/blob/master/README.md
+[js-beautifyNPMVersionImage]: https://badgen.net/npm/v/js-beautify?cache=86400
+[js-beautifyGHUrl]: https://github.com/beautifier/js-beautify
+[js-beautifyDOCUrl]: https://github.com/beautifier/js-beautify/blob/main/README.md
[jsdomNPMUrl]: https://www.npmjs.com/package/jsdom
-[jsdomNPMVersionImage]: https://img.shields.io/npm/v/jsdom.svg?style=flat
-[jsdomGHUrl]: https://github.com/tmpvar/jsdom
-[jsdomDOCUrl]: https://github.com/tmpvar/jsdom/blob/master/README.md
+[jsdomNPMVersionImage]: https://badgen.net/npm/v/jsdom?cache=86400
+[jsdomGHUrl]: https://github.com/jsdom/jsdom
+[jsdomDOCUrl]: https://github.com/jsdom/jsdom/blob/master/README.md
[kerberosNPMUrl]: https://www.npmjs.com/package/kerberos
-[kerberosNPMVersionImage]: https://img.shields.io/npm/v/kerberos.svg?style=flat
-[kerberosGHUrl]: https://github.com/christkv/kerberos
-[kerberosDOCUrl]: https://github.com/christkv/kerberos/blob/master/README.md
+[kerberosNPMVersionImage]: https://badgen.net/npm/v/kerberos?cache=86400
+[kerberosGHUrl]: https://github.com/mongodb-js/kerberos
+[kerberosDOCUrl]: https://github.com/mongodb-js/kerberos/blob/master/README.md
[less-middlewareGHUrl]: https://github.com/emberfeather/less.js-middleware
[less-middlewareDOCUrl]: https://github.com/emberfeather/less.js-middleware/blob/master/readme.md
[less-middlewareNPMUrl]: https://www.npmjs.com/package/less-middleware
-[less-middlewareNPMVersionImage]: https://img.shields.io/npm/v/less-middleware.svg?style=flat
+[less-middlewareNPMVersionImage]: https://badgen.net/npm/v/less-middleware?cache=86400
[lessGHUrl]: https://github.com/less/less.js
-[lessDOCUrl]: http://lesscss.org/
+[lessDOCUrl]: https://lesscss.org/
[markedGHUrl]: https://github.com/markedjs/marked
[markedDOCUrl]: https://github.com/markedjs/marked/blob/master/README.md
[markedNPMUrl]: https://www.npmjs.com/package/marked
-[markedNPMVersionImage]: https://img.shields.io/npm/v/marked.svg?style=flat
+[markedNPMVersionImage]: https://badgen.net/npm/v/marked?cache=86400
+
+[marked-highlightGHUrl]: https://github.com/markedjs/marked-highlight
+[marked-highlightDOCUrl]: https://github.com/markedjs/marked-highlight/blob/main/README.md
+[marked-highlightNPMUrl]: https://www.npmjs.com/package/marked-highlight
+[marked-highlightNPMVersionImage]: https://badgen.net/npm/v/marked-highlight?cache=86400
[media-typeGHUrl]: https://github.com/lovell/media-type
[media-typeDOCUrl]: https://github.com/lovell/media-type/blob/master/README.md
[media-typeNPMUrl]: https://www.npmjs.com/package/media-type
-[media-typeNPMVersionImage]: https://img.shields.io/npm/v/media-type.svg?style=flat
+[media-typeNPMVersionImage]: https://badgen.net/npm/v/media-type?cache=86400
[method-overrideGHUrl]: https://github.com/expressjs/method-override
[method-overrideDOCUrl]: https://github.com/expressjs/method-override/blob/master/README.md
[method-overrideNPMUrl]: https://www.npmjs.com/package/method-override
-[method-overrideNPMVersionImage]: https://img.shields.io/npm/v/method-override.svg?style=flat
+[method-overrideNPMVersionImage]: https://badgen.net/npm/v/method-override?cache=86400
[mime-dbGHUrl]: https://github.com/jshttp/mime-db
[mime-dbDOCUrl]: https://github.com/jshttp/mime-db/blob/master/README.md
[mime-dbNPMUrl]: https://www.npmjs.com/package/mime-db
-[mime-dbNPMVersionImage]: https://img.shields.io/npm/v/mime-db.svg?style=flat
+[mime-dbNPMVersionImage]: https://badgen.net/npm/v/mime-db?cache=86400
[momentGHUrl]: https://github.com/moment/moment
-[momentDOCUrl]: http://momentjs.com/docs/
+[momentDOCUrl]: https://momentjs.com/docs/
[momentNPMUrl]: https://www.npmjs.com/package/moment
-[momentNPMVersionImage]: https://img.shields.io/npm/v/moment.svg?style=flat
+[momentNPMVersionImage]: https://badgen.net/npm/v/moment?cache=86400
[moment-duration-formatGHUrl]: https://github.com/jsmreese/moment-duration-format
[moment-duration-formatDOCUrl]: https://github.com/jsmreese/moment-duration-format/blob/master/README.md
[moment-duration-formatNPMUrl]: https://www.npmjs.com/package/moment-duration-format
-[moment-duration-formatNPMVersionImage]: https://img.shields.io/npm/v/moment-duration-format.svg?style=flat
+[moment-duration-formatNPMVersionImage]: https://badgen.net/npm/v/moment-duration-format?cache=86400
[mongodbGHUrl]: https://github.com/mongodb/node-mongodb-native
[mongodbDOCUrl]: https://github.com/mongodb/node-mongodb-native/blob/3.0/README.md
[mongodbNPMUrl]: https://www.npmjs.com/package/mongodb
-[mongodbNPMVersionImage]: https://img.shields.io/npm/v/mongodb.svg?style=flat
+[mongodbNPMVersionImage]: https://badgen.net/npm/v/mongodb?cache=86400
-[mongooseGHUrl]: https://github.com/LearnBoost/mongoose
-[mongooseDOCUrl]: http://mongoosejs.com
+[mongooseGHUrl]: https://github.com/Automattic/mongoose
+[mongooseDOCUrl]: https://mongoosejs.com
[mongooseNPMUrl]: https://www.npmjs.com/package/mongoose
-[mongooseNPMVersionImage]: https://img.shields.io/npm/v/mongoose.svg?style=flat
+[mongooseNPMVersionImage]: https://badgen.net/npm/v/mongoose?cache=86400
[morganGHUrl]: https://github.com/expressjs/morgan
[morganDOCUrl]: https://github.com/expressjs/morgan/blob/master/README.md
[morganNPMUrl]: https://www.npmjs.com/package/morgan
-[morganNPMVersionImage]: https://img.shields.io/npm/v/morgan.svg?style=flat
+[morganNPMVersionImage]: https://badgen.net/npm/v/morgan?cache=86400
[mu2GHUrl]: https://github.com/raycmorgan/Mu
[mu2DOCUrl]: https://github.com/raycmorgan/Mu/blob/master/README.md
[mu2NPMUrl]: https://www.npmjs.com/package/mu2
-[mu2NPMVersionImage]: https://img.shields.io/npm/v/mu2.svg?style=flat
+[mu2NPMVersionImage]: https://badgen.net/npm/v/mu2?cache=86400
[octiconsUrl]: https://octicons.github.com/
[octiconsGHUrl]: https://github.com/primer/octicons
[octiconsDOCUrl]: https://github.com/primer/octicons#install
[octiconsNPMUrl]: https://www.npmjs.com/package/octicons
-[octiconsNPMVersionImage]: https://img.shields.io/npm/v/octicons.svg?style=flat
+[octiconsNPMVersionImage]: https://badgen.net/npm/v/octicons?cache=86400
[passportGHUrl]: https://github.com/jaredhanson/passport
-[passportDOCUrl]: http://passportjs.org/
+[passportDOCUrl]: https://www.passportjs.org/
[passportNPMUrl]: https://www.npmjs.com/package/passport
-[passportNPMVersionImage]: https://img.shields.io/npm/v/passport.svg?style=flat
+[passportNPMVersionImage]: https://badgen.net/npm/v/passport?cache=86400
[passport-openid]: https://github.com/OpenUserJs/passport-openid/tree/OpenID2
-[passport-facebookGHUrl]: https://github.com/jaredhanson/passport-facebook
-[passport-facebookDOCUrl]: https://github.com/jaredhanson/passport-facebook/blob/master/README.md
-[passport-facebookNPMUrl]: https://www.npmjs.com/package/passport-facebook
-[passport-facebookNPMVersionImage]: https://img.shields.io/npm/v/passport-facebook.svg?style=flat
-
[passport-githubGHUrl]: https://github.com/jaredhanson/passport-github
[passport-githubDOCUrl]: https://github.com/jaredhanson/passport-github/blob/master/README.md
[passport-githubNPMUrl]: https://www.npmjs.com/package/passport-github
-[passport-githubNPMVersionImage]: https://img.shields.io/npm/v/passport-github.svg?style=flat
+[passport-githubNPMVersionImage]: https://badgen.net/npm/v/passport-github?cache=86400
[passport-gitlab2GHUrl]: https://github.com/fh1ch/passport-gitlab2
[passport-gitlab2DOCUrl]: https://github.com/fh1ch/passport-gitlab2/blob/master/README.md
[passport-gitlab2NPMUrl]: https://www.npmjs.com/package/passport-gitlab2
-[passport-gitlab2NPMVersionImage]: https://img.shields.io/npm/v/passport-gitlab2.svg?style=flat
+[passport-gitlab2NPMVersionImage]: https://badgen.net/npm/v/passport-gitlab2?cache=86400
[passport-google-oauth2GHUrl]: https://github.com/jaredhanson/passport-google-oauth2
[passport-google-oauth2DOCUrl]: https://github.com/jaredhanson/passport-google-oauth2/blob/master/README.md
[passport-google-oauth2NPMUrl]: https://www.npmjs.com/package/passport-google-oauth20
-[passport-google-oauth2NPMVersionImage]: https://img.shields.io/npm/v/passport-google-oauth20.svg?style=flat
+[passport-google-oauth2NPMVersionImage]: https://badgen.net/npm/v/passport-google-oauth20?cache=86400
[passport-imgurGHUrl]: https://github.com/mindfreakthemon/passport-imgur
[passport-imgurDOCUrl]: https://github.com/mindfreakthemon/passport-imgur/blob/master/README.md
[passport-imgurNPMUrl]: https://www.npmjs.com/package/passport-imgur
-[passport-imgurNPMVersionImage]: https://img.shields.io/npm/v/passport-imgur.svg?style=flat
+[passport-imgurNPMVersionImage]: https://badgen.net/npm/v/passport-imgur?cache=86400
[passport-redditGHUrl]: https://github.com/Slotos/passport-reddit
[passport-redditDOCUrl]: https://github.com/Slotos/passport-reddit/blob/master/README.md
[passport-redditNPMUrl]: https://www.npmjs.com/package/passport-reddit
-[passport-redditNPMVersionImage]: https://img.shields.io/npm/v/passport-reddit.svg?style=flat
+[passport-redditNPMVersionImage]: https://badgen.net/npm/v/passport-reddit?cache=86400
+
+[passport-reddit-commonjsGHUrl]: https://github.com/536b656c6c79/passport-reddit-commonJS
+[passport-reddit-commonjsDOCUrl]: https://github.com/536b656c6c79/passport-reddit-commonJS/blob/master/README.md
+[passport-reddit-commonjsNPMUrl]: https://www.npmjs.com/package/passport-reddit-commonjs
+[passport-reddit-commonjsNPMVersionImage]: https://badgen.net/npm/v/passport-reddit-commonjs?cache=86400
[passport-steamGHUrl]: https://github.com/liamcurry/passport-steam
[passport-steamGHOpenIDUrl]: https://github.com/OpenUserJs/passport-steam/tree/OpenID2
[passport-steamDOCUrl]: https://github.com/liamcurry/passport-steam/blob/master/README.md
[passport-steamNPMUrl]: https://www.npmjs.com/package/passport-steam
-[passport-steamNPMVersionImage]: https://img.shields.io/npm/v/passport-steam.svg?style=flat
-
-[passport-twitterGHUrl]: https://github.com/jaredhanson/passport-twitter
-[passport-twitterDOCUrl]: https://github.com/jaredhanson/passport-twitter/blob/master/README.md
-[passport-twitterNPMUrl]: https://www.npmjs.com/package/passport-twitter
-[passport-twitterNPMVersionImage]: https://img.shields.io/npm/v/passport-twitter.svg?style=flat
-
-[passport-yahooGHUrl]: https://github.com/jaredhanson/passport-yahoo
-[passport-yahooGHOpenIDUrl]: https://github.com/OpenUserJs/passport-yahoo/tree/OpenID2
-[passport-yahooDOCUrl]: https://github.com/jaredhanson/passport-yahoo/blob/master/README.md
-[passport-yahooNPMUrl]: https://www.npmjs.com/package/passport-yahoo
-[passport-yahooNPMVersionImage]: https://img.shields.io/npm/v/passport-yahoo.svg?style=flat
+[passport-steamNPMVersionImage]: https://badgen.net/npm/v/passport-steam?cache=86400
[pegjsGHUrl]: https://github.com/pegjs/pegjs
[pegjsDOCUrl]: https://github.com/pegjs/pegjs/blob/master/README.md
[pegjsNPMUrl]: https://www.npmjs.com/package/pegjs
-[pegjsNPMVersionImage]: https://img.shields.io/npm/v/pegjs.svg?style=flat
+[pegjsNPMVersionImage]: https://badgen.net/npm/v/pegjs?cache=86400
[rate-limit-mongoGHUrl]: https://github.com/2do2go/rate-limit-mongo
[rate-limit-mongoDOCUrl]: https://github.com/2do2go/rate-limit-mongo/blob/master/README.md
[rate-limit-mongoNPMUrl]: https://www.npmjs.com/package/rate-limit-mongo
-[rate-limit-mongoNPMVersionImage]: https://img.shields.io/npm/v/rate-limit-mongo.svg?style=flat
+[rate-limit-mongoNPMVersionImage]: https://badgen.net/npm/v/rate-limit-mongo?cache=86400
[remarkGHUrl]: https://github.com/remarkjs/remark
[remarkDOCUrl]: https://github.com/remarkjs/remark/blob/master/readme.md
[remarkNPMUrl]: https://www.npmjs.com/package/remark
-[remarkNPMVersionImage]: https://img.shields.io/npm/v/remark.svg?style=flat
+[remarkNPMVersionImage]: https://badgen.net/npm/v/remark?cache=86400
[remark-strip-htmlGHUrl]: https://github.com/craftzdog/remark-strip-html
[remark-strip-htmlDOCUrl]: https://github.com/craftzdog/remark-strip-html/blob/master/readme.md
[remark-strip-htmlNPMUrl]: https://www.npmjs.com/package/remark-strip-html
-[remark-strip-htmlNPMVersionImage]: https://img.shields.io/npm/v/remark-strip-html.svg?style=flat
+[remark-strip-htmlNPMVersionImage]: https://badgen.net/npm/v/remark-strip-html?cache=86400
[requestGHUrl]: https://github.com/request/request
[requestDOCUrl]: https://github.com/request/request/blob/master/README.md
[requestNPMUrl]: https://www.npmjs.com/package/request
-[requestNPMVersionImage]: https://img.shields.io/npm/v/request.svg?style=flat
+[requestNPMVersionImage]: https://badgen.net/npm/v/request?cache=86400
[rfc2047GHUrl]: https://github.com/One-com/rfc2047
[rfc2047DOCUrl]: https://github.com/One-com/rfc2047/blob/master/README.md
[rfc2047NPMUrl]: https://www.npmjs.com/package/rfc2047
-[rfc2047NPMVersionImage]: https://img.shields.io/npm/v/rfc2047.svg?style=flat
+[rfc2047NPMVersionImage]: https://badgen.net/npm/v/rfc2047?cache=86400
[s3rverGHUrl]: https://github.com/jamhall/s3rver
[s3rverDOCUrl]: https://github.com/jamhall/s3rver/blob/master/README.md
[s3rverNPMUrl]: https://www.npmjs.com/package/s3rver
-[s3rverNPMVersionImage]: https://img.shields.io/npm/v/s3rver.svg?style=flat
+[s3rverNPMVersionImage]: https://badgen.net/npm/v/s3rver?cache=86400
-[sanitize-htmlGHUrl]: https://github.com/punkave/sanitize-html
-[sanitize-htmlDOCUrl]: https://github.com/punkave/sanitize-html/blob/master/README.md
+[sanitize-htmlGHUrl]: https://github.com/apostrophecms/sanitize-html
+[sanitize-htmlDOCUrl]: https://github.com/apostrophecms/sanitize-html/blob/master/README.md
[sanitize-htmlNPMUrl]: https://www.npmjs.com/package/sanitize-html
-[sanitize-htmlNPMVersionImage]: https://img.shields.io/npm/v/sanitize-html.svg?style=flat
+[sanitize-htmlNPMVersionImage]: https://badgen.net/npm/v/sanitize-html?cache=86400
[select2GHUrl]: https://github.com/ivaynberg/select2
[select2DOCUrl]: https://select2.github.io/
[select2NPMUrl]: https://www.npmjs.com/package/select2
-[select2NPMVersionImage]: https://img.shields.io/npm/v/select2.svg?style=flat
+[select2NPMVersionImage]: https://badgen.net/npm/v/select2?cache=86400
[select2-bootstrap-cssGHUrl]: https://github.com/t0m/select2-bootstrap-css/blob/bootstrap3/select2-bootstrap.css
[select2-bootstrap-cssDOCUrl]: https://github.com/t0m/select2-bootstrap-css/blob/bootstrap3/README.md
[select2-bootstrap-cssNPMUrl]: https://www.npmjs.com/package/select2-bootstrap-css
-[select2-bootstrap-cssNPMVersionImage]: https://img.shields.io/npm/v/select2-bootstrap-css.svg?style=flat
+[select2-bootstrap-cssNPMVersionImage]: https://badgen.net/npm/v/select2-bootstrap-css?cache=86400
[select2-bootstrap-cssGHHASHUrl]: https://github.com/t0m/select2-bootstrap-css/blob/fce5f9f984b0cc6c8483ce7225ad2639f3a4dae5/select2-bootstrap.css
[serve-faviconGHUrl]: https://github.com/expressjs/serve-favicon
[serve-faviconDOCUrl]: https://github.com/expressjs/serve-favicon/blob/master/README.md
[serve-faviconNPMUrl]: https://www.npmjs.com/package/serve-favicon
-[serve-faviconNPMVersionImage]: https://img.shields.io/npm/v/serve-favicon.svg?style=flat
+[serve-faviconNPMVersionImage]: https://badgen.net/npm/v/serve-favicon?cache=86400
-[spdx-license-idsGHUrl]: https://github.com/shinnn/spdx-license-ids
-[spdx-license-idsDOCUrl]: https://github.com/shinnn/spdx-license-ids/blob/master/README.md
+[spdx-license-idsGHUrl]: https://github.com/jslicense/spdx-license-ids
+[spdx-license-idsDOCUrl]: https://github.com/jslicense/spdx-license-ids/blob/master/README.md
[spdx-license-idsNPMUrl]: https://www.npmjs.com/package/spdx-license-ids
-[spdx-license-idsNPMVersionImage]: https://img.shields.io/npm/v/spdx-license-ids.svg?style=flat
+[spdx-license-idsNPMVersionImage]: https://badgen.net/npm/v/spdx-license-ids?cache=86400
[strip-markdownGHUrl]: https://github.com/remarkjs/strip-markdown
[strip-markdownDOCUrl]: https://github.com/remarkjs/strip-markdown/blob/master/readme.md
[strip-markdownNPMUrl]: https://www.npmjs.com/package/strip-markdown
-[strip-markdownNPMVersionImage]: https://img.shields.io/npm/v/strip-markdown.svg?style=flat
+[strip-markdownNPMVersionImage]: https://badgen.net/npm/v/strip-markdown?cache=86400
[terserGHUrl]: https://github.com/terser/terser
[terserDOCUrl]: https://github.com/terser/terser/blob/master/README.md
[terserNPMUrl]: https://www.npmjs.com/package/terser
-[terserNPMVersionImage]: https://img.shields.io/npm/v/terser.svg?style=flat
+[terserNPMVersionImage]: https://badgen.net/npm/v/terser?cache=86400
[toobusy-jsGHUrl]: https://github.com/STRML/node-toobusy
[toobusy-jsGHUrlHarmonyUrl]: https://github.com/OpenUserJs/node-toobusy/tree/harmony
[toobusy-jsDOCUrl]: https://github.com/STRML/node-toobusy/blob/master/README.md
[toobusy-jsNPMUrl]: https://npmjs.com/package/toobusy-js
-[toobusy-jsNPMVersionImage]: https://img.shields.io/npm/v/toobusy-js.svg?style=flat
+[toobusy-jsNPMVersionImage]: https://badgen.net/npm/v/toobusy-js?cache=86400
[underscoreGHUrl]: https://github.com/jashkenas/underscore
-[underscoreDOCUrl]: http://underscorejs.org/
+[underscoreDOCUrl]: https://underscorejs.org/
+[underscoreDOCCLUrl]: https://underscorejs.org/#changelog
[underscoreNPMUrl]: https://www.npmjs.com/package/underscore
-[underscoreNPMVersionImage]: https://img.shields.io/npm/v/underscore.svg?style=flat
+[underscoreNPMVersionImage]: https://badgen.net/npm/v/underscore?cache=86400
[useragentGHUrl]: https://github.com/3rd-Eden/useragent
[useragentDOCUrl]: https://github.com/3rd-Eden/useragent/blob/master/README.md
[useragentNPMUrl]: https://www.npmjs.com/package/useragent
-[useragentNPMVersionImage]: https://img.shields.io/npm/v/useragent.svg?style=flat
+[useragentNPMVersionImage]: https://badgen.net/npm/v/useragent?cache=86400
[bootswatchGHUrl]: https://github.com/thomaspark/bootswatch/blob/gh-pages/custom/bootstrap.css
-[bootswatchREPOUrl]: http://bootswatch.com
+[bootswatchREPOUrl]: https://bootswatch.com
[bootswatchNPMUrl]: https://www.npmjs.com/package/bootswatch
-[bootswatchNPMVersionImage]: https://img.shields.io/npm/v/bootswatch.svg?style=flat
+[bootswatchNPMVersionImage]: https://badgen.net/npm/v/bootswatch?cache=86400
[bootswatchDOCUrl]: https://github.com/thomaspark/bootswatch/blob/gh-pages/README.md
-[bootswatchBSUrl]: http://bootswatch.com/bower_components/bootstrap/dist/css/bootstrap.css
[normalizeGHUrl]: https://github.com/necolas/normalize.css
-[normalizeDOCUrl]: http://git.io/normalize
+[normalizeDOCUrl]: https://github.com/necolas/normalize.css/blob/master/README.md
[squadaOneGHUrl]: https://github.com/google/fonts/tree/master/ofl/squadaone
[squadaOneREPOUrl]: https://www.google.com/fonts/specimen/Squada+One
[squadaOneDOCUrl]: https://github.com/google/fonts/blob/master/README.md
[squadaOneGHUrlRecent]: https://github.com/google/fonts/blob/master/ofl/squadaone/SquadaOne-Regular.ttf
+[auth-oauth-appGHUrl]: https://github.com/octokit/auth-oauth-app.js
+[auth-oauth-appDOCUrl]: https://github.com/octokit/auth-oauth-app.js/blob/master/README.md
+[auth-oauth-appNPMUrl]: https://www.npmjs.com/package/@octokit/auth-oauth-app
+[auth-oauth-appNPMVersionImage]: https://badgen.net/npm/v/@octokit/auth-oauth-app?cache=86400
+
+[githubGHUrl]: https://github.com/octokit/rest.js
+[githubDOCUrl]: https://github.com/octokit/rest.js/blob/master/README.md
+[githubNPMUrl]: https://www.npmjs.com/package/@octokit/rest
+[githubNPMVersionImage]: https://badgen.net/npm/v/@octokit/rest?cache=86400
[styleguide]: STYLEGUIDE.md
[contributing]: .github/CONTRIBUTING.md
-[gaCFGUrl]: https://www.google.com/analytics/
-[gaDOCUrl]: https://developers.google.com/analytics/devguides/collection/analyticsjs/advanced
-[gaCDNUrl]: //www.google-analytics.com/analytics.js
-
[oauthLogo]: https://raw.githubusercontent.com/wiki/OpenUserJS/OpenUserJS.org/images/oauth.png "OAuth"
[oauth1Logo]: https://raw.githubusercontent.com/wiki/OpenUserJS/OpenUserJS.org/images/oauth1.png "OAuth1"
[oauth2Logo]: https://raw.githubusercontent.com/wiki/OpenUserJS/OpenUserJS.org/images/oauth2.png "OAuth2"
diff --git a/app.js b/app.js
index ad74394b1..7dcb11691 100755
--- a/app.js
+++ b/app.js
@@ -13,15 +13,23 @@ var isPro = require('./libs/debug').isPro;
var isDev = require('./libs/debug').isDev;
var isDbg = require('./libs/debug').isDbg;
+var uaOUJS = require('./libs/debug').uaOUJS;
+
var isSecured = require('./libs/debug').isSecured;
var privkey = require('./libs/debug').privkey;
var fullchain = require('./libs/debug').fullchain;
var chain = require('./libs/debug').chain;
+var isRenewable = require('./libs/debug').isRenewable;
//
var path = require('path');
var crypto = require('crypto');
+var events = require('events');
+events.EventEmitter.defaultMaxListeners = 15;
+
+var ensureIntegerOrNull = require('./libs/helpers').ensureIntegerOrNull;
+
var express = require('express');
var toobusy = require('toobusy-js');
var statusCodePage = require('./libs/templateHelpers').statusCodePage;
@@ -38,7 +46,7 @@ var Terser = require('terser');
var lessMiddleware = require('less-middleware');
var session = require('express-session');
-var MongoStore = require('connect-mongo')(session);
+var MongoStore = require('connect-mongo');
var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
@@ -52,6 +60,7 @@ var pingCertTimer = null;
var ttlSanityTimer = null;
var app = express();
+app.disable('x-powered-by');
var modifySessions = require('./libs/modifySessions');
@@ -66,7 +75,7 @@ var _ = require('underscore');
var findSessionData = require('./libs/modifySessions').findSessionData;
var dbOptions = {};
-var defaultPoolSize = 10;
+var defaultPoolSize = ensureIntegerOrNull(process.env.CONNECT_POOL_SIZE) || 100; // Current *mongoose* default
if (isPro) {
dbOptions = {
poolSize: defaultPoolSize,
@@ -90,6 +99,7 @@ if (isPro) {
}
var fs = require('fs');
+var execSync = require('child_process').execSync;
var http = require('http');
var https = require('https');
var sslOptions = null;
@@ -100,11 +110,20 @@ app.set('port', process.env.PORT || 8080);
app.set('securePort', process.env.SECURE_PORT || null);
// Connect to the database
-mongoose.connect(connectStr, dbOptions);
+var clientP = mongoose.connect(connectStr, dbOptions).then(
+ function (aMongoose) {
+ return aMongoose.connection.getClient();
+ }
+);
// Trap a few events for MongoDB
-db.on('error', function () {
- console.error(colors.red('MongoDB connection error'));
+db.on('error', function (aErr) {
+ console.error( colors.red( [
+ 'MongoDB connection error',
+ aErr.message,
+ 'Terminating app'
+ ].join('\n')));
+ process.exit(1); // NOTE: Watchpoint
});
db.once('open', function () {
@@ -171,14 +190,13 @@ process.on('SIGINT', function () {
process.exit(0);
});
-var sessionStore = new MongoStore({
- mongooseConnection: db,
+var sessionStore = MongoStore.create({
+ clientPromise: clientP,
autoRemove: 'native',
ttl: settings.ttl.timerSanity * 60 // sec to min; 14 * 24 * 60 * 60 = 14 days. Default
});
// See https://hacks.mozilla.org/2013/01/building-a-node-js-server-that-wont-melt-a-node-js-holiday-season-part-5/
-var ensureIntegerOrNull = require('./libs/helpers').ensureIntegerOrNull;
var isSameOrigin = require('./libs/helpers').isSameOrigin;
var maxLag = ensureIntegerOrNull(process.env.BUSY_MAXLAG) || 70;
@@ -198,6 +216,8 @@ var maxMem = ensureIntegerOrNull(process.env.BUSY_MAXMEM) || 50; // 50% default
var forceBusyAbsolute = process.env.FORCE_BUSY_ABSOLUTE === 'true';
var forceBusy = process.env.FORCE_BUSY === 'true';
+var forceBusyMessage = process.env.FORCE_BUSY_MESSAGE
+ || 'We are experiencing technical difficulties right now.';
app.use(function (aReq, aRes, aNext) {
var pathname = aReq._parsedUrl.pathname;
@@ -210,12 +230,37 @@ app.use(function (aReq, aRes, aNext) {
aRes.oujsOptions = {};
}
- // Middleware for DNT
- aRes.oujsOptions.DNT = aReq.get('DNT') === '1' || aReq.get('DNT') === 'yes' ? true : false;
-
// Middleware for GDPR Notice
aRes.oujsOptions.hideReminderGDPR = isSameOrigin(referer).result;
+ // Middleware for Notices
+ if (aReq._parsedUrl && aReq._parsedUrl.query) {
+ // NOTE: Keep in sync with muExpress.js, auth.js, user.js, and headerReminders.html
+ if (/^\/login$/.test(pathname)) {
+ // Middleware for Auth Notices
+ aRes.oujsOptions.showInvalidAuth = aReq.query.invalidauth === '';
+ aRes.oujsOptions.showStratFail = aReq.query.stratfail === '';
+ aRes.oujsOptions.showNoConsent = aReq.query.noconsent === '';
+ aRes.oujsOptions.showNoName = aReq.query.noname === '';
+ aRes.oujsOptions.showTooLong = aReq.query.toolong === '';
+ aRes.oujsOptions.showUsernameFail = aReq.query.usernamefail === '';
+ aRes.oujsOptions.showROAuth = aReq.query.roauth === '';
+ aRes.oujsOptions.showRetryAuth = aReq.query.retryauth === '';
+ aRes.oujsOptions.showAuthFail = aReq.query.authfail === '';
+ }
+
+ // NOTE: Keep in sync with muExpress, user.js and headerReminders.html
+ if (/^(?:\/admin\/session\/active|\/user\/preferences)$/.test(pathname)) {
+ // Middleware for Session Notices
+ aRes.oujsOptions.showSesssionNoExtend = aReq.query.noextend === '';
+ aRes.oujsOptions.showSessionMissingUsername = aReq.query.noname === '';
+ aRes.oujsOptions.showSesssionCurrentSessionProhibited = aReq.query.curses === '';
+ aRes.oujsOptions.showSesssionHigherRankProhibited = aReq.query.hirank === '';
+ aRes.oujsOptions.showSesssionNoOwned = aReq.query.noown === '';
+ aRes.oujsOptions.showSesssionNoAdmin = aReq.query.noadmin === '';
+ }
+ }
+
//
if (
/^\/favicon\.ico$/.test(pathname) ||
@@ -244,7 +289,7 @@ app.use(function (aReq, aRes, aNext) {
statusCodePage(aReq, aRes, aNext, {
statusCode: 503,
statusMessage:
- 'We are experiencing technical difficulties right now. Please try again later.'
+ forceBusyMessage + ' Please try again later.'
});
return;
@@ -259,7 +304,7 @@ app.use(function (aReq, aRes, aNext) {
if (usedMem > maxMem) {
statusCodePage(aReq, aRes, aNext, {
statusCode: 503,
- statusMessage: 'We are very busy right now\u2026 Please try again later.'
+ statusMessage: 'We are very busy right now \u2026 Please try again later.'
});
return;
}
@@ -273,7 +318,7 @@ app.use(function (aReq, aRes, aNext) {
return;
} else {
aNext(); // not toobusy
- // fallthrough
+ // fallsthrough
}
}
});
@@ -331,27 +376,30 @@ if (isSecured) {
secureServer.listen(app.get('securePort'));
} catch (aE) {
- console.error(colors.red('Server is NOT secured. Certificates may already be expired'));
+ console.error(colors.red('Server is NOT secured. Certificate may already be expired'));
isSecured = false;
- console.warn(colors.cyan('Attempting to rename certificates'));
+ console.warn(colors.cyan('Attempting to rename errored certificate'));
try {
- fs.renameSync(privkey, privkey + '.expired')
- fs.renameSync(fullchain, fullchain + '.expired');
- fs.renameSync(chain, chain + '.expired');
+ fs.renameSync(privkey, privkey + '.error')
+ fs.renameSync(fullchain, fullchain + '.error');
+ fs.renameSync(chain, chain + '.error');
- console.warn(colors.green('Certificates renamed'));
+ console.warn(colors.green('Errored certificate renamed'));
// NOTE: Cached modules and/or callbacks may not reflect this change immediately
// so must conclude with server trip
} catch (aE) {
- console.warn(colors.red('Error renaming certificates'));
+ console.warn(colors.red('Error renaming errored certificate'));
}
- // Trip the server now to try any alternate fallback certificates
- // If there aren't any it should run in http mode however usually no access through web
+ // Trip the server now to try any alternate fallback certificate or updated
+ // If there aren't any it should run in http mode however usually no easy access through web
// This should prevent logging DoS
+ // NOTE: Pro will always stay in unsecure for `setInterval` value plus
+ // ~ n seconds (hours) until renewable check if available.
+
beforeExit(); // NOTE: Event not triggered for direct `process.exit()`
process.exit(1);
@@ -364,7 +412,15 @@ server.listen(app.get('port'));
if (isDev || isDbg) {
app.use(morgan('dev'));
} else if (process.env.FORCE_MORGAN_PREDEF_FORMAT) {
- app.use(morgan(process.env.FORCE_MORGAN_PREDEF_FORMAT));
+ app.use(morgan(process.env.FORCE_MORGAN_PREDEF_FORMAT, {
+ skip: function (aReq, aRes) {
+ if (process.env.FORCE_MORGAN_PREDEF_FORMAT_SKIP === 'true') {
+ return (aRes.statusCode >= 400 && aRes.statusCode <= 499 || aRes.statusCode === 304);
+ } else {
+ return false;
+ }
+ }
+ }));
}
app.use(bodyParser.urlencoded({
@@ -391,16 +447,16 @@ app.use(session({
saveUninitialized: false,
unset: 'destroy',
cookie: {
- maxAge: 5 * 60 * 1000, // minutes in ms NOTE: Expanded after successful auth
+ maxAge: 2 * 60 * 1000, // minutes in ms NOTE: Expanded after successful auth
secure: (isSecured ? true : false),
- sameSite: 'lax' // NOTE: Current auth necessity
+ sameSite: 'strict'
},
rolling: true,
secret: sessionSecret,
store: sessionStore
}));
app.use(function (aReq, aRes, aNext) {
- if (aReq.session[passport._key]) {
+ if (aReq.session && aReq.session[passport._key]) {
// load data from existing session
aReq._passport.session = aReq.session[passport._key];
}
@@ -453,7 +509,8 @@ app.use(function(aReq, aRes, aNext) {
/^\/mod\/removed\//.test(pathname)
) {
aRes.minifyOptions = aRes.minifyOptions || {}; // Ensure object exists on response
- aRes.minifyOptions.minify = false; // Skip using release minification because we control this with *terser*
+ aRes.minifyOptions.minify = false; // Skip minification because we use *terser* with .js
+ aRes.minifyOptions.enabled = false; // Force no processing on remainder of types on these routes
}
aNext();
});
@@ -472,39 +529,56 @@ require('./routes')(app);
// Timers
-function tripServerOnCertExpire(aValidToString) {
+function tripServerOnCertExpire(aValidToString, aStayResident) {
var tlsDate = new Date(aValidToString);
- var nowDate = new Date();
+ var now = new Date();
+ var trippy = false;
var tripDate = new Date(tlsDate.getTime() - (2 * 60 * 60 * 1000)); // ~2 hours before fault
- if (nowDate.getTime() >= tripDate.getTime()) {
- console.error(colors.red('Certificates expiring very soon. Tripping server to unsecure mode'));
+ console.log('Validating certificate');
- isSecured = false;
+ if (now.getTime() >= tripDate.getTime()) {
+ console.error(colors.red('Valid secure certificate not available.'));
- console.warn(colors.cyan('Attempting to rename certificates'));
- try {
- fs.renameSync(privkey, privkey + '.expiring')
- fs.renameSync(fullchain, fullchain + '.expiring');
- fs.renameSync(chain, chain + '.expiring');
- console.log(colors.green('Certificates renamed'));
+ if (!isRenewable && isSecured) {
+ console.warn(colors.cyan('Attempting to rename expiring certificate'));
+ try {
+ fs.renameSync(privkey, privkey + '.expiry');
+ fs.renameSync(fullchain, fullchain + '.expiry');
+ fs.renameSync(chain, chain + '.expiry');
- // NOTE: Cached modules and/or callbacks may not reflect this change immediately
- // so must conclude with server trip
+ console.log(colors.green('Expiring certificate renamed'));
+ trippy = true;
- } catch (aE) {
- console.warn(colors.red('Error renaming certificates'));
+ // NOTE: Cached modules and/or callbacks may not reflect this change immediately
+ // so must conclude with server trip
+
+ } catch (aE) {
+ console.error(colors.red('Error renaming expiring certificate ' + aE.code));
+ }
+ } else if (isSecured) {
+ console.warn(colors.cyan('Attempting to renew expiring certificate'));
+
+ try {
+ execSync(process.env.ATTEMPT_RENEWAL); // NOTE: Synchronous wait
+ trippy = true;
+
+ } catch (aE) {
+ console.warn(colors.red('Error renewing expiring certificate ' + aE.code));
+ }
}
- // Trip the server now to try any alternate fallback certificates
- // If there aren't any it should run in http mode however usually no access through web
- // This should prevent logging DoS
+ if (trippy && !aStayResident) {
+ // Trip the server now to try any alternate fallback certificate or updated
+ // If there aren't any it should run in http mode however usually no easy access through web
+ // This should prevent logging DoS
- beforeExit(); // NOTE: Event not triggered for direct `process.exit()`
+ beforeExit(); // NOTE: Event not triggered for direct `process.exit()`
- process.exit(1);
+ process.exit(1);
+ }
}
}
@@ -516,7 +590,10 @@ function pingCert() {
(isPro && app.get('securePort')
? ':' + app.get('securePort')
: ':' + app.get('port'))
- + '/api'
+ + '/api',
+ headers: {
+ 'User-Agent': uaOUJS + (process.env.UA_SECRET ? ' ' + process.env.UA_SECRET : '')
+ }
}, function (aErr, aRes, aBody) {
if (aErr) {
if (aErr.cert) {
@@ -525,15 +602,22 @@ function pingCert() {
// browsers as well as false credentials supplied
// Test for time limit of expiration
- tripServerOnCertExpire(aErr.cert.valid_to);
+ tripServerOnCertExpire(new Date(aErr.cert.valid_to).toUTCString()); // NOTE: Strong coercion
} else {
console.warn([
- colors.red(aErr),
- colors.red('Server may not be running on specified port or port blocked by firewall'),
- colors.red('Encryption not available')
+ colors.red(aErr.code),
+ colors.red('Server may not be running on specified port or port blocked by firewall.'),
+ colors.red('Encryption may not be available.')
].join('\n'));
+
+ if (isDev && aErr.code === 'EPROTO') {
+ tripServerOnCertExpire(new Date().toUTCString(), true); // NOTE: Strong coercion
+ } else {
+ tripServerOnCertExpire(new Date().toUTCString()); // NOTE: Strong coercion
+ }
+
}
return;
}
@@ -544,23 +628,26 @@ function pingCert() {
console.warn(colors.red('Firewall pass-through detected'));
// Test for time limit of expiration
- tripServerOnCertExpire(aRes.req.connection.getPeerCertificate().valid_to);
+ tripServerOnCertExpire(
+ new Date(aRes.req.connection.getPeerCertificate().valid_to).toUTCString()); // NOTE: Strong coercion
} else {
- console.warn(colors.yellow('Encryption not available'));
+ console.warn(colors.yellow('Encryption not available.'));
}
});
};
-if (isSecured) {
- pingCertTimer = setInterval(pingCert, 60 * 60 * 1000); // NOTE: Check every hour
-}
+// Re-test cert at init
+pingCert();
+
+// Re-test cert at interval
+pingCertTimer = setInterval(pingCert, (isPro ? 60 * 60 : 1 * 15) * 1000); // NOTE: Check about every hour for pro
function ttlSanity() {
var options = {};
findSessionData({}, sessionStore, options, function (aErr) {
if (aErr) {
- console.error('some error during ttlSanity', aErr);
+ console.error('Some error during ttlSanity', aErr);
return;
}
diff --git a/controllers/admin.js b/controllers/admin.js
index e060c2246..cbad96492 100644
--- a/controllers/admin.js
+++ b/controllers/admin.js
@@ -34,6 +34,7 @@ var Vote = require('../models/vote').Vote;
var modelParser = require('../libs/modelParser');
var nil = require('../libs/helpers').nil;
+var baseOrigin = require('../libs/helpers').baseOrigin;
var loadPassport = require('../libs/passportLoader').loadPassport;
var strategyInstances = require('../libs/passportLoader').strategyInstances;
@@ -377,6 +378,8 @@ exports.adminSessionActiveView = function (aReq, aRes, aNext) {
var store = aReq.sessionStore;
+ var thisURL = null;
+
// Session
options.authedUser = authedUser = modelParser.parseUser(authedUser);
options.isMod = authedUser && authedUser.isMod;
@@ -396,6 +399,15 @@ exports.adminSessionActiveView = function (aReq, aRes, aNext) {
username = aReq.query.q;
+ // redirectTo (forced)
+ thisURL = new URL(aReq.url, baseOrigin);
+ ['noname', 'curses', 'hirank', 'noown', 'noadmin', 'noextend']
+ .forEach(function (aE, aI, aA) {
+ thisURL.searchParams.delete(aE);
+ }
+ );
+ options.redirectToo = thisURL.pathname + (thisURL.search ? thisURL.search : '');
+
// Page metadata
pageMetadata(options, ['Sessions', 'Admin']);
diff --git a/controllers/auth.js b/controllers/auth.js
index 90e407a01..6ad16070b 100644
--- a/controllers/auth.js
+++ b/controllers/auth.js
@@ -8,8 +8,8 @@ var isDbg = require('../libs/debug').isDbg;
//
//--- Dependency inclusions
+var moment = require('moment');
var passport = require('passport');
-var url = require('url');
var colors = require('ansi-colors');
//--- Model inclusions
@@ -25,12 +25,17 @@ var loadPassport = require('../libs/passportLoader').loadPassport;
var strategyInstances = require('../libs/passportLoader').strategyInstances;
var verifyPassport = require('../libs/passportVerify').verify;
var cleanFilename = require('../libs/helpers').cleanFilename;
+var getRedirect = require('../libs/helpers').getRedirect;
+var isSameOrigin = require('../libs/helpers').isSameOrigin;
var addSession = require('../libs/modifySessions').add;
var expandSession = require('../libs/modifySessions').expand;
var statusCodePage = require('../libs/templateHelpers').statusCodePage;
+var modelParser = require('../libs/modelParser');
+
//--- Configuration inclusions
var allStrategies = require('./strategies.json');
+var settings = require('../models/settings.json');
//---
@@ -42,7 +47,13 @@ passport.serializeUser(function (aUser, aDone) {
// Setup all the auth strategies
var openIdStrategies = {};
Strategy.find({}, function (aErr, aStrategies) {
- // WARNING: No err handling
+ if (aErr) {
+ // Some possible catastrophic error
+ console.error(colors.red(aErr));
+
+ process.exit(1);
+ return;
+ }
// Get OpenId strategies
for (var name in allStrategies) {
@@ -58,20 +69,101 @@ Strategy.find({}, function (aErr, aStrategies) {
});
});
-// Get the referer url for redirect after login/logout
-// WARNING: Also found in `./controller/index.js`
-function getRedirect(aReq) {
- var referer = aReq.get('Referer');
- var redirect = '/';
+exports.preauth = function (aReq, aRes, aNext) {
+ var authedUser = aReq.session.user;
+
+ var username = aReq.body.username;
+ var userauth = aReq.body.auth;
+ var SITEKEY = process.env.HCAPTCHA_SITE_KEY;
+
+ if (!authedUser) {
+ if (!username) {
+ aRes.redirect('/login?noname');
+ return;
+ }
+ // Clean the username of leading and trailing whitespace,
+ // and other stuff that is unsafe in a url
+ username = cleanFilename(username.replace(/^\s+|\s+$/g, ''));
- if (referer) {
- referer = url.parse(referer);
- if (referer.hostname === aReq.hostname) {
- redirect = referer.path;
+ // The username could be empty after the replacements
+ if (!username) {
+ aRes.redirect('/login?noname');
+ return;
}
+
+ if (username.length > 64) {
+ aRes.redirect('/login?toolong');
+ return;
+ }
+
+ User.findOne({ name: { $regex: new RegExp('^' + username + '$', 'i') } },
+ function (aErr, aUser) {
+ var user = null;
+
+ if (aErr) {
+ console.error('Authfail with no User found of', username, aErr);
+ aRes.redirect('/login?usernamefail');
+ return;
+ }
+
+ if (aUser) {
+ user = modelParser.parseUser(aUser);
+
+ // Ensure that casing is identical so we still have it, correctly, when they
+ // get back from authentication
+ aReq.body.username = user.name;
+
+ if (userauth) {
+ aReq.body.userauth = userauth;
+ } else {
+ aReq.body.userauth = user.userStrategies[user.userStrategies.length - 1];
+ }
+ aReq.userrole = user.roleName;
+
+ if (!user._probationary) {
+ // Skip captcha for well known individual
+ aReq.wellKnownUser = true;
+
+ exports.auth(aReq, aRes, aNext);
+ } else {
+ // Validate captcha for lesser known individual
+ if (!SITEKEY) {
+ // Skip captcha for not implemented
+ exports.auth(aReq, aRes, aNext);
+ } else {
+ aNext();
+ }
+ }
+ } else {
+ // Match cleansed name and this is the casing they have chosen
+ aReq.body.username = username;
+
+ aReq.body.userauth = userauth;
+ aReq.newUser = true;
+
+ // Validate captcha for unknown individual
+ if (!SITEKEY) {
+ // Skip captcha for not implemented
+ exports.auth(aReq, aRes, aNext);
+ } else {
+ aNext();
+ }
+ }
+ });
+ } else {
+ // Skip captcha for already logged in
+ exports.auth(aReq, aRes, aNext);
}
- return redirect;
+};
+
+exports.errauth = function (aErr, aReq, aRes, aNext) {
+ if (aErr) {
+ console.error(aErr.status, aErr.message);
+ aRes.redirect(302, '/login?authfail');
+ } else {
+ aNext();
+ }
}
exports.auth = function (aReq, aRes, aNext) {
@@ -95,7 +187,16 @@ exports.auth = function (aReq, aRes, aNext) {
if (aReq.session.cookie.sameSite !== 'lax') {
aReq.session.cookie.sameSite = 'lax';
aReq.session.save(function (aErr) {
- // WARNING: No err handling
+ if (aErr) {
+ // Some possible catastrophic error
+ console.error(colors.red(aErr));
+
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: 'Save Session failed.'
+ });
+ return;
+ }
authenticate(aReq, aRes, aNext);
});
@@ -104,108 +205,151 @@ exports.auth = function (aReq, aRes, aNext) {
}
}
+ function sessionauth() {
+ var redirectTo = null;
+ var captchaToken = aReq.body['g-captcha-response'] ?? aReq.body['h-captcha-response'];
+
+ // Yet another passport hack.
+ // Initialize the passport session data only when we need it. i.e. late binding
+ if (!aReq.session[passportKey] && aReq._passport.session) {
+ aReq.session[passportKey] = {};
+ aReq._passport.session = aReq.session[passportKey];
+ }
+
+ // Validate and save redirect url from the form submission on the session
+ redirectTo = isSameOrigin(aReq.body.redirectTo || getRedirect(aReq));
+ if (redirectTo.result) {
+ aReq.session.redirectTo = redirectTo.URL.pathname;
+ } else {
+ delete aReq.body.redirectTo;
+ aReq.session.redirectTo = '/';
+ }
+
+ // Save the known statuses of the user on the session and remove
+ aReq.session.userauth = aReq.body.userauth;
+ aReq.session.userrole = aReq.userrole;
+ aReq.session.wellKnownUser = aReq.wellKnownUser;
+ aReq.session.newUser = aReq.newUser;
+ delete aReq.userrole;
+ delete aReq.wellKnownUser;
+ delete aReq.newUser;
+
+ // Save the token from the captcha on the session and remove from body
+ if (captchaToken) {
+ aReq.session.captchaToken = captchaToken;
+ aReq.session.captchaSuccess = aReq.hcaptcha;
+
+ delete aReq.body['g-captcha-response'];
+ delete aReq.body['h-captcha-response'];
+ delete aReq.hcaptcha;
+ }
+ }
+
+ function anteauth() {
+ // Store the useragent always so we still have it when they
+ // get back from authentication and/or attaching
+ aReq.session.useragent = aReq.get('user-agent');
+
+ User.findOne({ name: username },
+ function (aErr, aUser) {
+ var strategies = null;
+ var strat = null;
+
+ if (aErr) { // NOTE: Possible DB error
+ console.error('Authfail with no User found of', username, aErr);
+ aRes.redirect('/login?usernamefail');
+ return;
+ }
+
+ if (aUser) {
+ strategies = aUser.strategies;
+ strat = strategies.pop();
+
+ if (aReq.session.newstrategy) { // authenticate with a new strategy
+ strategy = aReq.session.newstrategy;
+ } else if (!strategy) { // use an existing strategy
+ strategy = strat;
+ } else if (strategies.indexOf(strategy) === -1) {
+ // add a new strategy but first authenticate with existing strategy
+ aReq.session.newstrategy = strategy;
+ strategy = strat;
+ } // else {
+ // use the strategy that was given in the POST
+ // }
+ }
+
+ if (!strategy) {
+ aRes.redirect('/login?stratfail');
+ return;
+ } else {
+ auth();
+ return;
+ }
+ }
+ );
+ }
+
var authedUser = aReq.session.user;
var consent = aReq.body.consent;
var strategy = aReq.body.auth || aReq.params.strategy;
- var username = aReq.body.username || aReq.session.username ||
- (authedUser ? authedUser.name : null);
+ var username = null;
var authOpts = { failureRedirect: '/login?stratfail' };
var passportKey = aReq._passport.instance._key;
- // Yet another passport hack.
- // Initialize the passport session data only when we need it.
- if (!aReq.session[passportKey] && aReq._passport.session) {
- aReq.session[passportKey] = {};
- aReq._passport.session = aReq.session[passportKey];
- }
+ if (!authedUser) {
+ // Already validated username
+ username = aReq.body.username;
- // Save redirect url from the form submission on the session
- aReq.session.redirectTo = aReq.body.redirectTo || getRedirect(aReq);
-
- // Allow a logged in user to add a new strategy
- if (strategy && authedUser) {
- aReq.session.passport.oujsOptions.authAttach = true;
- aReq.session.newstrategy = strategy;
- aReq.session.username = authedUser.name;
- } else if (authedUser) {
- aRes.redirect(aReq.session.redirectTo || '/');
- delete aReq.session.redirectTo;
- return;
- } else if (consent !== 'true') {
- aRes.redirect('/login?noconsent');
- return;
- }
+ if (consent !== 'true') {
+ aRes.redirect('/login?noconsent');
+ return;
+ }
- if (!username) {
- aRes.redirect('/login?noname');
- return;
- }
- // Clean the username of leading and trailing whitespace,
- // and other stuff that is unsafe in a url
- username = cleanFilename(username.replace(/^\s+|\s+$/g, ''));
+ sessionauth();
- // The username could be empty after the replacements
- if (!username) {
- aRes.redirect('/login?noname');
- return;
- }
-
- // Store the username in the session so we still have it when they
- // get back from authentication
- if (!aReq.session.username) {
+ // Store the username always so we still have it when they
+ // get back from authentication
aReq.session.username = username;
- }
- // Store the useragent always so we still have it when they
- // get back from authentication and attaching
- aReq.session.useragent = aReq.get('user-agent');
- User.findOne({ name: { $regex: new RegExp('^' + username + '$', 'i') } },
- function (aErr, aUser) {
- var strategies = null;
- var strat = null;
+ anteauth();
- if (aErr) {
- console.error('Authfail with no User found of', username, aErr);
- aRes.redirect('/login?usernamefail');
- return;
- }
+ } else {
+ // Already validated username
+ username = aReq.session.username || (authedUser ? authedUser.name : null);
- if (aUser) {
- // Ensure that casing is identical so we still have it, correctly, when they
- // get back from authentication
- if (aUser.name !== username) {
- aReq.session.username = aUser.name;
- }
- strategies = aUser.strategies;
- strat = strategies.pop();
-
- if (aReq.session.newstrategy) { // authenticate with a new strategy
- strategy = aReq.session.newstrategy;
- } else if (!strategy) { // use an existing strategy
- strategy = strat;
- } else if (strategies.indexOf(strategy) === -1) {
- // add a new strategy but first authenticate with existing strategy
- aReq.session.newstrategy = strategy;
- strategy = strat;
- } // else use the strategy that was given in the POST
- }
+ sessionauth();
- if (!strategy) {
- aRes.redirect('/login');
- return;
- } else {
- auth();
- return;
- }
- });
+ // Allow a logged in user to add a new strategy
+ if (strategy) {
+ aReq.session.passport.oujsOptions.authAttach = true;
+ aReq.session.newstrategy = strategy;
+ aReq.session.username = authedUser.name;
+ } else {
+ aRes.redirect(aReq.session.redirectTo || '/');
+ delete aReq.session.redirectTo;
+ return;
+ }
+
+ anteauth();
+ }
};
exports.callback = function (aReq, aRes, aNext) {
var strategy = aReq.params.strategy;
var username = aReq.session.username;
+ var wellKnownUser = aReq.session.wellKnownUser;
var newstrategy = aReq.session.newstrategy;
+ var captchaToken = aReq.session.captchaToken;
+ var captchaSuccess = aReq.session.captchaSuccess;
+
var strategyInstance = null;
var doneUri = aReq.session.user ? '/user/preferences' : '/';
+ var SITEKEY = process.env.HCAPTCHA_SITE_KEY;
+
+ if (SITEKEY && !wellKnownUser && !captchaToken && !captchaSuccess) {
+ aRes.redirect('/login?authfail');
+ return;
+ }
// The callback was called improperly or sesssion expired
if (!strategy || !username) {
@@ -280,6 +424,14 @@ exports.callback = function (aReq, aRes, aNext) {
}
aReq.logIn(aUser, function (aErr) {
+ var now = null;
+ var lastAuthed = null;
+
+ var fudgeMin = settings.fudgeMin;
+ var fudgeSec = settings.fudgeSec;
+
+ var waitAuthCapMin = isDev ? settings.waitAuthCapMin.dev: settings.waitAuthCapMin.pro;
+
if (aErr) {
console.error('Not logged in');
console.error(aErr);
@@ -292,8 +444,61 @@ exports.callback = function (aReq, aRes, aNext) {
}
// Show a console notice that successfully logged in
- if (isDev || isDbg) {
- console.log(colors.green('Logged in'));
+ now = new Date();
+
+ console.log(
+ colors.green('Logged in'),
+ aUser.name,
+ colors.green('at'),
+ aReq.connection.remoteAddress,
+ colors.green('on'),
+ now.toISOString()
+ );
+
+ lastAuthed = aUser.authed;
+
+ // Save the last date a user sucessfully logged in
+ aUser.authed = now;
+
+ // Check probationary status vs lastAuthed for alt IP circumvention prevention
+ if (aUser._probationary && lastAuthed && !newstrategy) {
+ if (!moment().isAfter(moment(lastAuthed).add(waitAuthCapMin, 'minutes'))) {
+ aUser.save(function (aErr, aUser) {
+ if (aErr) {
+ // NOTE: A user could get back in quicker but still delayed from `authed`
+ console.error(
+ colors.red(
+ 'Probationary logged out failed to write current authentication date to aUser'),
+ aUser.name,
+ colors.red('at'),
+ aReq.connection.remoteAddress,
+ colors.red('on'),
+ now.toISOString()
+ );
+ }
+ });
+
+ console.log(
+ colors.red('Logged out probationary User'),
+ aUser.name,
+ colors.red('at'),
+ aReq.connection.remoteAddress,
+ colors.red('on'),
+ now.toISOString()
+ );
+
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 429,
+ statusMessage: 'Too many requests.',
+ suppressNavigation: true,
+ isCustomView: true,
+ statusData: {
+ isListView: true,
+ retryAfter: waitAuthCapMin * 60 + (isDev ? fudgeSec : fudgeMin)
+ }
+ });
+ return;
+ }
}
// Store the user info in the session
@@ -312,8 +517,6 @@ exports.callback = function (aReq, aRes, aNext) {
aReq.session.passport.oujsOptions.strategy = strategy;
}
- // Save the last date a user sucessfully logged in
- aUser.authed = new Date();
// Save consent
aUser.consented = true;
@@ -327,6 +530,26 @@ exports.callback = function (aReq, aRes, aNext) {
}
addSession(aReq, aUser, function () {
+ var ID = null;
+ var intervalId = function () {
+ if (aReq.session.cookie.sameSite !== 'strict') {
+ aReq.session.cookie.sameSite = 'strict';
+ aReq.session.save(function (aErr, aSession) {
+ if (aErr) {
+ // Some catastrophic error
+ console.error(colors.red(aErr));
+ return;
+ }
+ if (ID) {
+ clearTimeout(ID);
+ }
+ })
+ };
+ };
+ var timeoutId = function () {
+ ID = setInterval(intervalId, 1);
+ }
+
if (newstrategy && newstrategy !== strategy) {
// Allow a user to link to another account
aRes.redirect('/auth/' + newstrategy); // NOTE: Watchpoint... careful with encoding
@@ -340,7 +563,16 @@ exports.callback = function (aReq, aRes, aNext) {
if (!aReq.session.passport.oujsOptions.authAttach) {
expandSession(aReq, aUser, function (aErr) {
- // WARNING: No err handling
+ if (aErr) {
+ // Some possible catastrophic error
+ console.error(colors.red(aErr));
+
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: 'Expand Session failed.'
+ });
+ return;
+ }
aRes.redirect(doneUri);
});
@@ -350,12 +582,7 @@ exports.callback = function (aReq, aRes, aNext) {
// Ensure `sameSite` is set to max after redirect
// Elevate for optimal future protection
- setTimeout(function () {
- if (aReq.session.cookie.sameSite !== 'strict') {
- aReq.session.cookie.sameSite = 'strict';
- aReq.session.save();
- }
- }, 500);
+ setTimeout(timeoutId, 0);
}
});
});
diff --git a/controllers/discussion.js b/controllers/discussion.js
index 2d34c38e8..76c9a875b 100644
--- a/controllers/discussion.js
+++ b/controllers/discussion.js
@@ -29,9 +29,9 @@ var modelQuery = require('../libs/modelQuery');
var cleanFilename = require('../libs/helpers').cleanFilename;
var execQueryTask = require('../libs/tasks').execQueryTask;
-var statusCodePage = require('../libs/templateHelpers').statusCodePage;
var pageMetadata = require('../libs/templateHelpers').pageMetadata;
var orderDir = require('../libs/templateHelpers').orderDir;
+var statusCodePage = require('../libs/templateHelpers').statusCodePage;
//--- Configuration inclusions
@@ -41,23 +41,27 @@ var categories = [
{
slug: 'announcements',
name: 'Announcements',
- description: 'UserScripts News (OpenUserJS, GreaseMonkey, etc)',
+ description: 'UserScripts News (OpenUserJS, Greasemonkey, etc)',
+ active: true,
roleReqToPostTopic: 3 // Moderator
},
{
slug: 'garage',
name: 'The Garage',
- description: 'Talk shop, and get help with user script development'
+ description: 'Talk shop, and get help with user script development',
+ active: true
},
{
slug: 'corner',
- name: 'Beggar\'s Corner',
- description: 'Propose ideas and request user scripts'
+ name: 'Beggars Corner',
+ description: 'Propose ideas and request user scripts',
+ active: true
},
{
slug: 'discuss',
name: 'General Discussion',
- description: 'Off-topic discussion about anything related to user scripts or OpenUserJS.org'
+ description: 'Off-topic discussion about anything related to user scripts or OpenUserJS.org',
+ active: true
},
{
slug: 'issues',
@@ -69,6 +73,7 @@ var categories = [
slug: 'all',
name: 'All Discussions',
description: 'Overview of all discussions',
+ clear: true,
virtual: true
}
];
@@ -139,6 +144,14 @@ exports.categoryListPage = function (aReq, aRes, aNext) {
// discussionListQuery: Defaults
modelQuery.applyDiscussionListQueryDefaults(discussionListQuery, options, aReq);
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// discussionListQuery: Pagination
pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
@@ -230,6 +243,14 @@ exports.list = function (aReq, aRes, aNext) {
// discussionListQuery: Defaults
modelQuery.applyDiscussionListQueryDefaults(discussionListQuery, options, aReq);
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// discussionListQuery: Pagination
pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
@@ -477,14 +498,15 @@ function postTopic(aUser, aCategory, aTopic, aContent, aIssue, aUserAgent, aCall
Discussion.findOne({ path: path }, null, params, function (aErr, aDiscussion) {
var newDiscussion = null;
+ var now = new Date();
var props = {
topic: aTopic,
category: aCategory,
comments: 0,
author: aUser.name,
- created: new Date(),
+ created: now,
lastCommentor: aUser.name,
- updated: new Date(),
+ updated: now,
rating: 0,
flagged: false,
path: path,
@@ -534,6 +556,12 @@ exports.createTopic = function (aReq, aRes, aNext) {
var content = aReq.body['comment-content'];
var userAgent = aReq.headers['user-agent'];
+ var parser = 'UserScript';
+ var rHeaderContent = new RegExp(
+ '^(?:\\uFEFF)?\/\/ ==' + parser + '==([\\s\\S]*?)^\/\/ ==\/'+ parser + '==', 'm'
+ );
+ var headerContent = null;
+
if (!category) {
aNext();
return;
@@ -560,6 +588,16 @@ exports.createTopic = function (aReq, aRes, aNext) {
return;
}
+ // Simple validation check
+ headerContent = rHeaderContent.exec(content);
+ if (headerContent) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Source Code not allowed in Comment.'
+ });
+ return;
+ }
+
postTopic(authedUser, category.slug, topic, content, false, userAgent, function (aDiscussion) {
if (!aDiscussion) {
exports.newTopic(aReq, aRes, aNext);
@@ -585,6 +623,12 @@ exports.createComment = function (aReq, aRes, aNext) {
var content = aReq.body['comment-content'];
var userAgent = aReq.headers['user-agent'];
+ var parser = 'UserScript';
+ var rHeaderContent = new RegExp(
+ '^(?:\\uFEFF)?\/\/ ==' + parser + '==([\\s\\S]*?)^\/\/ ==\/'+ parser + '==', 'm'
+ );
+ var headerContent = null;
+
if (!aDiscussion) {
aNext();
return;
@@ -592,12 +636,22 @@ exports.createComment = function (aReq, aRes, aNext) {
if (!content || !content.trim()) {
statusCodePage(aReq, aRes, aNext, {
- statusCode: 403,
+ statusCode: 403, // Forbidden
statusMessage: 'You cannot post an empty comment to this discussion'
});
return;
}
+ // Simple validation check
+ headerContent = rHeaderContent.exec(content);
+ if (headerContent) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Source Code not allowed in Comment.'
+ });
+ return;
+ }
+
postComment(authedUser, aDiscussion, content, false, userAgent, function (aDiscussion) {
if (!aDiscussion) {
statusCodePage(aReq, aRes, aNext, {
diff --git a/controllers/document.js b/controllers/document.js
index ee22f9b83..7a96e7d41 100644
--- a/controllers/document.js
+++ b/controllers/document.js
@@ -72,6 +72,12 @@ exports.view = function (aReq, aRes, aNext) {
case 'QupZilla':
aRes.redirect(301, aReq.url.replace(/QupZilla\/?$/, 'Falkon'));
return;
+ case 'Violentmonkey-for-Chrome':
+ aRes.redirect(301, aReq.url.replace(/Violentmonkey-for-Chrome\/?$/, 'Violentmonkey-for-Brave'));
+ return;
+ case 'Violentmonkey-for-Chromium':
+ aRes.redirect(301, aReq.url.replace(/Violentmonkey-for-Chromium\/?$/, 'Violentmonkey-for-Brave'));
+ return;
}
documentPath = 'views/includes/documents';
@@ -128,16 +134,21 @@ exports.view = function (aReq, aRes, aNext) {
return;
}
- // Dynamically create a file listing of the pages
+ // Dynamically create a TOC file listing of the pages
options.fileList = [];
- for (file in aFileList) {
- if (/\.md$/.test(aFileList[file])) {
- options.fileList.push({
- href: aFileList[file].replace(/\.md$/, ''),
- textContent: aFileList[file].replace(/\.md$/, '').replace(/-/g, ' ')
- });
+ for (file in aFileList.sort(
+ function (aA, aB) {
+ return aA.localeCompare(aB, 'en', { 'sensitivity' : 'base' });
+ })) {
+
+ if (/\.md$/.test(aFileList[file])) {
+ options.fileList.push({
+ active: document === aFileList[file].replace(/\.md$/, ''),
+ href: aFileList[file].replace(/\.md$/, ''),
+ textContent: aFileList[file].replace(/\.md$/, '').replace(/-/g, ' ')
+ });
+ }
}
- }
aCallback(null);
});
diff --git a/controllers/flag.js b/controllers/flag.js
index 241f5776f..708f58d25 100644
--- a/controllers/flag.js
+++ b/controllers/flag.js
@@ -26,6 +26,7 @@ var flagLib = require('../libs/flag');
var statusCodePage = require('../libs/templateHelpers').statusCodePage;
//--- Configuration inclusions
+var userRoles = require('../models/userRoles.json');
//---
@@ -46,7 +47,7 @@ exports.flag = function (aReq, aRes, aNext) {
form.parse(aReq, function (aErr, aFields) {
// WARNING: No err handling
- var flag = aFields.flag === 'false' ? false : true;
+ var flag = aFields.flag && aFields.flag[0] ? aFields.flag[0] : null;
var reason = null;
var type = aReq.params[0];
@@ -57,8 +58,18 @@ exports.flag = function (aReq, aRes, aNext) {
var authedUser = aReq.session.user;
+ if (!flag) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403,
+ statusMessage: 'Missing flag field.'
+ });
+ return;
+ }
+
+ flag = aFields.flag[0] === 'false' ? false : true;
+
if (flag) {
- reason = aFields.reason;
+ reason = aFields.reason && aFields.reason[0] ? aFields.reason[0] : null;
// Check to make sure form submission has this name available.
// This occurs either when no reason is supplied,
@@ -66,20 +77,30 @@ exports.flag = function (aReq, aRes, aNext) {
if (!reason) {
statusCodePage(aReq, aRes, aNext, {
statusCode: 403,
- statusMessage: 'Missing reason for removal.'
+ statusMessage: 'Missing reason for flagging.'
});
return;
}
// Simple error check for string null and limit to max characters
reason = reason.trim();
- if (reason === '' || reason.length > 300) {
+
+ if (reason === '') {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403,
+ statusMessage: 'Invalid reason for flagging.'
+ });
+ return;
+ }
+
+ if (reason.length > 300) {
statusCodePage(aReq, aRes, aNext, {
statusCode: 403,
- statusMessage: 'Invalid reason for removal.'
+ statusMessage: 'Reason for flagging too long.'
});
return;
}
+
}
switch (type) {
@@ -184,8 +205,9 @@ exports.getFlaggedListForContent = function (aModelName, aOptions, aCallback) {
contentList[aContentKey].flaggedList.push({
name: aUser.name,
+ rank: userRoles[aUser.role],
reason: aFlagList[aFlagKey].reason,
- since: aFlagList[aFlagKey]._since
+ since: aFlagList[aFlagKey].created
});
aEachInnerCallback();
});
diff --git a/controllers/group.js b/controllers/group.js
index e1a6974db..d806dd2e9 100644
--- a/controllers/group.js
+++ b/controllers/group.js
@@ -28,6 +28,7 @@ var getRating = require('../libs/collectiveRating').getRating;
var execQueryTask = require('../libs/tasks').execQueryTask;
var pageMetadata = require('../libs/templateHelpers').pageMetadata;
var orderDir = require('../libs/templateHelpers').orderDir;
+var statusCodePage = require('../libs/templateHelpers').statusCodePage;
//--- Configuration inclusions
@@ -132,10 +133,12 @@ exports.addScriptToGroups = function (aScript, aGroupNames, aCallback) {
// Create a custom group for the script
if (!aScript._groupId && newGroup) {
tasks.push(function (aCallback) {
+ var now = new Date();
var group = new Group({
name: newGroup,
rating: 0,
- updated: new Date(),
+ created: now,
+ updated: now,
_scriptIds: [aScript._id]
});
@@ -186,7 +189,7 @@ exports.addScriptToGroups = function (aScript, aGroupNames, aCallback) {
};
// list groups
-exports.list = function (aReq, aRes) {
+exports.list = function (aReq, aRes, aNext) {
function preRender() {
// groupList
options.groupList = _.map(options.groupList, modelParser.parseGroup);
@@ -236,6 +239,14 @@ exports.list = function (aReq, aRes) {
// groupListQuery: Defaults
modelQuery.applyGroupListQueryDefaults(groupListQuery, options, aReq);
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// groupListQuery: Pagination
var pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
@@ -372,6 +383,14 @@ exports.view = function (aReq, aRes, aNext) {
// scriptListQuery: Defaults
modelQuery.applyScriptListQueryDefaults(scriptListQuery, options, aReq);
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// scriptListQuery: Pagination
pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
diff --git a/controllers/index.js b/controllers/index.js
index a18fd7414..897146585 100644
--- a/controllers/index.js
+++ b/controllers/index.js
@@ -11,6 +11,7 @@ var isDbg = require('../libs/debug').isDbg;
var async = require('async');
var _ = require('underscore');
var url = require('url');
+var crypto = require('crypto');
//--- Model inclusions
var Discussion = require('../models/discussion').Discussion;
@@ -27,6 +28,8 @@ var getFlaggedListForContent = require('./flag').getFlaggedListForContent;
//--- Library inclusions
// var indexLib = require('../libs/index');
+var getRedirect = require('../libs/helpers').getRedirect;
+
var modelParser = require('../libs/modelParser');
var modelQuery = require('../libs/modelQuery');
@@ -42,7 +45,7 @@ var strategies = require('./strategies.json');
//---
// The site home page has scriptList, and groups in a sidebar
-exports.home = function (aReq, aRes) {
+exports.home = function (aReq, aRes, aNext) {
function preRender() {
// scriptList
options.scriptList = _.map(options.scriptList, modelParser.parseScript);
@@ -100,7 +103,7 @@ exports.home = function (aReq, aRes) {
async.parallel([
function (aCallback) {
- if (!!!options.isFlagged || !options.isAdmin) { // NOTE: Watchpoint
+ if (!!!options.isFlagged || !options.isMod) { // NOTE: Watchpoint
aCallback();
return;
}
@@ -156,6 +159,14 @@ exports.home = function (aReq, aRes) {
modelQuery.applyScriptListQueryDefaults(scriptListQuery, options, aReq);
}
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// scriptListQuery: Pagination
pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
@@ -195,21 +206,6 @@ exports.home = function (aReq, aRes) {
async.parallel(tasks, asyncComplete);
};
-// Get the referer url for redirect after login/logout
-function getRedirect(aReq) {
- var referer = aReq.get('Referer');
- var redirect = '/';
-
- if (referer) {
- referer = url.parse(referer);
- if (referer.hostname === aReq.hostname) {
- redirect = referer.path;
- }
- }
-
- return redirect;
-}
-
// UI for user registration
exports.register = function (aReq, aRes) {
//
@@ -217,12 +213,16 @@ exports.register = function (aReq, aRes) {
var authedUser = aReq.session.user;
var tasks = [];
+ var SITEKEY = process.env.HCAPTCHA_SITE_KEY;
+
// If already logged in, go back.
if (authedUser) {
aRes.redirect(getRedirect(aReq));
return;
}
+ options.hasCaptcha = (SITEKEY ? SITEKEY : '');
+
options.redirectTo = getRedirect(aReq);
// Page metadata
@@ -246,7 +246,8 @@ exports.register = function (aReq, aRes) {
if (!aStrategy.oauth) {
options.strategies.push({
'strat': aStrategyKey,
- 'display': aStrategy.name
+ 'display': aStrategy.name,
+ 'disabled': aStrategy.readonly
});
}
});
@@ -254,6 +255,10 @@ exports.register = function (aReq, aRes) {
//
Strategy.find({}, function (aErr, aAvailableStrategies) {
+ var SITEKEY = process.env.HCAPTCHA_SITE_KEY;
+ var defaultCSP = ' https: \'self\'';
+ var captchaCSP = (SITEKEY ? ' hcaptcha.com *.hcaptcha.com' : '');
+
if (aErr || !aAvailableStrategies) {
statusCodePage(aReq, aRes, aNext, {
statusCode: 503,
@@ -265,10 +270,16 @@ exports.register = function (aReq, aRes) {
aAvailableStrategies.forEach(function (aStrategy) {
options.strategies.push({
'strat': aStrategy.name,
- 'display': aStrategy.display
+ 'display': aStrategy.display,
+ 'disabled': (strategies[aStrategy.name] ? strategies[aStrategy.name].readonly : true)
});
});
+ options.hasCaptcha = (SITEKEY ? SITEKEY : '');
+
+ options.nonce = crypto.randomBytes(512).toString('base64');
+ defaultCSP += ' \'nonce-' + options.nonce + '\'';
+
// Insert an empty default strategy at the beginning
// NOTE: Safari always autoselects an option when disabled
options.strategies.unshift({'strat': '', 'display': '(default preferred authentication)'});
@@ -278,10 +289,27 @@ exports.register = function (aReq, aRes) {
return aStrategy.display;
});
+
aRes.header('Cache-Control', 'no-cache, no-store, must-revalidate');
aRes.header('Pragma', 'no-cache');
aRes.header('Expires', '0');
+ //
+ aRes.header('Content-Security-Policy',
+ 'default-src \'none\'' +
+ '; base-uri' + defaultCSP +
+ '; child-src' + defaultCSP +
+ '; connect-src' + defaultCSP + captchaCSP +
+ '; font-src' + defaultCSP +
+ '; frame-src' + defaultCSP + captchaCSP +
+ '; img-src' + defaultCSP +
+ '; script-src' + defaultCSP + captchaCSP +
+ '; style-src' + defaultCSP + captchaCSP +
+ '; form-action' + defaultCSP +
+ '; navigate-to' + defaultCSP +
+ ''
+ );
+
aRes.render('pages/loginPage', options);
}
});
diff --git a/controllers/issue.js b/controllers/issue.js
index 9507d9c20..10ed7e9d6 100644
--- a/controllers/issue.js
+++ b/controllers/issue.js
@@ -28,9 +28,9 @@ var modelQuery = require('../libs/modelQuery');
var execQueryTask = require('../libs/tasks').execQueryTask;
var countTask = require('../libs/tasks').countTask;
-var statusCodePage = require('../libs/templateHelpers').statusCodePage;
var pageMetadata = require('../libs/templateHelpers').pageMetadata;
var orderDir = require('../libs/templateHelpers').orderDir;
+var statusCodePage = require('../libs/templateHelpers').statusCodePage;
//--- Configuration inclusions
@@ -159,11 +159,19 @@ exports.list = function (aReq, aRes, aNext) {
// discussionListQuery: Defaults
modelQuery.applyDiscussionListQueryDefaults(discussionListQuery, options, aReq);
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// discussionListQuery: Pagination
pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
// SearchBar
- options.searchBarPlaceholder = 'Search Issues';
+ options.searchBarPlaceholder = options.searchBarPlaceholder.replace(/Topics/, 'Issues');
//--- Tasks
@@ -273,8 +281,9 @@ exports.view = function (aReq, aRes, aNext) {
options.discussion = discussion;
options.canClose = authedUser &&
- (authedUser._id == script._authorId || authedUser._id == discussion._authorId);
- options.canOpen = authedUser && authedUser._id == script._authorId;
+ (authedUser._id == script._authorId || authedUser._id == discussion._authorId) &&
+ discussion.open;
+ options.canOpen = authedUser && (authedUser._id == script._authorId) && !discussion.open;
// commentListQuery
commentListQuery = Comment.find();
@@ -285,6 +294,14 @@ exports.view = function (aReq, aRes, aNext) {
// commentListQuery: Defaults
modelQuery.applyCommentListQueryDefaults(commentListQuery, options, aReq);
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// commentListQuery: Pagination
pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
@@ -348,6 +365,12 @@ exports.open = function (aReq, aRes, aNext) {
var userAgent = aReq.headers['user-agent'];
var tasks = [];
+ var parser = 'UserScript';
+ var rHeaderContent = new RegExp(
+ '^(?:\\uFEFF)?\/\/ ==' + parser + '==([\\s\\S]*?)^\/\/ ==\/'+ parser + '==', 'm'
+ );
+ var headerContent = null;
+
// Session
options.authedUser = authedUser = modelParser.parseUser(authedUser);
options.isMod = authedUser && authedUser.isMod;
@@ -377,6 +400,16 @@ exports.open = function (aReq, aRes, aNext) {
return;
}
+ // Simple validation check
+ headerContent = rHeaderContent.exec(content);
+ if (headerContent) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Source Code not allowed in Comment.'
+ });
+ return;
+ }
+
// Issue Submission
discussionLib.postTopic(authedUser, category.slug, topic, content, true, userAgent,
function (aDiscussion) {
@@ -419,6 +452,12 @@ exports.comment = function (aReq, aRes, aNext) {
var category = type + '/' + installNameBase + '/issues';
var topic = aReq.params.topic;
+ var parser = 'UserScript';
+ var rHeaderContent = new RegExp(
+ '^(?:\\uFEFF)?\/\/ ==' + parser + '==([\\s\\S]*?)^\/\/ ==\/'+ parser + '==', 'm'
+ );
+ var headerContent = null;
+
if (aErr || !aScript) {
aNext();
return;
@@ -426,12 +465,22 @@ exports.comment = function (aReq, aRes, aNext) {
if (!content || !content.trim()) {
statusCodePage(aReq, aRes, aNext, {
- statusCode: 403,
+ statusCode: 403, // Forbidden
statusMessage: 'You cannot post an empty comment to this issue'
});
return;
}
+ // Simple validation check
+ headerContent = rHeaderContent.exec(content);
+ if (headerContent) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Source Code not allowed in Comment.'
+ });
+ return;
+ }
+
discussionLib.findDiscussion(category, topic, function (aIssue) {
//
var authedUser = aReq.session.user;
diff --git a/controllers/moderation.js b/controllers/moderation.js
index b90f76c38..fdc55c587 100644
--- a/controllers/moderation.js
+++ b/controllers/moderation.js
@@ -42,10 +42,10 @@ exports.removedItemPage = function (aReq, aRes, aNext) {
options.isMod = authedUser && authedUser.isMod;
options.isAdmin = authedUser && authedUser.isAdmin;
- if (!options.isMod) {
+ if (!options.isAdmin) {
statusCodePage(aReq, aRes, aNext, {
statusCode: 403,
- statusMessage: 'This page is only accessible by moderators',
+ statusMessage: 'This page is only accessible by admins',
});
return;
}
@@ -160,6 +160,13 @@ exports.removedItemListPage = function (aReq, aRes, aNext) {
break;
default:
modelQuery.applyRemovedItemListQueryDefaults(removedItemListQuery, options, aReq);
+ options.filterUser = true;
+ options.filterScript = true;
+ options.filterComment = true;
+ options.filterDiscussion = true;
+ options.filterFlag = true;
+ options.filterGroup = true;
+ options.filterVote = true;
}
// removedItemListQuery: Pagination
diff --git a/controllers/remove.js b/controllers/remove.js
index db62f7f4d..9c5d252b0 100644
--- a/controllers/remove.js
+++ b/controllers/remove.js
@@ -44,7 +44,7 @@ exports.rm = function (aReq, aRes, aNext) {
form.parse(aReq, function (aErr, aFields) {
// WARNING: No err handling
- var reason = aFields.reason;
+ var reason = aFields.reason && aFields.reason[0] ? aFields.reason[0] : null;
var type = aReq.params[0];
var isLib = null;
diff --git a/controllers/script.js b/controllers/script.js
index 31f109200..62f330da4 100644
--- a/controllers/script.js
+++ b/controllers/script.js
@@ -29,6 +29,7 @@ var getFlaggedListForContent = require('./flag').getFlaggedListForContent;
//--- Library inclusions
// var scriptLib = require('../libs/script');
+var statusCodePage = require('../libs/templateHelpers').statusCodePage;
var isSameOrigin = require('../libs/helpers').isSameOrigin;
var voteLib = require('../libs/vote');
@@ -80,6 +81,8 @@ var getScriptPageTasks = function (aOptions) {
var copyright = null;
var license = null;
var licenseConflict = false;
+ var antifeature = null;
+ var types = [];
var author = null;
var collaborator = null;
@@ -174,6 +177,24 @@ var getScriptPageTasks = function (aOptions) {
aOptions.script.licenseConflict = true;
}
+ // Show antifeatures of the script
+ antifeature = scriptStorage.findMeta(script.meta, 'UserScript.antifeature');
+ if (antifeature) {
+ aOptions.hasAntiFeature = true;
+
+ antifeature.forEach(function (aElement, aIndex, aArray) {
+ var type = types[aElement.value1];
+ var comment = type ? (type.comment || '') : '';
+
+ types[aElement.value1] = { name: aElement.value1, comment:
+ (aElement.value2 ? aElement.value2 : '')
+ + (comment ? (aElement.value2 ? '\n' : '') + comment: '')
+ };
+ });
+
+ aOptions.script.antifeatures = Object.values(types).reverse();
+ }
+
// Show collaborators of the script
author = scriptStorage.findMeta(script.meta, 'OpenUserJS.author.0.value');
collaborator = scriptStorage.findMeta(script.meta, 'OpenUserJS.collaborator');
@@ -340,7 +361,7 @@ exports.view = function (aReq, aRes, aNext) {
async.parallel([
function (aCallback) {
- if (!options.isAdmin) { // NOTE: Watchpoint
+ if (!options.isMod) { // NOTE: Watchpoint
aCallback();
return;
}
@@ -438,6 +459,12 @@ exports.edit = function (aReq, aRes, aNext) {
var scriptGroups = null;
var tasks = [];
+ var parser = 'UserScript';
+ var rHeaderContent = new RegExp(
+ '^(?:\\uFEFF)?\/\/ ==' + parser + '==([\\s\\S]*?)^\/\/ ==\/'+ parser + '==', 'm'
+ );
+ var headerContent = null;
+
// ---
if (aErr || !aScript) {
aNext();
@@ -474,6 +501,16 @@ exports.edit = function (aReq, aRes, aNext) {
// POST
aScript.about = aReq.body.about;
+ // Simple validation check
+ headerContent = rHeaderContent.exec(aScript.about);
+ if (headerContent) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Source Code not allowed in Script Info.'
+ });
+ return;
+ }
+
remark().use(stripHTML).use(stripMD).process(aScript.about, function(aErr, aFile) {
if (aErr || !aFile) {
aScript._about = (
diff --git a/controllers/scriptStorage.js b/controllers/scriptStorage.js
index 4fdb25ffe..99a38ceb8 100644
--- a/controllers/scriptStorage.js
+++ b/controllers/scriptStorage.js
@@ -4,8 +4,12 @@
var isPro = require('../libs/debug').isPro;
var isDev = require('../libs/debug').isDev;
var isDbg = require('../libs/debug').isDbg;
-var isSecured = require('../libs/debug').isSecured;
var statusError = require('../libs/debug').statusError;
+var isSecured = require('../libs/debug').isSecured;
+var uaOUJS = require('../libs/debug').uaOUJS;
+
+var rLogographic = require('../libs/debug').rLogographic;
+var logographicDivisor = require('../libs/debug').logographicDivisor;
//
@@ -26,7 +30,6 @@ var mediaType = require('media-type');
var mediaDB = require('mime-db');
var async = require('async');
var moment = require('moment');
-var Base62 = require('base62/lib/ascii');
var SPDX = require('spdx-license-ids');
var sizeOf = require('image-size');
var ipRangeCheck = require("ip-range-check");
@@ -40,7 +43,7 @@ var Discussion = require('../models/discussion').Discussion;
//--- Controller inclusions
//--- Library inclusions
-// var scriptStorageLib = require('../libs/scriptStorage');
+var scriptStorageLib = require('../libs/scriptStorage');
var patternHasSameOrigin = require('../libs/helpers').patternHasSameOrigin;
var patternMaybeSameOrigin = require('../libs/helpers').patternMaybeSameOrigin;
@@ -193,7 +196,7 @@ if (isSecured) {
request({
url: 'https://api.github.com/meta',
headers: {
- 'User-Agent': 'OpenUserJS'
+ 'User-Agent': uaOUJS + (process.env.UA_SECRET ? ' ' + process.env.UA_SECRET : '')
}
}, function (aErr, aRes, aBody) {
var meta = null;
@@ -214,7 +217,7 @@ if (isSecured) {
try {
meta = JSON.parse(aBody);
} catch (aE) {
- console.error(colors.red('Error retrieving GitHub `hooks`', aE));
+ console.error(colors.red('Error retrieving GitHub `hooks` ' + aE));
return;
}
@@ -224,7 +227,8 @@ if (isSecured) {
githubHookAddresses.push(aEl);
} else {
console.warn(
- colors.yellow('GitHub `hooks` element', aEl, 'does not match IPv4 CIDR specification')
+ colors.yellow('GitHub `hooks` element ' + aEl +
+ ' does not match IPv4 CIDR specification')
);
}
});
@@ -547,28 +551,19 @@ exports.sendScript = function (aReq, aRes, aNext) {
((aReq.headers.accept || '*/*').split(',').indexOf('text/x-userscript-meta') > -1 ||
rMetaJS.test(pathname))) {
+ // Search engine affirmation
+ aRes.set('X-Robots-Tag', 'noindex');
+
exports.sendMeta(aReq, aRes, aNext);
return;
}
exports.getSource(aReq, function (aScript, aStream) {
let chunks = [];
- let updateURL = null;
- let updateUtf = null;
-
- let matches = null;
- let rAnyLocalMetaUrl = new RegExp(
- '^' + patternHasSameOrigin +
- '/(?:meta|install|src/scripts)/(.+?)/(.+?)\.(?:meta|user)\.js$'
- );
- let hasAlternateLocalUpdateURL = false;
-
- let rSameOrigin = new RegExp(
- '^' + patternHasSameOrigin
- );
var lastModified = null;
var eTag = null;
+ var hashSRI = null;
var maxAge = 7 * 60 * 60 * 24; // nth day(s) in seconds
var now = null;
var continuation = true;
@@ -578,59 +573,35 @@ exports.sendScript = function (aReq, aRes, aNext) {
return;
}
- if (process.env.FORCE_BUSY_UPDATEURL_CHECK === 'true') {
- // `@updateURL` must be exact here for OUJS hosted checks
- // e.g. no `search`, no `hash`
-
- updateURL = findMeta(aScript.meta, 'UserScript.updateURL.0.value');
- if (updateURL) {
-
- // Check for decoding error
- try {
- updateUtf = decodeURIComponent(updateURL);
- } catch (aE) {
- aRes.set('Warning', '199 ' + aReq.headers.host +
- rfc2047.encode(' Invalid @updateURL'));
- aRes.status(400).send(); // Bad request
- return;
- }
-
- // Validate `author` and `name` (installNameBase) to this scripts meta only
- let matches = updateUtf.match(rAnyLocalMetaUrl);
- if (matches) {
- if (cleanFilename(aScript.author, '').toLowerCase() +
- '/' + cleanFilename(aScript.name, '') === matches[1].toLowerCase() + '/' + matches[2])
- {
- // Same script
- } else {
- hasAlternateLocalUpdateURL = true;
- }
- } else {
- // Allow offsite checks
- updateURL = new URL(updateURL);
- if (rSameOrigin.test(updateURL.origin)) {
- hasAlternateLocalUpdateURL = true;
- }
- }
- } else {
- if (!aScript.isLib) {
- // Don't serve the script anywhere in this mode and if absent
- hasAlternateLocalUpdateURL = true;
- }
- }
-
- if (hasAlternateLocalUpdateURL) {
- aRes.set('Warning', '199 ' + aReq.headers.host +
- rfc2047.encode(' Invalid @updateURL in lockdown'));
- aRes.status(404).send(); // Not found
- return;
- }
+ if (scriptStorageLib.invalidKey(
+ aScript.author,
+ aScript.name,
+ aScript.isLib,
+ 'updateURL',
+ findMeta(aScript.meta, 'UserScript.updateURL.0.value')
+ )) {
+ aRes.set('Warning', '199 ' + aReq.headers.host +
+ rfc2047.encode(' Invalid @updateURL') +
+ (process.env.FORCE_BUSY_UPDATEURL_CHECK === 'true' ? ' in lockdown.' : '.'));
+
+ // NOTE: Force HTTP stack response for Chromium based browsers #1856
+ aRes.set('Content-Type', 'text/html; charset=UTF-8');
+
+ aRes.status(403).send(); // Forbidden
+ return;
}
+ hashSRI = aScript.hash
+ ? 'sha512-' + Buffer.from(aScript.hash, 'hex').toString('base64')
+ : 'undefined';
+
// HTTP/1.1 Caching
aRes.set('Cache-Control', 'public, max-age=' + maxAge +
', no-cache, no-transform, must-revalidate');
+ // Search engine affirmation
+ aRes.set('X-Robots-Tag', 'noindex');
+
// Only minify for response that doesn't contain `.min.` extension
if (!/\.min(\.user)?\.js$/.test(aReq._parsedUrl.pathname) ||
process.env.DISABLE_SCRIPT_MINIFICATION === 'true') {
@@ -638,8 +609,8 @@ exports.sendScript = function (aReq, aRes, aNext) {
lastModified = moment(aScript.updated)
.utc().format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT';
- // Convert a based representation of the hex sha512sum
- eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + ' .user.js"';
+ // Use SRI of the stored sha512sum
+ eTag = '"' + hashSRI + ' .user.js"';
// If already client-side... HTTP/1.1 Caching
if (aReq.get('if-none-match') === eTag || aReq.get('if-modified-since') === lastModified) {
@@ -682,6 +653,9 @@ exports.sendScript = function (aReq, aRes, aNext) {
source = chunks.join(''); // NOTE: Watchpoint
// Send the script
+ if (aScript.isLib) {
+ aRes.set('Access-Control-Allow-Origin', '*');
+ }
aRes.set('Content-Type', 'text/javascript; charset=UTF-8');
aStream.setEncoding('utf8');
@@ -693,6 +667,8 @@ exports.sendScript = function (aReq, aRes, aNext) {
aRes.set('Last-modified', lastModified);
aRes.set('Etag', eTag);
+ aRes.set('Content-Length', Buffer.byteLength(source, 'utf8'));
+
aRes.write(source);
aRes.end();
@@ -705,6 +681,16 @@ exports.sendScript = function (aReq, aRes, aNext) {
return;
}
+ // Don't count installs from tagged XHR
+ if (aReq.get('x-requested-with')) {
+ return;
+ }
+
+ // Don't count installs on browser request in Fx
+ if (aReq.get('accept') && aReq.get('accept').indexOf('text/html') > -1) { // NOTE: Watchpoint
+ return;
+ }
+
// Update the install count
++aScript.installs;
++aScript.installsSinceUpdate;
@@ -785,10 +771,12 @@ exports.sendScript = function (aReq, aRes, aNext) {
} else {
source = result.code;
- // Calculate a based representation of the hex sha512sum
- eTag = '"' + Base62.encode(
- parseInt('0x' + crypto.createHash('sha512').update(source).digest('hex'), 16)) +
- ' .min.user.js"';
+ // Calculate SRI of the source sha512sum
+ eTag = '"sha512-' +
+ Buffer.from(
+ crypto.createHash('sha512').update(source).digest('base64')
+ ) + ' .min.user.js"';
+
}
} catch (aE) { // On any failure default to unminified
if (isDev) {
@@ -816,8 +804,8 @@ exports.sendScript = function (aReq, aRes, aNext) {
lastModified = moment(aScript.updated)
.utc().format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT';
- // Reset to convert a based representation of the hex sha512sum
- eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + ' .user.js"';
+ // Reset SRI of the stored sha512sum
+ eTag = '"' + hashSRI + ' .user.js"';
}
// If already client-side... partial HTTP/1.1 Caching
@@ -844,6 +832,8 @@ exports.sendScript = function (aReq, aRes, aNext) {
aRes.set('Last-Modified', lastModified);
aRes.set('Etag', eTag);
+ aRes.set('Content-Length', Buffer.byteLength(source, 'utf8'));
+
aRes.write(source);
aRes.end();
@@ -876,7 +866,34 @@ exports.sendMeta = function (aReq, aRes, aNext) {
}
function render() {
- aRes.end(JSON.stringify(meta, null, isPro ? '' : ' '));
+ var metaJSON = JSON.stringify(meta, null, isPro ? '' : ' ');
+ var hash = null;
+
+ // HTTP/1.1 Caching
+ aRes.set('Cache-Control', 'public, max-age=' + maxAge +
+ ', no-cache, no-transform, must-revalidate');
+
+ // Use SRI of the recalculated sha512sum
+ hash = crypto.createHash('sha512').update(metaJSON).digest('hex');
+ eTag = '"' + 'sha512-' + Buffer.from(hash, 'hex').toString('base64') + ' .meta.json"';
+
+ // If already client-side... HTTP/1.1 Caching
+ if (aReq.get('if-none-match') === eTag) {
+ aRes.status(304).send(); // Not Modified
+ return;
+ }
+
+ // Okay to send .meta.json...
+ aRes.set('Content-Type', 'application/json; charset=UTF-8');
+
+ // HTTP/1.0 Caching
+ aRes.set('Expires', moment(moment() + maxAge * 1000).utc()
+ .format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT');
+
+ // HTTP/1.1 Caching
+ aRes.set('Etag', eTag);
+
+ aRes.end(metaJSON);
}
function asyncComplete(aErr) {
@@ -892,6 +909,9 @@ exports.sendMeta = function (aReq, aRes, aNext) {
var installNameBase = getInstallNameBase(aReq, { hasExtension: true });
var meta = null;
+ var eTag = null;
+ var maxAge = 7 * 60 * 60 * 24; // nth day(s) in seconds
+
Script.findOne({ installName: caseSensitive(installNameBase + '.user.js') },
function (aErr, aScript) {
// WARNING: No err handling
@@ -901,40 +921,16 @@ exports.sendMeta = function (aReq, aRes, aNext) {
var whitespace = '\u0020\u0020\u0020\u0020';
var tasks = [];
- var eTag = null;
- var maxAge = 7 * 60 * 60 * 24; // nth day(s) in seconds
-
if (!aScript) {
aNext();
return;
}
- // HTTP/1.1 Caching
- aRes.set('Cache-Control', 'public, max-age=' + maxAge +
- ', no-cache, no-transform, must-revalidate');
-
script = modelParser.parseScript(aScript);
meta = script.meta; // NOTE: Watchpoint
if (/\.json$/.test(aReq.params.scriptname)) {
- // Create a based representation of the hex sha512sum
- eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + ' .meta.json"';
-
- // If already client-side... HTTP/1.1 Caching
- if (aReq.get('if-none-match') === eTag) {
- aRes.status(304).send(); // Not Modified
- return;
- }
-
- // Okay to send .meta.json...
- aRes.set('Content-Type', 'application/json; charset=UTF-8');
-
- // HTTP/1.0 Caching
- aRes.set('Expires', moment(moment() + maxAge * 1000).utc()
- .format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT');
-
- // HTTP/1.1 Caching
- aRes.set('Etag', eTag);
+ // NOTE: Caching is managed on rendering for more live stats
// Check for existance of OUJS metadata block
if (!meta.OpenUserJS) {
@@ -944,7 +940,8 @@ exports.sendMeta = function (aReq, aRes, aNext) {
// Overwrite any keys found with the following...
meta.OpenUserJS.installs = [{ value: script.installs }];
meta.OpenUserJS.issues = [{ value: 'n/a' }];
- meta.OpenUserJS.hash = aScript.hash ? [{ value: aScript.hash }] : undefined;
+ meta.OpenUserJS.rating = [{ value: script.rating }];
+ meta.OpenUserJS.hash = script.hash ? [{ value: script.hashSRI }] : 'undefined';
// Get the number of open issues
scriptOpenIssueCountQuery = Discussion.find({ category: exports
@@ -955,8 +952,8 @@ exports.sendMeta = function (aReq, aRes, aNext) {
async.parallel(tasks, asyncComplete);
} else {
- // Create a based representation of the hex sha512sum
- eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + ' .meta.js"';
+ // Use SRI of the stored sha512sum
+ eTag = '"' + script.hashSRI + ' .meta.js"';
// If already client-side... HTTP/1.1 Caching
if (aReq.get('if-none-match') === eTag) {
@@ -1027,7 +1024,9 @@ exports.findMeta = findMeta;
// Parse a specific metadata block content with a specified *pegjs* parser
function parseMeta(aParser, aString) {
var lines = {};
+ var CEVLines = {};
var rLine = /\/\/ @(\S+)(?:\s+(.*))?/;
+ var rCEVLine = /\/\/@/;
var line = null;
var header = null;
var key = null;
@@ -1042,6 +1041,10 @@ function parseMeta(aParser, aString) {
return (aElement.match(rLine));
});
+ CEVLines = aString.split(/[\r\n]+/).filter(function (aElement, aIndex, aArray) {
+ return (aElement.match(rCEVLine));
+ });
+
for (line in lines) {
try {
header = aParser.parse(lines[line], { startRule: 'line' });
@@ -1091,6 +1094,15 @@ function parseMeta(aParser, aString) {
});
}
+ headers[':CVE'] = []; // NOTE: Important to prevent spoofing
+
+ if (CEVLines.length > 0) {
+ headers[':CVE'].push(
+ {
+ value: 'Comment only in Metadata Block may be parsed as a Key in certain non-standard UserScript engines.'
+ }
+ );
+ }
return headers;
}
exports.parseMeta = parseMeta;
@@ -1109,6 +1121,8 @@ exports.getMeta = function (aBufs, aCallback) {
var hasUserScriptHeaderContent = false;
var blocksContent = {};
var blocks = {};
+ var CVE = null;
+ var CVES = [];
for (; i < aBufs.length; ++i) {
// Accumulate the indexed Buffer length to use with `totalLength` parameter
@@ -1136,8 +1150,22 @@ exports.getMeta = function (aBufs, aCallback) {
for (parser in parsers) {
if (blocksContent[parser]) {
blocks[parser] = parseMeta(parsers[parser], blocksContent[parser]);
+ if (parser !== 'OpenUserJS' && blocks[parser][':CVE']) {
+ for (CVE in blocks[parser][':CVE']) {
+ CVES.push(blocks[parser][':CVE'][CVE]);
+ }
+ }
+ delete blocks[parser][':CVE']
}
}
+
+ if (CVES.length > 0) {
+ if (!blocks['OpenUserJS']) {
+ blocks['OpenUserJS'] = {};
+ }
+ blocks['OpenUserJS'].CVE = CVES;
+ }
+
aCallback(blocks);
return;
}
@@ -1170,10 +1198,12 @@ function isEqualKeyset(aSlaveKeyset, aMasterKeyset) {
exports.storeScript = function (aUser, aMeta, aBuf, aUpdate, aCallback) {
var isLib = !!findMeta(aMeta, 'UserLibrary');
+ var userName = findMeta(aMeta, 'OpenUserJS.author.0.value') || aUser.name;
var scriptName = null;
var scriptDescription = null;
var thisName = null;
var thisDescription = null;
+ var hasInvalidKey = null;
async.series([
function (aInnerCallback) {
@@ -1519,14 +1549,19 @@ exports.storeScript = function (aUser, aMeta, aBuf, aUpdate, aCallback) {
req = request.get({
url: icon,
headers: {
- 'User-Agent': 'request'
+ 'User-Agent': 'request' // NOTE: Anonymous intended
}
})
.on('response', function (aRes) {
// TODO: Probably going to be something here
})
.on('error', function (aErr) {
- aInnerCallback(aErr);
+ if (aErr && aErr.code === 'ECONNRESET') {
+ console.error('*request* ECONNRESET error with `@icon` validation at', icon);
+ // fallsthrough
+ } else {
+ aInnerCallback(aErr);
+ }
})
.on('data', function (aChunk) {
var buf = null;
@@ -1636,20 +1671,17 @@ exports.storeScript = function (aUser, aMeta, aBuf, aUpdate, aCallback) {
aInnerCallback(null);
},
function (aInnerCallback) {
- // `@updateURL` validations
- var updateURL = null;
-
- updateURL = findMeta(aMeta, 'UserScript.updateURL.0.value');
- if (updateURL) {
- if (!isFQUrl(updateURL)) {
-
- // Not a web url... reject
- aInnerCallback(new statusError({
- message: '`@updateURL` not a web url',
- code: 400
- }), null);
- return;
- }
+ // `@updateURL` validation
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'updateURL',
+ findMeta(aMeta, 'UserScript.updateURL.0.value'));
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
}
aInnerCallback(null);
@@ -1701,28 +1733,189 @@ exports.storeScript = function (aUser, aMeta, aBuf, aUpdate, aCallback) {
aInnerCallback(null);
},
+ function (aInnerCallback) {
+ // `@include` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'include',
+ findMeta(aMeta, 'UserScript.include.value')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
+ function (aInnerCallback) {
+ // `@match` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'match',
+ findMeta(aMeta, 'UserScript.match.value')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
function (aInnerCallback) {
// `@exclude` validations
- var excludes = null;
- var missingExcludeAll = true;
-
- if (isLib) {
- excludes = findMeta(aMeta, 'UserScript.exclude.value');
- if (excludes) {
- excludes.forEach(function (aElement, aIndex, aArray) {
- if (aElement === '*') {
- missingExcludeAll = false;
- }
- });
- }
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'exclude',
+ findMeta(aMeta, 'UserScript.exclude.value')
+ );
- if (missingExcludeAll) {
- aInnerCallback(new statusError({
- message: 'UserScript Metadata Block missing `@exclude *`.',
- code: 400
- }), null);
- return;
- }
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
+ function (aInnerCallback) {
+ // `@grant` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'grant',
+ findMeta(aMeta, 'UserScript.grant.value')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
+ function (aInnerCallback) {
+ // `@require` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'require',
+ findMeta(aMeta, 'UserScript.require.value')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
+ function (aInnerCallback) {
+ // `@resource` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'resource',
+ findMeta(aMeta, 'UserScript.resource.value')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
+ function (aInnerCallback) {
+ // `@run-at` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'run-at',
+ findMeta(aMeta, 'UserScript.run-at.value')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
+ function (aInnerCallback) {
+ // `@connect` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'connect',
+ findMeta(aMeta, 'UserScript.connect.value')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
+ function (aInnerCallback) {
+ // `@antifeature` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'antifeature',
+ findMeta(aMeta, 'UserScript.antifeature.value1')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
+ function (aInnerCallback) {
+ // `@noframes` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'noframes',
+ findMeta(aMeta, 'UserScript.noframes')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
+ }
+
+ aInnerCallback(null);
+ },
+ function (aInnerCallback) {
+ // `@unwrap` validations
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ userName,
+ scriptName,
+ isLib,
+ 'unwrap',
+ findMeta(aMeta, 'UserScript.unwrap')
+ );
+
+ if (hasInvalidKey) {
+ aInnerCallback(hasInvalidKey, null);
+ return;
}
aInnerCallback(null);
@@ -1838,7 +2031,7 @@ exports.storeScript = function (aUser, aMeta, aBuf, aUpdate, aCallback) {
try {
url = new URL(aRequire);
- require = url.origin + url.pathname;
+ require = url.origin + url.pathname;
} catch (aE) {
// NOTE: Currently not always a real error in every .user.js engine so...
/* falls through */
@@ -1866,6 +2059,9 @@ exports.storeScript = function (aUser, aMeta, aBuf, aUpdate, aCallback) {
function (aAlive, aScript, aRemoved) {
var script = null;
var s3 = null;
+ var now = null;
+
+ var storeDescriptionLength = null;
if (aRemoved) {
aCallback(new statusError({
@@ -1882,16 +2078,24 @@ exports.storeScript = function (aUser, aMeta, aBuf, aUpdate, aCallback) {
} else if (!aScript && aUpdate) {
aCallback(new statusError({
message: 'Updating but no script found.',
- code: 500 // Status code unknown... could be user error too
+ code: 404
}), null);
return;
} else if (!aScript) {
// New script
+ now = new Date();
+
+
+ storeDescriptionLength = settings.scriptSearchQueryStoreMaxDescription;
+ storeDescriptionLength = rLogographic.test(thisDescription)
+ ? parseInt(storeDescriptionLength / logographicDivisor)
+ : storeDescriptionLength;
+
aScript = new Script({
name: thisName,
_description: (
thisDescription
- ? thisDescription.substr(0, settings.scriptSearchQueryStoreMaxDescription).trim()
+ ? thisDescription.substr(0, storeDescriptionLength).trim()
: ''
),
author: aUser.name,
@@ -1899,7 +2103,8 @@ exports.storeScript = function (aUser, aMeta, aBuf, aUpdate, aCallback) {
rating: 0,
about: '',
_about: '',
- updated: new Date(),
+ created: now,
+ updated: now,
hash: crypto.createHash('sha512').update(aBuf).digest('hex'),
votes: 0,
flags: { critical: 0, absolute: 0 },
@@ -1930,9 +2135,15 @@ exports.storeScript = function (aUser, aMeta, aBuf, aUpdate, aCallback) {
}), null);
return;
}
+
+ storeDescriptionLength = settings.scriptSearchQueryStoreMaxDescription;
+ storeDescriptionLength = rLogographic.test(thisDescription)
+ ? parseInt(storeDescriptionLength / logographicDivisor)
+ : storeDescriptionLength;
+
aScript._description = (
thisDescription
- ? thisDescription.substr(0, settings.scriptSearchQueryStoreMaxDescription).trim()
+ ? thisDescription.substr(0, storeDescriptionLength).trim()
: ''
);
aScript.meta = aMeta;
@@ -2096,10 +2307,11 @@ exports.webhook = function (aReq, aRes) {
var reponame = null;
var repos = {};
var repo = null;
+ var update = null;
// Return if script storage is in read-only mode
if (process.env.READ_ONLY_SCRIPT_STORAGE === 'true') {
- aRes.status(423).send(); // Locked
+ aRes.status(423).send('Resource is in lockdown.'); // Locked
return;
}
@@ -2109,30 +2321,30 @@ exports.webhook = function (aReq, aRes) {
// IPv6 address for caller will return `false` from dep in this
// configuration
if (githubHookAddresses.length < 1) {
- aRes.status(502).send(); // Bad gateway
+ aRes.status(502).send('Authorized gateways absent.'); // Bad gateway
return;
}
if (!ipRangeCheck(aReq.connection.remoteAddress, githubHookAddresses)) {
- aRes.status(401).send(); // Unauthorized: No challenge and silent iterations
+ aRes.status(401).send('Unauthorized gateway.'); // Unauthorized: No challenge and silent iterations
return;
}
// If media type is not corectly set up in GH webhook then reject
// NOTE: Keep in sync with newScriptPage.html view
if (!aReq.is('application/x-www-form-urlencoded')) {
- aRes.status(415).send(); // Unsupported media type
+ aRes.status(415).send('Unsupported Content-Type for webhook.'); // Unsupported media type
return;
}
if (!aReq.body.payload) {
- aRes.status(502).send(); // Bad gateway
+ aRes.status(502).send('No initial payload.'); // Bad gateway
return;
}
payload = JSON.parse(aReq.body.payload);
if (!payload) {
- aRes.status(400).send(); // Bad request
+ aRes.status(400).send('No parsed payload.'); // Bad request
return;
}
@@ -2140,23 +2352,24 @@ exports.webhook = function (aReq, aRes) {
case 'ping':
// Initial setup of the webhook checks... informational
if (!payload.hook && !payload.hook.events) {
- aRes.status(502).send(); // Bad gateway e.g. something catastrophic to look into
+ aRes.status(502).send('Bad gateway.'); // Bad gateway e.g. something catastrophic to look into
return;
}
if (payload.hook.events.length !== 1 || payload.hook.events.indexOf('push') !== 0) {
- aRes.status(413).send(); // Payload (events) too large
+ aRes.status(413).send('Too many events.'); // Payload (events) too large
return;
}
- aRes.status(200).send(); // Send acknowledgement for GH history
+ aRes.status(200).send('pong.'); // Send acknowledgement for GH history
return;
break;
case 'push':
// Pushing a change
+ update = aReq.get('X-GitHub-Delivery');
break;
default:
- aRes.status(400).send(); // Bad request
+ aRes.status(400).send('Unsupported event.'); // Bad request
return;
}
@@ -2164,7 +2377,7 @@ exports.webhook = function (aReq, aRes) {
// Only accept commits from the `master` branch
if (payload.ref !== 'refs/heads/master') {
- aRes.status(403).send(); // Forbidden
+ aRes.status(403).send('Default branch is not `master`.'); // Forbidden
return;
}
@@ -2179,21 +2392,26 @@ exports.webhook = function (aReq, aRes) {
var repoManager = null;
if (aErr) {
- aRes.status(500).send(); // Internal server error: Possibly 502 Bad gateway to DB or bad dep.
+ aRes.status(500).send('Internal server error.'); // Internal server error: Possibly 502 Bad gateway to DB or bad dep.
return;
}
if (!aUser) {
- aRes.status(400).send(); // Bad request: Possibly 410 Gone from DB but not GH
+ aRes.status(400).send('Bad request.'); // Bad request: Possibly 410 Gone from DB but not GH
return;
}
if (!aUser.consented) {
- aRes.status(451).send(); // Reject until consented
+ aRes.status(451).send('Consent is required for access.'); // Reject until consented
return;
}
- aRes.status(202).send(); // Close connection with Accepted but processing
+ if (aUser.strategies.indexOf('github') <= -1) { // Don't rely on just `ghUsername`!
+ aRes.status(403).send('Requires supported authentication strategy on account.'); // Reject due to lack of GitHub as Auth
+ return;
+ }
+
+ aRes.status(202).send('Your request is queued.'); // Close connection with Accepted but processing
// Gather the modified user scripts
payload.commits.forEach(function (aCommit) {
@@ -2206,10 +2424,28 @@ exports.webhook = function (aReq, aRes) {
// Update modified scripts
repoManager = RepoManager.getManager(null, aUser, repos);
- repoManager.loadScripts(true, function (aErr) {
+
+ repoManager.loadSyncs(update, function (aErr) {
if (aErr) {
- console.error(aErr);
+ // These currently should be server side errors so always log
+ console.error(update, aErr);
+ return;
}
+
+ repoManager.loadScripts(update, function (aErr) {
+
+ var code = null;
+ if (aErr) {
+ // Some errors could be user generated or dep generated user error,
+ // usually ignore those since handled with Sync model and visible
+ // to end user. We shouldn't be sending non-numeric codes.
+ code = (aErr instanceof statusError ? aErr.status.code : aErr.code);
+ if (code && !isNaN(code) && code >= 500) {
+ console.error(update, aErr);
+ }
+ }
+ });
});
+
});
};
diff --git a/controllers/strategies.json b/controllers/strategies.json
index 7ec165d17..1c37d5799 100644
--- a/controllers/strategies.json
+++ b/controllers/strategies.json
@@ -1,9 +1,4 @@
{
- "facebook": {
- "name": "Facebook",
- "oauth": true,
- "readonly": false
- },
"github": {
"name": "GitHub",
"oauth": true,
@@ -27,21 +22,11 @@
"reddit": {
"name": "Reddit",
"oauth": true,
- "readonly": false
+ "readonly": true
},
"steam": {
"name": "Steam",
"oauth": false,
"readonly": false
- },
- "twitter": {
- "name": "Twitter",
- "oauth": true,
- "readonly": false
- },
- "yahoo": {
- "name": "Yahoo!",
- "oauth": false,
- "readonly": false
}
}
diff --git a/controllers/user.js b/controllers/user.js
index 55bfcae93..eeb9fe004 100644
--- a/controllers/user.js
+++ b/controllers/user.js
@@ -15,12 +15,17 @@ var async = require('async');
var _ = require('underscore');
var util = require('util');
var rfc2047 = require('rfc2047');
+var expressCaptcha = require('express-svg-captcha');
+var svgCaptcha = require('svg-captcha');
var SPDX = require('spdx-license-ids');
//--- Model inclusions
-var Comment = require('../models/comment').Comment;
var Script = require('../models/script').Script;
+var Comment = require('../models/comment').Comment;
+var Vote = require('../models/vote').Vote;
+var Flag = require('../models/flag').Flag;
+var Sync = require('../models/sync').Sync;
var Strategy = require('../models/strategy').Strategy;
var User = require('../models/user').User;
var Discussion = require('../models/discussion').Discussion;
@@ -39,6 +44,8 @@ var helpers = require('../libs/helpers');
var modelParser = require('../libs/modelParser');
var modelQuery = require('../libs/modelQuery');
+var scriptStorageLib = require('../libs/scriptStorage');
+
var flagLib = require('../libs/flag');
var removeLib = require('../libs/remove');
var stats = require('../libs/stats');
@@ -46,11 +53,11 @@ var github = require('./../libs/githubClient');
var renderMd = require('../libs/markdown').renderMd;
var getDefaultPagination = require('../libs/templateHelpers').getDefaultPagination;
-var statusCodePage = require('../libs/templateHelpers').statusCodePage;
var execQueryTask = require('../libs/tasks').execQueryTask;
var countTask = require('../libs/tasks').countTask;
var pageMetadata = require('../libs/templateHelpers').pageMetadata;
var orderDir = require('../libs/templateHelpers').orderDir;
+var statusCodePage = require('../libs/templateHelpers').statusCodePage;
var getSessionDataList = require('../libs/modifySessions').getSessionDataList;
var extendSession = require('../libs/modifySessions').extend;
@@ -112,24 +119,53 @@ exports.exist = function (aReq, aRes) {
aRes.set('Warning', msg);
}
- aRes.status(200).send();
+ if (aUser._probationary) {
+ aRes.status(204).send();
+ } else {
+ aRes.status(200).send();
+ }
});
};
// API - Request for extending a logged in user session
exports.extend = function (aReq, aRes, aNext) {
var authedUser = aReq.session.user;
+ var redirectTo = helpers.isSameOrigin(aReq.body.redirectTo);
+
+ redirectTo = redirectTo.result ? redirectTo.URL : new URL('/', helpers.baseOrigin);
User.findOne({
_id: authedUser._id,
sessionIds: { "$in": [ aReq.sessionID ] }
}, function (aErr, aUser) {
- // WARNING: No err handling
+ if (aErr) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: aErr.code || 500,
+ statusMessage: aErr.message
+ });
+ return;
+ }
+
+ if (!aUser) {
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'noextend';
+ aRes.redirect(redirectTo);
+ return;
+ }
+
+ if (aUser._probationary) {
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'noextend';
+ aRes.redirect(redirectTo);
+ return;
+ }
extendSession(aReq, aUser, function (aErr) {
if (aErr) {
if (aErr === 'Already extended') {
- aNext();
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'noextend';
+ aRes.redirect(redirectTo);
return;
}
@@ -140,7 +176,7 @@ exports.extend = function (aReq, aRes, aNext) {
return;
}
- aRes.redirect('back');
+ aRes.redirect(redirectTo);
});
});
};
@@ -151,20 +187,21 @@ exports.destroyOne = function (aReq, aRes, aNext) {
var authedUser = aReq.session.user;
var username = aReq.body.username;;
var id = aReq.body.id;
+ var redirectTo = helpers.isSameOrigin(aReq.body.redirectTo);
+
+ redirectTo = redirectTo.result ? redirectTo.URL : new URL('/', helpers.baseOrigin);
if (!username) {
- statusCodePage(aReq, aRes, aNext, {
- statusCode: 400,
- statusMessage: 'username must be set.'
- });
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'noname';
+ aRes.redirect(redirectTo);
return;
}
if (aReq.sessionID === id) {
- statusCodePage(aReq, aRes, aNext, {
- statusCode: 403,
- statusMessage: 'Cannot delete the current session.'
- });
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'curses';
+ aRes.redirect(redirectTo);
return;
}
@@ -175,57 +212,65 @@ exports.destroyOne = function (aReq, aRes, aNext) {
var store = aReq.sessionStore;
var user = null;
- if (aErr || !aUser) {
- aNext();
- return;
- }
-
- user = aUser; // NOTE: We really shouldn't need modelParser here
-
- if (authedUser.role > user.role) {
+ if (aErr) {
+ console.error(aErr);
statusCodePage(aReq, aRes, aNext, {
- statusCode: 403,
- statusMessage: 'Cannot delete a session with a higher rank.'
+ statusCode: aErr.code,
+ statusMessage: aErr.message
});
return;
}
- // You can only delete your own other sessions when you are not an admin
- if (!authedUser.isAdmin && authedUser.name !== user.name) {
- statusCodePage(aReq, aRes, aNext, {
- statusCode: 403,
- statusMessage: 'Cannot delete a session that is not owned.'
- });
+ if (aUser) {
+ user = aUser; // NOTE: We really shouldn't need modelParser here
+
+ if (authedUser.role > user.role) {
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'hirank';
+ aRes.redirect(redirectTo);
+ return;
+ }
+
+ // You can only delete your own other sessions when you are not an admin
+ if (!authedUser.isAdmin && authedUser.name !== user.name) {
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'noown';
+ aRes.redirect(redirectTo);
+ return;
+ }
+ } else if (!authedUser.isAdmin) {
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'curses';
+ aRes.redirect(redirectTo);
return;
}
store.get(id, function (aErr, aSess) {
if (aErr) {
statusCodePage(aReq, aRes, aNext, {
- statusCode: 500,
- statusMessage: aErr
+ statusCode: aErr.code || 500,
+ statusMessage: aErr.message
});
return;
}
- if (!authedUser.isAdmin && aSess.passport.oujsOptions.authFrom) {
- statusCodePage(aReq, aRes, aNext, {
- statusCode: 403,
- statusMessage: 'Cannot delete a session that is being administered.'
- });
+ if (!authedUser.isAdmin && aSess.passport && aSess.passport.oujsOptions.authFrom) {
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'noadmin';
+ aRes.redirect(redirectTo);
return;
}
- destroyOneSession(aReq, user, id, function (aErr) {
+ destroyOneSession(aReq, authedUser.isAdmin, user, id, function (aErr) {
if (aErr) {
- statusCodePage(aReq, aRes, aNext, {
- statusCode: 500,
- statusMessage: aErr
- });
+ // NOTE: Watchpoint
+ redirectTo.search = (redirectTo.search ? redirectTo.search + '&' : '') +
+ 'curses';
+ aRes.redirect(redirectTo);
return;
}
- aRes.redirect('back');
+ aRes.redirect(redirectTo);
});
});
});
@@ -284,6 +329,9 @@ var getUserPageTasks = function (aOptions) {
var user = null;
var userScriptListCountQuery = null;
var userCommentListCountQuery = null;
+ var userVoteListCountQuery = null;
+ var userFlagListCountQuery = null;
+ var userSyncListCountQuery = null;
var tasks = [];
// Shortcuts
@@ -299,6 +347,18 @@ var getUserPageTasks = function (aOptions) {
userCommentListCountQuery = Comment.find({ _authorId: user._id, flagged: { $ne: true } });
tasks.push(countTask(userCommentListCountQuery, aOptions, 'commentListCount'));
+ // userVoteListCountQuery
+ userVoteListCountQuery = Vote.find({ _userId: user._id });
+ tasks.push(countTask(userVoteListCountQuery, aOptions, 'voteListCount'));
+
+ // userFlagListCountQuery
+ userFlagListCountQuery = Flag.find({ _userId: user._id });
+ tasks.push(countTask(userFlagListCountQuery, aOptions, 'flagListCount'));
+
+ // userSyncListCountQuery
+ userSyncListCountQuery = Sync.find({ _authorId: user._id });
+ tasks.push(countTask(userSyncListCountQuery, aOptions, 'syncListCount'));
+
return tasks;
};
@@ -392,7 +452,7 @@ exports.userListPage = function (aReq, aRes, aNext) {
async.parallel([
function (aCallback) {
- if (!!!options.isFlagged || !options.isAdmin) { // NOTE: Watchpoint
+ if (!!!options.isFlagged || !options.isMod) { // NOTE: Watchpoint
aCallback();
return;
}
@@ -421,7 +481,9 @@ exports.userListPage = function (aReq, aRes, aNext) {
pageMetadata(options, 'Users');
// Order dir
- orderDir(aReq, options, 'name', 'desc');
+ orderDir(aReq, options, 'name', 'asc');
+ orderDir(aReq, options, 'created', 'desc');
+ orderDir(aReq, options, 'updated', 'desc');
orderDir(aReq, options, 'role', 'asc');
// userListQuery
@@ -430,6 +492,14 @@ exports.userListPage = function (aReq, aRes, aNext) {
// userListQuery: Defaults
modelQuery.applyUserListQueryDefaults(userListQuery, options, aReq);
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// userListQuery: Pagination
pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
@@ -457,6 +527,10 @@ exports.view = function (aReq, aRes, aNext) {
}
function render() {
+ // Set crawlers to ignore for indexing. Following is currently managed in markdown rendering.
+ // Because we use common html across multiple pages meta tags shouldn't be used.
+ aRes.set('X-Robots-Tag', 'noindex');
+
aRes.render('pages/userPage', options);
}
@@ -464,7 +538,7 @@ exports.view = function (aReq, aRes, aNext) {
async.parallel([
function (aCallback) {
- if (!options.isAdmin) { // NOTE: Watchpoint
+ if (!options.isMod) { // NOTE: Watchpoint
aCallback();
return;
}
@@ -627,6 +701,14 @@ exports.userCommentListPage = function (aReq, aRes, aNext) {
modelQuery.applyCommentListQueryDefaults(commentListQuery, options, aReq);
commentListQuery.sort('-created');
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// commentListQuery: Pagination
pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
@@ -637,7 +719,7 @@ exports.userCommentListPage = function (aReq, aRes, aNext) {
});
// SearchBar
- options.searchBarPlaceholder = 'Search Comments from ' + user.name;
+ options.searchBarPlaceholder = options.authRequired + 'Search Comments from ' + user.name;
options.searchBarFormAction = '';
//--- Tasks
@@ -696,7 +778,7 @@ exports.userScriptListPage = function (aReq, aRes, aNext) {
async.parallel([
function (aCallback) {
- if (!!!options.isFlagged || !options.isAdmin) { // NOTE: Watchpoint
+ if (!!!options.isFlagged || !options.isMod) { // NOTE: Watchpoint
aCallback();
return;
}
@@ -811,12 +893,20 @@ exports.userScriptListPage = function (aReq, aRes, aNext) {
modelQuery.applyScriptListQueryDefaults(scriptListQuery, options, aReq);
}
+ if (options.authToSearch) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403, // Forbidden
+ statusMessage: 'Please Sign In to Search'
+ });
+ return;
+ }
+
// scriptListQuery: Pagination
pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
// SearchBar
- options.searchBarPlaceholder = 'Search ' +
- (options.librariesOnly ? 'Libraries' : 'Scripts') + ' from ' + user.name;
+ options.searchBarPlaceholder = options.authRequired + 'Search ' +
+ (librariesOnly ? 'Libraries' : 'Scripts') + ' from ' + user.name;
options.searchBarFormAction = '';
//--- Tasks
@@ -838,6 +928,115 @@ exports.userScriptListPage = function (aReq, aRes, aNext) {
});
};
+exports.userSyncListPage = function (aReq, aRes, aNext) {
+ //
+ var username = aReq.params.username;
+
+ User.findOne({
+ name: caseInsensitive(username)
+ }, function (aErr, aUser) {
+ function preRender() {
+ // syncList
+ options.syncList = _.map(options.syncList, modelParser.parseSync);
+
+ // Pagination
+ options.paginationRendered = pagination.renderDefault(aReq);
+ }
+
+ function render() {
+ aRes.render('pages/userSyncListPage', options);
+ }
+
+ function asyncComplete() {
+ preRender();
+ render();
+ }
+
+ //
+ var options = {};
+ var authedUser = aReq.session.user;
+ var user = null;
+ var syncListQuery = null;
+ var pagination = null;
+ var tasks = [];
+
+ if (aErr || !aUser) {
+ aNext();
+ return;
+ }
+
+ // Session
+ options.authedUser = authedUser = modelParser.parseUser(authedUser);
+ options.isMod = authedUser && authedUser.isMod;
+ options.isAdmin = authedUser && authedUser.isAdmin;
+
+ // User
+ user = options.user = modelParser.parseUser(aUser);
+ options.isYou = authedUser && user && authedUser._id == user._id;
+
+ // If not you or not synacable auth strategy move along
+ if (!(options.isYou || options.isAdmin) || !options.user.canSync) {
+ aNext();
+ return;
+ }
+
+ // Page metadata
+ pageMetadata(options, [user.name, 'Users']);
+ options.isUserSyncListPage = true;
+
+ // Order dir
+ orderDir(aReq, options, 'target', 'desc');
+ orderDir(aReq, options, 'created', 'asc');
+ orderDir(aReq, options, 'updated', 'asc');
+ orderDir(aReq, options, 'response', 'desc');
+
+ // SyncListQuery
+ syncListQuery = Sync.find();
+
+ // syncListQuery: author=user
+ syncListQuery.find({ _authorId: user._id });
+
+ // syncListQuery: Defaults
+ modelQuery.applySyncListQueryDefaults(syncListQuery, options, aReq);
+
+ // syncListQuery: Pagination
+ pagination = options.pagination; // is set in modelQuery.apply___ListQueryDefaults
+
+ // SearchBar
+ options.searchBarPlaceholder = 'Search Syncs from ' + user.name;
+ options.searchBarFormAction = '';
+
+ //--- Tasks
+
+ // Pagination
+ tasks.push(pagination.getCountTask(syncListQuery));
+
+ // syncListQuery
+ tasks.push(execQueryTask(syncListQuery, options, 'syncList'));
+
+ // UserPage tasks
+ tasks = tasks.concat(getUserPageTasks(options));
+
+ //--
+ async.parallel(tasks, asyncComplete);
+ });
+};
+
+var captcha = new expressCaptcha(settings.captchaOpts);
+
+exports.userEditProfilePageCaptcha = function (aReq, aRes, aNext) {
+ var authedUser = aReq.session.user;
+ var username = aReq.params.username;
+
+
+ if (authedUser.slugUrl === username) {
+ (captcha.generate())(aReq, aRes, aNext);
+ } else {
+ aRes.set('X-Robots-Tag', 'noindex, nofollow');
+ aRes.type('svg').status(200).send(svgCaptcha('3.14 x 2.71 / 0', settings.captchaOpts));
+ }
+}
+
exports.userEditProfilePage = function (aReq, aRes, aNext) {
var authedUser = aReq.session.user;
@@ -876,6 +1075,8 @@ exports.userEditProfilePage = function (aReq, aRes, aNext) {
options.user = user = modelParser.parseUser(aUser);
options.isYou = authedUser && user && authedUser._id == user._id;
+ options.user.hasCaptcha = true;
+
// Page metadata
pageMetadata(options, [user.name, 'Users']);
@@ -934,6 +1135,8 @@ exports.userEditPreferencesPage = function (aReq, aRes, aNext) {
var scriptListQuery = null;
var tasks = [];
+ var thisURL = null;
+
if (aErr || !aUser) {
aNext();
return;
@@ -948,6 +1151,14 @@ exports.userEditPreferencesPage = function (aReq, aRes, aNext) {
options.user = user = modelParser.parseUser(aUser);
options.isYou = authedUser && user && authedUser._id == user._id;
+ // redirectTo (forced)
+ thisURL = new URL(aReq.url, helpers.baseOrigin);
+ ['noname', 'curses', 'hirank', 'noown', 'noadmin', 'noextend']
+ .forEach(function (aE, aI, aA) {
+ thisURL.searchParams.delete(aE);
+ });
+ options.redirectToo = thisURL.pathname + (thisURL.search ? thisURL.search : '');
+
// Page metadata
pageMetadata(options, [user.name, 'Users']);
@@ -992,12 +1203,14 @@ exports.userEditPreferencesPage = function (aReq, aRes, aNext) {
if (userStrats.indexOf(aStrat.name) > -1) {
options.usedStrategies.push({
'strat': aStrat.name,
- 'display': aStrat.display
+ 'display': aStrat.display,
+ 'disabled': strategies[aStrat.name] ? strategies[aStrat.name].readonly : true
});
} else {
options.openStrategies.push({
'strat': aStrat.name,
- 'display': aStrat.display
+ 'display': aStrat.display,
+ 'disabled': strategies[aStrat.name] ? strategies[aStrat.name].readonly : true
});
}
});
@@ -1010,12 +1223,14 @@ exports.userEditPreferencesPage = function (aReq, aRes, aNext) {
if (userStrats.indexOf(name) > -1) {
options.usedStrategies.push({
'strat': name,
- 'display': strategy.name
+ 'display': strategy.name,
+ 'disabled': strategy.readonly
});
} else {
options.openStrategies.push({
'strat': name,
- 'display': strategy.name
+ 'display': strategy.name,
+ 'disabled': strategy.readonly
});
}
}
@@ -1029,6 +1244,9 @@ exports.userEditPreferencesPage = function (aReq, aRes, aNext) {
options.defaultStrategy = strategies[defaultStrategy]
? strategies[defaultStrategy].name
: null;
+ options.defaultStrategyDisabled = strategies[defaultStrategy]
+ ? strategies[defaultStrategy].readonly
+ : true;
options.defaultStrat = defaultStrategy;
options.haveOtherStrategies = options.usedStrategies.length > 0;
@@ -1146,12 +1364,31 @@ exports.userGitHubRepoListPage = function (aReq, aRes, aNext) {
}
function asyncComplete(aErr) {
+ var msg = null;
+
if (aErr) {
- console.error(aErr);
- statusCodePage(aReq, aRes, aNext, {
- statusCode: 500,
- statusMessage: 'Server Error'
- });
+ switch (aErr.code) { // NOTE: Important to test for GH code vs potential OUJS code
+ case 401:
+ // fallsthrough
+ case 403:
+ try {
+ msg = JSON.parse(aErr.message);
+ } catch (aE) {
+ msg = { message: aErr.message };
+ }
+ console.warn(msg.message);
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 503,
+ statusMessage: 'Service unavailable. Please check back later.'
+ });
+ break;
+ default:
+ console.error(aErr);
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: 'Server Error'
+ });
+ }
return;
}
@@ -1172,6 +1409,22 @@ exports.userGitHubRepoListPage = function (aReq, aRes, aNext) {
options.isAdmin = authedUser && authedUser.isAdmin;
// GitHub
+ if (!options.authedUser.hasGithub) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403,
+ statusMessage: 'You do not have GitHub as an auth strategy.'
+ });
+ return;
+ }
+
+ if (process.env.DISABLE_SCRIPT_IMPORT === 'true') {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 503,
+ statusMessage: 'Service unavailable. Please check back later.'
+ });
+ return;
+ }
+
options.githubUserId = githubUserId =
aReq.query.user || authedUser.ghUsername || authedUser.githubUserId();
@@ -1257,8 +1510,31 @@ exports.userGitHubRepoPage = function (aReq, aRes, aNext) {
}
function asyncComplete(aErr) {
+ var msg = null;
+
if (aErr) {
- aNext();
+ switch (aErr.code) { // NOTE: Important to test for GH code vs potential OUJS code
+ case 401:
+ // fallsthrough
+ case 403:
+ try {
+ msg = JSON.parse(aErr.message);
+ } catch (aE) {
+ msg = { message: aErr.message };
+ }
+ console.warn(msg.message);
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 503,
+ statusMessage: 'Service unavailable. Please check back later.'
+ });
+ break;
+ default:
+ console.error(aErr);
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: 'Server Error'
+ });
+ }
return;
}
@@ -1279,6 +1555,22 @@ exports.userGitHubRepoPage = function (aReq, aRes, aNext) {
options.isAdmin = authedUser && authedUser.isAdmin;
// GitHub
+ if (!options.authedUser.hasGithub) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403,
+ statusMessage: 'You do not have GitHub as an auth strategy.'
+ });
+ return;
+ }
+
+ if (process.env.DISABLE_SCRIPT_IMPORT === 'true') {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 503,
+ statusMessage: 'Service unavailable. Please check back later.'
+ });
+ return;
+ }
+
options.githubUserId = githubUserId =
aReq.query.user || authedUser.ghUsername || authedUser.githubUserId();
@@ -1386,6 +1678,22 @@ exports.userGitHubImportScriptPage = function (aReq, aRes, aNext) {
options.isOwnRepo = authedUser.ghUsername && authedUser.ghUsername === options.githubUserId;
+ if (!options.authedUser.hasGithub) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 403,
+ statusMessage: 'You do not have GitHub as an auth strategy.'
+ });
+ return;
+ }
+
+ if (process.env.DISABLE_SCRIPT_IMPORT === 'true') {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 503,
+ statusMessage: 'Service unavailable. Please check back later.'
+ });
+ return;
+ }
+
if (!options.isOwnRepo) {
statusCodePage(aReq, aRes, aNext, {
statusCode: 403,
@@ -1561,32 +1869,54 @@ exports.userGitHubImportScriptPage = function (aReq, aRes, aNext) {
},
], function (aErr) {
var script = null;
+ var code = null;
+ var msg = null;
if (aErr) {
- console.error([
- aErr,
- authedUser.name + ' ' + githubUserId + ' ' + githubRepoName + ' ' + githubBlobPath
+ code = (aErr instanceof statusError ? aErr.status.code : aErr.code);
+ if (code && !isNaN(code) && code >= 500) {
+ console.error([
+ aErr,
+ authedUser.name + ' ' + githubUserId + ' ' + githubRepoName + ' ' + githubBlobPath
- ].join('\n'));
+ ].join('\n'));
+ }
if (!(aErr instanceof String)) {
- statusCodePage(aReq, aRes, aNext, {
- statusCode: (aErr instanceof statusError ? aErr.status.code : aErr.code),
- statusMessage: (aErr instanceof statusError ? aErr.status.message : aErr.message),
- isCustomView: true,
- statusData: {
- isGHImport: true,
- utf_pathname: githubPathName,
- utf_pathext: githubPathExt,
- user: encodeURIComponent(githubUserId),
- repo: encodeURIComponent(githubRepoName),
- default_branch: encodeURIComponent(githubDefaultBranch),
- path: encodeURIComponent(githubBlobPath)
- }
- });
+ switch (aErr.code) { // NOTE: Important to test for GH code vs potential OUJS code
+ case 401:
+ // fallsthrough
+ case 403:
+ try {
+ msg = JSON.parse(aErr.message);
+ } catch (aE) {
+ msg = { message: aErr.message };
+ }
+ console.warn(msg.message);
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 503,
+ statusMessage: 'Service unavailable. Please check back later.'
+ });
+ break;
+ default:
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: (aErr instanceof statusError ? aErr.status.code : aErr.code),
+ statusMessage: (aErr instanceof statusError ? aErr.status.message : aErr.message),
+ isCustomView: true,
+ statusData: {
+ isGHImport: true,
+ utf_pathname: githubPathName,
+ utf_pathext: githubPathExt,
+ user: encodeURIComponent(githubUserId),
+ repo: encodeURIComponent(githubRepoName),
+ default_branch: encodeURIComponent(githubDefaultBranch),
+ path: encodeURIComponent(githubBlobPath)
+ }
+ });
+ }
} else {
statusCodePage(aReq, aRes, aNext, {
- statusCode: 500, // NOTE: Watchpoint
+ statusCode: 500,
statusMessage: aErr
});
}
@@ -1595,7 +1925,7 @@ exports.userGitHubImportScriptPage = function (aReq, aRes, aNext) {
script = modelParser.parseScript(options.script);
- aRes.redirect(script.scriptPageUri);
+ aRes.redirect(script.scriptEditMetadataPageUri);
});
};
@@ -1660,14 +1990,59 @@ exports.uploadScript = function (aReq, aRes, aNext) {
form = new formidable.IncomingForm();
form.parse(aReq, function (aErr, aFields, aFiles) {
- // WARNING: No err handling
+ var msg = null;
+
+ if (aErr) {
+ if (aErr) {
+ msg = 'Unknown error when form parsing at `uploadScript`.'
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: [msg, 'Please contact Development.'].join(' ')
+ });
+ console.error(aErr);
+ return;
+ }
+ }
+
+ if (!aFields || (aFields && aFields.uploadScript && !aFields.uploadScript[0])) {
+ msg = '`uploadScript` field is missing.';
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: msg
+ });
+ console.error(msg);
+ return;
+ }
+
+ if (aFields.uploadScript[0] !== 'true') {
+ msg = '`uploadScript` field is invalid :=';
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: [msg, aFields.uploadScript].join(' ')
+ });
+ console.error([msg, aFields.uploadScript].join(' '));
+ return;
+ }
+
+ // TODO: Maybe add more fields validation
+
+ if (!aFiles) {
+ msg = 'Upload Script is missing File.';
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: msg
+ });
+ console.error(msg);
+ return;
+ }
//
var isLib = aReq.params.isLib;
- var script = aFiles.script;
+ var script = aFiles.script && aFiles.script[0] ? aFiles.script[0] : null;
var stream = null;
var bufs = [];
var authedUser = aReq.session.user;
+ var msg = null;
// Reject missing files
if (!script) {
@@ -1680,9 +2055,9 @@ exports.uploadScript = function (aReq, aRes, aNext) {
// Reject non-js file
if (isDev) {
- console.log('Upload Script MIME Content-Type is `' + script.type + '`');
+ console.log('Upload Script MIME Content-Type is `' + script.mimetype + '`');
}
- switch (script.type) {
+ switch (script.mimetype) {
case 'application/x-javascript': // #872 #1661
case 'application/javascript': // #1599
case 'text/javascript': // Default
@@ -1697,36 +2072,71 @@ exports.uploadScript = function (aReq, aRes, aNext) {
// Reject huge file
if (script.size > settings.maximum_upload_script_size) {
+ msg = util.format('Selected file size is larger than maximum (%s bytes).',
+ settings.maximum_upload_script_size)
statusCodePage(aReq, aRes, aNext, {
statusCode: 400,
- statusMessage: 'Selected file is too big.'
+ statusMessage: msg
});
return;
}
- stream = fs.createReadStream(script.path);
+ stream = fs.createReadStream(script.filepath);
+
+ stream.on('error', function(aErr) {
+ msg = 'Upload Script failed.';
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: msg
+ });
+ console.error(aErr);
+ return;
+ });
+
stream.on('data', function (aData) {
bufs.push(aData);
});
stream.on('end', function () {
User.findOne({ _id: authedUser._id }, function (aErr, aUser) {
- // WARNING: No err handling
+ var msg = null;
+
+ if (aErr) {
+ msg = 'Unknown error when finding User at processing Script stream.'
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: [msg, 'Please contact Development.'].join(' ')
+ });
+ console.error(aErr);
+ return;
+ }
+
+ if (!aUser) {
+ msg = 'No user found.'
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: msg
+ });
+ return;
+ }
var bufferConcat = Buffer.concat(bufs);
scriptStorage.getMeta(bufs, function (aMeta) {
+ var msg = null;
+
if (!isLib && !!scriptStorage.findMeta(aMeta, 'UserLibrary')) {
+ msg = 'UserLibrary metadata block found while attempting to upload as a UserScript.';
statusCodePage(aReq, aRes, aNext, {
statusCode: 400,
- statusMessage:
- 'UserLibrary metadata block found while attempting to upload as a UserScript.'
+ statusMessage: msg
});
return;
} else if (isLib && !!!scriptStorage.findMeta(aMeta, 'UserLibrary')) {
+ msg = 'UserLibrary metadata block missing.';
statusCodePage(aReq, aRes, aNext, {
statusCode: 400,
- statusMessage: 'UserLibrary metadata block missing.'
+ statusMessage: msg
});
return;
}
@@ -1744,7 +2154,8 @@ exports.uploadScript = function (aReq, aRes, aNext) {
'/' + (isLib ? 'libs' : 'scripts') + '/' +
encodeURIComponent(helpers.cleanFilename(aScript.author)) +
'/' +
- encodeURIComponent(helpers.cleanFilename(aScript.name))
+ encodeURIComponent(helpers.cleanFilename(aScript.name)) +
+ (aScript._about !== '' ? '' : '/edit')
);
});
});
@@ -1759,16 +2170,57 @@ exports.update = function (aReq, aRes, aNext) {
var authedUser = aReq.session.user;
// Update the about section of a user's profile
- User.findOneAndUpdate({ _id: authedUser._id }, { about: aReq.body.about },
- function (aErr, aUser) {
+ User.findOne({ _id: authedUser._id }, function (aErr, aUser) {
+ if (aErr) {
+ aRes.redirect('/');
+ return;
+ }
+
+ if (!aUser) {
+ msg = 'No user found.'
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: msg
+ });
+ return;
+ }
+
+ if (!captcha.validate(aReq, aReq.body.captcha)) {
+ aRes.redirect('/users/' + encodeURIComponent(aUser.name));
+ return;
+ }
+
+ // Update DB
+ aUser.updated = new Date();
+ aUser.about = aReq.body.about;
+ aUser.save(function (aErr, aUser) {
+ var msg = null;
+
if (aErr) {
- aRes.redirect('/');
+ msg = 'Unknown error when saving Profile.';
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: [msg, 'Please contact Development'].join(' ')
+ });
+ console.error(aErr);
+ return;
+ }
+ if (!aUser) {
+ msg = 'No user handle when saving Profile.';
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: [msg, 'Please contact Development'].join(' ')
+ });
+ console.error(msg)
return;
}
+ // Update session
authedUser.about = aUser.about;
+
aRes.redirect('/users/' + encodeURIComponent(aUser.name));
});
+ });
};
// Submit a script through the web editor
@@ -1781,15 +2233,39 @@ exports.submitSource = function (aReq, aRes, aNext) {
function storeScript(aMeta, aSource) {
User.findOne({ _id: authedUser._id }, function (aErr, aUser) {
- // WARNING: No err handling
+ var msg = null;
+
+ if (aErr) {
+ msg = 'Unknown error when finding User at `submitSource`.'
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: [msg, 'Please contact Development.'].join(' ')
+ });
+ console.error(aErr);
+ return;
+ }
+
+ if (!aUser) {
+ msg = 'No user found.'
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: msg
+ });
+ return;
+ }
scriptStorage.storeScript(aUser, aMeta, aSource, false, function (aErr, aScript) {
- var redirectUri = aScript
- ? ((aScript.isLib ? '/libs/' : '/scripts/') +
- encodeURIComponent(helpers.cleanFilename(aScript.author)) +
- '/' +
- encodeURIComponent(helpers.cleanFilename(aScript.name)))
- : aReq.body.url;
+ var msg = null;
+
+ var redirectUri = (
+ aScript
+ ? ((aScript.isLib ? '/libs/' : '/scripts/') +
+ encodeURIComponent(helpers.cleanFilename(aScript.author)) +
+ '/' +
+ encodeURIComponent(helpers.cleanFilename(aScript.name))) +
+ (aScript._about !== '' ? '' : '/edit')
+ : aReq.body.url // NOTE: Watchpoint
+ );
if (aErr) {
statusCodePage(aReq, aRes, aNext, {
@@ -1800,9 +2276,10 @@ exports.submitSource = function (aReq, aRes, aNext) {
}
if (!aScript) {
+ msg = 'No script found.';
statusCodePage(aReq, aRes, aNext, {
- statusCode: 500, // NOTE: Watch point
- statusMessage: 'No script'
+ statusCode: 500,
+ statusMessage: msg
});
return;
}
@@ -1834,7 +2311,26 @@ exports.submitSource = function (aReq, aRes, aNext) {
aScript.fork = fork;
aScript.save(function (aErr, aScript) {
- // WARNING: No err handling
+ var msg = null;
+
+ if (aErr) {
+ msg = 'Unknown error when saving at `submitSource`.';
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: [msg, 'Please contact Development'].join(' ')
+ });
+ console.error(aErr);
+ return;
+ }
+ if (!aScript) {
+ msg = 'No script handle when saving at `submitSource`.';
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: 500,
+ statusMessage: [msg, 'Please contact Development'].join(' ')
+ });
+ console.error(msg)
+ return;
+ }
aRes.redirect(redirectUri);
});
@@ -2042,7 +2538,7 @@ exports.editScript = function (aReq, aRes, aNext) {
var installNameBase = null;
var isLib = aReq.params.isLib;
var tasks = [];
- var nowDate = null;
+ var now = null;
// Session
options.authedUser = authedUser = modelParser.parseUser(authedUser);
@@ -2065,6 +2561,11 @@ exports.editScript = function (aReq, aRes, aNext) {
});
});
+ // Lockdown
+ options.lockdown = {};
+ options.lockdown.scriptStorageRO = process.env.READ_ONLY_SCRIPT_STORAGE === 'true';
+ options.lockdown.updateURLCheck = process.env.FORCE_BUSY_UPDATEURL_CHECK === 'true';
+
if (!isNew) {
installNameBase = scriptStorage.getInstallNameBase(aReq);
@@ -2081,7 +2582,10 @@ exports.editScript = function (aReq, aRes, aNext) {
var licensePrimary = null;
var copyrights = null;
var copyrightPrimary = null;
- var sinceDate = null;
+ var createdDate = null;
+ var tryURL = null;
+ var tryInstallNameBase = null;
+ var hasInvalidKey = null;
//---
if (aErr || !aScript) {
@@ -2089,11 +2593,6 @@ exports.editScript = function (aReq, aRes, aNext) {
return;
}
- // Lockdown
- options.lockdown = {};
- options.lockdown.scriptStorageRO = process.env.READ_ONLY_SCRIPT_STORAGE === 'true';
- options.lockdown.updateURLCheck = process.env.FORCE_BUSY_UPDATEURL_CHECK === 'true';
-
// Script
options.script = script = modelParser.parseScript(aScript);
@@ -2116,14 +2615,37 @@ exports.editScript = function (aReq, aRes, aNext) {
script.copyrightPrimary = copyrightPrimary;
} else {
if (authedUser) {
- sinceDate = new Date(script._sinceISOFormat);
- script.copyrightPrimary = sinceDate.getFullYear() + ', ' + authedUser.name +
+ createdDate = new Date(script.createdISOFormat);
+ script.copyrightPrimary = createdDate.getFullYear() + ', ' + authedUser.name +
' (' + helpers.baseOrigin + authedUser.userPageUrl + ')';
}
}
options.isOwner = authedUser && (authedUser._id == script._authorId
|| collaborators.indexOf(authedUser.name) > -1);
+
+ hasInvalidKey = scriptStorageLib.invalidKey(
+ aScript.author,
+ aScript.name,
+ aScript.isLib,
+ 'updateURL',
+ scriptStorage.findMeta(aScript.meta, 'UserScript.updateURL.0.value')
+ );
+
+ if (!options.isOwner && !options.isMod && hasInvalidKey) {
+ statusCodePage(aReq, aRes, aNext, {
+ statusCode: (hasInvalidKey instanceof statusError
+ ? hasInvalidKey.status.code
+ : hasInvalidKey.code
+ ),
+ statusMessage: (hasInvalidKey instanceof statusError
+ ? hasInvalidKey.status.message
+ : hasInvalidKey.message
+ )
+ });
+ return;
+ }
+
modelParser.renderScript(script);
script.installNameSlug = installNameBase;
@@ -2132,11 +2654,25 @@ exports.editScript = function (aReq, aRes, aNext) {
script.scriptPermalinkInstallPageUrlMin = 'https://' + aReq.get('host') +
script.scriptInstallPageXUrl + ".min.user.js";
+ tryInstallNameBase = scriptStorage.getInstallNameBase(aReq);
+
+ try {
+ tryURL = new URL('../' + tryInstallNameBase, 'https://example.org/');
+
+ if (
+ decodeURIComponent(tryURL.toString()) !== 'https://example.org/' + tryInstallNameBase
+ ) {
+ tryInstallNameBase = scriptStorage.getInstallNameBase(aReq, { encoding: 'uri' });
+ }
+ } catch (aE) {
+ tryInstallNameBase = scriptStorage.getInstallNameBase(aReq, { encoding: 'uri' });
+ }
+
script.scriptRawPageUrl = '/src/' + (isLib ? 'libs' : 'scripts') + '/' +
- scriptStorage.getInstallNameBase(aReq, { encoding: 'uri' }) +
+ tryInstallNameBase +
(isLib ? '.js' : '.user.js');
script.scriptRawPageXUrl = '/src/' + (isLib ? 'libs' : 'scripts') + '/' +
- scriptStorage.getInstallNameBase(aReq, { encoding: 'uri' }) +
+ tryInstallNameBase +
(isLib ? '.min.js' : '.min.user.js');
script.scriptPermalinkRawPageUrl = 'https://' + aReq.get('host') +
@@ -2224,8 +2760,8 @@ exports.editScript = function (aReq, aRes, aNext) {
options.script.licensePrimary = 'MIT'; // NOTE: Site default
if (authedUser) {
- nowDate = new Date();
- options.script.copyrightPrimary = nowDate.getFullYear() + ', ' + authedUser.name +
+ now = new Date();
+ options.script.copyrightPrimary = now.getFullYear() + ', ' + authedUser.name +
' (' + helpers.baseOrigin + authedUser.userPageUrl + ')';
}
diff --git a/controllers/vote.js b/controllers/vote.js
index 746ee05ef..c12a6dcaa 100644
--- a/controllers/vote.js
+++ b/controllers/vote.js
@@ -45,7 +45,7 @@ exports.vote = function (aReq, aRes, aNext) {
form.parse(aReq, function (aErr, aFields) {
// WARNING: No err handling
- var vote = aFields.vote;
+ var vote = aFields.vote && aFields.vote[0] ? aFields.vote[0] : null;
var unvote = false;
var type = aReq.params[0];
@@ -56,9 +56,9 @@ exports.vote = function (aReq, aRes, aNext) {
switch (vote) {
case 'up':
- // fallthrough
+ // fallsthrough
case 'down':
- // fallthrough
+ // fallsthrough
case 'un':
break;
default:
diff --git a/dev/devDBclean.gz b/dev/devDBclean.gz
new file mode 100644
index 000000000..24ef030d2
Binary files /dev/null and b/dev/devDBclean.gz differ
diff --git a/dev/devDBdirty.gz b/dev/devDBdirty.gz
new file mode 100644
index 000000000..1cfe23002
Binary files /dev/null and b/dev/devDBdirty.gz differ
diff --git a/dev/preinstall.js b/dev/preinstall.js
index 2f73abefd..0580b1f21 100644
--- a/dev/preinstall.js
+++ b/dev/preinstall.js
@@ -1,10 +1,5 @@
'use strict';
-// Define some pseudo module globals
-var isPro = require('../libs/debug').isPro;
-var isDev = require('../libs/debug').isDev;
-var isDbg = require('../libs/debug').isDbg;
-
//
// NOTE: Only use native *node* `require`s in this file
// since dependencies may not be installed yet
diff --git a/libs/blockSPDX.json b/libs/blockSPDX.json
index ee8c7f780..4e11d69d3 100644
--- a/libs/blockSPDX.json
+++ b/libs/blockSPDX.json
@@ -1,95 +1,146 @@
[
"AAL",
- "Abstyles",
- "Adobe-2006",
- "Adobe-Glyph",
"ADSL",
"AFL-1.1",
"AFL-1.2",
"AFL-2.0",
"AFL-2.1",
"AFL-3.0",
- "Afmparse",
- "AGPL-1.0",
"AGPL-1.0-only",
"AGPL-1.0-or-later",
- "AGPL-1.0",
- "Aladdin",
"AMDPLPA",
"AML",
+ "AML-glslang",
"AMPAS",
"ANTLR-PD",
- "Apache-1.0",
- "Apache-1.1",
+ "ANTLR-PD-fallback",
"APAFML",
"APSL-1.0",
"APSL-1.1",
"APSL-1.2",
"APSL-2.0",
- "Artistic-1.0-cl8",
- "Artistic-1.0-Perl",
+ "ASWF-Digital-Assets-1.0",
+ "ASWF-Digital-Assets-1.1",
+ "Abstyles",
+ "AdaCore-doc",
+ "Adobe-2006",
+ "Adobe-Display-PostScript",
+ "Adobe-Glyph",
+ "Adobe-Utopia",
+ "Afmparse",
+ "Aladdin",
+ "Apache-1.0",
+ "Apache-1.1",
+ "App-s2p",
+ "Arphic-1999",
"Artistic-1.0",
- "Bahyph",
- "Barr",
- "Beerware",
- "BitTorrent-1.0",
- "BitTorrent-1.1",
- "blessing",
- "BlueOak-1.0.0",
- "Borceux",
+ "Artistic-1.0-Perl",
+ "Artistic-1.0-cl8",
"BSD-1-Clause",
- "BSD-2-Clause-FreeBSD",
- "BSD-2-Clause-NetBSD",
+ "BSD-2-Clause-Darwin",
"BSD-2-Clause-Patent",
+ "BSD-2-Clause-Views",
"BSD-3-Clause-Attribution",
"BSD-3-Clause-Clear",
+ "BSD-3-Clause-HP",
"BSD-3-Clause-LBNL",
- "BSD-3-Clause-No-Nuclear-License-2014",
+ "BSD-3-Clause-Modification",
+ "BSD-3-Clause-No-Military-License",
"BSD-3-Clause-No-Nuclear-License",
+ "BSD-3-Clause-No-Nuclear-License-2014",
"BSD-3-Clause-No-Nuclear-Warranty",
"BSD-3-Clause-Open-MPI",
- "BSD-4-Clause-UC",
+ "BSD-3-Clause-Sun",
+ "BSD-3-Clause-acpica",
+ "BSD-3-Clause-flex",
"BSD-4-Clause",
+ "BSD-4-Clause-Shortened",
+ "BSD-4-Clause-UC",
+ "BSD-4.3RENO",
+ "BSD-4.3TAHOE",
+ "BSD-Advertising-Acknowledgement",
+ "BSD-Attribution-HPND-disclaimer",
+ "BSD-Inferno-Nettverk",
"BSD-Protection",
"BSD-Source-Code",
- "bzip2-1.0.5",
- "bzip2-1.0.6",
- "Caldera",
+ "BSD-Source-beginning-file",
+ "BSD-Systemics",
+ "BSD-Systemics-W3Works",
+ "BUSL-1.1",
+ "Baekmuk",
+ "Bahyph",
+ "Barr",
+ "Beerware",
+ "BitTorrent-1.0",
+ "BitTorrent-1.1",
+ "Bitstream-Charter",
+ "Bitstream-Vera",
+ "BlueOak-1.0.0",
+ "Boehm-GC",
+ "Borceux",
+ "Brian-Gladman-2-Clause",
+ "Brian-Gladman-3-Clause",
+ "C-UDA-1.0",
+ "CAL-1.0",
+ "CAL-1.0-Combined-Work-Exception",
"CATOSL-1.1",
"CC-BY-1.0",
"CC-BY-2.0",
"CC-BY-2.5",
+ "CC-BY-2.5-AU",
"CC-BY-3.0",
+ "CC-BY-3.0-AT",
+ "CC-BY-3.0-AU",
+ "CC-BY-3.0-DE",
+ "CC-BY-3.0-IGO",
+ "CC-BY-3.0-NL",
+ "CC-BY-3.0-US",
"CC-BY-4.0",
"CC-BY-NC-1.0",
"CC-BY-NC-2.0",
"CC-BY-NC-2.5",
"CC-BY-NC-3.0",
+ "CC-BY-NC-3.0-DE",
"CC-BY-NC-4.0",
"CC-BY-NC-ND-1.0",
"CC-BY-NC-ND-2.0",
"CC-BY-NC-ND-2.5",
"CC-BY-NC-ND-3.0",
+ "CC-BY-NC-ND-3.0-DE",
+ "CC-BY-NC-ND-3.0-IGO",
"CC-BY-NC-ND-4.0",
"CC-BY-NC-SA-1.0",
"CC-BY-NC-SA-2.0",
+ "CC-BY-NC-SA-2.0-DE",
+ "CC-BY-NC-SA-2.0-FR",
+ "CC-BY-NC-SA-2.0-UK",
"CC-BY-NC-SA-2.5",
"CC-BY-NC-SA-3.0",
+ "CC-BY-NC-SA-3.0-DE",
+ "CC-BY-NC-SA-3.0-IGO",
"CC-BY-NC-SA-4.0",
"CC-BY-ND-1.0",
"CC-BY-ND-2.0",
"CC-BY-ND-2.5",
"CC-BY-ND-3.0",
+ "CC-BY-ND-3.0-DE",
"CC-BY-ND-4.0",
"CC-BY-SA-1.0",
"CC-BY-SA-2.0",
+ "CC-BY-SA-2.0-UK",
+ "CC-BY-SA-2.1-JP",
"CC-BY-SA-2.5",
"CC-BY-SA-3.0",
+ "CC-BY-SA-3.0-AT",
+ "CC-BY-SA-3.0-DE",
+ "CC-BY-SA-3.0-IGO",
"CC-BY-SA-4.0",
"CC-PDDC",
"CC0-1.0",
"CDDL-1.1",
+ "CDL-1.0",
"CDLA-Permissive-1.0",
+ "CDLA-Permissive-2.0",
"CDLA-Sharing-1.0",
"CECILL-1.0",
"CECILL-1.1",
@@ -98,84 +149,142 @@
"CECILL-C",
"CERN-OHL-1.1",
"CERN-OHL-1.2",
- "ClArtistic",
+ "CERN-OHL-P-2.0",
+ "CERN-OHL-S-2.0",
+ "CERN-OHL-W-2.0",
+ "CFITSIO",
+ "CMU-Mach",
+ "CMU-Mach-nodoc",
"CNRI-Jython",
- "CNRI-Python-GPL-Compatible",
"CNRI-Python",
- "Condor-1.1",
- "copyleft-next-0.3.0",
- "copyleft-next-0.3.1",
+ "CNRI-Python-GPL-Compatible",
+ "COIL-1.0",
"CPL-1.0",
"CPOL-1.02",
+ "CUA-OPL-1.0",
+ "Caldera",
+ "Caldera-no-preamble",
+ "ClArtistic",
+ "Clips",
+ "Community-Spec-1.0",
+ "Condor-1.1",
+ "Cornell-Lossless-JPEG",
+ "Cronyx",
"Crossword",
"CrystalStacker",
- "CUA-OPL-1.0",
"Cube",
- "curl",
"D-FSL-1.0",
- "diffmark",
+ "DEC-3-Clause",
+ "DL-DE-BY-2.0",
+ "DL-DE-ZERO-2.0",
"DOC",
- "Dotseqn",
+ "DRL-1.0",
+ "DRL-1.1",
"DSDP",
- "dvipdfm",
+ "Dotseqn",
"ECL-1.0",
"EFL-1.0",
"EFL-2.0",
- "eGenix",
- "Entessa",
+ "EPICS",
"EPL-1.0",
"EPL-2.0",
- "ErlPL-1.1",
"EUDatagrid",
"EUPL-1.0",
+ "Elastic-2.0",
+ "Entessa",
+ "ErlPL-1.1",
"Eurosym",
- "Fair",
- "Frameworx-1.0",
- "FreeImage",
+ "FBM",
+ "FDK-AAC",
"FSFAP",
+ "FSFAP-no-warranty-disclaimer",
"FSFUL",
"FSFULLR",
+ "FSFULLRWD",
"FTL",
+ "Fair",
+ "Ferguson-Twofish",
+ "Frameworx-1.0",
+ "FreeBSD-DOC",
+ "FreeImage",
+ "Furuseth",
+ "GCR-docs",
+ "GD",
+ "GFDL-1.1-invariants-only",
+ "GFDL-1.1-invariants-or-later",
+ "GFDL-1.1-no-invariants-only",
+ "GFDL-1.1-no-invariants-or-later",
"GFDL-1.1-only",
"GFDL-1.1-or-later",
+ "GFDL-1.2-invariants-only",
+ "GFDL-1.2-invariants-or-later",
+ "GFDL-1.2-no-invariants-only",
+ "GFDL-1.2-no-invariants-or-later",
"GFDL-1.2-only",
"GFDL-1.2-or-later",
+ "GFDL-1.3-invariants-only",
+ "GFDL-1.3-invariants-or-later",
+ "GFDL-1.3-no-invariants-only",
+ "GFDL-1.3-no-invariants-or-later",
"GFDL-1.3-only",
"GFDL-1.3-or-later",
- "Giftware",
"GL2PS",
- "Glide",
- "Glulxe",
- "gnuplot",
+ "GLWTPL",
"GPL-1.0-only",
"GPL-1.0-or-later",
- "gSOAP-1.3b",
- "HaskellReport",
+ "Giftware",
+ "Glide",
+ "Glulxe",
+ "Graphics-Gems",
+ "HP-1986",
+ "HP-1989",
"HPND",
+ "HPND-DEC",
+ "HPND-Fenneberg-Livingston",
+ "HPND-INRIA-IMAG",
+ "HPND-Kevlin-Henney",
+ "HPND-MIT-disclaimer",
+ "HPND-Markus-Kuhn",
+ "HPND-Pbmplus",
+ "HPND-UC",
+ "HPND-doc",
+ "HPND-doc-sell",
+ "HPND-export-US",
+ "HPND-export-US-modify",
+ "HPND-sell-MIT-disclaimer-xserver",
+ "HPND-sell-regexpr",
"HPND-sell-variant",
+ "HPND-sell-variant-MIT-disclaimer",
+ "HTMLTIDY",
+ "HaskellReport",
+ "Hippocratic-2.1",
"IBM-pibs",
"ICU",
+ "IEC-Code-Components-EULA",
"IJG",
+ "IJG-short",
+ "IPL-1.0",
+ "ISC-Veillard",
"ImageMagick",
- "iMatix",
"Imlib2",
"Info-ZIP",
- "Intel-ACPI",
+ "Inner-Net-2.0",
"Intel",
+ "Intel-ACPI",
"Interbase-1.0",
- "IPL-1.0",
- "JasPer-2.0",
+ "JPL-image",
"JPNIC",
"JSON",
+ "Jam",
+ "JasPer-2.0",
+ "Kastrup",
+ "Kazlib",
+ "Knuth-CTAN",
"LAL-1.2",
"LAL-1.3",
- "Latex2e",
- "Leptonica",
"LGPLLR",
- "Libpng",
- "libpng-2.0",
- "libtiff",
- "Linux-OpenIB",
+ "LOOP",
+ "LPD-document",
"LPL-1.0",
"LPL-1.02",
"LPPL-1.0",
@@ -183,41 +292,84 @@
"LPPL-1.2",
"LPPL-1.3a",
"LPPL-1.3c",
- "MakeIndex",
+ "LZMA-SDK-9.11-to-9.20",
+ "LZMA-SDK-9.22",
+ "Latex2e",
+ "Latex2e-translated-notice",
+ "Leptonica",
+ "Libpng",
+ "Linux-OpenIB",
+ "Linux-man-pages-1-para",
+ "Linux-man-pages-copyleft",
+ "Linux-man-pages-copyleft-2-para",
+ "Linux-man-pages-copyleft-var",
+ "Lucida-Bitmap-Fonts",
"MIT-0",
- "MIT-advertising",
"MIT-CMU",
+ "MIT-Festival",
+ "MIT-Modern-Variant",
+ "MIT-Wu",
+ "MIT-advertising",
"MIT-enna",
"MIT-feh",
+ "MIT-open-group",
+ "MIT-testregex",
"MITNFA",
- "Motosoto",
- "mpich2",
+ "MMIXware",
+ "MPEG-SSG",
"MPL-1.0",
"MPL-1.1",
"MPL-2.0-no-copyleft-exception",
+ "MS-LPL",
"MTLL",
+ "Mackerras-3-Clause",
+ "Mackerras-3-Clause-acknowledgment",
+ "MakeIndex",
+ "Martin-Birgmeier",
+ "McPhee-slideshow",
+ "Minpack",
+ "Motosoto",
+ "MulanPSL-1.0",
+ "MulanPSL-2.0",
"Multics",
"Mup",
- "Naumen",
+ "NAIST-2003",
"NBPL-1.0",
+ "NCGL-UK-2.0",
"NCSA",
- "Net-SNMP",
- "NetCDF",
- "Newsletr",
"NGPL",
+ "NICTA-1.0",
+ "NIST-PD",
+ "NIST-PD-fallback",
+ "NIST-Software",
"NLOD-1.0",
+ "NLOD-2.0",
"NLPL",
- "Nokia",
"NOSL",
- "Noweb",
"NPL-1.0",
"NPL-1.1",
"NRL",
+ "NTP-0",
+ "Naumen",
+ "Net-SNMP",
+ "NetCDF",
+ "Newsletr",
+ "Nokia",
+ "Noweb",
+ "O-UDA-1.0",
"OCCT-PL",
"OCLC-2.0",
- "ODbL-1.0",
"ODC-By-1.0",
+ "ODbL-1.0",
+ "OFFIS",
+ "OFL-1.0-RFN",
+ "OFL-1.0-no-RFN",
"OFL-1.1",
+ "OFL-1.1-RFN",
+ "OFL-1.1-no-RFN",
+ "OGC-1.0",
+ "OGDL-Taiwan-1.0",
+ "OGL-Canada-2.0",
"OGL-UK-1.0",
"OGL-UK-2.0",
"OGL-UK-3.0",
@@ -225,101 +377,185 @@
"OLDAP-1.2",
"OLDAP-1.3",
"OLDAP-1.4",
- "OLDAP-2.0.1",
"OLDAP-2.0",
+ "OLDAP-2.0.1",
"OLDAP-2.1",
+ "OLDAP-2.2",
"OLDAP-2.2.1",
"OLDAP-2.2.2",
- "OLDAP-2.2",
"OLDAP-2.3",
"OLDAP-2.4",
"OLDAP-2.5",
"OLDAP-2.6",
"OLDAP-2.7",
"OLDAP-2.8",
+ "OLFL-1.3",
"OML",
- "OpenSSL",
"OPL-1.0",
+ "OPL-UK-3.0",
+ "OPUBL-1.0",
"OSET-PL-2.1",
"OSL-1.0",
"OSL-1.1",
"OSL-2.0",
- "Parity-6.0.0",
+ "OpenPBS-2.3",
+ "OpenSSL",
+ "OpenSSL-standalone",
+ "OpenVision",
+ "PADL",
"PDDL-1.0",
"PHP-3.0",
"PHP-3.01",
+ "PSF-2.0",
+ "Parity-6.0.0",
+ "Parity-7.0.0",
+ "Pixar",
"Plexus",
+ "PolyForm-Noncommercial-1.0.0",
+ "PolyForm-Small-Business-1.0.0",
"PostgreSQL",
- "psfrag",
- "psutils",
"Python-2.0",
+ "Python-2.0.1",
+ "QPL-1.0-INRIA-2004",
"Qhull",
- "Rdisc",
"RHeCos-1.1",
"RPL-1.1",
"RPSL-1.0",
"RSA-MD",
"RSCPL",
+ "Rdisc",
"Ruby",
"SAX-PD",
- "Saxpath",
+ "SAX-PD-2.0",
"SCEA",
- "Sendmail-8.23",
- "Sendmail",
"SGI-B-1.0",
"SGI-B-1.1",
"SGI-B-2.0",
+ "SGI-OpenGL",
+ "SGP4",
"SHL-0.5",
"SHL-0.51",
- "SISSL-1.2",
"SISSL",
- "Sleepycat",
+ "SISSL-1.2",
+ "SL",
"SMLNJ",
"SMPPL",
"SNIA",
+ "SPL-1.0",
+ "SSH-OpenSSH",
+ "SSH-short",
+ "SSLeay-standalone",
+ "SSPL-1.0",
+ "SWL",
+ "Saxpath",
+ "SchemeReport",
+ "Sendmail",
+ "Sendmail-8.23",
+ "Sleepycat",
+ "Soundex",
"Spencer-86",
"Spencer-94",
"Spencer-99",
- "SPL-1.0",
- "SSPL-1.0",
"SugarCRM-1.1.3",
- "SWL",
+ "Sun-PPP",
+ "SunPro",
+ "Symlinks",
"TAPR-OHL-1.0",
"TCL",
"TCP-wrappers",
+ "TGPPL-1.0",
"TMate",
"TORQUE-1.1",
"TOSL",
+ "TPDL",
+ "TPL-1.0",
+ "TTWL",
+ "TTYP0",
"TU-Berlin-1.0",
"TU-Berlin-2.0",
+ "TermReadKey",
+ "UCAR",
+ "UCL-1.0",
+ "UMich-Merit",
+ "URT-RLE",
+ "Unicode-3.0",
"Unicode-DFS-2015",
"Unicode-DFS-2016",
"Unicode-TOU",
+ "UnixCrypt",
"Unlicense",
- "Vim",
"VOSTROM",
"VSL-1.0",
+ "Vim",
+ "W3C",
"W3C-19980720",
"W3C-20150513",
- "W3C",
+ "WTFPL",
"Watcom-1.0",
+ "Widget-Workshop",
"Wsuipa",
- "WTFPL",
"X11",
- "Xerox",
+ "X11-distribute-modifications-variant",
"XFree86-1.1",
- "xinetd",
- "Xnet",
- "xpp",
"XSkat",
+ "Xdebug-1.03",
+ "Xerox",
+ "Xfig",
+ "Xnet",
"YPL-1.0",
"YPL-1.1",
+ "ZPL-1.1",
+ "ZPL-2.0",
+ "ZPL-2.1",
"Zed",
+ "Zeeff",
"Zend-2.0",
"Zimbra-1.3",
"Zimbra-1.4",
- "zlib-acknowledgement",
- "ZPL-1.1",
- "ZPL-2.0",
- "ZPL-2.1"
+ "bcrypt-Solar-Designer",
+ "blessing",
+ "bzip2-1.0.6",
+ "check-cvs",
+ "checkmk",
+ "copyleft-next-0.3.0",
+ "copyleft-next-0.3.1",
+ "curl",
+ "diffmark",
+ "dtoa",
+ "dvipdfm",
+ "eGenix",
+ "etalab-2.0",
+ "fwlw",
+ "gSOAP-1.3b",
+ "gnuplot",
+ "gtkbook",
+ "hdparm",
+ "iMatix",
+ "libpng-2.0",
+ "libselinux-1.0",
+ "libtiff",
+ "libutil-David-Nugent",
+ "lsof",
+ "magaz",
+ "mailprio",
+ "metamail",
+ "mpi-permissive",
+ "mpich2",
+ "mplus",
+ "pnmstitch",
+ "psfrag",
+ "psutils",
+ "python-ldap",
+ "radvd",
+ "snprintf",
+ "softSurfer",
+ "ssh-keyscan",
+ "swrule",
+ "ulem",
+ "w3m",
+ "xinetd",
+ "xkeyboard-config-Zinoviev",
+ "xlock",
+ "xpp",
+ "zlib-acknowledgement"
]
diff --git a/libs/debug.js b/libs/debug.js
index 43e9ed0d0..8afbfdffd 100644
--- a/libs/debug.js
+++ b/libs/debug.js
@@ -1,15 +1,27 @@
'use strict';
+var inspector = require('inspector');
var fs = require('fs');
+var os = require('os');
+
+var git = require('git-rev-sync');
+
+var pkg = require('../package.json');
+pkg.org = pkg.name.substring(0, pkg.name.indexOf('.'));
var isPro = process.env.NODE_ENV === 'production';
var isDev = !isPro;
-var isDbg = typeof v8debug === 'object';
+var isDbg = typeof v8debug === 'object' || inspector.url();
-var isSecure = null;
var privkey = null;
var fullchain = null;
var chain = null;
+var isSecured = null;
+var isRenewable = null;
+
+var uaOUJS = null;
+var hash = null;
+
try {
// Check for primary keys
@@ -25,6 +37,7 @@ try {
exports.fullchain = fullchain;
exports.chain = chain;
exports.isSecured = true;
+ exports.isRenewable = false;
} catch (aE) {
// Check for backup alternate keys
@@ -41,6 +54,7 @@ try {
exports.fullchain = fullchain;
exports.chain = chain;
exports.isSecured = true;
+ exports.isRenewable = !!process.env.ATTEMPT_RENEWAL;
} catch (aE) {
// Ensure that all items are nulled or equivalent
@@ -48,6 +62,7 @@ try {
exports.fullchain = null;
exports.chain = null;
exports.isSecured = false;
+ exports.isRenewable = !!process.env.ATTEMPT_RENEWAL;
}
}
@@ -55,6 +70,19 @@ exports.isPro = isPro;
exports.isDev = isDev;
exports.isDbg = isDbg;
+
+hash = git.short(); // NOTE: Synchronous
+uaOUJS = pkg.org + '/' + pkg.version
+ + ' (' + os.type() + '; ' + os.arch() + '; rv:' + hash + ') '
+ + 'OUJS/20131106 ' + pkg.name + '/' + hash;
+exports.uaOUJS = uaOUJS;
+
+// NOTE: Requires DB migration for changing below settings
+exports.rLogographic = /[\p{sc=Han}\p{sc=Hiragana}\p{sc=Katakana}\p{sc=Hangul}]/u;
+exports.logographicDivisor = 4;
+
+// /NOTE:
+
// ES6+ in use to eliminate extra property
class statusError extends Error {
constructor (aStatus, aCode) {
diff --git a/libs/flag.js b/libs/flag.js
index b509ceca5..81db8f53f 100644
--- a/libs/flag.js
+++ b/libs/flag.js
@@ -114,7 +114,9 @@ function getThreshold(aModel, aContent, aAuthor, aCallback) {
}
exports.getThreshold = getThreshold;
-function saveContent(aModel, aContent, aAuthor, aFlags, aIsFlagging, aCallback) {
+function saveContent(aModel, aContent, aAuthor, aWeight, aIsFlagging, aCallback) {
+ var weight = (aAuthor.role < 4 ? 2 : 1);
+
if (!aContent.flags) {
aContent.flags = {};
}
@@ -127,14 +129,14 @@ function saveContent(aModel, aContent, aAuthor, aFlags, aIsFlagging, aCallback)
aContent.flags.absolute = 0;
}
- aContent.flags.critical += aFlags;
+ aContent.flags.critical += aWeight;
if (aIsFlagging) {
aContent.flags.absolute +=
- (aFlags > 0 ? 1 : (aFlags < 0 && aContent.flags.absolute !== 0 ? -1 : 0));
+ (aWeight > 0 ? 1 : (aWeight < 0 && aContent.flags.absolute !== 0 ? -1 : 0));
}
- if (aContent.flags.critical >= thresholds[aModel.modelName] * (aAuthor.role < 4 ? 2 : 1)) {
+ if (aContent.flags.critical >= thresholds[aModel.modelName] * weight) {
return getThreshold(aModel, aContent, aAuthor, function (aThreshold) {
aContent.flagged = aContent.flags.critical >= aThreshold;
@@ -163,11 +165,14 @@ function saveContent(aModel, aContent, aAuthor, aFlags, aIsFlagging, aCallback)
exports.saveContent = saveContent;
function flag(aModel, aContent, aUser, aAuthor, aReason, aCallback) {
+ var now = new Date();
var flag = new Flag({
'model': aModel.modelName,
'_contentId': aContent._id,
'_userId': aUser._id,
- 'reason': aReason
+ 'weight': aUser.role < 4 ? 2 : 1,
+ 'reason': aReason,
+ 'created': now
});
flag.save(function (aErr, aFlag) {
@@ -189,7 +194,7 @@ function flag(aModel, aContent, aUser, aAuthor, aReason, aCallback) {
aContent.flagged = false;
}
- saveContent(aModel, aContent, aAuthor, aUser.role < 4 ? 2 : 1, true, aCallback);
+ saveContent(aModel, aContent, aAuthor, aFlag.weight, true, aCallback);
});
}
@@ -236,7 +241,7 @@ exports.unflag = function (aModel, aContent, aUser, aReason, aCallback) {
aFlag.remove(function (aErr) {
// WARNING: No err handling
- saveContent(aModel, aContent, aAuthor, aUser.role < 4 ? -2 : -1, true, aCallback);
+ saveContent(aModel, aContent, aAuthor, -aFlag.weight, true, aCallback);
});
}
diff --git a/libs/githubClient.js b/libs/githubClient.js
index 20c058b27..0c94dc414 100644
--- a/libs/githubClient.js
+++ b/libs/githubClient.js
@@ -4,40 +4,87 @@
var isPro = require('../libs/debug').isPro;
var isDev = require('../libs/debug').isDev;
var isDbg = require('../libs/debug').isDbg;
+var uaOUJS = require('../libs/debug').uaOUJS;
//
-var GitHubApi = require("github");
var _ = require("underscore");
var async = require('async');
var util = require('util');
var request = require('request');
var colors = require('ansi-colors');
+var GitHubApi = require("github");
+var createOAuthAppAuth = require("@octokit/auth-oauth-app").createOAuthAppAuth;
+
// Client
var github = new GitHubApi({
- version: "3.0.0"
+ version: "3.0.0",
+ debug: (isDbg ? true : false),
+ headers: {
+ "User-Agent": uaOUJS + (process.env.UA_SECRET ? ' ' + process.env.UA_SECRET : '')
+ }
});
module.exports = github;
// Authenticate Client
var Strategy = require('../models/strategy').Strategy;
-Strategy.findOne({ name: 'github' }, function (aErr, aStrat) {
+Strategy.findOne({ name: 'github' }, async function (aErr, aStrat) {
+ var auth = null;
+ var appAuthentication = null;
+
if (aErr)
console.error(aErr);
- if (aStrat) {
+ if (aStrat && process.env.DISABLE_SCRIPT_IMPORT !== 'true') {
+
+ // TODO: Incomplete migration here
+ auth = createOAuthAppAuth({
+ clientType: 'oauth-app',
+ clientId: aStrat.id,
+ clientSecret: aStrat.key
+ });
+
+ appAuthentication = await auth({
+ type: "oauth-app"
+ });
+
+ // TODO: Do something with `appAuthentication`
+
+
+ // DEPRECATED: See #1705
+ // NOTE: We are technically an oauth app client but uses the same authentication type
+ // methodology in the static version of the dependency. In future versions it may be different.
github.authenticate({
- type: 'oauth',
- key: aStrat.id,
- secret: aStrat.key,
+ type: 'basic',
+ username: aStrat.id,
+ password: aStrat.key
});
- console.log(colors.green('GitHub client authenticated'));
+
+ // TODO: error handler for UnhandledPromiseRejectionWarning if it crops up after deprecation.
+ // Forced invalid credentials and no error thrown but doesn't mean that they won't appear.
+
+ if (github.auth) {
+ console.log(colors.green([
+ 'GitHub client (a.k.a this app) DOES contain authentication credentials.',
+ 'Higher rate limit may be available'
+ ].join('\n')));
+ }
+ else {
+ console.log(colors.red([
+ 'GitHub client (a.k.a this app) DOES NOT contain authentication credentials.',
+ 'Critical error with dependency.'
+ ].join('\n')));
+ }
} else {
- console.warn(colors.yellow('GitHub client NOT authenticated. Will have a lower Rate Limit.'));
+ console.warn(colors.yellow([
+ 'GitHub client (a.k.a this app) DOES NOT contain authentication credentials.',
+ 'Lower rate limit will be available.'
+ ].join('\n')));
}
});
+
// Util functions for the client.
github.usercontent = github.usercontent || {};
@@ -67,7 +114,12 @@ var githubUserContentGetBlobAsUtf8 = function (aMsg, aCallback) {
async.waterfall([
function (aCallback) {
var url = githubUserContentBuildUrl(aMsg.user, aMsg.repo, aMsg.path);
- request.get(url, aCallback);
+ request.get({
+ url: url,
+ headers: {
+ 'User-Agent': uaOUJS + (process.env.UA_SECRET ? ' ' + process.env.UA_SECRET : '')
+ }
+ }, aCallback);
},
function (aResponse, aBody, aCallback) {
if (aResponse.statusCode !== 200)
diff --git a/libs/helpers.js b/libs/helpers.js
index f38ec587f..9af9852e2 100644
--- a/libs/helpers.js
+++ b/libs/helpers.js
@@ -197,6 +197,15 @@ exports.isFQUrl = function (aString, aOptions) {
return false;
};
+exports.appendUrlLeaf = function (aUrl, aLeaf) {
+ let target = new URL(aUrl);
+
+ target.pathname = target.pathname.replace(/\/$/, '') + '/'
+ + aLeaf.replace(/^\//, '').replace(/\/$/);
+
+ return target.href;
+}
+
// Helper function to ensure value is type Integer `number` or `null`
// Please be very careful if this is edited
exports.ensureIntegerOrNull = function (aEnvVar) {
@@ -217,12 +226,12 @@ exports.ensureIntegerOrNull = function (aEnvVar) {
exports.port = process.env.PORT || 8080;
exports.securePort = process.env.SECURE_PORT || 8081;
-exports.baseOrigin = 'https://openuserjs.org';
+exports.baseOrigin = (isPro ? 'https://openuserjs.org' : 'http://localhost:' + exports.port); // NOTE: Watchpoint
// Absolute pattern and is combined for pro and dev
exports.patternHasSameOrigin =
- '(?:https?://openuserjs\.org(?::' + exports.securePort + ')?|http://(?:oujs\.org' +
- (isDev ? '|localhost:' + exports.port : '' ) + '))';
+ '(?:https?://openuserjs\.org(?::' + exports.securePort + ')?' +
+ (isDev ? '|http://localhost:' + exports.port + ')' : ')' );
// Possible pattern and is split for pro vs. dev
// NOTE: This re is quite sensitive to changes esp. with `|`
@@ -253,3 +262,18 @@ exports.isSameOrigin = function (aUrl) {
URL: url
};
}
+
+exports.getRedirect = function (aReq) {
+ var referer = aReq.get('Referer');
+ var redirect = '/';
+
+ if (referer) {
+ referer = url.parse(referer); // NOTE: Legacy
+ if (referer.hostname === aReq.hostname) {
+ redirect = referer.path;
+ }
+ }
+
+ return redirect;
+}
+
diff --git a/libs/markdown.js b/libs/markdown.js
index 0871c9ec0..5b5799c80 100644
--- a/libs/markdown.js
+++ b/libs/markdown.js
@@ -7,19 +7,83 @@ var isDbg = require('../libs/debug').isDbg;
//
var _ = require('underscore');
-var marked = require('marked');
+var { Marked } = require('marked');
+var { markedHighlight } = require('marked-highlight');
var hljs = require('highlight.js');
var sanitizeHtml = require('sanitize-html');
var colors = require('ansi-colors');
var isSameOrigin = require('./helpers').isSameOrigin;
-var jsdom = require("jsdom");
-var { JSDOM } = jsdom;
+var { JSDOM } = require("jsdom");
var htmlWhitelistPost = require('./htmlWhitelistPost.json');
var htmlWhitelistFollow = require('./htmlWhitelistFollow.json');
+
+function highlighter(aCode, aLang) {
+ var obj = null;
+ var langs = [ // NOTE: More likely to less likely
+ 'javascript', 'xpath', 'xml',
+ 'css', 'less', 'scss',
+ 'json',
+ 'diff',
+ 'shell', 'console',
+ 'bash', 'dos',
+ 'vbscript'
+ ];
+
+ if (aLang && hljs.getLanguage(aLang)) {
+ try {
+ return hljs.highlight(aCode, { language: aLang }).value;
+ } catch (aE) {
+ if (isDev) {
+ console.error([
+ colors.red('Dependency named highlighting failed with:'),
+ aE
+
+ ].join('\n'));
+ }
+ }
+ }
+
+ try {
+ obj = hljs.highlightAuto(aCode);
+
+ if (langs.indexOf(obj.language) > -1) {
+ return obj.value;
+ } else {
+ if (isDev) {
+ console.log([
+ colors.yellow('Unusual auto-detected md language code is')
+ + '`' + colors.cyan(obj.language) + '`',
+
+ ].join('\n'));
+ }
+ return hljs.highlightAuto(aCode, langs).value;
+ }
+ } catch (aE) {
+ if (isDev) {
+ console.error([
+ colors.red('Dependency automatic named highlighting failed with:'),
+ aE
+
+ ].join('\n'));
+ }
+ }
+
+ // If any external package failure don't block return e.g. prevent empty
+ return aCode;
+};
+
+var marked = new Marked(
+ markedHighlight({
+ langPrefix: 'hljs language-',
+ highlight: highlighter
+ })
+);
+
var renderer = new marked.Renderer();
+
var blockRenderers = [
'blockquote',
'html',
@@ -28,6 +92,8 @@ var blockRenderers = [
'table'
];
+
+
// Transform exact Github Flavored Markdown generated style tags to bootstrap custom classes
// to allow the sanitizer to whitelist on th and td tags for table alignment
function gfmStyleToBootstrapClass(aTagName, aAttribs) {
@@ -62,8 +128,12 @@ function externalPolicy(aTagName, aAttribs) {
var attribRelAdd = [];
var attribRelReject = [
'dns-prefetch',
+ 'modulepreload',
+ 'pingback',
'preconnect',
- 'prefetch'
+ 'prefetch',
+ 'preload',
+ 'prerender'
];
var obj = null;
var dn = null;
@@ -76,6 +146,7 @@ function externalPolicy(aTagName, aAttribs) {
attribRelAdd.push('external');
attribRelAdd.push('noreferrer');
attribRelAdd.push('noopener');
+ attribRelAdd.push('ugc');
if (obj.URL) {
matches = obj.URL.hostname.match(/\.?(.*?\..*)$/);
@@ -138,11 +209,10 @@ function sanitize(aHtml) {
// Sanitize the output from the block level renderers
blockRenderers.forEach(function (aType) {
renderer[aType] = function () {
- // Sanitize first to close any tags
+ // Render Markdown type first then sanitize HTML including any closing of tags
var sanitized = sanitize(marked.Renderer.prototype[aType].apply(renderer, arguments));
- // Autolink most usernames
-
+ // Autolink most usernames.
var dom = new JSDOM('