├── .devcontainer ├── Dockerfile ├── devcontainer.json └── docker-compose.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── examples └── login │ ├── app.js │ ├── bin │ └── www │ ├── models │ └── account.js │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── css │ │ ├── bootstrap-responsive.css │ │ ├── bootstrap-responsive.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.min.css │ │ └── custom.css │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ └── glyphicons-halflings.png │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── jquery.min.js │ ├── routes.js │ └── views │ ├── error.pug │ ├── index.pug │ ├── layout.pug │ ├── login.pug │ └── register.pug ├── index.d.ts ├── index.js ├── lib ├── authenticate.js ├── errors.js └── pbkdf2.js ├── package-lock.json ├── package.json ├── test ├── alternative-query-field.js ├── errors.js ├── issues.js ├── passport-local-mongoose.js └── types │ └── index.test-d.ts └── tsconfig.json /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster 2 | ARG VARIANT=16-bullseye 3 | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} 4 | 5 | # Install MongoDB command line tools if on buster and x86_64 (arm64 not supported) 6 | ARG MONGO_TOOLS_VERSION=5.0 7 | RUN . /etc/os-release \ 8 | && if [ "${VERSION_CODENAME}" = "buster" ] && [ "$(dpkg --print-architecture)" = "amd64" ]; then \ 9 | curl -sSL "https://www.mongodb.org/static/pgp/server-${MONGO_TOOLS_VERSION}.asc" | gpg --dearmor > /usr/share/keyrings/mongodb-archive-keyring.gpg \ 10 | && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] http://repo.mongodb.org/apt/debian $(lsb_release -cs)/mongodb-org/${MONGO_TOOLS_VERSION} main" | tee /etc/apt/sources.list.d/mongodb-org-${MONGO_TOOLS_VERSION}.list \ 11 | && apt-get update && export DEBIAN_FRONTEND=noninteractive \ 12 | && apt-get install -y mongodb-database-tools mongodb-mongosh \ 13 | && apt-get clean -y && rm -rf /var/lib/apt/lists/*; \ 14 | fi 15 | 16 | # [Optional] Uncomment this section to install additional OS packages. 17 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 18 | # && apt-get -y install --no-install-recommends 19 | 20 | # [Optional] Uncomment if you want to install an additional version of node using nvm 21 | # ARG EXTRA_NODE_VERSION=10 22 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 23 | 24 | # [Optional] Uncomment if you want to install more global node modules 25 | RUN su node -c "npm install -g npm-check-updates" 26 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // Update the VARIANT arg in docker-compose.yml to pick a Node.js version 2 | { 3 | "name": "Node.js & Mongo DB", 4 | "dockerComposeFile": "docker-compose.yml", 5 | "service": "app", 6 | "workspaceFolder": "/workspace", 7 | 8 | // Set *default* container specific settings.json values on container create. 9 | "settings": {}, 10 | 11 | // Add the IDs of extensions you want installed when the container is created. 12 | "extensions": [ 13 | "dbaeumer.vscode-eslint", 14 | "esbenp.prettier-vscode", 15 | "mongodb.mongodb-vscode", 16 | "hbenl.vscode-test-explorer", 17 | "ms-azuretools.vscode-docker" 18 | ], 19 | 20 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 21 | // "forwardPorts": [3000, 27017], 22 | 23 | // Use 'postCreateCommand' to run commands after the container is created. 24 | // "postCreateCommand": "yarn install", 25 | 26 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 27 | "remoteUser": "node" 28 | } 29 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | args: 9 | # Update 'VARIANT' to pick an LTS version of Node.js: 16, 14, 12. 10 | # Append -bullseye or -buster to pin to an OS version. 11 | # Use -bullseye variants on local arm64/Apple Silicon. 12 | VARIANT: 16-bullseye 13 | volumes: 14 | - ..:/workspace:cached 15 | 16 | # Overrides default command so things don't shut down after the process ends. 17 | command: sleep infinity 18 | 19 | # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. 20 | network_mode: service:db 21 | 22 | # Uncomment the next line to use a non-root user for all processes. 23 | # user: node 24 | 25 | # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. 26 | # (Adding the "ports" property to this file will not forward from a Codespace.) 27 | 28 | db: 29 | image: mongo:latest 30 | restart: unless-stopped 31 | volumes: 32 | - mongodb-data:/data/db 33 | 34 | # Uncomment to change startup options 35 | # environment: 36 | # MONGO_INITDB_ROOT_USERNAME: root 37 | # MONGO_INITDB_ROOT_PASSWORD: example 38 | # MONGO_INITDB_DATABASE: your-database-here 39 | 40 | # Add "forwardPorts": ["27017"] to **devcontainer.json** to forward MongoDB locally. 41 | # (Adding the "ports" property to this file will not forward from a Codespace.) 42 | 43 | volumes: 44 | mongodb-data: -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_size = 2 8 | end_of_line = lf 9 | indent_style = space 10 | insert_final_newline = false 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/** 2 | examples/** 3 | release.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "prettier" 5 | ], 6 | "plugins": [ 7 | "prettier" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 2018 11 | }, 12 | "env": { 13 | "es6": true, 14 | "node": true, 15 | "mocha": true 16 | }, 17 | "rules": { 18 | "no-var": "error", 19 | "prefer-const": "error", 20 | "prettier/prettier": "error" 21 | } 22 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set EOL to lf to avoid problems when building a package 2 | * text eol=lf 3 | 4 | # Explicitly declare text files 5 | *.js text 6 | *.json text 7 | *.md text 8 | *.ejs text 9 | *.jade text 10 | 11 | # Declare binary files 12 | *.png -text 13 | *.jpeg -text 14 | *.jpg -text 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "npm" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | allow: 9 | - dependency-type: "production" 10 | 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [14, 16, 18] 17 | mongodb-version: ['4.0', '4.2', '4.4', '5.0', '6.0'] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - name: Start MongoDB 26 | uses: supercharge/mongodb-github-action@1.9.0 27 | with: 28 | mongodb-version: ${{ matrix.mongodb-version }} 29 | - run: npm ci 30 | - run: npm run lint 31 | - run: npm run test:ci 32 | - name: Coveralls Parallel 33 | uses: coverallsapp/github-action@master 34 | with: 35 | github-token: ${{ secrets.github_token }} 36 | flag-name: run-node-${{ matrix.node-version }}-mongodb-${{ matrix.mongodb-version }} 37 | parallel: true 38 | 39 | coverage: 40 | needs: build 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Coveralls Finished 44 | uses: coverallsapp/github-action@master 45 | with: 46 | github-token: ${{ secrets.github_token }} 47 | parallel-finished: true 48 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: 'Publish to NPM' 2 | 3 | on: 4 | workflow_run: 5 | workflows: ['Node.js CI'] 6 | types: [completed] 7 | branches: [main] 8 | 9 | jobs: 10 | publish-new-version: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | fetch-depth: '0' 17 | - name: git setup 18 | run: | 19 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 20 | git config --local user.name "github-actions[bot]" 21 | - name: setup node 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: 16.x 25 | registry-url: 'https://registry.npmjs.org' 26 | - name: npm install 27 | run: npm ci 28 | 29 | - name: Should release 30 | id: should_release 31 | continue-on-error: true 32 | run: npm run should-release -- -v 33 | 34 | - name: No release 35 | if: steps.should_release.outcome != 'success' 36 | run: echo "No release required. Skipping publishing." 37 | 38 | - name: Version bump 39 | if: steps.should_release.outcome == 'success' 40 | run: npm run release 41 | 42 | - name: Publish to NPM 43 | if: steps.should_release.outcome == 'success' 44 | run: npm publish 45 | env: 46 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | 48 | - name: Push commits to GitHub 49 | if: steps.should_release.outcome == 'success' 50 | uses: ad-m/github-push-action@master 51 | with: 52 | github_token: ${{ secrets.GITHUB_TOKEN }} 53 | branch: ${{ github.ref }} 54 | tags: true 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # test related 2 | coverage/ 3 | .nyc_output/ 4 | testing 5 | testapp 6 | 7 | # OSX 8 | .DS_Store 9 | 10 | # npm 11 | node_modules 12 | npm-debug.log 13 | 14 | # IDE 15 | .idea/ 16 | *~ 17 | *.swp 18 | 19 | # Things 20 | README.html 21 | *.tgz 22 | docs/ 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | examples 3 | coverage 4 | docs 5 | *.tgz 6 | *~ 7 | gulpfile.js 8 | .* 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "printWidth": 140 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [8.0.0](https://github.com/saintedlama/passport-local-mongoose/compare/v7.1.2...v8.0.0) (2023-03-14) 6 | 7 | 8 | ### ⚠ BREAKING CHANGES 9 | 10 | * Upgraded to mongoose 7 and removed node 12 support 11 | 12 | ### Features 13 | 14 | * upgrade to mongoose 7 ([#370](https://github.com/saintedlama/passport-local-mongoose/issues/370)) ([a397c00](https://github.com/saintedlama/passport-local-mongoose/commit/a397c00330494563c8141bb6e1334b83d6aa16c7)) 15 | 16 | ### [7.1.2](https://github.com/saintedlama/passport-local-mongoose/compare/v7.1.1...v7.1.2) (2022-05-21) 17 | 18 | ### [7.1.1](https://github.com/saintedlama/passport-local-mongoose/compare/v7.1.0...v7.1.1) (2022-05-17) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * add usernameCaseInsensitive to the typings ([#355](https://github.com/saintedlama/passport-local-mongoose/issues/355)) ([26f4991](https://github.com/saintedlama/passport-local-mongoose/commit/26f49919220db8f489cfe7fd7f6534535ff0c544)) 24 | 25 | ## [7.1.0](https://github.com/saintedlama/passport-local-mongoose/compare/v7.0.0...v7.1.0) (2022-05-17) 26 | 27 | 28 | ### Features 29 | 30 | * add basic type definitions ([#354](https://github.com/saintedlama/passport-local-mongoose/issues/354)) ([92849db](https://github.com/saintedlama/passport-local-mongoose/commit/92849db3329efd1f10af52a16f8b020e657e1b98)), closes [#335](https://github.com/saintedlama/passport-local-mongoose/issues/335) 31 | 32 | ## [7.0.0](https://github.com/saintedlama/passport-local-mongoose/compare/v6.2.2...v7.0.0) (2022-03-13) 33 | 34 | 35 | ### Features 36 | 37 | * reset maxAttempts after unlockInterval if activated ([#349](https://github.com/saintedlama/passport-local-mongoose/issues/349)) ([a752854](https://github.com/saintedlama/passport-local-mongoose/commit/a752854ce66220bff9e4c09f9b3713fafb9a0f2f)) 38 | 39 | ## [6.3.0](https://github.com/saintedlama/passport-local-mongoose/compare/v6.2.2...v6.3.0) (2022-03-12) 40 | 41 | 42 | ### Features 43 | 44 | * reset maxAttempts after unlockInterval if activated ([#349](https://github.com/saintedlama/passport-local-mongoose/issues/349)) ([a752854](https://github.com/saintedlama/passport-local-mongoose/commit/a752854ce66220bff9e4c09f9b3713fafb9a0f2f)) 45 | 46 | ### [6.2.2](https://github.com/saintedlama/passport-local-mongoose/compare/v6.2.1...v6.2.2) (2022-03-12) 47 | 48 | ### [6.2.1](https://github.com/saintedlama/passport-local-mongoose/compare/v6.2.0...v6.2.1) (2022-03-12) 49 | 50 | ## [6.2.0](https://github.com/saintedlama/passport-local-mongoose/compare/v6.1.0...v6.2.0) (2022-03-12) 51 | 52 | 53 | ### Features 54 | 55 | * escape username regex ([#296](https://github.com/saintedlama/passport-local-mongoose/issues/296)) ([6713c4e](https://github.com/saintedlama/passport-local-mongoose/commit/6713c4e02c9a2aabbe3cd4a9c2efc032e686eb3d)) 56 | * upgrade dependencies and drop support of legacy mongodb and node versions ([#344](https://github.com/saintedlama/passport-local-mongoose/issues/344)) ([2d88e70](https://github.com/saintedlama/passport-local-mongoose/commit/2d88e70c9afcbddd46076cde46e34111e6ab029d)) 57 | 58 | ## [6.1.0](https://github.com/saintedlama/passport-local-mongoose/compare/v6.0.1...v6.1.0) (2021-01-09) 59 | 60 | 61 | ### Features 62 | 63 | * remove babel and semver checks for crypto parameters ([857f93a](https://github.com/saintedlama/passport-local-mongoose/commit/857f93aabd3ee392aefe3dfb005ac4d88b1252d4)) 64 | 65 | ### [6.0.1](https://github.com/saintedlama/passport-local-mongoose/compare/v6.0.0...v6.0.1) (2020-01-04) 66 | 67 | 68 | ### Bug Fixes 69 | 70 | * remove superfluous files from package ([b1681a5](https://github.com/saintedlama/passport-local-mongoose/commit/b1681a5d47ef3dc0d50a60d795c925f6a03e0683)) 71 | 72 | ## [6.0.0](https://github.com/saintedlama/passport-local-mongoose/compare/v5.0.1...v6.0.0) (2020-01-04) 73 | 74 | 75 | ### ⚠ BREAKING CHANGES 76 | 77 | * mongodb driver update requires server port to be specified 78 | 79 | ### Bug Fixes 80 | 81 | * make debug a dev dependency ([c9b0a78](https://github.com/saintedlama/passport-local-mongoose/commit/c9b0a78761c53edfde6850ed997662e001c430e7)) 82 | * update dependencies and specify server port in tests ([807d9cf](https://github.com/saintedlama/passport-local-mongoose/commit/807d9cf669f7a7c433eb0206c97574761c03b8e5)) 83 | * use Buffer.from instead of new Buffer ([37375b8](https://github.com/saintedlama/passport-local-mongoose/commit/37375b8b5555d82e6e6241fbd053f2f6b8d670d1)) 84 | 85 | 86 | ## [5.0.1](https://github.com/saintedlama/passport-local-mongoose/compare/v5.0.0...v5.0.1) (2018-06-20) 87 | 88 | 89 | ### Bug Fixes 90 | 91 | * require node engine >= 6.0.0 ([b18f083](https://github.com/saintedlama/passport-local-mongoose/commit/b18f083)) 92 | * update nyc dev dependency ([74854b1](https://github.com/saintedlama/passport-local-mongoose/commit/74854b1)) 93 | 94 | 95 | 96 | 97 | # [5.0.0](https://github.com/saintedlama/passport-local-mongoose/compare/v4.5.0...v5.0.0) (2018-03-01) 98 | 99 | 100 | ### Bug Fixes 101 | 102 | * add back passwordValidatorPromisified ([4794266](https://github.com/saintedlama/passport-local-mongoose/commit/4794266)) 103 | * promise return signature ([0612b5b](https://github.com/saintedlama/passport-local-mongoose/commit/0612b5b)) 104 | * update build matrix ([788e0c9](https://github.com/saintedlama/passport-local-mongoose/commit/788e0c9)) 105 | 106 | 107 | ### Features 108 | 109 | * implement promises for authenticate and static authenticate including tests ([6752e0c](https://github.com/saintedlama/passport-local-mongoose/commit/6752e0c)) 110 | * promisify external functions not part of passport.js interface ([e687753](https://github.com/saintedlama/passport-local-mongoose/commit/e687753)) 111 | 112 | 113 | ### BREAKING CHANGES 114 | 115 | * drop support for node 4 and 5 116 | 117 | 118 | 119 | 4.4.0 / 2017-10-25 120 | ================== 121 | 122 | * 4.4.0 123 | * Merge pull request [#233](https://github.com/saintedlama/passport-local-mongoose/issues/233) from 4umfreak/master 124 | Issue [#79](https://github.com/saintedlama/passport-local-mongoose/issues/79) and Bug [#58](https://github.com/saintedlama/passport-local-mongoose/issues/58), handle save() asynchronously 125 | * Update changelog 126 | 127 | 4.3.0 / 2017-10-25 128 | ================== 129 | 130 | * 4.3.0 131 | * Merge pull request [#234](https://github.com/saintedlama/passport-local-mongoose/issues/234) from MeestorHok/master 132 | Fixed vulnerable dependency 133 | * Fixed vulnerable dependency 134 | * fixed up code tabbing style differences 135 | * added code and tests to handle mongoose errors and concurrency gracefully. 136 | 137 | 4.2.1 / 2017-08-26 138 | ================== 139 | 140 | * 4.2.1 141 | * Revert setting hash and salt to null in model since this is a breaking change with possibly the implication to loos credentials in a running system 142 | * Remove superfluous parameters and ; 143 | 144 | 4.2.0 / 2017-08-24 145 | ================== 146 | 147 | * 4.2.0 148 | * Remove methuselah aged node.js versions 0.10 and 0.12 from travis build matrix 149 | * Correct test to check that salt and hash are null 150 | * Merge branch 'master' of github.com:saintedlama/passport-local-mongoose 151 | * Implement findByUsername option. Fixes [#227](https://github.com/saintedlama/passport-local-mongoose/issues/227) 152 | * Move function setPasswordAndAuthenticate to end of file 153 | * Merge pull request [#226](https://github.com/saintedlama/passport-local-mongoose/issues/226) from guoyunhe/patch-1 154 | Hide hash and salt fields of user in register() 155 | * Change undefined to null 156 | * Hide hash and salt of user in authenticate callback 157 | After authentication, salt and hash are usually not used anymore. It is better to drop them to avoid exposing in `req.user` 158 | * Hide hash and salt fields of user in register() 159 | Usually, in `register()` callback, you do not need salt and hash anymore. They should be hidden to avoid exposing to API. 160 | 161 | 4.1.0 / 2017-08-08 162 | ================== 163 | 164 | * 4.1.0 165 | * Move to nyc for coverage 166 | * Adapt change password functionality and tests 167 | * Refactor authenticate function to its own module 168 | * Merge pull request [#128](https://github.com/saintedlama/passport-local-mongoose/issues/128) from Gentlee/change-password 169 | Implement changePassword method [#127](https://github.com/saintedlama/passport-local-mongoose/issues/127) 170 | * Merge pull request [#140](https://github.com/saintedlama/passport-local-mongoose/issues/140) from AshfordN/patch-2 171 | Update index.js 172 | * Add syntax highlighting to code examples 173 | * Modernize example code by using single line variable declarations and const 174 | * Refactor pbkdf2 adapter to a module of its own 175 | * Update dependencies 176 | * Update build matrix to test against node 7, 8 and mongodb 3.4 177 | * Compare fields and not object to avoid fields added by mongoose to break the build 178 | * Downgrade to cross-env ^2.0.0.0 to run tests on node 0.10 and 0.12 179 | * Update dependencies and adapt code to pass buffers to scmp 2 180 | * Set timeout to 5000ms for all tests 181 | * Use the ^ semver operator instead of 4.5.x operator 182 | * Update dependencies and add debug dependency 183 | * Minor code style fixes 184 | * Migrate from assert to chai.expect 185 | * Retry dropping mongodb collections 186 | Implementation works around a mongoose issue that background indexes are created while trying to drop a collection 187 | * Migrate to chai.expect 188 | * Migrate to chai.expect and cleanup code 189 | * Rename test "error" to "errors" to match tested file 190 | * Update index.js 191 | Corrected Grammatical error in the IncorrectUsernameError and IncorrectUsernameError messages 192 | * Simplify .travis.yml by moving dependencies required for coverage to dev dependencies 193 | * Adapt .travis.yml to new container based infrastructure 194 | * Fix output handling in shelljs 0.7 195 | * Use cross-env for cross platform tests 196 | * if user model doesn't include salt/hash, get them from db, change tests timeouts 197 | * optimize and add test for situation when passwords are the same 198 | * fix changePassword() test 199 | * implement changePassword method 200 | * Merge pull request [#123](https://github.com/saintedlama/passport-local-mongoose/issues/123) from Gentlee/optimize-lowercase 201 | optimize username lowercasing 202 | * Remove io.js from build matrix 203 | * Use travis container-based infrastructure 204 | * Simplify repository field 205 | * Use digestAlgorithm sha1 and sha1 generated hash for backward compatibility tests 206 | * optimize username lowercase 207 | * Add test to verify that authenticate/hashing is 3.0.0 compatible 208 | 209 | 4.0.0 / 2016-01-15 210 | ================== 211 | 212 | * 4.0.0 213 | * Revert "Revert "Use semver to do a version check instead of argument length checks"" 214 | This reverts commit e17e720867eb283789d9461ec9b452fb513ee52e. 215 | 216 | 3.1.2 / 2016-01-15 217 | ================== 218 | 219 | * 3.1.2 220 | * Revert "Use semver to do a version check instead of argument length checks" 221 | This reverts commit 8732239272636272badcc7e88e0483fdd2be0366. 222 | 223 | 3.1.1 / 2016-01-15 224 | ================== 225 | 226 | * 3.1.1 227 | * Run tests against latest 4.x and latest 5.x versions 228 | * Use semver to do a version check instead of argument length checks 229 | * Update changelog 230 | 231 | 3.1.0 / 2015-10-05 232 | ================== 233 | 234 | * 3.1.0 235 | * Bring back customizable error messages 236 | 237 | 3.0.0 / 2015-09-21 238 | ================== 239 | 240 | * 3.0.0 241 | * Make the example depend on the latest npm version 242 | * Move main file to index.js to simplify the package 243 | * Refactor error generation and yielding 244 | * Rename variable Err to errors 245 | * Move mongotest module to helpers 246 | * Merge pull request [#105](https://github.com/saintedlama/passport-local-mongoose/issues/105) from opencharterhub/fix/error-handling 247 | Error handling: Always return instance of 'AuthenticationError' 248 | * Lint: Add some semicolons 249 | * Lint: Handle error case 250 | * Lint: Don't shadow variable names 251 | * Error handling: Always return instance of 'AuthenticationError' 252 | 253 | 2.0.0 / 2015-09-14 254 | ================== 255 | 256 | * 2.0.0 257 | * Update changelog 258 | * Add upgrade warning and document new default digest algorithm 259 | * Add node.js 4.0.0 as build target 260 | * Reformat code 261 | * Add editorconfig 262 | * Update dependencies 263 | 264 | 1.3.0 / 2015-09-14 265 | ================== 266 | 267 | * 1.3.0 268 | * Remove superfluous queryParameters declaration 269 | * Add missing semicolon 270 | * Merge pull request [#98](https://github.com/saintedlama/passport-local-mongoose/issues/98) from theanimal666/master 271 | Fix Issue [#96](https://github.com/saintedlama/passport-local-mongoose/issues/96) 272 | * Replace my tabs with spaces to macth project coding style 273 | * Support test MongoDB server other then localhost 274 | Implemented using MONGO_SERVER environment variable 275 | * Merge remote-tracking branch 'upstream/master' 276 | * Make authenticate work without salt/hash selected by default 277 | * Add a generated changelog 278 | 279 | 1.2.0 / 2015-08-28 280 | ================== 281 | 282 | * 1.2.0 283 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Christoph Walcher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | __THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE.__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Passport-Local Mongoose 2 | 3 | Passport-Local Mongoose is a [Mongoose](http://mongoosejs.com/) [plugin](http://mongoosejs.com/docs/plugins.html) 4 | that simplifies building username and password login with [Passport](http://passportjs.org). 5 | 6 | [![Node.js CI](https://github.com/saintedlama/passport-local-mongoose/actions/workflows/ci.yml/badge.svg)](https://github.com/saintedlama/passport-local-mongoose/actions/workflows/ci.yml) 7 | [![Coverage Status](https://coveralls.io/repos/saintedlama/passport-local-mongoose/badge.png?branch=master)](https://coveralls.io/r/saintedlama/passport-local-mongoose?branch=master) 8 | 9 | ## Tutorials 10 | 11 | Michael Herman gives a comprehensible walk through for setting up mongoose, 12 | passport, passport-local and passport-local-mongoose for user authentication in his blog post [User Authentication With Passport.js](http://mherman.org/blog/2013/11/11/user-authentication-with-passport-dot-js/) 13 | 14 | ## Installation 15 | 16 | ```bash 17 | > npm install passport-local-mongoose 18 | ``` 19 | 20 | Passport-Local Mongoose does not require `passport` or `mongoose` dependencies directly but expects you 21 | to have these dependencies installed. 22 | 23 | In case you need to install the whole set of dependencies 24 | 25 | ```bash 26 | > npm install passport mongoose passport-local-mongoose 27 | ``` 28 | 29 | ## Usage 30 | 31 | ### Plugin Passport-Local Mongoose 32 | 33 | First you need to plugin Passport-Local Mongoose into your User schema 34 | 35 | ```javascript 36 | const mongoose = require('mongoose'); 37 | const Schema = mongoose.Schema; 38 | const passportLocalMongoose = require('passport-local-mongoose'); 39 | 40 | const User = new Schema({}); 41 | 42 | User.plugin(passportLocalMongoose); 43 | 44 | module.exports = mongoose.model('User', User); 45 | ``` 46 | 47 | You're free to define your User how you like. Passport-Local Mongoose will add a username, hash and salt field to store the username, the hashed password and the salt value. 48 | 49 | Additionally, Passport-Local Mongoose adds some methods to your Schema. See the [API Documentation](https://github.com/saintedlama/passport-local-mongoose#api-documentation) section for more details. 50 | 51 | ### Configure Passport/Passport-Local 52 | 53 | You should configure Passport/Passport-Local as described in [the Passport Guide](http://passportjs.org/guide/configure/). 54 | 55 | Passport-Local Mongoose supports this setup by implementing a `LocalStrategy` and serializeUser/deserializeUser functions. 56 | 57 | To setup Passport-Local Mongoose use this code 58 | 59 | ```javascript 60 | // requires the model with Passport-Local Mongoose plugged in 61 | const User = require('./models/user'); 62 | 63 | // use static authenticate method of model in LocalStrategy 64 | passport.use(new LocalStrategy(User.authenticate())); 65 | 66 | // use static serialize and deserialize of model for passport session support 67 | passport.serializeUser(User.serializeUser()); 68 | passport.deserializeUser(User.deserializeUser()); 69 | ``` 70 | 71 | Make sure that you have mongoose connected to mongodb and you're done. 72 | 73 | #### Simplified Passport/Passport-Local Configuration 74 | 75 | Starting from version 0.2.1, passport-local-mongoose adds a helper method `createStrategy` as static method to your schema. 76 | The `createStrategy` is responsible to setup passport-local `LocalStrategy` with the correct options. 77 | 78 | ```javascript 79 | const User = require('./models/user'); 80 | 81 | // CHANGE: USE "createStrategy" INSTEAD OF "authenticate" 82 | passport.use(User.createStrategy()); 83 | 84 | passport.serializeUser(User.serializeUser()); 85 | passport.deserializeUser(User.deserializeUser()); 86 | ``` 87 | 88 | The reason for this functionality is that when using the `usernameField` option to specify an alternative usernameField name, for example "email" passport-local would still expect your frontend login form to contain an input field with name "username" instead of email. This can be configured for passport-local but this is double the work. So we got this shortcut implemented. 89 | 90 | ### Async/Await 91 | 92 | Starting from version `5.0.0`, passport-local-mongoose is async/await enabled by returning 93 | Promises for all instance and static methods except `serializeUser` and `deserializeUser`. 94 | 95 | ```js 96 | const user = new DefaultUser({username: 'user'}); 97 | await user.setPassword('password'); 98 | await user.save(); 99 | const { user } = await DefaultUser.authenticate()('user', 'password'); 100 | ``` 101 | 102 | ### Options 103 | 104 | When plugging in Passport-Local Mongoose plugin, additional options can be provided to configure 105 | the hashing algorithm. 106 | 107 | ```javascript 108 | User.plugin(passportLocalMongoose, options); 109 | ``` 110 | 111 | #### Main Options 112 | 113 | * `saltlen`: specifies the salt length in bytes. Default: 32 114 | * `iterations`: specifies the number of iterations used in pbkdf2 hashing algorithm. Default: 25000 115 | * `keylen`: specifies the length in byte of the generated key. Default: 512 116 | * `digestAlgorithm`: specifies the pbkdf2 digest algorithm. Default: sha256. (get a list of supported algorithms with crypto.getHashes()) 117 | * `interval`: specifies the interval in milliseconds between login attempts, which increases exponentially based on the number of failed attempts, up to maxInterval. Default: 100 118 | * `maxInterval`: specifies the maximum amount of time an account can be locked. Default 300000 (5 minutes) 119 | * `usernameField`: specifies the field name that holds the username. Defaults to 'username'. This option can be used if you want to use a different field to hold the username for example "email". 120 | * `usernameUnique`: specifies if the username field should be enforced to be unique by a mongodb index or not. Defaults to true. 121 | * `saltField`: specifies the field name that holds the salt value. Defaults to 'salt'. 122 | * `hashField`: specifies the field name that holds the password hash value. Defaults to 'hash'. 123 | * `attemptsField`: specifies the field name that holds the number of login failures since the last successful login. Defaults to 'attempts'. 124 | * `lastLoginField`: specifies the field name that holds the timestamp of the last login attempt. Defaults to 'last'. 125 | * `selectFields`: specifies the fields of the model to be selected from mongodb (and stored in the session). Defaults to 'undefined' so that all fields of the model are selected. 126 | * `usernameCaseInsensitive`: specifies the usernames to be case insensitive. Defaults to 'false'. 127 | * `usernameLowerCase`: convert username field value to lower case when saving an querying. Defaults to 'false'. 128 | * `populateFields`: specifies fields to populate in findByUsername function. Defaults to 'undefined'. 129 | * `encoding`: specifies the encoding the generated salt and hash will be stored in. Defaults to 'hex'. 130 | * `limitAttempts`: specifies whether login attempts should be limited and login failures should be penalized. Default: false. 131 | * `maxAttempts`: specifies the maximum number of failed attempts allowed before preventing login. Default: Infinity. 132 | * `unlockInterval`: specifies the interval in milliseconds, which is for unlock user automatically after the interval is reached. Defaults to 'undefined' which means deactivated. 133 | * `passwordValidator`: specifies your custom validation function for the password in the form: 134 | ```js 135 | passwordValidator = function(password,cb) { 136 | if (someValidationErrorExists(password)) { 137 | return cb('this is my custom validation error message') 138 | } 139 | // return an empty cb() on success 140 | return cb() 141 | } 142 | ``` 143 | Default: validates non-empty passwords. 144 | * `passwordValidatorAsync`: specifies your custom validation function for the password with promises in the form: 145 | ```js 146 | passwordValidatorAsync = function(password) { 147 | return someAsyncValidation(password) 148 | .catch(function(err){ 149 | return Promise.reject(err) 150 | }) 151 | } 152 | ``` 153 | * `usernameQueryFields`: specifies alternative fields of the model for identifying a user (e.g. email). 154 | * `findByUsername`: Specifies a query function that is executed with query parameters to restrict the query with extra query parameters. For example query only users with field "active" set to `true`. Default: `function(model, queryParameters) { return model.findOne(queryParameters); }`. See the examples section for a use case. 155 | 156 | **_Attention!_** Changing any of the hashing options (saltlen, iterations or keylen) in a production environment will prevent existing users from authenticating! 157 | 158 | #### Error Messages 159 | 160 | Override default error messages by setting `options.errorMessages`. 161 | 162 | * `MissingPasswordError`: 'No password was given' 163 | * `AttemptTooSoonError`: 'Account is currently locked. Try again later' 164 | * `TooManyAttemptsError`: 'Account locked due to too many failed login attempts' 165 | * `NoSaltValueStoredError`: 'Authentication not possible. No salt value stored' 166 | * `IncorrectPasswordError`: 'Password or username are incorrect' 167 | * `IncorrectUsernameError`: 'Password or username are incorrect' 168 | * `MissingUsernameError`: 'No username was given' 169 | * `UserExistsError`: 'A user with the given username is already registered' 170 | 171 | ### Hash Algorithm 172 | 173 | Passport-Local Mongoose use the pbkdf2 algorithm of the node crypto library. 174 | [Pbkdf2](http://en.wikipedia.org/wiki/PBKDF2) was chosen because platform independent 175 | (in contrary to bcrypt). For every user a generated salt value is saved to make 176 | rainbow table attacks even harder. 177 | 178 | ### Examples 179 | 180 | For a complete example implementing a registration, login and logout see the 181 | [login example](https://github.com/saintedlama/passport-local-mongoose/tree/master/examples/login). 182 | 183 | ## API Documentation 184 | 185 | ### Instance methods 186 | 187 | #### setPassword(password, [cb]) 188 | 189 | Sets a user password. Does not save the user object. If no callback `cb` is provided a `Promise` is returned. 190 | 191 | #### changePassword(oldPassword, newPassword, [cb]) 192 | 193 | Changes a user's password hash and salt, resets the user's number of failed password attempts and saves the user object (everything only if oldPassword is correct). If no callback `cb` is provided a `Promise` is returned. If oldPassword does not match the user's old password, an `IncorrectPasswordError` is passed to `cb` or the `Promise` is rejected. 194 | 195 | #### authenticate(password, [cb]) 196 | 197 | Authenticates a user object. If no callback `cb` is provided a `Promise` is returned. 198 | 199 | #### resetAttempts([cb]) 200 | 201 | Resets a user's number of failed password attempts and saves the user object. If no callback `cb` is provided a `Promise` is returned. This method is only defined if `options.limitAttempts` is `true`. 202 | 203 | ### Callback Arguments 204 | 205 | * `err` 206 | * null unless the hashing algorithm throws an error 207 | * `thisModel` 208 | * the model getting authenticated **_if_** authentication was successful otherwise false 209 | * `passwordErr` 210 | * an instance of `AuthenticationError` describing the reason the password failed, else undefined. 211 | 212 | Using `setPassword()` will only update the document's password fields, but will not save the document. 213 | To commit the changed document, remember to use Mongoose's `document.save()` after using `setPassword()`. 214 | 215 | ### Error Handling 216 | 217 | * `IncorrectPasswordError`: specifies the error message returned when the password is incorrect. Defaults to 'Incorrect password'. 218 | * `IncorrectUsernameError`: specifies the error message returned when the username is incorrect. Defaults to 'Incorrect username'. 219 | * `MissingUsernameError`: specifies the error message returned when the username has not been set during registration. Defaults to 'Field %s is not set'. 220 | * `MissingPasswordError`: specifies the error message returned when the password has not been set during registration. Defaults to 'Password argument not set!'. 221 | * `UserExistsError`: specifies the error message returned when the user already exists during registration. Defaults to 'User already exists with name %s'. 222 | * `NoSaltValueStored`: Occurs in case no salt value is stored in the MongoDB collection. 223 | * `AttemptTooSoonError`: Occurs if the option `limitAttempts` is set to true and a login attept occures while the user is still penalized. 224 | * `TooManyAttemptsError`: Returned when the user's account is locked due to too many failed login attempts. 225 | 226 | All those errors inherit from `AuthenticationError`, if you need a more general error class for checking. 227 | 228 | ### Static methods 229 | 230 | Static methods are exposed on the model constructor. For example to use createStrategy function use 231 | 232 | ```javascript 233 | const User = require('./models/user'); 234 | User.createStrategy(); 235 | ``` 236 | 237 | * `authenticate()` Generates a function that is used in Passport's LocalStrategy 238 | * `serializeUser()` Generates a function that is used by Passport to serialize users into the session 239 | * `deserializeUser()` Generates a function that is used by Passport to deserialize users into the session 240 | * `register(user, password, cb)` Convenience method to register a new user instance with a given password. Checks if username is unique. See [login example](https://github.com/saintedlama/passport-local-mongoose/tree/master/examples/login). 241 | * `findByUsername()` Convenience method to find a user instance by it's unique username. 242 | * `createStrategy()` Creates a configured passport-local `LocalStrategy` instance that can be used in passport. 243 | 244 | ## Examples 245 | 246 | ### Allow only "active" users to authenticate 247 | 248 | First, we define a schema with an additional field `active` of type Boolean. 249 | 250 | ```javascript 251 | const UserSchema = new Schema({ 252 | active: Boolean 253 | }); 254 | ``` 255 | 256 | When plugging in Passport-Local Mongoose, we set `usernameUnique` to avoid creating a unique mongodb index on field `username`. To avoid 257 | non active users being queried by mongodb, we can specify the option `findByUsername` that allows us to restrict a query. In our case 258 | we want to restrict the query to only query users with field `active` set to `true`. The `findByUsername` MUST return a Mongoose query. 259 | 260 | ```javascript 261 | UserSchema.plugin(passportLocalMongoose, { 262 | // Set usernameUnique to false to avoid a mongodb index on the username column! 263 | usernameUnique: false, 264 | 265 | findByUsername: function(model, queryParameters) { 266 | // Add additional query parameter - AND condition - active: true 267 | queryParameters.active = true; 268 | return model.findOne(queryParameters); 269 | } 270 | }); 271 | ``` 272 | 273 | To test the implementation, we can simply create (register) a user with field `active` set to `false` and try to authenticate this user 274 | in a second step: 275 | 276 | ```javascript 277 | const User = mongoose.model('Users', UserSchema); 278 | 279 | User.register({username:'username', active: false}, 'password', function(err, user) { 280 | if (err) { ... } 281 | 282 | const authenticate = User.authenticate(); 283 | authenticate('username', 'password', function(err, result) { 284 | if (err) { ... } 285 | 286 | // Value 'result' is set to false. The user could not be authenticated since the user is not active 287 | }); 288 | }); 289 | ``` 290 | 291 | ## Updating from 1.x to 2.x 292 | 293 | The default digest algorithm was changed due to security implications from **sha1** to **sha256**. If you decide to upgrade a production system from 1.x to 2.x, your users **will not be able to login** since the digest algorithm was changed! In these cases plan some migration strategy and/or use the **sha1** option for the digest algorithm. 294 | 295 | ## License 296 | 297 | Passport-Local Mongoose is licensed under the [MIT license](http://opensource.org/licenses/MIT). 298 | -------------------------------------------------------------------------------- /examples/login/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const favicon = require('serve-favicon'); 4 | const logger = require('morgan'); 5 | const cookieParser = require('cookie-parser'); 6 | const session = require('cookie-session'); 7 | const bodyParser = require('body-parser'); 8 | 9 | const mongoose = require('mongoose'); 10 | const passport = require('passport'); 11 | const LocalStrategy = require('passport-local').Strategy; 12 | const flash = require('connect-flash'); 13 | 14 | const app = express(); 15 | 16 | // view engine setup 17 | app.set('views', path.join(__dirname, 'views')); 18 | app.set('view engine', 'pug'); 19 | 20 | // uncomment after placing your favicon in /public 21 | //app.use(favicon(__dirname + '/public/favicon.ico')); 22 | app.use(logger('dev')); 23 | app.use(bodyParser.json()); 24 | app.use(bodyParser.urlencoded({extended: false})); 25 | app.use(cookieParser()); 26 | app.use(session({keys: ['secretkey1', 'secretkey2', '...']})); 27 | app.use(flash()); 28 | 29 | app.use(express.static(path.join(__dirname, 'public'))); 30 | 31 | // Configure passport middleware 32 | app.use(passport.initialize()); 33 | app.use(passport.session()); 34 | 35 | // Configure passport-local to use account model for authentication 36 | const Account = require('./models/account'); 37 | passport.use(new LocalStrategy(Account.authenticate())); 38 | 39 | passport.serializeUser(Account.serializeUser()); 40 | passport.deserializeUser(Account.deserializeUser()); 41 | 42 | // Connect mongoose 43 | mongoose.connect('mongodb://localhost/passport_local_mongoose_examples', { useNewUrlParser: true, useUnifiedTopology: true }, function(err) { 44 | if (err) { 45 | console.log('Could not connect to mongodb on localhost. Ensure that you have mongodb running on localhost and mongodb accepts connections on standard ports!'); 46 | } 47 | }); 48 | mongoose.set('useCreateIndex', true); 49 | 50 | // Register routes 51 | app.use('/', require('./routes')); 52 | 53 | // catch 404 and forward to error handler 54 | app.use(function(req, res, next) { 55 | const err = new Error('Not Found'); 56 | err.status = 404; 57 | next(err); 58 | }); 59 | 60 | // error handlers 61 | 62 | // development error handler 63 | // will print stacktrace 64 | if (app.get('env') === 'development') { 65 | app.use(function(err, req, res, next) { 66 | res.status(err.status || 500); 67 | res.render('error', { 68 | message: err.message, 69 | error: err 70 | }); 71 | }); 72 | } 73 | 74 | // production error handler 75 | // no stacktraces leaked to user 76 | app.use(function(err, req, res, next) { 77 | res.status(err.status || 500); 78 | res.render('error', { 79 | message: err.message, 80 | error: {} 81 | }); 82 | }); 83 | 84 | module.exports = app; 85 | -------------------------------------------------------------------------------- /examples/login/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var app = require('../app'); 3 | var pkg = require('../package.json'); 4 | 5 | app.set('port', process.env.PORT || 3000); 6 | 7 | var server = app.listen(app.get('port'), function() { 8 | console.log(pkg.name, 'listening on port ', server.address().port); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/login/models/account.js: -------------------------------------------------------------------------------- 1 | let mongoose = require('mongoose'), 2 | Schema = mongoose.Schema, 3 | passportLocalMongoose = require('passport-local-mongoose'); 4 | 5 | const Account = new Schema({ 6 | nickname: String, 7 | birthdate: Date 8 | }); 9 | 10 | Account.plugin(passportLocalMongoose); 11 | 12 | module.exports = mongoose.model('Account', Account); -------------------------------------------------------------------------------- /examples/login/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-local-mongoose-login", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/helper-validator-identifier": { 8 | "version": "7.16.7", 9 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", 10 | "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" 11 | }, 12 | "@babel/parser": { 13 | "version": "7.17.3", 14 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", 15 | "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==" 16 | }, 17 | "@babel/types": { 18 | "version": "7.17.0", 19 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", 20 | "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", 21 | "requires": { 22 | "@babel/helper-validator-identifier": "^7.16.7", 23 | "to-fast-properties": "^2.0.0" 24 | } 25 | }, 26 | "@types/node": { 27 | "version": "17.0.21", 28 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", 29 | "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==" 30 | }, 31 | "@types/webidl-conversions": { 32 | "version": "6.1.1", 33 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", 34 | "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" 35 | }, 36 | "@types/whatwg-url": { 37 | "version": "8.2.1", 38 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", 39 | "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", 40 | "requires": { 41 | "@types/node": "*", 42 | "@types/webidl-conversions": "*" 43 | } 44 | }, 45 | "accepts": { 46 | "version": "1.3.8", 47 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 48 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 49 | "requires": { 50 | "mime-types": "~2.1.34", 51 | "negotiator": "0.6.3" 52 | } 53 | }, 54 | "acorn": { 55 | "version": "7.4.1", 56 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 57 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" 58 | }, 59 | "array-flatten": { 60 | "version": "1.1.1", 61 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 62 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 63 | }, 64 | "asap": { 65 | "version": "2.0.6", 66 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 67 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" 68 | }, 69 | "assert-never": { 70 | "version": "1.2.1", 71 | "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", 72 | "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" 73 | }, 74 | "babel-walk": { 75 | "version": "3.0.0-canary-5", 76 | "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", 77 | "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", 78 | "requires": { 79 | "@babel/types": "^7.9.6" 80 | } 81 | }, 82 | "base64-js": { 83 | "version": "1.5.1", 84 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 85 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 86 | }, 87 | "basic-auth": { 88 | "version": "2.0.1", 89 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 90 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 91 | "requires": { 92 | "safe-buffer": "5.1.2" 93 | }, 94 | "dependencies": { 95 | "safe-buffer": { 96 | "version": "5.1.2", 97 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 98 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 99 | } 100 | } 101 | }, 102 | "body-parser": { 103 | "version": "1.19.2", 104 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", 105 | "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", 106 | "requires": { 107 | "bytes": "3.1.2", 108 | "content-type": "~1.0.4", 109 | "debug": "2.6.9", 110 | "depd": "~1.1.2", 111 | "http-errors": "1.8.1", 112 | "iconv-lite": "0.4.24", 113 | "on-finished": "~2.3.0", 114 | "qs": "6.9.7", 115 | "raw-body": "2.4.3", 116 | "type-is": "~1.6.18" 117 | }, 118 | "dependencies": { 119 | "debug": { 120 | "version": "2.6.9", 121 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 122 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 123 | "requires": { 124 | "ms": "2.0.0" 125 | } 126 | } 127 | } 128 | }, 129 | "bson": { 130 | "version": "4.6.1", 131 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz", 132 | "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==", 133 | "requires": { 134 | "buffer": "^5.6.0" 135 | } 136 | }, 137 | "buffer": { 138 | "version": "5.7.1", 139 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 140 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 141 | "requires": { 142 | "base64-js": "^1.3.1", 143 | "ieee754": "^1.1.13" 144 | } 145 | }, 146 | "bytes": { 147 | "version": "3.1.2", 148 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 149 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 150 | }, 151 | "call-bind": { 152 | "version": "1.0.2", 153 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 154 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 155 | "requires": { 156 | "function-bind": "^1.1.1", 157 | "get-intrinsic": "^1.0.2" 158 | } 159 | }, 160 | "character-parser": { 161 | "version": "2.2.0", 162 | "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", 163 | "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", 164 | "requires": { 165 | "is-regex": "^1.0.3" 166 | } 167 | }, 168 | "connect-flash": { 169 | "version": "0.1.1", 170 | "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", 171 | "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=" 172 | }, 173 | "constantinople": { 174 | "version": "4.0.1", 175 | "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", 176 | "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", 177 | "requires": { 178 | "@babel/parser": "^7.6.0", 179 | "@babel/types": "^7.6.1" 180 | } 181 | }, 182 | "content-disposition": { 183 | "version": "0.5.4", 184 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 185 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 186 | "requires": { 187 | "safe-buffer": "5.2.1" 188 | } 189 | }, 190 | "content-type": { 191 | "version": "1.0.4", 192 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 193 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 194 | }, 195 | "cookie": { 196 | "version": "0.4.1", 197 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 198 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" 199 | }, 200 | "cookie-parser": { 201 | "version": "1.4.6", 202 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", 203 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", 204 | "requires": { 205 | "cookie": "0.4.1", 206 | "cookie-signature": "1.0.6" 207 | } 208 | }, 209 | "cookie-session": { 210 | "version": "2.0.0", 211 | "resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.0.0.tgz", 212 | "integrity": "sha512-hKvgoThbw00zQOleSlUr2qpvuNweoqBtxrmx0UFosx6AGi9lYtLoA+RbsvknrEX8Pr6MDbdWAb2j6SnMn+lPsg==", 213 | "requires": { 214 | "cookies": "0.8.0", 215 | "debug": "3.2.7", 216 | "on-headers": "~1.0.2", 217 | "safe-buffer": "5.2.1" 218 | }, 219 | "dependencies": { 220 | "debug": { 221 | "version": "3.2.7", 222 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 223 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 224 | "requires": { 225 | "ms": "^2.1.1" 226 | } 227 | }, 228 | "ms": { 229 | "version": "2.1.3", 230 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 231 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 232 | } 233 | } 234 | }, 235 | "cookie-signature": { 236 | "version": "1.0.6", 237 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 238 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 239 | }, 240 | "cookies": { 241 | "version": "0.8.0", 242 | "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", 243 | "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", 244 | "requires": { 245 | "depd": "~2.0.0", 246 | "keygrip": "~1.1.0" 247 | }, 248 | "dependencies": { 249 | "depd": { 250 | "version": "2.0.0", 251 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 252 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 253 | } 254 | } 255 | }, 256 | "debug": { 257 | "version": "4.3.3", 258 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 259 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 260 | "requires": { 261 | "ms": "2.1.2" 262 | }, 263 | "dependencies": { 264 | "ms": { 265 | "version": "2.1.2", 266 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 267 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 268 | } 269 | } 270 | }, 271 | "denque": { 272 | "version": "2.0.1", 273 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", 274 | "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" 275 | }, 276 | "depd": { 277 | "version": "1.1.2", 278 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 279 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 280 | }, 281 | "destroy": { 282 | "version": "1.0.4", 283 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 284 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 285 | }, 286 | "doctypes": { 287 | "version": "1.1.0", 288 | "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", 289 | "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" 290 | }, 291 | "ee-first": { 292 | "version": "1.1.1", 293 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 294 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 295 | }, 296 | "encodeurl": { 297 | "version": "1.0.2", 298 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 299 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 300 | }, 301 | "escape-html": { 302 | "version": "1.0.3", 303 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 304 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 305 | }, 306 | "etag": { 307 | "version": "1.8.1", 308 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 309 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 310 | }, 311 | "express": { 312 | "version": "4.17.3", 313 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", 314 | "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", 315 | "requires": { 316 | "accepts": "~1.3.8", 317 | "array-flatten": "1.1.1", 318 | "body-parser": "1.19.2", 319 | "content-disposition": "0.5.4", 320 | "content-type": "~1.0.4", 321 | "cookie": "0.4.2", 322 | "cookie-signature": "1.0.6", 323 | "debug": "2.6.9", 324 | "depd": "~1.1.2", 325 | "encodeurl": "~1.0.2", 326 | "escape-html": "~1.0.3", 327 | "etag": "~1.8.1", 328 | "finalhandler": "~1.1.2", 329 | "fresh": "0.5.2", 330 | "merge-descriptors": "1.0.1", 331 | "methods": "~1.1.2", 332 | "on-finished": "~2.3.0", 333 | "parseurl": "~1.3.3", 334 | "path-to-regexp": "0.1.7", 335 | "proxy-addr": "~2.0.7", 336 | "qs": "6.9.7", 337 | "range-parser": "~1.2.1", 338 | "safe-buffer": "5.2.1", 339 | "send": "0.17.2", 340 | "serve-static": "1.14.2", 341 | "setprototypeof": "1.2.0", 342 | "statuses": "~1.5.0", 343 | "type-is": "~1.6.18", 344 | "utils-merge": "1.0.1", 345 | "vary": "~1.1.2" 346 | }, 347 | "dependencies": { 348 | "cookie": { 349 | "version": "0.4.2", 350 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", 351 | "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" 352 | }, 353 | "debug": { 354 | "version": "2.6.9", 355 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 356 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 357 | "requires": { 358 | "ms": "2.0.0" 359 | } 360 | } 361 | } 362 | }, 363 | "finalhandler": { 364 | "version": "1.1.2", 365 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 366 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 367 | "requires": { 368 | "debug": "2.6.9", 369 | "encodeurl": "~1.0.2", 370 | "escape-html": "~1.0.3", 371 | "on-finished": "~2.3.0", 372 | "parseurl": "~1.3.3", 373 | "statuses": "~1.5.0", 374 | "unpipe": "~1.0.0" 375 | }, 376 | "dependencies": { 377 | "debug": { 378 | "version": "2.6.9", 379 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 380 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 381 | "requires": { 382 | "ms": "2.0.0" 383 | } 384 | } 385 | } 386 | }, 387 | "forwarded": { 388 | "version": "0.2.0", 389 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 390 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 391 | }, 392 | "fresh": { 393 | "version": "0.5.2", 394 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 395 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 396 | }, 397 | "function-bind": { 398 | "version": "1.1.1", 399 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 400 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 401 | }, 402 | "generaterr": { 403 | "version": "1.5.0", 404 | "resolved": "https://registry.npmjs.org/generaterr/-/generaterr-1.5.0.tgz", 405 | "integrity": "sha1-sM62zFFk3yoGEzjMNAqGFTlcUvw=" 406 | }, 407 | "get-intrinsic": { 408 | "version": "1.1.1", 409 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 410 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 411 | "requires": { 412 | "function-bind": "^1.1.1", 413 | "has": "^1.0.3", 414 | "has-symbols": "^1.0.1" 415 | } 416 | }, 417 | "has": { 418 | "version": "1.0.3", 419 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 420 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 421 | "requires": { 422 | "function-bind": "^1.1.1" 423 | } 424 | }, 425 | "has-symbols": { 426 | "version": "1.0.3", 427 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 428 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 429 | }, 430 | "has-tostringtag": { 431 | "version": "1.0.0", 432 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", 433 | "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", 434 | "requires": { 435 | "has-symbols": "^1.0.2" 436 | } 437 | }, 438 | "http-errors": { 439 | "version": "1.8.1", 440 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 441 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 442 | "requires": { 443 | "depd": "~1.1.2", 444 | "inherits": "2.0.4", 445 | "setprototypeof": "1.2.0", 446 | "statuses": ">= 1.5.0 < 2", 447 | "toidentifier": "1.0.1" 448 | } 449 | }, 450 | "iconv-lite": { 451 | "version": "0.4.24", 452 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 453 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 454 | "requires": { 455 | "safer-buffer": ">= 2.1.2 < 3" 456 | } 457 | }, 458 | "ieee754": { 459 | "version": "1.2.1", 460 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 461 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 462 | }, 463 | "inherits": { 464 | "version": "2.0.4", 465 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 466 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 467 | }, 468 | "ip": { 469 | "version": "1.1.5", 470 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", 471 | "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" 472 | }, 473 | "ipaddr.js": { 474 | "version": "1.9.1", 475 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 476 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 477 | }, 478 | "is-core-module": { 479 | "version": "2.8.1", 480 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", 481 | "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", 482 | "requires": { 483 | "has": "^1.0.3" 484 | } 485 | }, 486 | "is-expression": { 487 | "version": "4.0.0", 488 | "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", 489 | "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", 490 | "requires": { 491 | "acorn": "^7.1.1", 492 | "object-assign": "^4.1.1" 493 | } 494 | }, 495 | "is-promise": { 496 | "version": "2.2.2", 497 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", 498 | "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" 499 | }, 500 | "is-regex": { 501 | "version": "1.1.4", 502 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", 503 | "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", 504 | "requires": { 505 | "call-bind": "^1.0.2", 506 | "has-tostringtag": "^1.0.0" 507 | } 508 | }, 509 | "js-stringify": { 510 | "version": "1.0.2", 511 | "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", 512 | "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" 513 | }, 514 | "jstransformer": { 515 | "version": "1.0.0", 516 | "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", 517 | "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", 518 | "requires": { 519 | "is-promise": "^2.0.0", 520 | "promise": "^7.0.1" 521 | } 522 | }, 523 | "kareem": { 524 | "version": "2.3.4", 525 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.4.tgz", 526 | "integrity": "sha512-Vcrt8lcpVl0s8ePx634BxwRqmFo+5DcOhlmNadehxreMTIQi/9hOL/B3hZQQbK5DgMS7Lem3xABXV7/S3jy+7g==" 527 | }, 528 | "keygrip": { 529 | "version": "1.1.0", 530 | "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", 531 | "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", 532 | "requires": { 533 | "tsscmp": "1.0.6" 534 | } 535 | }, 536 | "media-typer": { 537 | "version": "0.3.0", 538 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 539 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 540 | }, 541 | "memory-pager": { 542 | "version": "1.5.0", 543 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 544 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 545 | "optional": true 546 | }, 547 | "merge-descriptors": { 548 | "version": "1.0.1", 549 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 550 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 551 | }, 552 | "methods": { 553 | "version": "1.1.2", 554 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 555 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 556 | }, 557 | "mime": { 558 | "version": "1.6.0", 559 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 560 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 561 | }, 562 | "mime-db": { 563 | "version": "1.51.0", 564 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 565 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" 566 | }, 567 | "mime-types": { 568 | "version": "2.1.34", 569 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 570 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 571 | "requires": { 572 | "mime-db": "1.51.0" 573 | } 574 | }, 575 | "mongodb": { 576 | "version": "4.3.1", 577 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.3.1.tgz", 578 | "integrity": "sha512-sNa8APSIk+r4x31ZwctKjuPSaeKuvUeNb/fu/3B6dRM02HpEgig7hTHM8A/PJQTlxuC/KFWlDlQjhsk/S43tBg==", 579 | "requires": { 580 | "bson": "^4.6.1", 581 | "denque": "^2.0.1", 582 | "mongodb-connection-string-url": "^2.4.1", 583 | "saslprep": "^1.0.3", 584 | "socks": "^2.6.1" 585 | } 586 | }, 587 | "mongodb-connection-string-url": { 588 | "version": "2.5.2", 589 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz", 590 | "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==", 591 | "requires": { 592 | "@types/whatwg-url": "^8.2.1", 593 | "whatwg-url": "^11.0.0" 594 | } 595 | }, 596 | "mongoose": { 597 | "version": "6.2.5", 598 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.2.5.tgz", 599 | "integrity": "sha512-85EVVxv9ml84dLYR9QfULehLMepmaINMcBMVzBszbT5jbKGHkpbgl4lI46uTWqr6CFqirM3/6YLWEyTYm1xQZQ==", 600 | "requires": { 601 | "bson": "^4.2.2", 602 | "kareem": "2.3.4", 603 | "mongodb": "4.3.1", 604 | "mpath": "0.8.4", 605 | "mquery": "4.0.2", 606 | "ms": "2.1.3", 607 | "sift": "16.0.0" 608 | }, 609 | "dependencies": { 610 | "ms": { 611 | "version": "2.1.3", 612 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 613 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 614 | } 615 | } 616 | }, 617 | "morgan": { 618 | "version": "1.10.0", 619 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", 620 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", 621 | "requires": { 622 | "basic-auth": "~2.0.1", 623 | "debug": "2.6.9", 624 | "depd": "~2.0.0", 625 | "on-finished": "~2.3.0", 626 | "on-headers": "~1.0.2" 627 | }, 628 | "dependencies": { 629 | "debug": { 630 | "version": "2.6.9", 631 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 632 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 633 | "requires": { 634 | "ms": "2.0.0" 635 | } 636 | }, 637 | "depd": { 638 | "version": "2.0.0", 639 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 640 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 641 | } 642 | } 643 | }, 644 | "mpath": { 645 | "version": "0.8.4", 646 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", 647 | "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==" 648 | }, 649 | "mquery": { 650 | "version": "4.0.2", 651 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.2.tgz", 652 | "integrity": "sha512-oAVF0Nil1mT3rxty6Zln4YiD6x6QsUWYz927jZzjMxOK2aqmhEz5JQ7xmrKK7xRFA2dwV+YaOpKU/S+vfNqKxA==", 653 | "requires": { 654 | "debug": "4.x" 655 | } 656 | }, 657 | "ms": { 658 | "version": "2.0.0", 659 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 660 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 661 | }, 662 | "negotiator": { 663 | "version": "0.6.3", 664 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 665 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 666 | }, 667 | "object-assign": { 668 | "version": "4.1.1", 669 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 670 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 671 | }, 672 | "on-finished": { 673 | "version": "2.3.0", 674 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 675 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 676 | "requires": { 677 | "ee-first": "1.1.1" 678 | } 679 | }, 680 | "on-headers": { 681 | "version": "1.0.2", 682 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 683 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" 684 | }, 685 | "parseurl": { 686 | "version": "1.3.3", 687 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 688 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 689 | }, 690 | "passport": { 691 | "version": "0.5.2", 692 | "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.2.tgz", 693 | "integrity": "sha512-w9n/Ot5I7orGD4y+7V3EFJCQEznE5RxHamUxcqLT2QoJY0f2JdN8GyHonYFvN0Vz+L6lUJfVhrk2aZz2LbuREw==", 694 | "requires": { 695 | "passport-strategy": "1.x.x", 696 | "pause": "0.0.1" 697 | } 698 | }, 699 | "passport-local": { 700 | "version": "1.0.0", 701 | "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", 702 | "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", 703 | "requires": { 704 | "passport-strategy": "1.x.x" 705 | } 706 | }, 707 | "passport-local-mongoose": { 708 | "version": "6.1.0", 709 | "resolved": "https://registry.npmjs.org/passport-local-mongoose/-/passport-local-mongoose-6.1.0.tgz", 710 | "integrity": "sha512-kxRDejpBXoPmWau1RCrmEeNYEXGG9ec4aDYjd0pFAHIEAzZ0RXKn581ISfjpHZ1zZLoCCM2pWUo4SfGHNJNwnw==", 711 | "requires": { 712 | "generaterr": "^1.5.0", 713 | "passport-local": "^1.0.0", 714 | "scmp": "^2.1.0" 715 | } 716 | }, 717 | "passport-strategy": { 718 | "version": "1.0.0", 719 | "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", 720 | "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" 721 | }, 722 | "path-parse": { 723 | "version": "1.0.7", 724 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 725 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 726 | }, 727 | "path-to-regexp": { 728 | "version": "0.1.7", 729 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 730 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 731 | }, 732 | "pause": { 733 | "version": "0.0.1", 734 | "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", 735 | "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" 736 | }, 737 | "promise": { 738 | "version": "7.3.1", 739 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 740 | "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", 741 | "requires": { 742 | "asap": "~2.0.3" 743 | } 744 | }, 745 | "proxy-addr": { 746 | "version": "2.0.7", 747 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 748 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 749 | "requires": { 750 | "forwarded": "0.2.0", 751 | "ipaddr.js": "1.9.1" 752 | } 753 | }, 754 | "pug": { 755 | "version": "3.0.2", 756 | "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz", 757 | "integrity": "sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==", 758 | "requires": { 759 | "pug-code-gen": "^3.0.2", 760 | "pug-filters": "^4.0.0", 761 | "pug-lexer": "^5.0.1", 762 | "pug-linker": "^4.0.0", 763 | "pug-load": "^3.0.0", 764 | "pug-parser": "^6.0.0", 765 | "pug-runtime": "^3.0.1", 766 | "pug-strip-comments": "^2.0.0" 767 | } 768 | }, 769 | "pug-attrs": { 770 | "version": "3.0.0", 771 | "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", 772 | "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", 773 | "requires": { 774 | "constantinople": "^4.0.1", 775 | "js-stringify": "^1.0.2", 776 | "pug-runtime": "^3.0.0" 777 | } 778 | }, 779 | "pug-code-gen": { 780 | "version": "3.0.2", 781 | "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.2.tgz", 782 | "integrity": "sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==", 783 | "requires": { 784 | "constantinople": "^4.0.1", 785 | "doctypes": "^1.1.0", 786 | "js-stringify": "^1.0.2", 787 | "pug-attrs": "^3.0.0", 788 | "pug-error": "^2.0.0", 789 | "pug-runtime": "^3.0.0", 790 | "void-elements": "^3.1.0", 791 | "with": "^7.0.0" 792 | } 793 | }, 794 | "pug-error": { 795 | "version": "2.0.0", 796 | "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", 797 | "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" 798 | }, 799 | "pug-filters": { 800 | "version": "4.0.0", 801 | "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", 802 | "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", 803 | "requires": { 804 | "constantinople": "^4.0.1", 805 | "jstransformer": "1.0.0", 806 | "pug-error": "^2.0.0", 807 | "pug-walk": "^2.0.0", 808 | "resolve": "^1.15.1" 809 | } 810 | }, 811 | "pug-lexer": { 812 | "version": "5.0.1", 813 | "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", 814 | "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", 815 | "requires": { 816 | "character-parser": "^2.2.0", 817 | "is-expression": "^4.0.0", 818 | "pug-error": "^2.0.0" 819 | } 820 | }, 821 | "pug-linker": { 822 | "version": "4.0.0", 823 | "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", 824 | "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", 825 | "requires": { 826 | "pug-error": "^2.0.0", 827 | "pug-walk": "^2.0.0" 828 | } 829 | }, 830 | "pug-load": { 831 | "version": "3.0.0", 832 | "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", 833 | "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", 834 | "requires": { 835 | "object-assign": "^4.1.1", 836 | "pug-walk": "^2.0.0" 837 | } 838 | }, 839 | "pug-parser": { 840 | "version": "6.0.0", 841 | "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", 842 | "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", 843 | "requires": { 844 | "pug-error": "^2.0.0", 845 | "token-stream": "1.0.0" 846 | } 847 | }, 848 | "pug-runtime": { 849 | "version": "3.0.1", 850 | "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", 851 | "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==" 852 | }, 853 | "pug-strip-comments": { 854 | "version": "2.0.0", 855 | "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", 856 | "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", 857 | "requires": { 858 | "pug-error": "^2.0.0" 859 | } 860 | }, 861 | "pug-walk": { 862 | "version": "2.0.0", 863 | "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", 864 | "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" 865 | }, 866 | "punycode": { 867 | "version": "2.1.1", 868 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 869 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 870 | }, 871 | "qs": { 872 | "version": "6.9.7", 873 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", 874 | "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" 875 | }, 876 | "range-parser": { 877 | "version": "1.2.1", 878 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 879 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 880 | }, 881 | "raw-body": { 882 | "version": "2.4.3", 883 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", 884 | "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", 885 | "requires": { 886 | "bytes": "3.1.2", 887 | "http-errors": "1.8.1", 888 | "iconv-lite": "0.4.24", 889 | "unpipe": "1.0.0" 890 | } 891 | }, 892 | "resolve": { 893 | "version": "1.22.0", 894 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", 895 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", 896 | "requires": { 897 | "is-core-module": "^2.8.1", 898 | "path-parse": "^1.0.7", 899 | "supports-preserve-symlinks-flag": "^1.0.0" 900 | } 901 | }, 902 | "safe-buffer": { 903 | "version": "5.2.1", 904 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 905 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 906 | }, 907 | "safer-buffer": { 908 | "version": "2.1.2", 909 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 910 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 911 | }, 912 | "saslprep": { 913 | "version": "1.0.3", 914 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 915 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 916 | "optional": true, 917 | "requires": { 918 | "sparse-bitfield": "^3.0.3" 919 | } 920 | }, 921 | "scmp": { 922 | "version": "2.1.0", 923 | "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", 924 | "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" 925 | }, 926 | "send": { 927 | "version": "0.17.2", 928 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", 929 | "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", 930 | "requires": { 931 | "debug": "2.6.9", 932 | "depd": "~1.1.2", 933 | "destroy": "~1.0.4", 934 | "encodeurl": "~1.0.2", 935 | "escape-html": "~1.0.3", 936 | "etag": "~1.8.1", 937 | "fresh": "0.5.2", 938 | "http-errors": "1.8.1", 939 | "mime": "1.6.0", 940 | "ms": "2.1.3", 941 | "on-finished": "~2.3.0", 942 | "range-parser": "~1.2.1", 943 | "statuses": "~1.5.0" 944 | }, 945 | "dependencies": { 946 | "debug": { 947 | "version": "2.6.9", 948 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 949 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 950 | "requires": { 951 | "ms": "2.0.0" 952 | }, 953 | "dependencies": { 954 | "ms": { 955 | "version": "2.0.0", 956 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 957 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 958 | } 959 | } 960 | }, 961 | "ms": { 962 | "version": "2.1.3", 963 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 964 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 965 | } 966 | } 967 | }, 968 | "serve-favicon": { 969 | "version": "2.5.0", 970 | "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz", 971 | "integrity": "sha1-k10kDN/g9YBTB/3+ln2IlCosvPA=", 972 | "requires": { 973 | "etag": "~1.8.1", 974 | "fresh": "0.5.2", 975 | "ms": "2.1.1", 976 | "parseurl": "~1.3.2", 977 | "safe-buffer": "5.1.1" 978 | }, 979 | "dependencies": { 980 | "ms": { 981 | "version": "2.1.1", 982 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 983 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 984 | }, 985 | "safe-buffer": { 986 | "version": "5.1.1", 987 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 988 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 989 | } 990 | } 991 | }, 992 | "serve-static": { 993 | "version": "1.14.2", 994 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", 995 | "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", 996 | "requires": { 997 | "encodeurl": "~1.0.2", 998 | "escape-html": "~1.0.3", 999 | "parseurl": "~1.3.3", 1000 | "send": "0.17.2" 1001 | } 1002 | }, 1003 | "setprototypeof": { 1004 | "version": "1.2.0", 1005 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1006 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1007 | }, 1008 | "sift": { 1009 | "version": "16.0.0", 1010 | "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz", 1011 | "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ==" 1012 | }, 1013 | "smart-buffer": { 1014 | "version": "4.2.0", 1015 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", 1016 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" 1017 | }, 1018 | "socks": { 1019 | "version": "2.6.2", 1020 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", 1021 | "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", 1022 | "requires": { 1023 | "ip": "^1.1.5", 1024 | "smart-buffer": "^4.2.0" 1025 | } 1026 | }, 1027 | "sparse-bitfield": { 1028 | "version": "3.0.3", 1029 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 1030 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 1031 | "optional": true, 1032 | "requires": { 1033 | "memory-pager": "^1.0.2" 1034 | } 1035 | }, 1036 | "statuses": { 1037 | "version": "1.5.0", 1038 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1039 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1040 | }, 1041 | "supports-preserve-symlinks-flag": { 1042 | "version": "1.0.0", 1043 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1044 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" 1045 | }, 1046 | "to-fast-properties": { 1047 | "version": "2.0.0", 1048 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1049 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" 1050 | }, 1051 | "toidentifier": { 1052 | "version": "1.0.1", 1053 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1054 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 1055 | }, 1056 | "token-stream": { 1057 | "version": "1.0.0", 1058 | "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", 1059 | "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" 1060 | }, 1061 | "tr46": { 1062 | "version": "3.0.0", 1063 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", 1064 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", 1065 | "requires": { 1066 | "punycode": "^2.1.1" 1067 | } 1068 | }, 1069 | "tsscmp": { 1070 | "version": "1.0.6", 1071 | "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", 1072 | "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" 1073 | }, 1074 | "type-is": { 1075 | "version": "1.6.18", 1076 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1077 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1078 | "requires": { 1079 | "media-typer": "0.3.0", 1080 | "mime-types": "~2.1.24" 1081 | } 1082 | }, 1083 | "unpipe": { 1084 | "version": "1.0.0", 1085 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1086 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1087 | }, 1088 | "utils-merge": { 1089 | "version": "1.0.1", 1090 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1091 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1092 | }, 1093 | "vary": { 1094 | "version": "1.1.2", 1095 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1096 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1097 | }, 1098 | "void-elements": { 1099 | "version": "3.1.0", 1100 | "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", 1101 | "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" 1102 | }, 1103 | "webidl-conversions": { 1104 | "version": "7.0.0", 1105 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 1106 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" 1107 | }, 1108 | "whatwg-url": { 1109 | "version": "11.0.0", 1110 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", 1111 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", 1112 | "requires": { 1113 | "tr46": "^3.0.0", 1114 | "webidl-conversions": "^7.0.0" 1115 | } 1116 | }, 1117 | "with": { 1118 | "version": "7.0.2", 1119 | "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", 1120 | "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", 1121 | "requires": { 1122 | "@babel/parser": "^7.9.6", 1123 | "@babel/types": "^7.9.6", 1124 | "assert-never": "^1.2.1", 1125 | "babel-walk": "3.0.0-canary-5" 1126 | } 1127 | } 1128 | } 1129 | } 1130 | -------------------------------------------------------------------------------- /examples/login/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-local-mongoose-login", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "^1.19.2", 10 | "connect-flash": "^0.1.1", 11 | "cookie-parser": "^1.4.6", 12 | "cookie-session": "^2.0.0", 13 | "debug": "^4.3.3", 14 | "express": "^4.17.3", 15 | "mongoose": "^6.2.5", 16 | "morgan": "^1.10.0", 17 | "passport": "^0.5.2", 18 | "passport-local": "1.0.x", 19 | "passport-local-mongoose": "^6.1.0", 20 | "pug": "^3.0.2", 21 | "serve-favicon": "^2.5.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/login/public/css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.1.1 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | 11 | .clearfix { 12 | *zoom: 1; 13 | } 14 | 15 | .clearfix:before, 16 | .clearfix:after { 17 | display: table; 18 | line-height: 0; 19 | content: ""; 20 | } 21 | 22 | .clearfix:after { 23 | clear: both; 24 | } 25 | 26 | .hide-text { 27 | font: 0/0 a; 28 | color: transparent; 29 | text-shadow: none; 30 | background-color: transparent; 31 | border: 0; 32 | } 33 | 34 | .input-block-level { 35 | display: block; 36 | width: 100%; 37 | min-height: 30px; 38 | -webkit-box-sizing: border-box; 39 | -moz-box-sizing: border-box; 40 | box-sizing: border-box; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | visibility: hidden; 46 | } 47 | 48 | .visible-phone { 49 | display: none !important; 50 | } 51 | 52 | .visible-tablet { 53 | display: none !important; 54 | } 55 | 56 | .hidden-desktop { 57 | display: none !important; 58 | } 59 | 60 | .visible-desktop { 61 | display: inherit !important; 62 | } 63 | 64 | @media (min-width: 768px) and (max-width: 979px) { 65 | .hidden-desktop { 66 | display: inherit !important; 67 | } 68 | .visible-desktop { 69 | display: none !important ; 70 | } 71 | .visible-tablet { 72 | display: inherit !important; 73 | } 74 | .hidden-tablet { 75 | display: none !important; 76 | } 77 | } 78 | 79 | @media (max-width: 767px) { 80 | .hidden-desktop { 81 | display: inherit !important; 82 | } 83 | .visible-desktop { 84 | display: none !important; 85 | } 86 | .visible-phone { 87 | display: inherit !important; 88 | } 89 | .hidden-phone { 90 | display: none !important; 91 | } 92 | } 93 | 94 | @media (min-width: 1200px) { 95 | .row { 96 | margin-left: -30px; 97 | *zoom: 1; 98 | } 99 | .row:before, 100 | .row:after { 101 | display: table; 102 | line-height: 0; 103 | content: ""; 104 | } 105 | .row:after { 106 | clear: both; 107 | } 108 | [class*="span"] { 109 | float: left; 110 | min-height: 1px; 111 | margin-left: 30px; 112 | } 113 | .container, 114 | .navbar-static-top .container, 115 | .navbar-fixed-top .container, 116 | .navbar-fixed-bottom .container { 117 | width: 1170px; 118 | } 119 | .span12 { 120 | width: 1170px; 121 | } 122 | .span11 { 123 | width: 1070px; 124 | } 125 | .span10 { 126 | width: 970px; 127 | } 128 | .span9 { 129 | width: 870px; 130 | } 131 | .span8 { 132 | width: 770px; 133 | } 134 | .span7 { 135 | width: 670px; 136 | } 137 | .span6 { 138 | width: 570px; 139 | } 140 | .span5 { 141 | width: 470px; 142 | } 143 | .span4 { 144 | width: 370px; 145 | } 146 | .span3 { 147 | width: 270px; 148 | } 149 | .span2 { 150 | width: 170px; 151 | } 152 | .span1 { 153 | width: 70px; 154 | } 155 | .offset12 { 156 | margin-left: 1230px; 157 | } 158 | .offset11 { 159 | margin-left: 1130px; 160 | } 161 | .offset10 { 162 | margin-left: 1030px; 163 | } 164 | .offset9 { 165 | margin-left: 930px; 166 | } 167 | .offset8 { 168 | margin-left: 830px; 169 | } 170 | .offset7 { 171 | margin-left: 730px; 172 | } 173 | .offset6 { 174 | margin-left: 630px; 175 | } 176 | .offset5 { 177 | margin-left: 530px; 178 | } 179 | .offset4 { 180 | margin-left: 430px; 181 | } 182 | .offset3 { 183 | margin-left: 330px; 184 | } 185 | .offset2 { 186 | margin-left: 230px; 187 | } 188 | .offset1 { 189 | margin-left: 130px; 190 | } 191 | .row-fluid { 192 | width: 100%; 193 | *zoom: 1; 194 | } 195 | .row-fluid:before, 196 | .row-fluid:after { 197 | display: table; 198 | line-height: 0; 199 | content: ""; 200 | } 201 | .row-fluid:after { 202 | clear: both; 203 | } 204 | .row-fluid [class*="span"] { 205 | display: block; 206 | float: left; 207 | width: 100%; 208 | min-height: 30px; 209 | margin-left: 2.564102564102564%; 210 | *margin-left: 2.5109110747408616%; 211 | -webkit-box-sizing: border-box; 212 | -moz-box-sizing: border-box; 213 | box-sizing: border-box; 214 | } 215 | .row-fluid [class*="span"]:first-child { 216 | margin-left: 0; 217 | } 218 | .row-fluid .span12 { 219 | width: 100%; 220 | *width: 99.94680851063829%; 221 | } 222 | .row-fluid .span11 { 223 | width: 91.45299145299145%; 224 | *width: 91.39979996362975%; 225 | } 226 | .row-fluid .span10 { 227 | width: 82.90598290598291%; 228 | *width: 82.8527914166212%; 229 | } 230 | .row-fluid .span9 { 231 | width: 74.35897435897436%; 232 | *width: 74.30578286961266%; 233 | } 234 | .row-fluid .span8 { 235 | width: 65.81196581196582%; 236 | *width: 65.75877432260411%; 237 | } 238 | .row-fluid .span7 { 239 | width: 57.26495726495726%; 240 | *width: 57.21176577559556%; 241 | } 242 | .row-fluid .span6 { 243 | width: 48.717948717948715%; 244 | *width: 48.664757228587014%; 245 | } 246 | .row-fluid .span5 { 247 | width: 40.17094017094017%; 248 | *width: 40.11774868157847%; 249 | } 250 | .row-fluid .span4 { 251 | width: 31.623931623931625%; 252 | *width: 31.570740134569924%; 253 | } 254 | .row-fluid .span3 { 255 | width: 23.076923076923077%; 256 | *width: 23.023731587561375%; 257 | } 258 | .row-fluid .span2 { 259 | width: 14.52991452991453%; 260 | *width: 14.476723040552828%; 261 | } 262 | .row-fluid .span1 { 263 | width: 5.982905982905983%; 264 | *width: 5.929714493544281%; 265 | } 266 | .row-fluid .offset12 { 267 | margin-left: 105.12820512820512%; 268 | *margin-left: 105.02182214948171%; 269 | } 270 | .row-fluid .offset12:first-child { 271 | margin-left: 102.56410256410257%; 272 | *margin-left: 102.45771958537915%; 273 | } 274 | .row-fluid .offset11 { 275 | margin-left: 96.58119658119658%; 276 | *margin-left: 96.47481360247316%; 277 | } 278 | .row-fluid .offset11:first-child { 279 | margin-left: 94.01709401709402%; 280 | *margin-left: 93.91071103837061%; 281 | } 282 | .row-fluid .offset10 { 283 | margin-left: 88.03418803418803%; 284 | *margin-left: 87.92780505546462%; 285 | } 286 | .row-fluid .offset10:first-child { 287 | margin-left: 85.47008547008548%; 288 | *margin-left: 85.36370249136206%; 289 | } 290 | .row-fluid .offset9 { 291 | margin-left: 79.48717948717949%; 292 | *margin-left: 79.38079650845607%; 293 | } 294 | .row-fluid .offset9:first-child { 295 | margin-left: 76.92307692307693%; 296 | *margin-left: 76.81669394435352%; 297 | } 298 | .row-fluid .offset8 { 299 | margin-left: 70.94017094017094%; 300 | *margin-left: 70.83378796144753%; 301 | } 302 | .row-fluid .offset8:first-child { 303 | margin-left: 68.37606837606839%; 304 | *margin-left: 68.26968539734497%; 305 | } 306 | .row-fluid .offset7 { 307 | margin-left: 62.393162393162385%; 308 | *margin-left: 62.28677941443899%; 309 | } 310 | .row-fluid .offset7:first-child { 311 | margin-left: 59.82905982905982%; 312 | *margin-left: 59.72267685033642%; 313 | } 314 | .row-fluid .offset6 { 315 | margin-left: 53.84615384615384%; 316 | *margin-left: 53.739770867430444%; 317 | } 318 | .row-fluid .offset6:first-child { 319 | margin-left: 51.28205128205128%; 320 | *margin-left: 51.175668303327875%; 321 | } 322 | .row-fluid .offset5 { 323 | margin-left: 45.299145299145295%; 324 | *margin-left: 45.1927623204219%; 325 | } 326 | .row-fluid .offset5:first-child { 327 | margin-left: 42.73504273504273%; 328 | *margin-left: 42.62865975631933%; 329 | } 330 | .row-fluid .offset4 { 331 | margin-left: 36.75213675213675%; 332 | *margin-left: 36.645753773413354%; 333 | } 334 | .row-fluid .offset4:first-child { 335 | margin-left: 34.18803418803419%; 336 | *margin-left: 34.081651209310785%; 337 | } 338 | .row-fluid .offset3 { 339 | margin-left: 28.205128205128204%; 340 | *margin-left: 28.0987452264048%; 341 | } 342 | .row-fluid .offset3:first-child { 343 | margin-left: 25.641025641025642%; 344 | *margin-left: 25.53464266230224%; 345 | } 346 | .row-fluid .offset2 { 347 | margin-left: 19.65811965811966%; 348 | *margin-left: 19.551736679396257%; 349 | } 350 | .row-fluid .offset2:first-child { 351 | margin-left: 17.094017094017094%; 352 | *margin-left: 16.98763411529369%; 353 | } 354 | .row-fluid .offset1 { 355 | margin-left: 11.11111111111111%; 356 | *margin-left: 11.004728132387708%; 357 | } 358 | .row-fluid .offset1:first-child { 359 | margin-left: 8.547008547008547%; 360 | *margin-left: 8.440625568285142%; 361 | } 362 | input, 363 | textarea, 364 | .uneditable-input { 365 | margin-left: 0; 366 | } 367 | .controls-row [class*="span"] + [class*="span"] { 368 | margin-left: 30px; 369 | } 370 | input.span12, 371 | textarea.span12, 372 | .uneditable-input.span12 { 373 | width: 1156px; 374 | } 375 | input.span11, 376 | textarea.span11, 377 | .uneditable-input.span11 { 378 | width: 1056px; 379 | } 380 | input.span10, 381 | textarea.span10, 382 | .uneditable-input.span10 { 383 | width: 956px; 384 | } 385 | input.span9, 386 | textarea.span9, 387 | .uneditable-input.span9 { 388 | width: 856px; 389 | } 390 | input.span8, 391 | textarea.span8, 392 | .uneditable-input.span8 { 393 | width: 756px; 394 | } 395 | input.span7, 396 | textarea.span7, 397 | .uneditable-input.span7 { 398 | width: 656px; 399 | } 400 | input.span6, 401 | textarea.span6, 402 | .uneditable-input.span6 { 403 | width: 556px; 404 | } 405 | input.span5, 406 | textarea.span5, 407 | .uneditable-input.span5 { 408 | width: 456px; 409 | } 410 | input.span4, 411 | textarea.span4, 412 | .uneditable-input.span4 { 413 | width: 356px; 414 | } 415 | input.span3, 416 | textarea.span3, 417 | .uneditable-input.span3 { 418 | width: 256px; 419 | } 420 | input.span2, 421 | textarea.span2, 422 | .uneditable-input.span2 { 423 | width: 156px; 424 | } 425 | input.span1, 426 | textarea.span1, 427 | .uneditable-input.span1 { 428 | width: 56px; 429 | } 430 | .thumbnails { 431 | margin-left: -30px; 432 | } 433 | .thumbnails > li { 434 | margin-left: 30px; 435 | } 436 | .row-fluid .thumbnails { 437 | margin-left: 0; 438 | } 439 | } 440 | 441 | @media (min-width: 768px) and (max-width: 979px) { 442 | .row { 443 | margin-left: -20px; 444 | *zoom: 1; 445 | } 446 | .row:before, 447 | .row:after { 448 | display: table; 449 | line-height: 0; 450 | content: ""; 451 | } 452 | .row:after { 453 | clear: both; 454 | } 455 | [class*="span"] { 456 | float: left; 457 | min-height: 1px; 458 | margin-left: 20px; 459 | } 460 | .container, 461 | .navbar-static-top .container, 462 | .navbar-fixed-top .container, 463 | .navbar-fixed-bottom .container { 464 | width: 724px; 465 | } 466 | .span12 { 467 | width: 724px; 468 | } 469 | .span11 { 470 | width: 662px; 471 | } 472 | .span10 { 473 | width: 600px; 474 | } 475 | .span9 { 476 | width: 538px; 477 | } 478 | .span8 { 479 | width: 476px; 480 | } 481 | .span7 { 482 | width: 414px; 483 | } 484 | .span6 { 485 | width: 352px; 486 | } 487 | .span5 { 488 | width: 290px; 489 | } 490 | .span4 { 491 | width: 228px; 492 | } 493 | .span3 { 494 | width: 166px; 495 | } 496 | .span2 { 497 | width: 104px; 498 | } 499 | .span1 { 500 | width: 42px; 501 | } 502 | .offset12 { 503 | margin-left: 764px; 504 | } 505 | .offset11 { 506 | margin-left: 702px; 507 | } 508 | .offset10 { 509 | margin-left: 640px; 510 | } 511 | .offset9 { 512 | margin-left: 578px; 513 | } 514 | .offset8 { 515 | margin-left: 516px; 516 | } 517 | .offset7 { 518 | margin-left: 454px; 519 | } 520 | .offset6 { 521 | margin-left: 392px; 522 | } 523 | .offset5 { 524 | margin-left: 330px; 525 | } 526 | .offset4 { 527 | margin-left: 268px; 528 | } 529 | .offset3 { 530 | margin-left: 206px; 531 | } 532 | .offset2 { 533 | margin-left: 144px; 534 | } 535 | .offset1 { 536 | margin-left: 82px; 537 | } 538 | .row-fluid { 539 | width: 100%; 540 | *zoom: 1; 541 | } 542 | .row-fluid:before, 543 | .row-fluid:after { 544 | display: table; 545 | line-height: 0; 546 | content: ""; 547 | } 548 | .row-fluid:after { 549 | clear: both; 550 | } 551 | .row-fluid [class*="span"] { 552 | display: block; 553 | float: left; 554 | width: 100%; 555 | min-height: 30px; 556 | margin-left: 2.7624309392265194%; 557 | *margin-left: 2.709239449864817%; 558 | -webkit-box-sizing: border-box; 559 | -moz-box-sizing: border-box; 560 | box-sizing: border-box; 561 | } 562 | .row-fluid [class*="span"]:first-child { 563 | margin-left: 0; 564 | } 565 | .row-fluid .span12 { 566 | width: 100%; 567 | *width: 99.94680851063829%; 568 | } 569 | .row-fluid .span11 { 570 | width: 91.43646408839778%; 571 | *width: 91.38327259903608%; 572 | } 573 | .row-fluid .span10 { 574 | width: 82.87292817679558%; 575 | *width: 82.81973668743387%; 576 | } 577 | .row-fluid .span9 { 578 | width: 74.30939226519337%; 579 | *width: 74.25620077583166%; 580 | } 581 | .row-fluid .span8 { 582 | width: 65.74585635359117%; 583 | *width: 65.69266486422946%; 584 | } 585 | .row-fluid .span7 { 586 | width: 57.18232044198895%; 587 | *width: 57.12912895262725%; 588 | } 589 | .row-fluid .span6 { 590 | width: 48.61878453038674%; 591 | *width: 48.56559304102504%; 592 | } 593 | .row-fluid .span5 { 594 | width: 40.05524861878453%; 595 | *width: 40.00205712942283%; 596 | } 597 | .row-fluid .span4 { 598 | width: 31.491712707182323%; 599 | *width: 31.43852121782062%; 600 | } 601 | .row-fluid .span3 { 602 | width: 22.92817679558011%; 603 | *width: 22.87498530621841%; 604 | } 605 | .row-fluid .span2 { 606 | width: 14.3646408839779%; 607 | *width: 14.311449394616199%; 608 | } 609 | .row-fluid .span1 { 610 | width: 5.801104972375691%; 611 | *width: 5.747913483013988%; 612 | } 613 | .row-fluid .offset12 { 614 | margin-left: 105.52486187845304%; 615 | *margin-left: 105.41847889972962%; 616 | } 617 | .row-fluid .offset12:first-child { 618 | margin-left: 102.76243093922652%; 619 | *margin-left: 102.6560479605031%; 620 | } 621 | .row-fluid .offset11 { 622 | margin-left: 96.96132596685082%; 623 | *margin-left: 96.8549429881274%; 624 | } 625 | .row-fluid .offset11:first-child { 626 | margin-left: 94.1988950276243%; 627 | *margin-left: 94.09251204890089%; 628 | } 629 | .row-fluid .offset10 { 630 | margin-left: 88.39779005524862%; 631 | *margin-left: 88.2914070765252%; 632 | } 633 | .row-fluid .offset10:first-child { 634 | margin-left: 85.6353591160221%; 635 | *margin-left: 85.52897613729868%; 636 | } 637 | .row-fluid .offset9 { 638 | margin-left: 79.8342541436464%; 639 | *margin-left: 79.72787116492299%; 640 | } 641 | .row-fluid .offset9:first-child { 642 | margin-left: 77.07182320441989%; 643 | *margin-left: 76.96544022569647%; 644 | } 645 | .row-fluid .offset8 { 646 | margin-left: 71.2707182320442%; 647 | *margin-left: 71.16433525332079%; 648 | } 649 | .row-fluid .offset8:first-child { 650 | margin-left: 68.50828729281768%; 651 | *margin-left: 68.40190431409427%; 652 | } 653 | .row-fluid .offset7 { 654 | margin-left: 62.70718232044199%; 655 | *margin-left: 62.600799341718584%; 656 | } 657 | .row-fluid .offset7:first-child { 658 | margin-left: 59.94475138121547%; 659 | *margin-left: 59.838368402492065%; 660 | } 661 | .row-fluid .offset6 { 662 | margin-left: 54.14364640883978%; 663 | *margin-left: 54.037263430116376%; 664 | } 665 | .row-fluid .offset6:first-child { 666 | margin-left: 51.38121546961326%; 667 | *margin-left: 51.27483249088986%; 668 | } 669 | .row-fluid .offset5 { 670 | margin-left: 45.58011049723757%; 671 | *margin-left: 45.47372751851417%; 672 | } 673 | .row-fluid .offset5:first-child { 674 | margin-left: 42.81767955801105%; 675 | *margin-left: 42.71129657928765%; 676 | } 677 | .row-fluid .offset4 { 678 | margin-left: 37.01657458563536%; 679 | *margin-left: 36.91019160691196%; 680 | } 681 | .row-fluid .offset4:first-child { 682 | margin-left: 34.25414364640884%; 683 | *margin-left: 34.14776066768544%; 684 | } 685 | .row-fluid .offset3 { 686 | margin-left: 28.45303867403315%; 687 | *margin-left: 28.346655695309746%; 688 | } 689 | .row-fluid .offset3:first-child { 690 | margin-left: 25.69060773480663%; 691 | *margin-left: 25.584224756083227%; 692 | } 693 | .row-fluid .offset2 { 694 | margin-left: 19.88950276243094%; 695 | *margin-left: 19.783119783707537%; 696 | } 697 | .row-fluid .offset2:first-child { 698 | margin-left: 17.12707182320442%; 699 | *margin-left: 17.02068884448102%; 700 | } 701 | .row-fluid .offset1 { 702 | margin-left: 11.32596685082873%; 703 | *margin-left: 11.219583872105325%; 704 | } 705 | .row-fluid .offset1:first-child { 706 | margin-left: 8.56353591160221%; 707 | *margin-left: 8.457152932878806%; 708 | } 709 | input, 710 | textarea, 711 | .uneditable-input { 712 | margin-left: 0; 713 | } 714 | .controls-row [class*="span"] + [class*="span"] { 715 | margin-left: 20px; 716 | } 717 | input.span12, 718 | textarea.span12, 719 | .uneditable-input.span12 { 720 | width: 710px; 721 | } 722 | input.span11, 723 | textarea.span11, 724 | .uneditable-input.span11 { 725 | width: 648px; 726 | } 727 | input.span10, 728 | textarea.span10, 729 | .uneditable-input.span10 { 730 | width: 586px; 731 | } 732 | input.span9, 733 | textarea.span9, 734 | .uneditable-input.span9 { 735 | width: 524px; 736 | } 737 | input.span8, 738 | textarea.span8, 739 | .uneditable-input.span8 { 740 | width: 462px; 741 | } 742 | input.span7, 743 | textarea.span7, 744 | .uneditable-input.span7 { 745 | width: 400px; 746 | } 747 | input.span6, 748 | textarea.span6, 749 | .uneditable-input.span6 { 750 | width: 338px; 751 | } 752 | input.span5, 753 | textarea.span5, 754 | .uneditable-input.span5 { 755 | width: 276px; 756 | } 757 | input.span4, 758 | textarea.span4, 759 | .uneditable-input.span4 { 760 | width: 214px; 761 | } 762 | input.span3, 763 | textarea.span3, 764 | .uneditable-input.span3 { 765 | width: 152px; 766 | } 767 | input.span2, 768 | textarea.span2, 769 | .uneditable-input.span2 { 770 | width: 90px; 771 | } 772 | input.span1, 773 | textarea.span1, 774 | .uneditable-input.span1 { 775 | width: 28px; 776 | } 777 | } 778 | 779 | @media (max-width: 767px) { 780 | body { 781 | padding-right: 20px; 782 | padding-left: 20px; 783 | } 784 | .navbar-fixed-top, 785 | .navbar-fixed-bottom, 786 | .navbar-static-top { 787 | margin-right: -20px; 788 | margin-left: -20px; 789 | } 790 | .container-fluid { 791 | padding: 0; 792 | } 793 | .dl-horizontal dt { 794 | float: none; 795 | width: auto; 796 | clear: none; 797 | text-align: left; 798 | } 799 | .dl-horizontal dd { 800 | margin-left: 0; 801 | } 802 | .container { 803 | width: auto; 804 | } 805 | .row-fluid { 806 | width: 100%; 807 | } 808 | .row, 809 | .thumbnails { 810 | margin-left: 0; 811 | } 812 | .thumbnails > li { 813 | float: none; 814 | margin-left: 0; 815 | } 816 | [class*="span"], 817 | .row-fluid [class*="span"] { 818 | display: block; 819 | float: none; 820 | width: 100%; 821 | margin-left: 0; 822 | -webkit-box-sizing: border-box; 823 | -moz-box-sizing: border-box; 824 | box-sizing: border-box; 825 | } 826 | .span12, 827 | .row-fluid .span12 { 828 | width: 100%; 829 | -webkit-box-sizing: border-box; 830 | -moz-box-sizing: border-box; 831 | box-sizing: border-box; 832 | } 833 | .input-large, 834 | .input-xlarge, 835 | .input-xxlarge, 836 | input[class*="span"], 837 | select[class*="span"], 838 | textarea[class*="span"], 839 | .uneditable-input { 840 | display: block; 841 | width: 100%; 842 | min-height: 30px; 843 | -webkit-box-sizing: border-box; 844 | -moz-box-sizing: border-box; 845 | box-sizing: border-box; 846 | } 847 | .input-prepend input, 848 | .input-append input, 849 | .input-prepend input[class*="span"], 850 | .input-append input[class*="span"] { 851 | display: inline-block; 852 | width: auto; 853 | } 854 | .controls-row [class*="span"] + [class*="span"] { 855 | margin-left: 0; 856 | } 857 | .modal { 858 | position: fixed; 859 | top: 20px; 860 | right: 20px; 861 | left: 20px; 862 | width: auto; 863 | margin: 0; 864 | } 865 | .modal.fade.in { 866 | top: auto; 867 | } 868 | } 869 | 870 | @media (max-width: 480px) { 871 | .nav-collapse { 872 | -webkit-transform: translate3d(0, 0, 0); 873 | } 874 | .page-header h1 small { 875 | display: block; 876 | line-height: 20px; 877 | } 878 | input[type="checkbox"], 879 | input[type="radio"] { 880 | border: 1px solid #ccc; 881 | } 882 | .form-horizontal .control-label { 883 | float: none; 884 | width: auto; 885 | padding-top: 0; 886 | text-align: left; 887 | } 888 | .form-horizontal .controls { 889 | margin-left: 0; 890 | } 891 | .form-horizontal .control-list { 892 | padding-top: 0; 893 | } 894 | .form-horizontal .form-actions { 895 | padding-right: 10px; 896 | padding-left: 10px; 897 | } 898 | .modal { 899 | top: 10px; 900 | right: 10px; 901 | left: 10px; 902 | } 903 | .modal-header .close { 904 | padding: 10px; 905 | margin: -10px; 906 | } 907 | .carousel-caption { 908 | position: static; 909 | } 910 | } 911 | 912 | @media (max-width: 979px) { 913 | body { 914 | padding-top: 0; 915 | } 916 | .navbar-fixed-top, 917 | .navbar-fixed-bottom { 918 | position: static; 919 | } 920 | .navbar-fixed-top { 921 | margin-bottom: 20px; 922 | } 923 | .navbar-fixed-bottom { 924 | margin-top: 20px; 925 | } 926 | .navbar-fixed-top .navbar-inner, 927 | .navbar-fixed-bottom .navbar-inner { 928 | padding: 5px; 929 | } 930 | .navbar .container { 931 | width: auto; 932 | padding: 0; 933 | } 934 | .navbar .brand { 935 | padding-right: 10px; 936 | padding-left: 10px; 937 | margin: 0 0 0 -5px; 938 | } 939 | .nav-collapse { 940 | clear: both; 941 | } 942 | .nav-collapse .nav { 943 | float: none; 944 | margin: 0 0 10px; 945 | } 946 | .nav-collapse .nav > li { 947 | float: none; 948 | } 949 | .nav-collapse .nav > li > a { 950 | margin-bottom: 2px; 951 | } 952 | .nav-collapse .nav > .divider-vertical { 953 | display: none; 954 | } 955 | .nav-collapse .nav .nav-header { 956 | color: #777777; 957 | text-shadow: none; 958 | } 959 | .nav-collapse .nav > li > a, 960 | .nav-collapse .dropdown-menu a { 961 | padding: 9px 15px; 962 | font-weight: bold; 963 | color: #777777; 964 | -webkit-border-radius: 3px; 965 | -moz-border-radius: 3px; 966 | border-radius: 3px; 967 | } 968 | .nav-collapse .btn { 969 | padding: 4px 10px 4px; 970 | font-weight: normal; 971 | -webkit-border-radius: 4px; 972 | -moz-border-radius: 4px; 973 | border-radius: 4px; 974 | } 975 | .nav-collapse .dropdown-menu li + li a { 976 | margin-bottom: 2px; 977 | } 978 | .nav-collapse .nav > li > a:hover, 979 | .nav-collapse .dropdown-menu a:hover { 980 | background-color: #f2f2f2; 981 | } 982 | .navbar-inverse .nav-collapse .nav > li > a:hover, 983 | .navbar-inverse .nav-collapse .dropdown-menu a:hover { 984 | background-color: #111111; 985 | } 986 | .nav-collapse.in .btn-group { 987 | padding: 0; 988 | margin-top: 5px; 989 | } 990 | .nav-collapse .dropdown-menu { 991 | position: static; 992 | top: auto; 993 | left: auto; 994 | display: block; 995 | float: none; 996 | max-width: none; 997 | padding: 0; 998 | margin: 0 15px; 999 | background-color: transparent; 1000 | border: none; 1001 | -webkit-border-radius: 0; 1002 | -moz-border-radius: 0; 1003 | border-radius: 0; 1004 | -webkit-box-shadow: none; 1005 | -moz-box-shadow: none; 1006 | box-shadow: none; 1007 | } 1008 | .nav-collapse .dropdown-menu:before, 1009 | .nav-collapse .dropdown-menu:after { 1010 | display: none; 1011 | } 1012 | .nav-collapse .dropdown-menu .divider { 1013 | display: none; 1014 | } 1015 | .nav-collapse .nav > li > .dropdown-menu:before, 1016 | .nav-collapse .nav > li > .dropdown-menu:after { 1017 | display: none; 1018 | } 1019 | .nav-collapse .navbar-form, 1020 | .nav-collapse .navbar-search { 1021 | float: none; 1022 | padding: 10px 15px; 1023 | margin: 10px 0; 1024 | border-top: 1px solid #f2f2f2; 1025 | border-bottom: 1px solid #f2f2f2; 1026 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1027 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1028 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1029 | } 1030 | .navbar-inverse .nav-collapse .navbar-form, 1031 | .navbar-inverse .nav-collapse .navbar-search { 1032 | border-top-color: #111111; 1033 | border-bottom-color: #111111; 1034 | } 1035 | .navbar .nav-collapse .nav.pull-right { 1036 | float: none; 1037 | margin-left: 0; 1038 | } 1039 | .nav-collapse, 1040 | .nav-collapse.collapse { 1041 | height: 0; 1042 | overflow: hidden; 1043 | } 1044 | .navbar .btn-navbar { 1045 | display: block; 1046 | } 1047 | .navbar-static .navbar-inner { 1048 | padding-right: 10px; 1049 | padding-left: 10px; 1050 | } 1051 | } 1052 | 1053 | @media (min-width: 980px) { 1054 | .nav-collapse.collapse { 1055 | height: auto !important; 1056 | overflow: visible !important; 1057 | } 1058 | } 1059 | -------------------------------------------------------------------------------- /examples/login/public/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.1.1 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade.in{top:auto}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /examples/login/public/css/custom.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | } 4 | 5 | .display-data { 6 | display: block; 7 | width: 100%; 8 | padding: 0; 9 | margin-bottom: 20px; 10 | font-size: 21px; 11 | font-weight: normal; 12 | line-height: 40px; 13 | color: #333333; 14 | border: 0; 15 | border-bottom: 1px solid #e5e5e5; 16 | } 17 | 18 | .controls .error { 19 | color: #B94A48; 20 | } -------------------------------------------------------------------------------- /examples/login/public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saintedlama/passport-local-mongoose/50a3da6419d443d6215f176cf9cb6af9d7db8163/examples/login/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /examples/login/public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saintedlama/passport-local-mongoose/50a3da6419d443d6215f176cf9cb6af9d7db8163/examples/login/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /examples/login/public/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){e(function(){"use strict";e.support.transition=function(){const e=function(){let e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";let t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}let n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){let r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(function(){e("body").on("click.alert.data-api",t,n.prototype.close)})}(window.jQuery),!function(e){"use strict";const t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){let t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){const e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){let r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(function(){e("body").on("click.button.data-api","[data-toggle^=button]",function(t){let n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})})}(window.jQuery),!function(e){"use strict";const t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){let n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){let r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f=e.Event("slide",{relatedTarget:i[0]});this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u]();if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){let r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(function(){e("body").on("click.carousel.data-api","[data-slide]",function(t){let n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=!i.data("modal")&&e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})})}(window.jQuery),!function(e){"use strict";const t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){const e=this.$element.hasClass("width");return e?"width":"height"},show:function(){let t,n,r,i;if(this.transitioning)return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){let t;if(this.transitioning)return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){const t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){let i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},e.fn.collapse=function(n){return this.each(function(){let r=e(this),i=r.data("collapse"),s=typeof n=="object"&&n;i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e(function(){e("body").on("click.collapse.data-api","[data-toggle=collapse]",function(t){let n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})})}(window.jQuery),!function(e){"use strict";function r(){i(e(t)).removeClass("open")}function i(t){let n=t.attr("data-target"),r;return n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=e(n),r.length||(r=t.parent()),r}var t="[data-toggle=dropdown]",n=function(t){const n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){let n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||(s.toggleClass("open"),n.focus()),!1},keydown:function(t){let n,r,s,o,u,a;if(!/(38|40|27)/.test(t.keyCode))return;n=e(this),t.preventDefault(),t.stopPropagation();if(n.is(".disabled, :disabled"))return;o=i(n),u=o.hasClass("open");if(!u||u&&t.keyCode==27)return n.click();r=e("[role=menu] li:not(.divider) a",o);if(!r.length)return;a=r.index(r.filter(":focus")),t.keyCode==38&&a>0&&a--,t.keyCode==40&&a').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,e.proxy(this.removeBackdrop,this)):this.removeBackdrop()):t&&t()}},e.fn.modal=function(n){return this.each(function(){let r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e(function(){e("body").on("click.modal.data-api",'[data-toggle="modal"]',function(t){let n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})})}(window.jQuery),!function(e){"use strict";const t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){let i,s;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):this.options.trigger!="manual"&&(i=this.options.trigger=="hover"?"mouseenter":"focus",s=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this))),this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,t,this.$element.data()),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){const n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){const n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){let e,t,n,r,i,s,o;if(this.hasContent()&&this.enabled){e=this.tip(),this.setContent(),this.options.animation&&e.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,t=/in/.test(s),e.remove().css({top:0,left:0,display:"block"}).appendTo(t?this.$element:document.body),n=this.getPosition(t),r=e[0].offsetWidth,i=e[0].offsetHeight;switch(t?s.split(" ")[1]:s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}e.css(o).addClass(s).addClass("in")}},setContent:function(){let e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function r(){const t=setTimeout(function(){n.off(e.support.transition.end).remove()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.remove()})}var t=this,n=this.tip();return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?r():n.remove(),this},fixTitle:function(){const e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(t){return e.extend({},t?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){let e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},e.fn.tooltip=function(n){return this.each(function(){let r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'
',trigger:"hover",title:"",delay:0,html:!0}}(window.jQuery),!function(e){"use strict";const t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){let e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content > *")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){let e,t=this.$element,n=this.options;return e=t.attr("data-content")||(typeof n.content=="function"?n.content.call(t[0]):n.content),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),e.fn.popover=function(n){return this.each(function(){let r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'

'})}(window.jQuery),!function(e){"use strict";function t(t,n){let r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){let t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){let t=e(this),n=t.data("target")||t.attr("href"),r=/^#\w/.test(n)&&e(n);return r&&r.length&&[[r.position().top,n]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){let e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){let n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}},e.fn.scrollspy=function(n){return this.each(function(){let r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){const t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";const t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){let t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active a").last()[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}},e.fn.tab=function(n){return this.each(function(){let r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e(function(){e("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})})}(window.jQuery),!function(e){"use strict";const t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=e(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){const e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){const t=e.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:t.top+t.height,left:t.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){let n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){const n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){let n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){let t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),(e.browser.chrome||e.browser.webkit||e.browser.msie)&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this))},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=!~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(e){const t=this;setTimeout(function(){t.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(t){this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")}},e.fn.typeahead=function(n){return this.each(function(){let r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'
  • ',minLength:1},e.fn.typeahead.Constructor=t,e(function(){e("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){const n=e(this);if(n.data("typeahead"))return;t.preventDefault(),n.typeahead(n.data())})})}(window.jQuery),!function(e){"use strict";const t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;let t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))},e.fn.affix=function(n){return this.each(function(){let r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){let t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery); -------------------------------------------------------------------------------- /examples/login/routes.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const Account = require('./models/account'); 3 | const router = require('express').Router(); 4 | 5 | router.get('/', function(req, res) { 6 | res.render('index', {user: req.user}); 7 | }); 8 | 9 | router.get('/register', function(req, res) { 10 | res.render('register', {}); 11 | }); 12 | 13 | router.post('/register', function(req, res, next) { 14 | console.log('registering user'); 15 | Account.register(new Account({username: req.body.username}), req.body.password, function(err) { 16 | if (err) { 17 | console.log('error while user register!', err); 18 | return next(err); 19 | } 20 | 21 | console.log('user registered!'); 22 | 23 | res.redirect('/'); 24 | }); 25 | }); 26 | 27 | router.get('/login', function(req, res) { 28 | res.render('login', {user: req.user, message: req.flash('error')}); 29 | }); 30 | 31 | router.post('/login', passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }), function(req, res) { 32 | res.redirect('/'); 33 | }); 34 | 35 | router.get('/logout', function(req, res) { 36 | req.logout(); 37 | res.redirect('/'); 38 | }); 39 | 40 | module.exports = router; -------------------------------------------------------------------------------- /examples/login/views/error.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /examples/login/views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | if (user) 5 | p You are currently logged in as #{user.username} 6 | else 7 | p You are currently not logged in. Plaese login or register... -------------------------------------------------------------------------------- /examples/login/views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Passport Local Mongoose Login Example 5 | link(rel='stylesheet', href='/css/bootstrap.css') 6 | link(rel='stylesheet', href='/css/custom.css') 7 | block scripts 8 | script(src='/js/jquery.min.js') 9 | script(src='/js/bootstrap.min.js') 10 | 11 | body 12 | .navbar.navbar-inverse.navbar-fixed-top 13 | .navbar-inner 14 | .container 15 | a.brand(href="/") Example 16 | ul.nav 17 | if (typeof(user) == 'undefined') 18 | li 19 | a(href="/register") Register 20 | li 21 | a(href="/login") Log In 22 | else 23 | li 24 | a(href="/logout") Log Out 25 | 26 | .container 27 | block content 28 | -------------------------------------------------------------------------------- /examples/login/views/login.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h2 Log In 5 | 6 | if message != '' 7 | .alert.alert-error #{message} 8 | form(action="/login",method="post") 9 | div 10 | label Username 11 | input(type="text",name="username") 12 | 13 | div 14 | label Password 15 | input(type="password",name="password") 16 | 17 | div 18 | input(type="submit",value="Log In") 19 | -------------------------------------------------------------------------------- /examples/login/views/register.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h2 Register 5 | 6 | form(action="/register",method="post") 7 | div 8 | label Username 9 | input(type="text",name="username") 10 | 11 | div 12 | label Password 13 | input(type="password",name="password") 14 | 15 | div 16 | input(type="submit",value="Register") -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'mongoose' { 2 | import passportLocal = require('passport-local'); 3 | 4 | export interface AuthenticationResult { 5 | user: any; 6 | error: any; 7 | } 8 | 9 | // methods 10 | export interface PassportLocalDocument extends Document { 11 | setPassword(password: string): Promise; 12 | setPassword(password: string, cb: (err: any, res: any) => void): void; 13 | changePassword(oldPassword: string, newPassword: string): Promise; 14 | changePassword(oldPassword: string, newPassword: string, cb: (err: any, res: any) => void): void; 15 | authenticate(password: string): Promise; 16 | authenticate(password: string, cb: (err: any, user: any, error: any) => void): void; 17 | resetAttempts(): Promise; 18 | resetAttempts(cb: (err: any, res: any) => void): void; 19 | } 20 | 21 | interface AuthenticateMethod { 22 | (username: string, password: string): Promise; 23 | (username: string, password: string, cb: (err: any, user: T | boolean, error: any) => void): void; 24 | } 25 | 26 | // statics 27 | interface PassportLocalModel extends Model { 28 | authenticate(): AuthenticateMethod; 29 | serializeUser(): (user: T, cb: (err: any, id?: any) => void) => void; 30 | deserializeUser(): (username: string, cb: (err: any, user?: any) => void) => void; 31 | 32 | register(user: T, password: string): Promise; 33 | register(user: T, password: string, cb: (err: any, account: any) => void): void; 34 | findByUsername(username: string, selectHashSaltFields: boolean): Query; 35 | findByUsername(username: string, selectHashSaltFields: boolean, cb: (err: any, account: any) => void): any; 36 | createStrategy(): passportLocal.Strategy; 37 | } 38 | 39 | // error messages 40 | export interface PassportLocalErrorMessages { 41 | MissingPasswordError?: string | undefined; 42 | AttemptTooSoonError?: string | undefined; 43 | TooManyAttemptsError?: string | undefined; 44 | NoSaltValueStoredError?: string | undefined; 45 | IncorrectPasswordError?: string | undefined; 46 | IncorrectUsernameError?: string | undefined; 47 | MissingUsernameError?: string | undefined; 48 | UserExistsError?: string | undefined; 49 | } 50 | 51 | // plugin options 52 | export interface PassportLocalOptions { 53 | saltlen?: number | undefined; 54 | iterations?: number | undefined; 55 | keylen?: number | undefined; 56 | encoding?: string | undefined; 57 | digestAlgorithm?: string | undefined; 58 | passwordValidator?: ((password: string, cb: (err: any) => void) => void) | undefined; 59 | 60 | usernameField?: string | undefined; 61 | usernameUnique?: boolean | undefined; 62 | 63 | usernameQueryFields: Array; 64 | 65 | selectFields?: string | undefined; 66 | populateFields?: string | undefined; 67 | 68 | usernameCaseInsensitive?: boolean | undefined; 69 | usernameLowerCase?: boolean | undefined; 70 | 71 | hashField?: string | undefined; 72 | saltField?: string | undefined; 73 | 74 | limitAttempts?: boolean | undefined; 75 | lastLoginField?: string | undefined; 76 | attemptsField?: string | undefined; 77 | interval?: number | undefined; 78 | maxInterval?: number | undefined; 79 | maxAttempts?: number | undefined; 80 | 81 | errorMessages?: PassportLocalErrorMessages | undefined; 82 | } 83 | 84 | export interface PassportLocalSchema extends Schema { 85 | plugin( 86 | plugin: (schema: PassportLocalSchema, options?: PassportLocalOptions) => void, 87 | options?: PassportLocalOptions, 88 | ): this; 89 | 90 | // overload for the default mongoose plugin function 91 | plugin(plugin: (schema: Schema, options?: Object) => void, opts?: Object): this; 92 | } 93 | 94 | export function model( 95 | name: string, 96 | schema?: PassportLocalSchema>, 97 | collection?: string, 98 | skipInit?: boolean, 99 | ): PassportLocalModel; 100 | 101 | export function model>( 102 | name: string, 103 | schema?: PassportLocalSchema>, 104 | collection?: string, 105 | skipInit?: boolean, 106 | ): U; 107 | } 108 | 109 | declare module 'passport-local-mongoose' { 110 | import mongoose = require('mongoose'); 111 | var _: (schema: mongoose.Schema, options?: Object) => void; 112 | export = _; 113 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const LocalStrategy = require('passport-local').Strategy; 3 | 4 | const pbkdf2 = require('./lib/pbkdf2'); 5 | const errors = require('./lib/errors'); 6 | const authenticate = require('./lib/authenticate'); 7 | 8 | module.exports = function (schema, options) { 9 | options = options || {}; 10 | options.saltlen = options.saltlen || 32; 11 | options.iterations = options.iterations || 25000; 12 | options.keylen = options.keylen || 512; 13 | options.encoding = options.encoding || 'hex'; 14 | options.digestAlgorithm = options.digestAlgorithm || 'sha256'; // To get a list of supported hashes use crypto.getHashes() 15 | 16 | function defaultPasswordValidator(password, cb) { 17 | cb(null); 18 | } 19 | 20 | function defaultPasswordValidatorAsync(password) { 21 | return new Promise((resolve, reject) => { 22 | options.passwordValidator(password, (err) => (err ? reject(err) : resolve())); 23 | }); 24 | } 25 | 26 | options.passwordValidator = options.passwordValidator || defaultPasswordValidator; 27 | options.passwordValidatorAsync = options.passwordValidatorAsync || defaultPasswordValidatorAsync; 28 | 29 | // Populate field names with defaults if not set 30 | options.usernameField = options.usernameField || 'username'; 31 | options.usernameUnique = options.usernameUnique === undefined ? true : options.usernameUnique; 32 | 33 | // Populate username query fields with defaults if not set, 34 | // otherwise add username field to query fields. 35 | if (options.usernameQueryFields) { 36 | options.usernameQueryFields.push(options.usernameField); 37 | } else { 38 | options.usernameQueryFields = [options.usernameField]; 39 | } 40 | 41 | // option to find username case insensitively 42 | options.usernameCaseInsensitive = Boolean(options.usernameCaseInsensitive || false); 43 | 44 | // option to convert username to lowercase when finding 45 | options.usernameLowerCase = options.usernameLowerCase || false; 46 | 47 | options.hashField = options.hashField || 'hash'; 48 | options.saltField = options.saltField || 'salt'; 49 | 50 | if (options.limitAttempts) { 51 | options.lastLoginField = options.lastLoginField || 'last'; 52 | options.attemptsField = options.attemptsField || 'attempts'; 53 | options.interval = options.interval || 100; // 100 ms 54 | options.maxInterval = options.maxInterval || 300000; // 5 min 55 | options.maxAttempts = options.maxAttempts || Infinity; 56 | } 57 | 58 | options.findByUsername = 59 | options.findByUsername || 60 | function (model, queryParameters) { 61 | return model.findOne(queryParameters); 62 | }; 63 | 64 | options.errorMessages = options.errorMessages || {}; 65 | options.errorMessages.MissingPasswordError = options.errorMessages.MissingPasswordError || 'No password was given'; 66 | options.errorMessages.AttemptTooSoonError = options.errorMessages.AttemptTooSoonError || 'Account is currently locked. Try again later'; 67 | options.errorMessages.TooManyAttemptsError = 68 | options.errorMessages.TooManyAttemptsError || 'Account locked due to too many failed login attempts'; 69 | options.errorMessages.NoSaltValueStoredError = 70 | options.errorMessages.NoSaltValueStoredError || 'Authentication not possible. No salt value stored'; 71 | options.errorMessages.IncorrectPasswordError = options.errorMessages.IncorrectPasswordError || 'Password or username is incorrect'; 72 | options.errorMessages.IncorrectUsernameError = options.errorMessages.IncorrectUsernameError || 'Password or username is incorrect'; 73 | options.errorMessages.MissingUsernameError = options.errorMessages.MissingUsernameError || 'No username was given'; 74 | options.errorMessages.UserExistsError = options.errorMessages.UserExistsError || 'A user with the given username is already registered'; 75 | 76 | const schemaFields = {}; 77 | 78 | if (!schema.path(options.usernameField)) { 79 | schemaFields[options.usernameField] = { type: String, unique: options.usernameUnique }; 80 | } 81 | schemaFields[options.hashField] = { type: String, select: false }; 82 | schemaFields[options.saltField] = { type: String, select: false }; 83 | 84 | if (options.limitAttempts) { 85 | schemaFields[options.attemptsField] = { type: Number, default: 0 }; 86 | schemaFields[options.lastLoginField] = { type: Date, default: Date.now }; 87 | } 88 | 89 | schema.add(schemaFields); 90 | 91 | schema.pre('save', function (next) { 92 | if (options.usernameLowerCase && this[options.usernameField]) { 93 | this[options.usernameField] = this[options.usernameField].toLowerCase(); 94 | } 95 | 96 | next(); 97 | }); 98 | 99 | schema.methods.setPassword = function (password, cb) { 100 | const promise = Promise.resolve() 101 | .then(() => { 102 | if (!password) { 103 | throw new errors.MissingPasswordError(options.errorMessages.MissingPasswordError); 104 | } 105 | }) 106 | .then(() => options.passwordValidatorAsync(password)) 107 | .then(() => randomBytes(options.saltlen)) 108 | .then((saltBuffer) => saltBuffer.toString(options.encoding)) 109 | .then((salt) => { 110 | this.set(options.saltField, salt); 111 | 112 | return salt; 113 | }) 114 | .then((salt) => pbkdf2Promisified(password, salt, options)) 115 | .then((hashRaw) => { 116 | this.set(options.hashField, Buffer.from(hashRaw, 'binary').toString(options.encoding)); 117 | }) 118 | .then(() => this); 119 | 120 | if (!cb) { 121 | return promise; 122 | } 123 | 124 | promise.then((result) => cb(null, result)).catch((err) => cb(err)); 125 | }; 126 | 127 | schema.methods.changePassword = function (oldPassword, newPassword, cb) { 128 | const promise = Promise.resolve() 129 | .then(() => { 130 | if (!oldPassword || !newPassword) { 131 | throw new errors.MissingPasswordError(options.errorMessages.MissingPasswordError); 132 | } 133 | }) 134 | .then(() => this.authenticate(oldPassword)) 135 | .then(({ user, error }) => { 136 | if (!user) { 137 | throw error; 138 | } 139 | }) 140 | .then(() => this.setPassword(newPassword)) 141 | .then(() => this.save()) 142 | .then(() => this); 143 | 144 | if (!cb) { 145 | return promise; 146 | } 147 | 148 | promise.then((result) => cb(null, result)).catch((err) => cb(err)); 149 | }; 150 | 151 | schema.methods.authenticate = function (password, cb) { 152 | const promise = Promise.resolve().then(() => { 153 | if (this.get(options.saltField)) { 154 | return authenticate(this, password, options); 155 | } 156 | 157 | return this.constructor.findByUsername(this.get(options.usernameField), true).then((user) => { 158 | if (user) { 159 | return authenticate(user, password, options); 160 | } 161 | 162 | return { user: false, error: new errors.IncorrectUsernameError(options.errorMessages.IncorrectUsernameError) }; 163 | }); 164 | }); 165 | 166 | if (!cb) { 167 | return promise; 168 | } 169 | 170 | promise.then(({ user, error }) => cb(null, user, error)).catch((err) => cb(err)); 171 | }; 172 | 173 | if (options.limitAttempts) { 174 | schema.methods.resetAttempts = function (cb) { 175 | const promise = Promise.resolve().then(() => { 176 | this.set(options.attemptsField, 0); 177 | return this.save(); 178 | }); 179 | 180 | if (!cb) { 181 | return promise; 182 | } 183 | 184 | promise.then((result) => cb(null, result)).catch((err) => cb(err)); 185 | }; 186 | } 187 | 188 | // Passport Local Interface 189 | schema.statics.authenticate = function () { 190 | return (username, password, cb) => { 191 | const promise = Promise.resolve() 192 | .then(() => this.findByUsername(username, true)) 193 | .then((user) => { 194 | if (user) { 195 | return user.authenticate(password); 196 | } 197 | 198 | return { user: false, error: new errors.IncorrectUsernameError(options.errorMessages.IncorrectUsernameError) }; 199 | }); 200 | 201 | if (!cb) { 202 | return promise; 203 | } 204 | 205 | promise.then(({ user, error }) => cb(null, user, error)).catch((err) => cb(err)); 206 | }; 207 | }; 208 | 209 | // Passport Interface 210 | schema.statics.serializeUser = function () { 211 | return function (user, cb) { 212 | cb(null, user.get(options.usernameField)); 213 | }; 214 | }; 215 | 216 | schema.statics.deserializeUser = function () { 217 | return (username, cb) => { 218 | this.findByUsername(username, cb); 219 | }; 220 | }; 221 | 222 | schema.statics.register = function (user, password, cb) { 223 | // Create an instance of this in case user isn't already an instance 224 | if (!(user instanceof this)) { 225 | user = new this(user); 226 | } 227 | 228 | const promise = Promise.resolve() 229 | .then(() => { 230 | if (!user.get(options.usernameField)) { 231 | throw new errors.MissingUsernameError(options.errorMessages.MissingUsernameError); 232 | } 233 | }) 234 | .then(() => this.findByUsername(user.get(options.usernameField))) 235 | .then((existingUser) => { 236 | if (existingUser) { 237 | throw new errors.UserExistsError(options.errorMessages.UserExistsError); 238 | } 239 | }) 240 | .then(() => user.setPassword(password)) 241 | .then(() => user.save()); 242 | 243 | if (!cb) { 244 | return promise; 245 | } 246 | 247 | promise.then((result) => cb(null, result)).catch((err) => cb(err)); 248 | }; 249 | 250 | schema.statics.findByUsername = function (username, opts, cb) { 251 | if (typeof opts === 'function') { 252 | cb = opts; 253 | opts = {}; 254 | } 255 | 256 | if (typeof opts == 'boolean') { 257 | opts = { 258 | selectHashSaltFields: opts, 259 | }; 260 | } 261 | 262 | opts = opts || {}; 263 | opts.selectHashSaltFields = !!opts.selectHashSaltFields; 264 | 265 | // if specified, convert the username to lowercase 266 | if (username !== undefined && options.usernameLowerCase) { 267 | username = username.toLowerCase(); 268 | } 269 | 270 | // escape regex tokens 271 | if (username !== undefined && options.usernameCaseInsensitive) { 272 | username = username.replace(/[!#$()*+\-./:<=>?[\\\]^{|}]/g, '\\$&'); 273 | } 274 | 275 | // Add each username query field 276 | const queryOrParameters = []; 277 | for (let i = 0; i < options.usernameQueryFields.length; i++) { 278 | const parameter = {}; 279 | parameter[options.usernameQueryFields[i]] = options.usernameCaseInsensitive ? new RegExp(`^${username}$`, 'i') : username; 280 | queryOrParameters.push(parameter); 281 | } 282 | 283 | const query = options.findByUsername(this, { $or: queryOrParameters }); 284 | 285 | if (opts.selectHashSaltFields) { 286 | query.select('+' + options.hashField + ' +' + options.saltField); 287 | } 288 | 289 | if (options.selectFields) { 290 | query.select(options.selectFields); 291 | } 292 | 293 | if (options.populateFields) { 294 | query.populate(options.populateFields); 295 | } 296 | 297 | if (cb) { 298 | query 299 | .exec() 300 | .then((user) => cb(null, user)) 301 | .catch(cb); 302 | return; 303 | } 304 | 305 | return query; 306 | }; 307 | 308 | schema.statics.createStrategy = function () { 309 | return new LocalStrategy(options, this.authenticate()); 310 | }; 311 | }; 312 | 313 | function pbkdf2Promisified(password, salt, options) { 314 | return new Promise((resolve, reject) => pbkdf2(password, salt, options, (err, hashRaw) => (err ? reject(err) : resolve(hashRaw)))); 315 | } 316 | 317 | function randomBytes(saltlen) { 318 | return new Promise((resolve, reject) => crypto.randomBytes(saltlen, (err, saltBuffer) => (err ? reject(err) : resolve(saltBuffer)))); 319 | } 320 | 321 | module.exports.errors = errors; 322 | -------------------------------------------------------------------------------- /lib/authenticate.js: -------------------------------------------------------------------------------- 1 | const scmp = require('scmp'); 2 | 3 | const pbkdf2 = require('./pbkdf2'); 4 | const errors = require('./errors'); 5 | 6 | // authenticate function needs refactoring - to avoid bugs we wrapped a bit dirty 7 | module.exports = function (user, password, options, cb) { 8 | if (cb) { 9 | return authenticate(user, password, options, cb); 10 | } 11 | 12 | return new Promise((resolve, reject) => { 13 | authenticate(user, password, options, (err, user, error) => (err ? reject(err) : resolve({ user, error }))); 14 | }); 15 | }; 16 | 17 | function authenticate(user, password, options, cb) { 18 | let promise = Promise.resolve(); 19 | 20 | if (options.limitAttempts) { 21 | const attemptsInterval = Math.pow(options.interval, Math.log(user.get(options.attemptsField) + 1)); 22 | const calculatedInterval = attemptsInterval < options.maxInterval ? attemptsInterval : options.maxInterval; 23 | 24 | if (Date.now() - user.get(options.lastLoginField) < calculatedInterval) { 25 | user.set(options.lastLoginField, Date.now()); 26 | user 27 | .save() 28 | .then(function () { 29 | return cb(null, false, new errors.AttemptTooSoonError(options.errorMessages.AttemptTooSoonError)); 30 | }) 31 | .catch(function (saveErr) { 32 | return cb(saveErr); 33 | }); 34 | return; 35 | } 36 | 37 | if (user.get(options.attemptsField) >= options.maxAttempts) { 38 | if (options.unlockInterval && Date.now() - user.get(options.lastLoginField) > options.unlockInterval) { 39 | user.set(options.lastLoginField, Date.now()); 40 | user.set(options.attemptsField, 0); 41 | 42 | promise = user.save(); 43 | } else { 44 | return cb(null, false, new errors.TooManyAttemptsError(options.errorMessages.TooManyAttemptsError)); 45 | } 46 | } 47 | } 48 | 49 | promise.then(function () { 50 | if (!user.get(options.saltField)) { 51 | return cb(null, false, new errors.NoSaltValueStoredError(options.errorMessages.NoSaltValueStoredError)); 52 | } 53 | 54 | pbkdf2(password, user.get(options.saltField), options, function (err, hashBuffer) { 55 | if (err) { 56 | return cb(err); 57 | } 58 | 59 | if (scmp(hashBuffer, Buffer.from(user.get(options.hashField), options.encoding))) { 60 | if (options.limitAttempts) { 61 | user.set(options.lastLoginField, Date.now()); 62 | user.set(options.attemptsField, 0); 63 | user 64 | .save() 65 | .then(function (user) { 66 | return cb(null, user); 67 | }) 68 | .catch(function (saveErr) { 69 | return cb(saveErr); 70 | }); 71 | } else { 72 | return cb(null, user); 73 | } 74 | } else { 75 | if (options.limitAttempts) { 76 | user.set(options.lastLoginField, Date.now()); 77 | user.set(options.attemptsField, user.get(options.attemptsField) + 1); 78 | user 79 | .save() 80 | .then(function () { 81 | if (user.get(options.attemptsField) >= options.maxAttempts) { 82 | return cb(null, false, new errors.TooManyAttemptsError(options.errorMessages.TooManyAttemptsError)); 83 | } else { 84 | return cb(null, false, new errors.IncorrectPasswordError(options.errorMessages.IncorrectPasswordError)); 85 | } 86 | }) 87 | .catch(function (saveErr) { 88 | return cb(saveErr); 89 | }); 90 | } else { 91 | return cb(null, false, new errors.IncorrectPasswordError(options.errorMessages.IncorrectPasswordError)); 92 | } 93 | } 94 | }); 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | const generaterr = require('generaterr'); 2 | 3 | const AuthenticationError = generaterr('AuthenticationError'); 4 | 5 | module.exports = { 6 | AuthenticationError, 7 | IncorrectUsernameError: generaterr('IncorrectUsernameError', null, { inherits: AuthenticationError }), 8 | IncorrectPasswordError: generaterr('IncorrectPasswordError', null, { inherits: AuthenticationError }), 9 | MissingUsernameError: generaterr('MissingUsernameError', null, { inherits: AuthenticationError }), 10 | MissingPasswordError: generaterr('MissingPasswordError', null, { inherits: AuthenticationError }), 11 | UserExistsError: generaterr('UserExistsError', null, { inherits: AuthenticationError }), 12 | NoSaltValueStoredError: generaterr('NoSaltValueStoredError', null, { inherits: AuthenticationError }), 13 | AttemptTooSoonError: generaterr('AttemptTooSoonError', null, { inherits: AuthenticationError }), 14 | TooManyAttemptsError: generaterr('TooManyAttemptsError', null, { inherits: AuthenticationError }), 15 | }; 16 | -------------------------------------------------------------------------------- /lib/pbkdf2.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | module.exports = function pbkdf2(password, salt, options, callback) { 4 | crypto.pbkdf2(password, salt, options.iterations, options.keylen, options.digestAlgorithm, callback); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-local-mongoose", 3 | "description": "Mongoose plugin that simplifies building username and password login with Passport", 4 | "version": "8.0.0", 5 | "main": "index.js", 6 | "repository": "saintedlama/passport-local-mongoose", 7 | "author": "Christoph Walcher ", 8 | "license": "MIT", 9 | "keywords": [ 10 | "mongoose", 11 | "passport", 12 | "authentication", 13 | "login" 14 | ], 15 | "engines": { 16 | "node": ">= 8.0.0" 17 | }, 18 | "dependencies": { 19 | "generaterr": "^1.5.0", 20 | "passport-local": "^1.0.0", 21 | "scmp": "^2.1.0" 22 | }, 23 | "devDependencies": { 24 | "@types/passport": "1.0.7", 25 | "@types/passport-local": "1.0.34", 26 | "chai": "^4.3.6", 27 | "debug": "^4.3.4", 28 | "drop-mongodb-collections": "^2.0.0", 29 | "eslint": "^8.16.0", 30 | "eslint-config-prettier": "^8.5.0", 31 | "eslint-plugin-prettier": "^4.0.0", 32 | "mocha": "^10.0.0", 33 | "mocha-lcov-reporter": "^1.3.0", 34 | "mongoose": "^7.0.1", 35 | "nyc": "^15.1.0", 36 | "prettier": "^2.6.2", 37 | "should-release": "^1.2.0", 38 | "standard-version": "^9.5.0", 39 | "tsd": "^0.20.0" 40 | }, 41 | "scripts": { 42 | "test": "nyc --reporter=text-summary mocha --recursive --throw-deprecation", 43 | "test:ci": "nyc --reporter=lcov mocha --recursive --throw-deprecation", 44 | "test:tsd": "tsd", 45 | "lint": "eslint .", 46 | "lint:fix": "eslint . --fix", 47 | "should-release": "should-release", 48 | "release": "standard-version" 49 | }, 50 | "tsd": { 51 | "directory": "test/types" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/alternative-query-field.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | const expect = require('chai').expect; 4 | const dropMongodbCollections = require('drop-mongodb-collections'); 5 | const debug = require('debug')('passport:local:mongoose'); 6 | const passportLocalMongoose = require('../'); 7 | 8 | const dbName = 'passportlocalmongoosetests'; 9 | let connectionString = `mongodb://localhost:27017/${dbName}`; 10 | 11 | if (process.env.MONGO_SERVER) { 12 | connectionString = connectionString.replace('mongodb://localhost', 'mongodb://' + process.env.MONGO_SERVER); 13 | debug('Using mongodb server from environment variable %s', connectionString); 14 | } 15 | 16 | describe('alternative query field', function () { 17 | this.timeout(10000); // Ten seconds - mongo db access needed 18 | 19 | beforeEach(async () => await dropMongodbCollections(connectionString)); 20 | beforeEach(() => 21 | mongoose.connect(connectionString, { bufferCommands: false, autoIndex: false, useNewUrlParser: true, useUnifiedTopology: true }) 22 | ); 23 | afterEach(() => mongoose.disconnect()); 24 | 25 | it('should find an existing user by alternative query field', function (done) { 26 | const UserSchema = new Schema({ 27 | email: String, 28 | }); 29 | UserSchema.plugin(passportLocalMongoose, { iterations: 1, usernameQueryFields: ['email'] }); // 1 iteration - safes time in tests 30 | const User = mongoose.model('FindAlternativeQueryField', UserSchema); 31 | 32 | const email = 'hugo@test.org'; 33 | const user = new User({ username: 'hugo', email: email }); 34 | user.save().then(function () { 35 | User.findByUsername(email, function (err, user) { 36 | expect(err).to.not.exist; 37 | expect(user).to.exist; 38 | expect(user.email).to.equal(email); 39 | 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | it('should authenticate an existing user by alternative query field', function (done) { 46 | const UserSchema = new Schema({ 47 | email: String, 48 | }); 49 | UserSchema.plugin(passportLocalMongoose, { iterations: 1, usernameQueryFields: ['email'] }); // 1 iteration - safes time in tests 50 | const User = mongoose.model('AuthenticateAlternativeQueryField', UserSchema); 51 | 52 | const email = 'hugo@test.org'; 53 | const user = new User({ username: 'hugo', email: email }); 54 | User.register(user, 'password', function (err) { 55 | expect(err).to.not.exist; 56 | 57 | User.authenticate()('hugo@test.org', 'password', function (err, user, error) { 58 | expect(err).to.not.exist; 59 | expect(user).to.exist; 60 | expect(!error).to.exist; 61 | 62 | done(); 63 | }); 64 | }); 65 | }); 66 | 67 | it('should authenticate an existing user by default username field', function (done) { 68 | const UserSchema = new Schema({ 69 | email: String, 70 | }); 71 | UserSchema.plugin(passportLocalMongoose, { iterations: 1, usernameQueryFields: ['email'] }); // 1 iteration - safes time in tests 72 | const User = mongoose.model('AuthenticateDefaultField', UserSchema); 73 | 74 | const email = 'hugo@test.org'; 75 | const user = new User({ username: 'hugo', email: email }); 76 | User.register(user, 'password', function (err) { 77 | expect(err).to.not.exist; 78 | 79 | User.authenticate()('hugo', 'password', function (err, user, error) { 80 | expect(err).to.not.exist; 81 | expect(user).to.exist; 82 | expect(!error).to.exist; 83 | 84 | done(); 85 | }); 86 | }); 87 | }); 88 | 89 | it('should not authenticate an existing user by unconfigured alternative query field', function (done) { 90 | const UserSchema = new Schema({ 91 | email: String, 92 | }); 93 | 94 | UserSchema.plugin(passportLocalMongoose, { iterations: 1, usernameQueryFields: [] }); // 1 iteration - safes time in tests 95 | const User = mongoose.model('NotAuthenticateUnconfiguredAlternativeQueryField', UserSchema); 96 | 97 | const email = 'hugo@test.org'; 98 | const user = new User({ username: 'hugo', email: email }); 99 | User.register(user, 'password', function (err) { 100 | if (err) { 101 | return done(err); 102 | } 103 | 104 | User.authenticate()('hugo@test.org', 'password', function (err, user, error) { 105 | if (err) { 106 | return done(err); 107 | } 108 | 109 | expect(err).to.not.exist; 110 | expect(user).to.be.false; 111 | expect(error).to.exist; 112 | 113 | done(); 114 | }); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /test/errors.js: -------------------------------------------------------------------------------- 1 | const errors = require('../lib/errors.js'); 2 | const expect = require('chai').expect; 3 | 4 | describe('AuthenticationError', function () { 5 | it('should construct a valid error with stack trace and name', function () { 6 | const error = new errors.AuthenticationError(); 7 | 8 | expect(error.stack).to.exist; 9 | expect(error.name).to.equal('AuthenticationError'); 10 | }); 11 | 12 | it('should construct a bad request error with message passed', function () { 13 | const error = new errors.AuthenticationError('Test'); 14 | 15 | expect(error.message).to.equal('Test'); 16 | }); 17 | 18 | it('should construct an AuthenticationError with empty message if no message was passed', function () { 19 | const error = new errors.AuthenticationError(); 20 | 21 | expect(error.message).to.equal(''); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/issues.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | const expect = require('chai').expect; 4 | const dropMongodbCollections = require('drop-mongodb-collections'); 5 | const debug = require('debug')('passport:local:mongoose'); 6 | const passportLocalMongoose = require('../'); 7 | 8 | const dbName = 'passportlocalmongoosetests'; 9 | let connectionString = `mongodb://localhost:27017/${dbName}`; 10 | 11 | if (process.env.MONGO_SERVER) { 12 | connectionString = connectionString.replace('mongodb://localhost', 'mongodb://' + process.env.MONGO_SERVER); 13 | debug('Using mongodb server from environment variable %s', connectionString); 14 | } 15 | 16 | describe('issues', function () { 17 | this.timeout(10000); // Ten seconds - mongodb access needed 18 | 19 | beforeEach(async () => await dropMongodbCollections(connectionString)); 20 | beforeEach(() => 21 | mongoose.connect(connectionString, { bufferCommands: false, autoIndex: false, useNewUrlParser: true, useUnifiedTopology: true }) 22 | ); 23 | afterEach(() => mongoose.disconnect()); 24 | 25 | it('should support nested fields - Issue #9', function (done) { 26 | const UserSchema = new Schema({ 27 | sensitiveData1: String, 28 | sensitiveDate2: Number, 29 | account: { 30 | name: String, 31 | age: Number, 32 | }, 33 | }); 34 | 35 | UserSchema.plugin(passportLocalMongoose, { usernameField: 'account.email' }); 36 | const User = mongoose.model('ShouldSupportNestedFields_Issue_9', UserSchema); 37 | 38 | User.register({ account: { email: 'nestedemail' } }, 'password', function (err, user) { 39 | expect(err).to.not.exist; 40 | expect(user).to.exist; 41 | 42 | User.findByUsername('nestedemail', function (err, user) { 43 | expect(err).to.not.exist; 44 | expect(user).to.exist; 45 | done(); 46 | }); 47 | }); 48 | }); 49 | 50 | it('should support not throw exception in case hash or salt are not stored - Issue #27', function (done) { 51 | const UserSchema = new Schema({ 52 | name: String, 53 | age: Number, 54 | }); 55 | 56 | UserSchema.plugin(passportLocalMongoose); 57 | const User = mongoose.model('ShouldNotThrowIfPasswordOrSaltAreNotStored_Issue_27', UserSchema); 58 | 59 | User.create({ username: 'hugo', name: 'Hugo Wiener', age: 143 }).then(function (user) { 60 | expect(user).to.exist; 61 | 62 | User.authenticate()('hugo', 'none', function (err, auth, error) { 63 | expect(err).to.not.exist; 64 | expect(false).to.equal(auth); 65 | expect(error).to.exist; 66 | 67 | expect('Authentication not possible. No salt value stored').to.equal(error.message); 68 | 69 | done(); 70 | }); 71 | }); 72 | }); 73 | 74 | it('should support not throw exception in case hash and salt are not selected - Issue #27', function (done) { 75 | const UserSchema = new Schema({ 76 | name: String, 77 | age: Number, 78 | }); 79 | 80 | UserSchema.plugin(passportLocalMongoose, { selectFields: 'name' }); 81 | const User = mongoose.model('ShouldNotThrowIfPasswordAndSaltAreNotSelected_Issue_27', UserSchema); 82 | 83 | User.register(new User({ username: 'hugo' }), 'password', function (err, user) { 84 | expect(err).to.not.exist; 85 | expect(user).to.exist; 86 | 87 | const authenticate = User.authenticate(); 88 | 89 | authenticate('hugo', 'password', function (err, result) { 90 | expect(err).to.not.exist; 91 | expect(result).to.be.an.instanceOf(User); 92 | 93 | done(); 94 | }); 95 | }); 96 | }); 97 | 98 | it('should populate fields in findByUsername if option is given - Issue #20', function (done) { 99 | const LoginSchema = new Schema({ date: Date, success: Boolean }); 100 | const UserSchema = new Schema({ logins: [{ type: Schema.Types.ObjectId, ref: 'Login' }] }); 101 | 102 | UserSchema.plugin(passportLocalMongoose, { populateFields: 'logins' }); 103 | const User = mongoose.model('ShouldPopulateFields_Issue_20', UserSchema); 104 | const Login = mongoose.model('Login', LoginSchema); 105 | 106 | const loginDate = new Date(); 107 | const loginSuccess = true; 108 | 109 | Login.create({ date: loginDate, success: loginSuccess }).then(function (login) { 110 | expect(login).to.exist; 111 | 112 | const logins = []; 113 | logins.push(login._id); 114 | 115 | User.register(new User({ username: 'hugo', logins: logins }), 'password', function (err, user) { 116 | expect(err).to.not.exist; 117 | expect(user).to.exist; 118 | 119 | User.findByUsername('hugo', function (err, loadedUser) { 120 | expect(err).to.not.exist; 121 | expect(loadedUser).to.exist; 122 | expect(loadedUser.logins.length).to.equal(1); 123 | 124 | expect(loadedUser.logins[0].date.getTime()).to.equal(loginDate.getTime()); 125 | expect(loadedUser.logins[0].success).to.equal(loginSuccess); 126 | 127 | done(); 128 | }); 129 | }); 130 | }); 131 | }); 132 | 133 | /* Since password is not directly stored with mongo/mongoose, password cannot be easily validated */ 134 | it('should support password validation - Issue #57', function (done) { 135 | const UserSchema = new Schema({}); 136 | 137 | function passwordValidator(password, cb) { 138 | cb(new Error('No password is valid')); 139 | } 140 | 141 | UserSchema.plugin(passportLocalMongoose, { 142 | passwordValidator, 143 | }); 144 | 145 | const User = mongoose.model('ShouldSupportPasswordValidation_Issue_57', UserSchema); 146 | 147 | User.register({ username: 'nicolascage' }, 'password', function (err) { 148 | expect(err.message).to.equal('No password is valid'); 149 | done(); 150 | }); 151 | }); 152 | 153 | it('should support password validation with promises - Issue #57', function () { 154 | const UserSchema = new Schema({}); 155 | 156 | function passwordValidatorAsync() { 157 | return Promise.reject(new Error('No password is valid')); 158 | } 159 | 160 | UserSchema.plugin(passportLocalMongoose, { 161 | passwordValidatorAsync, 162 | }); 163 | 164 | const User = mongoose.model('ShouldSupportPasswordValidation_With_Promises_Issue_57', UserSchema); 165 | 166 | return User.register({ username: 'nicolascage' }, 'password') 167 | .then(() => { 168 | throw new Error('Expected password validator to throw!'); 169 | }) 170 | .catch((err) => { 171 | expect(err.message).to.equal('No password is valid'); 172 | }); 173 | }); 174 | 175 | it('should not expose hash and salt fields - Issue #72', function (done) { 176 | const UserSchema = new Schema({}); 177 | 178 | UserSchema.plugin(passportLocalMongoose, {}); 179 | const User = mongoose.model('ShouldNotExposeHashAndSaltFields_Issue_72', UserSchema); 180 | 181 | User.register({ username: 'nicolascage' }, 'password', function (err, user) { 182 | expect(err).to.not.exist; 183 | expect(user).to.exist; 184 | User.findOne({ username: 'nicolascage' }).then(function (user) { 185 | expect(user).to.exist; 186 | expect(user.username).to.equal('nicolascage'); 187 | expect(user.hash).to.equal(undefined); 188 | expect(user.salt).to.equal(undefined); 189 | done(); 190 | }); 191 | }); 192 | }); 193 | 194 | describe('authentication should work with salt/hash field marked as select: false - Issue #96', function () { 195 | const UserSchema = new Schema({}); 196 | UserSchema.plugin(passportLocalMongoose, {}); 197 | const userName = 'user_' + Math.random(); 198 | const User = mongoose.model('ShouldAuthenticateWithSaltAndHashNotExposed_Issue_96', UserSchema); 199 | beforeEach(function (done) { 200 | User.register({ username: userName }, 'password', function (err, user) { 201 | expect(err).to.not.exist; 202 | expect(user).to.exist; 203 | done(); 204 | }); 205 | }); 206 | 207 | it('instance.authenticate( password, callback )', function (done) { 208 | User.findOne({ username: userName }).then(function (user) { 209 | expect(user).to.exist; 210 | expect(user.username).to.equal(userName); 211 | user.authenticate('password', function (err, auth) { 212 | expect(err).to.not.exist; 213 | 214 | expect(auth).to.exist; 215 | done(); 216 | }); 217 | }); 218 | }); 219 | 220 | it('Model.autheticate(username, password, callback)', function (done) { 221 | User.authenticate()(userName, 'password', function (err, auth) { 222 | expect(err).to.not.exist; 223 | expect(auth).to.exist; 224 | 225 | done(); 226 | }); 227 | }); 228 | }); 229 | 230 | describe('backward compatible #authenticate()', function () { 231 | it('should authenticate a user created in a 3.x version - issue #115', function (done) { 232 | const UserSchema = new Schema(); 233 | // Backward compatible digest is used: sha1 because pre node.js 0.12 this was the only supported digest algorithm! 234 | UserSchema.plugin(passportLocalMongoose, { usernameLowerCase: true, digestAlgorithm: 'sha1' }); 235 | const User = mongoose.model('AuthenticateBackwardCompatible', UserSchema); 236 | 237 | // 3.0 generated hash and salt of 'username', 'password' 238 | User.create({ 239 | salt: 'fd4bb06e65cd4d582efde28427ffdeb8839c64169d72cfe131bd971f70dc08f8', 240 | hash: '2ce573e406497fcbc2c1e91532cdbcf198ecbe2692cd5b3dffc303c51e6ccf56ae6a1ed9bac17f6f185d2d289ed713f7bd2a7a6246a4974495a35ff95bba234e00757d8a78fb28836a984c3e67465a019ead84d684896712c50f670663134685225b6832ec5a0a99922eabe6ba03abc1e79bc6a29ca2fe23456034eff2987277331f9e32713b3293ab355882feebe9c37ecdcd1a22dcebd6e5799adeb6a4dc32e56398d21ece6eda07b84944c3918de6bda69ab7998be461f98ff1559a07fd5d3100d732da443110b3ac7d27d16098c4e1eab7489f6d2a5849981a5c9f5dadb86d8dbbb9b60ce67304e21221e77d1a2700cab460450702c16b99db2e3b67454765fe9e4054c87a9e436fb17db1774b9d22a129c1b120dad0925b58390b8a02241e7e06acbe87dbe7f0e91b5d000cd93fc7cc8f316f45b901b8eb58ea6853c8e7ead245a9329239ed4f3797bc12a151ffedd8e8d2533547a1aec7231a460ca128ebfb1bd6b6f988455505c21d2dbfe01ee4b321a3d20a5bf6e2a356b6f4dbb8ddb4cff7dc9779b9747881af4d08e2fbcf452746e07275ed350fad0d4e6e8fcbedb0575c1413be5a913ca6ef4fcf17d1021b93fe2b3b410cf612791f967521ae558459673156e431be5203ca944e80652559eaf3faa90250df3d24526d5f9fc3409e508a3e2175daaf492fd6efd748e4418834b631f84fe266ac32f4927c3a426b', 241 | username: 'username', 242 | }).then(function () { 243 | User.authenticate()('username', 'password', function (err, authenticated) { 244 | expect(err).to.not.exist; 245 | expect(authenticated).to.exist; 246 | 247 | done(); 248 | }); 249 | }); 250 | }); 251 | }); 252 | 253 | it('should support additional query restrictions in findByUsername - Issue #227', function (done) { 254 | const UserSchema = new Schema({ 255 | active: Boolean, 256 | }); 257 | 258 | UserSchema.plugin(passportLocalMongoose, { 259 | // Needed to set usernameUnique to true to avoid a mongodb index on the username column! 260 | usernameUnique: false, 261 | 262 | findByUsername: function (model, queryParameters) { 263 | // Add additional query parameter - AND condition - active: true 264 | queryParameters.active = true; 265 | return model.findOne(queryParameters); 266 | }, 267 | }); 268 | 269 | const User = mongoose.model('ShouldSupportAdditionalQueryRestrictions', UserSchema); 270 | 271 | User.register({ username: 'username', active: false }, 'password', function (err) { 272 | if (err) { 273 | return done(err); 274 | } 275 | 276 | const authenticate = User.authenticate(); 277 | authenticate('username', 'password', function (err, result) { 278 | if (err) { 279 | return done(err); 280 | } 281 | 282 | // Expect that non active users must not authenticate successfully! 283 | expect(result).to.be.false; 284 | done(); 285 | }); 286 | }); 287 | }); 288 | 289 | it('should allow already registered but not active usernames to be taken again - Issue #227', function (done) { 290 | const UserSchema = new Schema({ 291 | active: Boolean, 292 | }); 293 | 294 | UserSchema.plugin(passportLocalMongoose, { 295 | // Needed to set usernameUnique to true to avoid a mongodb index on the username column! 296 | usernameUnique: false, 297 | 298 | findByUsername: function (model, queryParameters) { 299 | // Add additional query parameter - AND condition - active: true 300 | queryParameters.active = true; 301 | return model.findOne(queryParameters); 302 | }, 303 | }); 304 | 305 | const User = mongoose.model('ShouldAllowRegisteredNonActiveUsernamesInRegister', UserSchema); 306 | 307 | User.register({ username: 'username', active: false }, 'password', function (err) { 308 | if (err) { 309 | return done(err); 310 | } 311 | 312 | User.register({ username: 'username', active: true }, 'password', function (err) { 313 | if (err) { 314 | return done(err); 315 | } 316 | 317 | const authenticate = User.authenticate(); 318 | authenticate('username', 'password', function (err, user) { 319 | if (err) { 320 | return done(err); 321 | } 322 | 323 | // Expect that active users can authenticate! 324 | expect(user).to.exist; 325 | done(); 326 | }); 327 | }); 328 | }); 329 | }); 330 | }); 331 | -------------------------------------------------------------------------------- /test/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Schema, 3 | model, 4 | PassportLocalSchema, 5 | PassportLocalModel, 6 | } from 'mongoose'; 7 | import passportLocalMongoose from 'passport-local-mongoose'; 8 | 9 | import * as passport from 'passport'; 10 | 11 | interface User { 12 | _id: string; 13 | username: string; 14 | hash: string; 15 | salt: string; 16 | attempts: number; 17 | last: Date; 18 | } 19 | 20 | interface UserModelType extends PassportLocalModel {} 21 | 22 | const UserSchema = new Schema({ 23 | username: String, 24 | hash: String, 25 | salt: String, 26 | attempts: Number, 27 | last: Date, 28 | }) as PassportLocalSchema; 29 | 30 | UserSchema.plugin(passportLocalMongoose); 31 | 32 | let UserModel: UserModelType = model('User', UserSchema); 33 | 34 | passport.use(UserModel.createStrategy()); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "paths": { 5 | "passport-local-mongoose" : ["./index.d.ts"] 6 | } 7 | } 8 | } --------------------------------------------------------------------------------