[
  {
    "path": ".eslintignore",
    "content": "dist\ncoverage\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"extends\": \"loopback\",\n  \"rules\": {\n    \"max-len\": [\"error\", 100, 4, {\n      \"ignoreComments\": true,\n      \"ignoreUrls\": true,\n      \"ignorePattern\": \"^\\\\s*var\\\\s.+=\\\\s*(require\\\\s*\\\\()|(/)\"\n    }]\n  }\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\nlabels: bug\n\n---\n\n<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨\n\nAre you using LoopBack version 4? Please report the bug here:\nhttps://github.com/strongloop/loopback-next/issues/new\n\nHELP US HELP YOU, PLEASE\n- Do a quick search to avoid duplicate issues\n- Provide as much information as possible (reproduction sandbox, use case for features, etc.)\n- Consider using a more suitable venue for questions such as Stack Overflow, Gitter, etc.\n\nPlease fill in the *entire* template below.\n\n-->\n\n## Steps to reproduce\n\n<!-- Describe how to reproduce the issue -->\n\n## Current Behavior\n\n<!-- Describe the observed result -->\n\n## Expected Behavior\n\n<!-- Describe what did you expect instead, what is the desired outcome? -->\n\n## Link to reproduction sandbox\n\n<!--\nSee https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-3x-bugs\nNote: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.\n-->\n\n## Additional information\n\n<!--\nCopy+paste the output of these two commands:\n  node -e 'console.log(process.platform, process.arch, process.versions.node)'\n  npm ls --prod --depth 0 | grep loopback\n-->\n\n## Related Issues\n\n<!-- Did you find other bugs that looked similar? -->\n\n_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\nlabels: feature\n\n---\n\n<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨\n\nLoopBack version 3 is in LTS mode, we are not accepting new features.\n\nWe are actively developing version 4, you can find the new GitHub\nrepository here: https://github.com/strongloop/loopback-next\n\n-->\n\n## Suggestion\n\n<!-- A summary of what you'd like to see added or changed -->\n\n## Use Cases\n\n<!--\nWhat do you want to use this for?\nWhat shortcomings exist with current approaches?\n-->\n\n## Examples\n\n<!-- Show how this would be used and what the behavior would be -->\n\n## Acceptance criteria\n\nTBD - will be filled by the team.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Question.md",
    "content": "---\nname: Question\nabout: The issue tracker is not for questions. Please use Stack Overflow or other resources for help.\nlabels: question\n\n---\n\n<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨\n\nTHE ISSUE TRACKER IS NOT FOR QUESTIONS.\n\nDO NOT CREATE A NEW ISSUE TO ASK A QUESTION.\n\nPlease use one of the following resources for help:\n\n**Questions**\n\n- https://stackoverflow.com/tags/loopbackjs\n- https://groups.google.com/forum/#!forum/loopbackjs\n- https://gitter.im/strongloop/loopback\n\n**Immediate support**\n\n- https://strongloop.com/api-connect-faqs/\n- https://strongloop.com/node-js/subscription-plans/\n\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Report a security vulnerability\n    url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues\n    about: >\n      LoopBack 3 has reached End-of-Life. No new security fixes will be provided\n      or accepted.\n      \n      Do not report security vulnerabilities using GitHub issues. Please send an\n      email to `reachsl@us.ibm.com` instead.\n  - name: Get help on StackOverflow\n    url: https://stackoverflow.com/tags/loopbackjs\n    about: Please ask and answer questions on StackOverflow.\n  - name: Join our mailing list\n    url: https://groups.google.com/forum/#!forum/loopbackjs\n    about: You can also post your question to our mailing list.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nPlease provide a high-level description of the changes made by your pull request.\n\nInclude references to all related GitHub issues and other pull requests, for example:\n\nFixes #123\nImplements #254\nSee also #23\n-->\n\n## Checklist\n\n👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback) 👈\n\n- [ ] `npm test` passes on your machine\n- [ ] New tests added or existing tests modified to cover all changes\n- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html)\n- [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html)\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 60\n# Number of days of inactivity before a stale issue is closed\ndaysUntilClose: 14\n# Issues with these labels will never be considered stale\nexemptLabels:\n  - pinned\n  - security\n  - critical\n# Label to use when marking an issue as stale\nstaleLabel: stale\n# Comment to post when marking an issue as stale. Set to `false` to disable\nmarkComment: >\n  This issue has been automatically marked as stale because it has not had\n  recent activity. It will be closed if no further activity occurs. Thank you\n  for your contributions.\n# Comment to post when closing a stale issue. Set to `false` to disable\ncloseComment: >\n  This issue has been closed due to continued inactivity. Thank you for your understanding.\n  If you believe this to be in error, please contact one of the code owners,\n  listed in the [`CODEOWNERS`](https://github.com/strongloop/loopback/blob/master/CODEOWNERS) file at the top-level of this repository.\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n.project\n.DS_Store\n.vscode/\n*.sublime*\n*.seed\n*.log\n*.csv\n*.dat\n*.out\n*.pid\n*.swp\n*.swo\nnode_modules\ndist\n*xunit.xml\n.nyc_output/\n"
  },
  {
    "path": ".npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": ".nycrc",
    "content": "{\n  \"exclude\":  [\n    \"Gruntfile.js\",\n    \"test/**/*.js\"\n  ],\n  \"cache\": true\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: node_js\nnode_js:\n  - \"8\"\n  - \"10\"\n  - \"12\"\n  - \"14\"\n\naddons:\n  chrome: stable\n\nafter_success: npm run coverage\n\nbefore_install:\n  - npm config set registry http://ci.strongloop.com:4873/\n"
  },
  {
    "path": "CHANGES.md",
    "content": "2020-11-25, Version 3.28.0\n==========================\n\n * upgrade nodemailer to greater than 6.4.16 (jannyHou)\n\n * chore: sync LoopBack 4 with Node.js v14 EOL (Rifa Achrinza)\n\n * chore: add Node.js 14 to travis (Diana Lau)\n\n * Remove \"major\" and \"p1\" from stalebot (Miroslav Bajtoš)\n\n\n2020-03-06, Version 3.27.0\n==========================\n\n * Update LTS status in README (Miroslav Bajtoš)\n\n * chore: update copyright year (Diana Lau)\n\n * feat: change hasone relation error message (Sujesh T)\n\n * chore: disable security issue reporting (Nora)\n\n * chore: fix eslint violations (Nora)\n\n * fixup! manual fixes (Miroslav Bajtoš)\n\n * fixup! eslint --fix . (Miroslav Bajtoš)\n\n * chore: update eslint & eslint-config to latest (Miroslav Bajtoš)\n\n * chore: update dev-dependencies (Miroslav Bajtoš)\n\n * chore: update chai to v4, dirty-chai to v2 (Miroslav Bajtoš)\n\n * Updated \"ismail\" package to v3.2 (Stanislav Sarbinski)\n\n * Introduce issue templates for bugs, features, etc. (Miroslav Bajtoš)\n\n * Improve PULL_REQUEST_TEMPLATE (Miroslav Bajtoš)\n\n * test: use Chromium (not Chrome) when available (Miroslav Bajtoš)\n\n * test: disable Chrome sandboxing when inside Docker (Miroslav Bajtoš)\n\n * test: switch from PhantomJS to HeadlessChrome (Miroslav Bajtoš)\n\n\n2019-05-31, Version 3.26.0\n==========================\n\n * fix: disallow queries in username and email fields (Hage Yaapa)\n\n * Ignore failing downstream dependencies (Miroslav Bajtoš)\n\n * Upgrade nyc to version 14 (Miroslav Bajtoš)\n\n * Update Karma dependencies to latest versions (Miroslav Bajtoš)\n\n * Drop Node.js 6.x from the supported versions (Miroslav Bajtoš)\n\n * Fix Model.exists() to work with remote connector (Maxim Sharai)\n\n * chore: update copyrights years (Agnes Lin)\n\n * Update LTS status (Diana Lau)\n\n * Enable Node.js 12.x on Travis CI (Miroslav Bajtoš)\n\n * chore: update copyright year (Diana Lau)\n\n * chore: update LB3 EOL date (Diana Lau)\n\n\n2019-03-15, Version 3.25.1\n==========================\n\n * Back-ticks added to highlight example JSON (Quentin Presley)\n\n * Add same change to description for findOne (Quentin Presley)\n\n * Update the description for persisted-models (Quentin Presley)\n\n * handle $2b$ in hashed password check (Sylvain Dumont)\n\n\n2019-02-05, Version 3.25.0\n==========================\n\n * Support middleware injected by AppDynamics. (Mike Li)\n\n\n2019-01-11, Version 3.24.2\n==========================\n\n * Fix crash when modifying an unknown user (Matheus Horstmann)\n\n\n2019-01-08, Version 3.24.1\n==========================\n\n * Update underscore.string to 3.3.5 (Francois)\n\n * Fix: treat empty access token string as undefined (andrey-abramow)\n\n\n2018-11-15, Version 3.24.0\n==========================\n\n * Set juggler options for remote calls (Raymond Feng)\n\n * Speed up ACL tests by reducing saltWorkFactor (Miroslav Bajtoš)\n\n\n2018-10-25, Version 3.23.2\n==========================\n\n * Fix ACL check to support model wildcard (Moshe Malka)\n\n\n2018-10-18, Version 3.23.1\n==========================\n\n * README: highlight Active LTS at the top (Miroslav Bajtoš)\n\n\n2018-10-09, Version 3.23.0\n==========================\n\n * Clear handler cache when a method is added/removed (Mohammed Essehemy)\n\n * Add `options.preserveAccessTokens` (lchaglla)\n\n * Update LB3 to be active LTS (Diana Lau)\n\n * Fix ACL tests to wait until all assertions finish (Moshe Malka)\n\n * chore: update to latest linting rules (virkt25)\n\n\n2018-09-12, Version 3.22.3\n==========================\n\n * chore: use grunt to install optional  phantomjs (virkt25)\n\n * [WebFM] fr translation (candytangnb)\n\n\n2018-08-29, Version 3.22.2\n==========================\n\n * [WebFM] tr translation (candytangnb)\n\n * [WebFM] de translation (candytangnb)\n\n * [WebFM] cs/es/fr/it/nl/pl/pt_BR/ru translation (candytangnb)\n\n\n2018-08-22, Version 3.22.1\n==========================\n\n * [WebFM] ja/ko/zh_CN/zh_TW translation (candytangnb)\n\n * remove unnecessary format call (Diana Lau)\n\n * Make desc when export-api-def translatable (Diana Lau)\n\n\n2018-08-08, Version 3.22.0\n==========================\n\n * fix: accessToken create default acl (virkt25)\n\n * add: ppc64 and s390x to not run UI tests (Thomas Leah)\n\n * chore: update deps + fix linting + .npmrc (virkt25)\n\n * Update Loopback 2.x EOL dates (Chris Bailey)\n\n * Fix formatting (Chris Bailey)\n\n * Update support badge and move LTS section (Chris Bailey)\n\n * Add badges and information for LTS and support (Chris Bailey)\n\n\n2018-07-09, Version 3.21.0\n==========================\n\n * Make verifyUserRelations() more robust (mcitdev)\n\n * Fix crash in verifyUserRelations (ryanxwelch)\n\n * Fix crash in User model's \"before delete\" hook (mcitdev)\n\n * [WebFM] cs/pl/ru translation (candytangnb)\n\n * Update strong-error-handler (shimks)\n\n\n2018-06-12, Version 3.20.0\n==========================\n\n * Update strong-globalize to 4.x (Miroslav Bajtoš)\n\n * Update nodemailer to v4.x (Dimitris)\n\n * Drop support for Node.js 4.x (Miroslav Bajtoš)\n\n\n2018-06-04, Version 3.19.3\n==========================\n\n * Provide link to CODEOWNERS (Aditya Agarwal)\n\n * fix bug in User.verify when confirm is disabled (wolrajhti)\n\n * Enable Node.js 10.x on Travis CI (Miroslav Bajtoš)\n\n\n2018-05-29, Version 3.19.2\n==========================\n\n * Add check for undefined user email in setter (Kevin Scroggins)\n\n\n2018-05-21, Version 3.19.1\n==========================\n\n * Fix isOwner() bug in multiple-principal setup (Miroslav Bajtoš)\n\n\n2018-04-17, Version 3.19.0\n==========================\n\n * feat: remove all references to a Model (Miroslav Bajtoš)\n\n\n2018-03-22, Version 3.18.3\n==========================\n\n * Remove forgotten debugger statement (Miroslav Bajtoš)\n\n * Fix role check in apps with multiple user models (Miroslav Bajtoš)\n\n * Fix formatting issues reported by recent eslint (Miroslav Bajtoš)\n\n * CODEOWNERS: add nitro404 (Miroslav Bajtoš)\n\n * test: add missing \"return\" in a promise-style test (Miroslav Bajtoš)\n\n\n2018-02-08, Version 3.18.2\n==========================\n\n * model: fix infinite loop on nestRemoting (Kevin Delisle)\n\n * Use statusCode prop for user errors (Zak Barbuto)\n\n\n2018-01-31, Version 3.18.1\n==========================\n\n * update: juggler to version including security fix. (Taranveer Virk)\n\n\n2018-01-29, Version 3.18.0\n==========================\n\n * fix: preserve datasource name (Kevin Scroggins)\n\n * Update Copyright Years (Justin Ross)\n\n * Support options.filter in createChangeStream (Edward Choh)\n\n * fixup! add top-level dep on eslint-plugin-mocha (Miroslav Bajtoš)\n\n * Update eslint and eslint-config to latest (Miroslav Bajtoš)\n\n\n2017-12-12, Version 3.17.1\n==========================\n\n * Update nestRemoting to pass optionsFromContext (bmatson)\n\n * fix(test): rem exclusive test (Samuel Reed)\n\n * fix(test): working test with 0 userId (Samuel Reed)\n\n * fix(AccessContext): Tighten userid/appid checks (Samuel Reed)\n\n * fix(id): replace with != null (Samuel Reed)\n\n\n2017-11-29, Version 3.17.0\n==========================\n\n * Added missing DateString type in loopback index (CSLTech)\n\n * chore:update license (Diana Lau)\n\n\n2017-10-30, Version 3.16.2\n==========================\n\n * Fix \"POST /change-password\" for multi-user setup (Miroslav Bajtoš)\n\n\n2017-10-27, Version 3.16.1\n==========================\n\n * Fix createOnlyInstance for related methods (Raymond Feng)\n\n\n2017-10-24, Version 3.16.0\n==========================\n\n * Fix \"POST /reset-password\" for multi-user setup (Miroslav Bajtoš)\n\n * test: extract helpers for logging HTTP errors (Miroslav Bajtoš)\n\n * CODEOWNERS: move @lehni to Alumni section (Miroslav Bajtoš)\n\n\n2017-10-13, Version 3.15.0\n==========================\n\n * update strong-globalize to 3.1.0 (shimks)\n\n * Fix handling of user verification options (Miroslav Bajtoš)\n\n * Handle missing getUpdateOnlyProperties fn (Jürg Lehni)\n\n * test: fix too strict test assertion (Miroslav Bajtoš)\n\n * Fix typo (Siegfried Ehret)\n\n\n2017-09-28, Version 3.14.0\n==========================\n\n * Allow declarative nestRemoting for relations (Raymond Feng)\n\n\n2017-09-27, Version 3.13.0\n==========================\n\n * Fix OWNER role to handle multiple relations (pierreclr)\n\n * Fix acl.resolvePermission for wildcard req (Farid Neshat)\n\n * CODEOWNERS: add zbarbuto (Miroslav Bajtoš)\n\n\n2017-09-25, Version 3.12.0\n==========================\n\n * Fix relation race condition in model glob (Zak Barbuto)\n\n * CODEOWNERS: add lehni (Miroslav Bajtoš)\n\n\n2017-08-23, Version 3.11.1\n==========================\n\n * Handle missing getUpdateOnlyProperties fn (Kevin Delisle)\n\n\n2017-08-22, Version 3.11.0\n==========================\n\n * Support createOnlyInstance in model (#3548) (Rashmi Hunt)\n\n * Add stalebot configuration (Kevin Delisle)\n\n * Catch errors on invalidate update (loay)\n\n * Update Issue and PR Templates (#3568) (Sakib Hasan)\n\n\n2017-08-16, Version 3.10.1\n==========================\n\n * fix(validatePassword): reword error message (Samuel Reed)\n\n * Do not add isStatic properties to method settings (Jürg Lehni)\n\n\n2017-08-14, Version 3.10.0\n==========================\n\n * Allow glob-style patterns for remote options (Zak Barbuto)\n\n * Fix case of values per doc issue (crandmck)\n\n * Update translated strings Q3 2017 (Allen Boone)\n\n * Revert \"Validate on updateAll\" (Sakib Hasan)\n\n * Add tests of HTTP normalization on app level (Jürg Lehni)\n\n * travis: drop Node.js 7.x, add 8.x (Miroslav Bajtoš)\n\n * Validate on updateAll (ssh24)\n\n * Update juggler version (loay)\n\n * update messages.json (Diana Lau)\n\n * small fix for the title (Michael Alaev)\n\n * Changed http to https (Michael Alaev)\n\n * Update Travis registry (loay)\n\n * Add unit test for empty password (loay)\n\n * Add CODEOWNER file (Diana Lau)\n\n\n2017-07-12, Version 3.9.0\n=========================\n\n * Remove observers from Model on end of the stream (Alexei Smirnov)\n\n * Fix Model#settings.acls doc type signature (Farid Nouri Neshat)\n\n * Use `localhost` instead of `::` for local (Daijiro Wachi)\n\n * Fix API doc for Model class property type (Candy)\n\n * Update package.json (sqlwwx)\n\n * Support remoting adapters with no ctx.req object (Piero Maltese)\n\n * update strong-error-handler (sqlwwx)\n\n\n2017-05-02, Version 3.8.0\n=========================\n\n * Refactor access token to make it extensible (Raymond Feng)\n\n\n2017-04-27, Version 3.7.0\n=========================\n\n * Remote method /user/:id/verify (ebarault)\n\n * Implement more secure password flow (Miroslav Bajtoš)\n\n * Add User.setPassword(id, new, cb) (Miroslav Bajtoš)\n\n * Fix method setup in authorization-scopes.test (Miroslav Bajtoš)\n\n * Add missing tests for reset password flow (Miroslav Bajtoš)\n\n * forwarding context options in user.verify (ebarault)\n\n * update deprecated dependencies (Diana Lau)\n\n * Add support for scoped access tokens (Miroslav Bajtoš)\n\n * Fix user-literal rewrite for anonymous requests (Aaron Buchanan)\n\n\n2017-03-31, Version 3.6.0\n=========================\n\n * Add new event \"remoteMethodAdded\" (Flavien DAVID)\n\n * Forward options in prepareForTokenInvalidation (Miroslav Bajtoš)\n\n * Check max password length in User.changePassword (Miroslav Bajtoš)\n\n * Add User.changePassword(id, old, new, cb) (Miroslav Bajtoš)\n\n * Propagate authorized roles in remoting context (ebarault)\n\n * Run the latest Node.js 7 version on Travis again (Miroslav Bajtoš)\n\n * Lock down Travis CI Node 7 version to 7.7.1 (Miroslav Bajtoš)\n\n * README: add a link to our announcements list (Miroslav Bajtoš)\n\n * Allow custom properties of Change Model (agriwebb build)\n\n * Fix User.verify to convert uid to string (phairow)\n\n * Pass options.verificationToken to templateFn (Hiran del Castillo)\n\n * fix custom token model in token middleware (ebarault)\n\n * Update runtime dependencies (Miroslav Bajtoš)\n\n * Verify User and AccessToken relations at startup (Miroslav Bajtoš)\n\n * Deep-clone model settings in lib/builtin-models (Miroslav Bajtoš)\n\n * Use local registry in test/replication.rest.test (Miroslav Bajtoš)\n\n * Fix test/access-token.test to use local registry (Miroslav Bajtoš)\n\n * Fix context passing in OWNER role resolver (Benjamin Schuster-Boeckler)\n\n\n2017-02-24, Version 3.4.0\n=========================\n\n * Fix access-token invalidation for missing relation (Miroslav Bajtoš)\n\n * Configure Travis CI to cache phantomjs binaries (Miroslav Bajtoš)\n\n * Optimise replication (kobaska)\n\n * Improve \"filter\" arg description (Raymond Camden)\n\n\n2017-02-17, Version 3.3.0\n=========================\n\n * Fix Role.isOwner() for multiple user models (ebarault)\n\n * Update ISSUE_TEMPLATE.md (Simon Ho)\n\n * Upgrade supertest to 3.x (Miroslav Bajtoš)\n\n * Fix creation of verification links (Miroslav Bajtoš)\n\n * Enable multiple user models (Eric)\n\n * Babelify juggler for Karma tests (Miroslav Bajtoš)\n\n * Fix Karma config to babelify node_modules too (Miroslav Bajtoš)\n\n * Add promise support to built-in model RoleMapping (ebarault)\n\n * Add promise support to built-in model ACL (ebarault)\n\n * Add nyc coverage, report data to coveralls.io (Miroslav Bajtoš)\n\n * Upgrade eslint config, fix linter errors (Miroslav Bajtoš)\n\n * Add missing type to Role properties definition (David Hernandez)\n\n * Preserve sessions on User.save() making no changes (Miroslav Bajtoš)\n\n * Fix logout to handle no or missing accessToken (Ritchie Martori)\n\n * Promise-ify built-in Role model (Miroslav Bajtoš)\n\n * Remove .jscsrc that's no longer used (Miroslav Bajtoš)\n\n * Enable ES6/ES2015 goodness (Miroslav Bajtoš)\n\n * Remove test/support.js from karma config (Miroslav Bajtoš)\n\n * Use English when running Mocha tests (Miroslav Bajtoš)\n\n * Update ISSUE_TEMPLATE (Simon Ho)\n\n * Updating README - add cli and remove arc (Joe Sepi)\n\n * Fix User methods to use correct Primary Key (Aris Kemper)\n\n * Fix User.resetPassword to call createAccessToken() (João Ribeiro)\n\n * Role model: resolves related models by name (Benjamin Kroeger)\n\n\n2017-01-16, Version 3.2.1\n=========================\n\n * Preserve current session when invalidating tokens (Miroslav Bajtoš)\n\n * Clean up access-token-invalidation tests (Miroslav Bajtoš)\n\n * Update docs.json (Rand McKinney)\n\n * Simplify issue template (#3083) (Simon Ho)\n\n * Warn about injectOptionsFromRemoteContext (Miroslav Bajtoš)\n\n\n2017-01-09, Version 3.2.0\n=========================\n\n * Upgrade eslint-config to 7.x (Miroslav Bajtoš)\n\n * Allow password reset request for users in realms (Bram Borggreve)\n\n * Fix construction of sharedCtor remoting metadata (Miroslav Bajtoš)\n\n * Add option disabling periodic change rectification (kobaska)\n\n * Fix annotation for persistedModel.count (lschricke)\n\n * Applied as reviewed by @flowersinthesand (박대선)\n\n * Fix false emailVerified on user model update (박대선)\n\n * Contextify DAO and relation methods (Miroslav Bajtoš)\n\n * Implement new http arg mapping optionsFromRequest (Miroslav Bajtoš)\n\n * Emit resetPasswordRequest event with options (Sergey Reus)\n\n\n2016-12-21, Version 3.1.1\n=========================\n\n * Update package.json for LB3 release (Simon Ho)\n\n * Invalidate AccessTokens on password change (Miroslav Bajtoš)\n\n * Fix registration of operation hooks in User model (Miroslav Bajtoš)\n\n * Remove \"options.template\" from Email payload (Miroslav Bajtoš)\n\n * Upgrade eslint config and grunt-eslint to latest (Miroslav Bajtoš)\n\n * Update paid support URL (siddhipai)\n\n * Update paid support URL (Siddhi Pai)\n\n * Remove duplicate warning in issue template (Siddhi Pai)\n\n\n2016-12-05, Version 3.1.0\n=========================\n\n * Fix use-strict issue with connectors after merge (Loay)\n\n * Fix connector naming in strict mode (ebarault)\n\n * Add \"returnOnlyRoleNames\" option to Role.getRoles (Eric)\n\n * Update translation files (Candy)\n\n * Fix broken document for `upsertWithWhere` (Amir Jafarian)\n\n * Fix js doc for deleteAll event (Candy)\n\n * add allowArray to relations' create remoteMethod (David Cheung)\n\n * Remove workaround for default value (Loay)\n\n * Fix remote method example (Amir Jafarian)\n\n * Remove `example/context` (Amir Jafarian)\n\n * Turn on \"no-unused-expressions\" rule for eslint (Miroslav Bajtoš)\n\n * Update eslint to loopback config v5 (Loay)\n\n * Fix total calculation in example (Candy)\n\n * make test individually runable (David Cheung)\n\n * Add options to bulkUpdate (Kogulan Baskaran)\n\n * Fix context within listByPrincipalType role method (codyolsen)\n\n * Add Node v7 to Travis CI platforms (Miroslav Bajtoš)\n\n * Drop support for Node v0.10 and v0.12 (Miroslav Bajtoš)\n\n * Add templateFn option to User#verify() (Adrien Kiren)\n\n * Require verification after email change (Loay)\n\n * Update doc links (Candy)\n\n * adding check of string for case insensitive emails (Dhaval Trivedi)\n\n * Update test confirmation text in PR template (#2897) (Simon Ho)\n\n * allow batch create for persisted models (David Cheung)\n\n * Fix PR template to not link all PRs to #49 (#2887) (Miroslav Bajtoš)\n\n * Need index on principalId for performance. (#2883) (Simon Ho)\n\n * Remove redundant items in PR template (#2877) (Simon Ho)\n\n * Refactor PR template based on feedback (#2865) (Simon Ho)\n\n * Add pull request template (#2843) (Simon Ho)\n\n * Update README.md (Rand McKinney)\n\n * Reword ticking checkbox note in issue template (#2854) (Simon Ho)\n\n * Add how to tick checkbox in issue template (#2851) (Simon Ho)\n\n * Fix description of updateAll response (Miroslav Bajtoš)\n\n * Allow tokens with eternal TTL (value -1) (Miroslav Bajtoš)\n\n * Use GitHub issue templates (#2810) (Simon Ho)\n\n * Update ja and nl translation files (Candy)\n\n * Remove 3.0 DEVELOPING & RELEASE-NOTES (Miroslav Bajtoš)\n\n * Fix support for remote hooks returning a Promise (Tim van der Staaij)\n\n * Validate non-email property partial update (Loay)\n\n * Update release notes (Amir Jafarian)\n\n * Update translation files - round#2 (Candy)\n\n * Add license text (Candy)\n\n * Temporarily disable Karma tests on Windows CI (Miroslav Bajtoš)\n\n\n2016-09-22, Version 3.0.0\n=========================\n\n * Update deps to 3.0.0 RC (Miroslav Bajtoš)\n\n * Update globalization structure (Candy)\n\n * Call new disable remote method from model class. (Richard Pringle)\n\n * Add translation strings (Candy)\n\n * Support uniqueness for realm users (David Cheung)\n\n * Invalidate sessions after email change (Loay)\n\n * Add docs for KeyValue model (Simon Ho)\n\n * Fix remote method inheritance (Candy)\n\n * Fix double-slash in confirmation URL (Miroslav Bajtoš)\n\n\n2016-09-09, Version 3.0.0-alpha.5\n=================================\n\n * Use strong-remoting's new TypeRegistry (Miroslav Bajtoš)\n\n * test/user: don't attach User model twice (Miroslav Bajtoš)\n\n * app.enableAuth: correctly detect attached models (Miroslav Bajtoš)\n\n * Fix remoting metadata for \"data\" arguments (Miroslav Bajtoš)\n\n * Add instructions for upgrading context (Miroslav Bajtoš)\n\n * Discard sugar method for model creation (gunjpan)\n\n * Remove one-var exceptions no longer needed (Miroslav Bajtoš)\n\n * Rework email validation to use isemail (Miroslav Bajtoš)\n\n * Expose upsertWithWhere method (Sonali Samantaray)\n\n\n2016-09-05, Version 3.0.0-alpha.4\n=================================\n\n * Update loopback-connector-remote to 2.0-alpha (Miroslav Bajtoš)\n\n * Add remoting for KeyValue model TTL feature (Simon Ho)\n\n * Add lint NPM script (Simon Ho)\n\n * Make the app instance available to connectors (Subramanian Krishnan)\n\n * Update pre-release dependencies (Miroslav Bajtoš)\n\n * Apply g.f to literal strings (Setogit)\n\n * Allow resetPassword if  emailVerified (Loay)\n\n * Reorder PATCH Vs PUT endpoints (Amir Jafarian)\n\n * streamline use if `self` (Benjamin Kroeger)\n\n * resolve related models from correct registry (Benjamin Kroeger)\n\n * KeyValueModel: add API for listing keys (Miroslav Bajtoš)\n\n * Fix token middleware crash (Carl Fürstenberg)\n\n * loopback#context: fix missing \"g\" symbol (Miroslav Bajtoš)\n\n * Update acl.js (Rand McKinney)\n\n * Support 'alias' in mail transport config. (Samuel Reed)\n\n * Remove unnecessary g.log (Setogit)\n\n * Revert globalization of Swagger descriptions (Miroslav Bajtoš)\n\n * Revert globalization of assert() messages (Miroslav Bajtoš)\n\n * Add bcrypt validation (Loay)\n\n\n2016-08-11, Version 3.0.0-alpha.3\n=================================\n\n * common: add KeyValueModel (Miroslav Bajtoš)\n\n * Globalize current-context error messages (Miroslav Bajtoš)\n\n * Remove current-context API (Miroslav Bajtoš)\n\n * Fix forceId in tests (jannyHou)\n\n * test: increase timeout to prevent CI failures (Miroslav Bajtoš)\n\n * Update globalization string (Candy)\n\n * Update globalization (Candy)\n\n * Add globalization (Candy)\n\n * test: fix \"socket hang up\" error in app.test (Miroslav Bajtoš)\n\n * test: increate timeout in Role test (Miroslav Bajtoš)\n\n * test: make status test more robust (Miroslav Bajtoš)\n\n * test: fix broken Role tests (Miroslav Bajtoš)\n\n * Update dependencies to their latest versions (Miroslav Bajtoš)\n\n * Increase timeout (jannyHou)\n\n * test: fix change-tracking setup (Miroslav Bajtoš)\n\n * test: use local registry in test fixtures (Miroslav Bajtoš)\n\n * Update loopback.js (Rand McKinney)\n\n * Fix test case error (Loay)\n\n * Update user.js (Loay)\n\n * Fix security issue 580 (Loay)\n\n * Update URLs in CONTRIBUTING.md (#2503) (Ryan Graham)\n\n * Remove legacyExplorer (gunjpan)\n\n * Remove `rectifyAllChanges` and `rectifyChange` (Candy)\n\n * Fix verificationToken bug (Loay)\n\n * update express version (Loay)\n\n * Cleanup unit-test added in 1fc51d129 (Miroslav Bajtoš)\n\n * update errorHandler template (Loay)\n\n\n2016-06-13, Version 3.0.0-alpha.2\n=================================\n\n * add missing unit tests for #2108 (Benjamin Kroeger)\n\n * Expose `Replace*` methods (Amir Jafarian)\n\n * Update tests for strong-error-handler (David Cheung)\n\n * Remove legacy express 3.x middleware getters (Miroslav Bajtoš)\n\n * Docuemtation for `replace*` methods (Amir Jafarian)\n\n * Make the doc clear for `findORCreate` cb (Amir Jafarian)\n\n * Fix JSCS unsupported rule error (Jason)\n\n * Remove env.json and strong-pm dir (Ritchie Martori)\n\n * Throw error upon extending unknown model (David Cheung)\n\n * Remove unused UserModel properties (David Cheung)\n\n * Remove Change.handleError (Candy)\n\n * Update user.js (Rik)\n\n * Separate error-checking and next/done logic from other logic in the test suite (Supasate Choochaisri)\n\n * Clean up by removing unnecessary comments (Supasate Choochaisri)\n\n * Add feature to not allow duplicate role name (Supasate Choochaisri)\n\n * update copyright statements (Ryan Graham)\n\n * relicense as MIT only (Ryan Graham)\n\n * Upgrade phantomjs to 2.x (Miroslav Bajtoš)\n\n * app: send port:0 instead of port:undefined (Miroslav Bajtoš)\n\n * travis: drop node@5, add node@6 (Miroslav Bajtoš)\n\n * Disable DEBUG output for eslint on Jenkins CI (Miroslav Bajtoš)\n\n * Remove \"loopback.autoAttach()\" (Miroslav Bajtoš)\n\n * test/rest.middleware: use local registry (Miroslav Bajtoš)\n\n * Fix role.isOwner to support app-local registry (Miroslav Bajtoš)\n\n * test/user: use local registry (Miroslav Bajtoš)\n\n * Resolver support return promise (juehou)\n\n * remove @private from jsdoc (Manu Phatak)\n\n * Fixes for emit `remoteMethodDisabled` PR (Simon Ho)\n\n * Add new feature to emit a `remoteMethodDisabled` event when disabling a remote method. (Supasate Choochaisri)\n\n * Fix typo in Model.nestRemoting (Tim Needham)\n\n * Update loopback.js (Rand McKinney)\n\n * Allow built-in token middleware to run repeatedly (Benjamin Kröger)\n\n * Use eslint with loopback config (Miroslav Bajtoš)\n\n * promise docs (Jue Hou)\n\n * Update JSDoc (sghung@ca.ibm.com)\n\n * Remove constraint making isStatic required (Candy)\n\n * Fix inconsistencies in JSDoc (sghung@ca.ibm.com)\n\n * Improve error message on connector init error (Miroslav Bajtoš)\n\n * application: correct spelling of \"cannont\" (Sam Roberts)\n\n * Remove sl-blip from dependency (Candy)\n\n * Use new strong-remoting API (Candy)\n\n * test: remove forgotten console.trace logs (Miroslav Bajtoš)\n\n * Fix race condition in replication tests (Miroslav Bajtoš)\n\n * Fix race condition in error handler test (Miroslav Bajtoš)\n\n * test: remove errant console.log from test (Ryan Graham)\n\n * Promisify Model Change (Jue Hou)\n\n * Travis: drop iojs, add v4.x and v5.x (Miroslav Bajtoš)\n\n * test: use ephemeral port for e2e server (Ryan Graham)\n\n * test: fail on error instead of crash (Ryan Graham)\n\n * ensure app is booted before integration tests (Ryan Graham)\n\n * Remove \"loopback.DataModel\" (Miroslav Bajtoš)\n\n * Correct JSDoc findOrCreate() callback in PersistedModel (Chris Coggburn)\n\n * Fix typo in package.json (publishConfig) (Miroslav Bajtoš)\n\n * Start development of 3.0 (Candy)\n\n * Hide verificationToken (Samuel Gaus)\n\n * Fix description for User.prototype.hasPassword (Jue Hou)\n\n * Checkpoint speedup (Amir Jafarian)\n\n * Always use bluebird as promise library Replace `global.Promise` with `bluebird` (Jue Hou)\n\n * Remove unused code from loopback-testing-helper (Simon Ho)\n\n * Make juggler a regular dependency (Miroslav Bajtoš)\n\n * Remove dependency on loopback-testing (Simon Ho)\n\n * Fix failing tests (Simon Ho)\n\n * Update persisted-model.js (Rand McKinney)\n\n * Update persisted-model.js (linguofeng)\n\n\n2015-12-22, Version 3.0.0-alpha.1\n=================================\n\n * Update juggler to ^3.0.0-alpha.1 (Miroslav Bajtoš)\n\n * Start development of 3.0 (Miroslav Bajtoš)\n\n\n2015-12-22, Version 2.26.2\n==========================\n\n * Fix bulkUpdate to not trigger rectifyAll (Amir Jafarian)\n\n\n2015-12-17, Version 2.26.1\n==========================\n\n * PersistedModel: log rectify/rectifyAll triggers (Miroslav Bajtoš)\n\n\n2015-12-09, Version 2.26.0\n==========================\n\n * change: skip cp lookup on no change (Miroslav Bajtoš)\n\n * Change: correctly rectify no-change (Miroslav Bajtoš)\n\n * Update model.js (Rand McKinney)\n\n * Adding properties description for User Model (David Cheung)\n\n * Add case-sensitve email option for User model. (Richard Pringle)\n\n\n2015-11-13, Version 2.25.0\n==========================\n\n * Fix typo in description of persistedModel.updateAttributes() (Richard Pringle)\n\n\n2015-11-09, Version 2.24.0\n==========================\n\n * Fix cookie-parser error (Simon Ho)\n\n\n2015-11-09, Version 2.23.0\n==========================\n\n * lib/registry: fix findModel for model ctor (Miroslav Bajtoš)\n\n * Refer to licenses with a link (Sam Roberts)\n\n * Fix user.resetPassword to fail on email not found (Simo Moujami)\n\n * Fix typo in doc comment (Rand McKinney)\n\n * Do not include redundant ports in verify links (Samuel Gaus)\n\n * Set application's id property only if it's empty. (wusuopu)\n\n * Check configs for shared method settings (Simon Ho)\n\n * Add test fixtures for shared methods (Simon Ho)\n\n * Clean up .jshintrc (Simon Ho)\n\n * Update comment about user ACL to reflect implementation (Felipe Oliveira Carvalho)\n\n\n2015-09-23, Version 2.22.2\n==========================\n\n * Use strongloop conventions for licensing (Sam Roberts)\n\n * Set package license to MIT (Sam Roberts)\n\n\n2015-09-18, Version 2.22.1\n==========================\n\n * Fix perf of rectification after updateAttributes (Miroslav Bajtoš)\n\n * Update persisted-model.js (Rand McKinney)\n\n * Stop NPM license warning (Simon Ho)\n\n\n2015-09-03, Version 2.22.0\n==========================\n\n * Create stack-removing errorhandler middleware (Richard Walker)\n\n * Update README.md (Rand McKinney)\n\n * Allow EJS templates to use includes (Samuel Gaus)\n\n * Fix options.to assertion message in user.verify (Farid Nouri Neshat)\n\n * Upgrade Travis to container-based infrastructure (Miroslav Bajtoš)\n\n * fix typo \"PeristedModel\" (Christoph)\n\n\n2015-08-13, Version 2.21.0\n==========================\n\n * Add util methods to ACL and clean up related model resolutions (Raymond Feng)\n\n * Promisify 'PersistedModel - replication' (Pradnya Baviskar)\n\n * Promisify 'Application' model (Pradnya Baviskar)\n\n\n2015-08-06, Version 2.20.0\n==========================\n\n * Allow methods filter for middleware config (Raymond Feng)\n\n * Don't load Bluebird for createPromiseCallback (Miroslav Bajtoš)\n\n * fix exit early when password is non-string closes #1437 (Berkeley Martinez)\n\n * Promisify User model (Pradnya Baviskar)\n\n * Add missing . to user model property descriptions (Richard Walker)\n\n\n2015-07-28, Version 2.19.1\n==========================\n\n * Disable application model test for karma (Raymond Feng)\n\n * Fix jsdocs for methods with where argument (Raymond Feng)\n\n * Add link to createChangeStream docs (Ritchie Martori)\n\n\n2015-07-09, Version 2.19.0\n==========================\n\n * Add PersistedModel.createChangeStream() (Ritchie Martori)\n\n * Remove trailing whitespace from jsdoc (Ritchie Martori)\n\n * Update model.js (Rand McKinney)\n\n * Downgrade version of loopback-testing (Ritchie Martori)\n\n * Auto-configure models required by `app.enableAuth` (Miroslav Bajtoš)\n\n * Add loadBuiltinModels flag to loopback(options) (Miroslav Bajtoš)\n\n * Add a unit-test for searchDefaultTokenKeys (Miroslav Bajtoš)\n\n * access-token: add option \"searchDefaultTokenKeys\" (Owen Brotherwood)\n\n * Fix the test case (Raymond Feng)\n\n * Fix code standards issues (Tom Kirkpatrick)\n\n * Add test case to highlight fatal error when trying to include a scoped relationship through a polymorphic relationship (Tom Kirkpatrick)\n\n * add callback args for listByPrincipalType to jsdoc comment, pass explicit arguments to callback (Esco Obong)\n\n * mark utiltiy function as private (Esco Obong)\n\n * fix linting errors (Esco Obong)\n\n * fix lint erros (Esco Obong)\n\n * consolidate Role methods roles, applications, and users into one, add query param to allow for pagination and restricting fields (Esco Obong)\n\n * fix implementation of Role methods: users,roles, and applications (Esco Obong)\n\n\n2015-05-13, Version 2.18.0\n==========================\n\n * Make the test compatible with latest juggler (Raymond Feng)\n\n\n2015-05-12, Version 2.17.3\n==========================\n\n * Use the new remoting.authorization hook for check access (Ritchie Martori)\n\n * Define remote methods via model settings/config (Miroslav Bajtoš)\n\n * Pass the full options object to the email send method in user verification process. (Alexandru Savin)\n\n * un-document _findLayerByHandler (Rand McKinney)\n\n * Gruntfile: disable debug & watch for CI builds (Miroslav Bajtoš)\n\n * Update devDependencies to the latest versions (Miroslav Bajtoš)\n\n * Remove trailing whitespace added by 242bcec (Miroslav Bajtoš)\n\n * Update model.js (Rand McKinney)\n\n\n2015-04-28, Version 2.17.2\n==========================\n\n * Fix regression in Model.getApp() (Miroslav Bajtoš)\n\n\n2015-04-28, Version 2.17.1\n==========================\n\n * Allow dataSource === false (Raymond Feng)\n\n * Fix remoting metadata for User.login#include (Miroslav Bajtoš)\n\n\n2015-04-21, Version 2.17.0\n==========================\n\n * Disable inclusion of User.accessTokens (Raymond Feng)\n\n * Upgrade test fixtures to use LB 2.x layout (Raymond Feng)\n\n\n2015-04-17, Version 2.16.3\n==========================\n\n * Rework global registry to be per-module-instance (Miroslav Bajtoš)\n\n\n2015-04-17, Version 2.16.1\n==========================\n\n * Add back loopback properties like modelBuilder (Miroslav Bajtoš)\n\n\n2015-04-16, Version 2.16.0\n==========================\n\n * Expose the `filter` argument for findById (Raymond Feng)\n\n * fixed the missing '.' in various description fields. (Edmond Lau)\n\n * Conflict resolution and Access control (Miroslav Bajtoš)\n\n * Fix the typo (Raymond Feng)\n\n * Fix PersistedModel._defineChangeModel (Miroslav Bajtoš)\n\n * AccessControl for change replication (Miroslav Bajtoš)\n\n * test: remove global autoAttach (Miroslav Bajtoš)\n\n * Add support for app level Model isolation (Ritchie Martori)\n\n * Implement ModelCtor.afterRemoteError (Miroslav Bajtoš)\n\n * Code cleanup, add Model._runWhenAttachedToApp (Miroslav Bajtoš)\n\n * Refactor Model and PersistedModel registration (Miroslav Bajtoš)\n\n * Fix the style issue (Raymond Feng)\n\n * Add missing error handlers to checkpoints() (Miroslav Bajtoš)\n\n * Fix where param format (Rand McKinney)\n\n * Test embedsOne CRUD methods (Fabien Franzen)\n\n\n2015-04-01, Version 2.15.0\n==========================\n\n * Improve error handling in replication (Miroslav Bajtoš)\n\n * Add `loopback.runInContext` (Miroslav Bajtoš)\n\n * Fix style issues (Raymond Feng)\n\n * Document the new third callback arg of replicate() (Miroslav Bajtoš)\n\n * Fix API doc for updateAll/deleteAll (Miroslav Bajtoš)\n\n * Import subset of underscore.string scripts only (Miroslav Bajtoš)\n\n * Use `ctx.instance` provided by \"after delete\" hook (Miroslav Bajtoš)\n\n * Add conflict resolution API (Miroslav Bajtoš)\n\n * Detect 3rd-party changes made during replication (Miroslav Bajtoš)\n\n * Ability to pass in custom verification token generator This commit adds the ability for the developer to use a custom token generator function for the user.verify(...) method. By default, the system will still use the crypto.randomBytes() method if no option is provided. (jakerella)\n\n * Remove unnecessary delay in tests. (Miroslav Bajtoš)\n\n * Update README.md (Simon Ho)\n\n * Remove duplicate cb func from getRoles and other doc cleanup (crandmck)\n\n * Enhance the token middleware to support current user literal (Raymond Feng)\n\n * Handling owner being a relation/function (Benjamin Boudreau)\n\n * Run replication tests in the browser too (Miroslav Bajtoš)\n\n * Add replication tests for conflict resolution (Miroslav Bajtoš)\n\n * Fix an assertion broke by recent chai upgrade. (Miroslav Bajtoš)\n\n * Static ACL support array of properties now (ulion)\n\n * Add more integration tests for replication (Miroslav Bajtoš)\n\n * Prevent more kinds of false replication conflicts (Miroslav Bajtoš)\n\n * Upgrade deps (Raymond Feng)\n\n * Fix \"Issues\" link in readme (Simon Ho)\n\n * Add more debug logs to replication (Miroslav Bajtoš)\n\n * Fixes #1158. (Jason Sturges)\n\n * Checkpoint: start with seq=1 instead of seq=0 (Miroslav Bajtoš)\n\n * Return new checkpoints in callback of replicate() (Miroslav Bajtoš)\n\n * Create a remote checkpoint during replication too (Miroslav Bajtoš)\n\n * Replication: fix checkpoint-related race condition (Miroslav Bajtoš)\n\n * Support different \"since\" for source and target (Miroslav Bajtoš)\n\n\n2015-03-03, Version 2.14.0\n==========================\n\n * Replace deprecated hooks with Operation hooks (Miroslav Bajtoš)\n\n * test: don't warn about running deprecated paths (Miroslav Bajtoš)\n\n * karma conf: prevent timeouts on Travis CI (Miroslav Bajtoš)\n\n * Pass options from User.login to createAccessToken (Raymond Feng)\n\n * Config option to disable legacy explorer routes Setting legacyExplorer to false in the loopback config will disable the routes /routes and /models made available in loopback.rest. The deprecate module has been added to the project with a reference added for the legacyExplorer option as it is no longer required by loopback-explorer. Tests added to validate functionality of disabled and enabled legacy explorer routes. (Ron Edgecomb)\n\n * test: setup GUID for all models tracking changes (Miroslav Bajtoš)\n\n * Change tracking requires a string id set to GUID (Miroslav Bajtoš)\n\n\n2015-02-25, Version 2.13.0\n==========================\n\n * Add a workaround to avoid conflicts with NewRelic (Raymond Feng)\n\n * Fix \"User.confirm\" to always call afterRemote hook (Pradnya Baviskar)\n\n * Skip hashing password if it's already hashed (Raymond Feng)\n\n * travis.yml: drop 0.11, add 0.12 and iojs (Miroslav Bajtoš)\n\n * Add docs for settings per #1069 (crandmck)\n\n * Fix change detection & tracking (Miroslav Bajtoš)\n\n * Minor doc fix (Ritchie Martori)\n\n * Upgrade jscs to ~1.11 via grunt-jscs ^1.5 (Miroslav Bajtoš)\n\n * Remove redundant dev-dep serve-favicon (Miroslav Bajtoš)\n\n * Fix test broken by recent juggler changes (Miroslav Bajtoš)\n\n * Fix coding style issue (Raymond Feng)\n\n * Remove trailing spaces (Raymond Feng)\n\n * Fix for issue 1099. (zane)\n\n * Fix API docs per #1041 (crandmck)\n\n * Fix API docs to add proper callback doc per #1041 (crandmck)\n\n * Fix #1080 - domain memory leak. (Samuel Reed)\n\n * Document user settings (Ritchie Martori)\n\n * Add wiki references to readme (Simon Ho)\n\n\n2015-02-03, Version 2.12.1\n==========================\n\n * Map not found to 404 for hasOne (Raymond Feng)\n\n\n2015-02-03, Version 2.12.0\n==========================\n\n * Fix the test case (Raymond Feng)\n\n * Enable remoting for hasOne relations (Raymond Feng)\n\n * README: add Gitter badge (Miroslav Bajtoš)\n\n\n2015-01-27, Version 2.11.0\n==========================\n\n * Document options for persistedmodel.save() (Rand McKinney)\n\n * Add test case to demonstrate url-encoded http path (Pradnya Baviskar)\n\n * Fix JSdocs per #888 (crandmck)\n\n * Add test case for loopback issue #698 (Pradnya Baviskar)\n\n * Remove usages of deprecated `req.param()` (Miroslav Bajtoš)\n\n * Add error code property to known error responses. (Ron Edgecomb)\n\n * test: use 127.0.0.1 instead of localhost (Ryan Graham)\n\n * Extend AccessToken to parse Basic auth headers (Ryan Graham)\n\n * tests: fix Bearer token test (Ryan Graham)\n\n * don't send queries to the DB when no changes are detected (bitmage)\n\n\n2015-01-16, Version 2.10.2\n==========================\n\n * Make sure EXECUTE access type matches READ or WRITE (Raymond Feng)\n\n\n2015-01-15, Version 2.10.1\n==========================\n\n * Optimize the creation of handlers for rest (Raymond Feng)\n\n * Add a link to gitter chat (Raymond Feng)\n\n * Added context middleware (Rand McKinney)\n\n * Use User.remoteMethod instead of loopbacks method This is needed for loopback-connector-remote authorization. Addresses https://github.com/strongloop/loopback/issues/622. (Berkeley Martinez)\n\n\n2015-01-07, Version 2.10.0\n==========================\n\n * Revert the peer dep change to avoid npm complaints (Raymond Feng)\n\n * Update strong-remoting dep (Raymond Feng)\n\n * Allow accessType per remote method (Raymond Feng)\n\n * API and REST tests added to ensure complete and valid credentials are supplied for verified error message to be returned  - tests added as suggested and fail under previous version of User model  - strongloop/loopback#931 (Ron Edgecomb)\n\n * Require valid login credentials before verified email check.  - strongloop/loopback#931. (Ron Edgecomb)\n\n\n2015-01-07, Version 2.9.0\n=========================\n\n * Update juggler dep (Raymond Feng)\n\n * Fix Geo test cases (Raymond Feng)\n\n * Allow User.hashPassword/validatePassword to be overridden (Raymond Feng)\n\n\n2015-01-07, Version 2.8.8\n=========================\n\n * Fix context middleware to preserve domains (Pham Anh Tuan)\n\n * Additional password reset unit tests for API and REST  - strongloop/loopback#944 (Ron Edgecomb)\n\n * Small formatting update to have consistency with identical logic in other areas.   - strongloop/loopback#944 (Ron Edgecomb)\n\n * Simplify the API test for invalidCredentials (removed create), move above REST calls for better grouping of tests   - strongloop/loopback#944 (Ron Edgecomb)\n\n * Force request to send body as string, this ensures headers aren't automatically set to application/json  - strongloop/loopback#944 (Ron Edgecomb)\n\n * Ensure error checking logic is in place for all REST calls, expand formatting for consistency with existing instances.  - strongloop/loopback#944 (Ron Edgecomb)\n\n * Correct invalidCredentials so that it differs from validCredentialsEmailVerified, unit test now passes as desired.  - strongloop/loopback#944 (Ron Edgecomb)\n\n * Update to demonstrate unit test is actually failing due to incorrect values of invalidCredentials  - strongloop/loopback#944 (Ron Edgecomb)\n\n * fix jscs warning (Clark Wang)\n\n * fix nestRemoting is nesting hooks from other relations (Clark Wang)\n\n\n2015-01-06, Version 2.8.7\n=========================\n\n * Change urlNotFound.js to url-not-found.js (Rand McKinney)\n\n * Add lib/server-app.js (Rand McKinney)\n\n * package: add versioned sl-blip dependency (Ryan Graham)\n\n * fix User.settings.ttl can't be overridden in sub model (Clark Wang)\n\n * Fix Change.getCheckpointModel() giving new models each call (Farid Neshat)\n\n * Update README.md (Rand McKinney)\n\n\n2014-12-15, Version 2.8.6\n=========================\n\n * server-app: make _sortLayersByPhase stable (Miroslav Bajtoš)\n\n * Rework phased middleware, fix several bugs (Miroslav Bajtoš)\n\n\n2014-12-12, Version 2.8.5\n=========================\n\n * fix jshint errors (Clark Wang)\n\n * test if cb exists (Clark Wang)\n\n * fix nested remoting function throwing error will crash app (Clark Wang)\n\n * Fix bcrypt issues for browserify (Raymond Feng)\n\n\n2014-12-08, Version 2.8.4\n=========================\n\n * Allow native bcrypt for performance (Raymond Feng)\n\n\n2014-12-08, Version 2.8.3\n=========================\n\n * Remove unused underscore dependency (Ryan Graham)\n\n\n2014-11-27, Version 2.8.2\n=========================\n\n * Prepend slash for nested remoting paths (Clark Wang)\n\n * fix jscs errors (Rob Halff)\n\n * enable jshint for tests (Rob Halff)\n\n * permit some globals (Rob Halff)\n\n * 'done' is not defined (Rob Halff)\n\n * 'memory' is already defined (Rob Halff)\n\n * singlequote, semicolon & /*jshint -W030 */ (Rob Halff)\n\n\n2014-11-25, Version 2.8.1\n=========================\n\n * Update docs.json (Rand McKinney)\n\n * Update favicon.js (Rand McKinney)\n\n\n2014-11-19, Version 2.8.0\n=========================\n\n * Expose more loopback middleware for require (Raymond Feng)\n\n * Scope app middleware to a list of paths (Miroslav Bajtoš)\n\n * Update CONTRIBUTING.md (Alex Voitau)\n\n * Fix the model name for hasMany/through relation (Raymond Feng)\n\n * Fixing the model attach (wfgomes)\n\n * Minor: update jsdoc for PersistedModel.updateAll (Alex Voitau)\n\n * AccessToken: optional `options` in findForRequest (Miroslav Bajtoš)\n\n * server-app: improve jsdoc comments (Miroslav Bajtoš)\n\n * server-app: middleware API improvements (Miroslav Bajtoš)\n\n * typo of port server (wfgomes)\n\n * Move middleware sources to `server/middleware` (Miroslav Bajtoš)\n\n * app.middleware: verify serial exec of handlers (Miroslav Bajtoš)\n\n * Simplify `app.defineMiddlewarePhases` (Miroslav Bajtoš)\n\n * Make sure loopback has all properties from express (Raymond Feng)\n\n * Implement `app.defineMiddlewarePhases` (Miroslav Bajtoš)\n\n * Implement app.middlewareFromConfig (Miroslav Bajtoš)\n\n * middleware/token: store the token in current ctx (Miroslav Bajtoš)\n\n * Fix `loopback.getCurrentContext` (Miroslav Bajtoš)\n\n * Update chai to ^1.10.0 (Miroslav Bajtoš)\n\n * package: fix deps (Miroslav Bajtoš)\n\n * Middleware phases - initial implementation (Miroslav Bajtoš)\n\n * Allows ACLs/settings in model config (Raymond Feng)\n\n * Remove context middleware per Ritchie (Rand McKinney)\n\n * Add API doc for context middleware - see #337 (crandmck)\n\n * Update persisted-model.js (Rand McKinney)\n\n * rest middleware: clean up context config (Miroslav Bajtoš)\n\n * Move `context` example to a standalone app (Miroslav Bajtoš)\n\n * Enable the context middleware from loopback.rest (Raymond Feng)\n\n * Add context propagation middleware (Raymond Feng)\n\n * Changes to JSdoc comments (Rand McKinney)\n\n * Reorder classes alphabetically in each section (Rand McKinney)\n\n * common: coding style cleanup (Miroslav Bajtoš)\n\n * Coding style cleanup (Gruntfile, lib) (Miroslav Bajtoš)\n\n * Enable jscs for `lib`, fix style violations (Rob Halff)\n\n * Add access-context.js to API doc (Rand McKinney)\n\n * Remove doc for debug function (Rand McKinney)\n\n * Update registry.js (Rand McKinney)\n\n * Fix the jsdoc for User.login (Raymond Feng)\n\n * Deleted instantiation of new Change model. This PR removes the instantiation of a new change model as models return from Change.find are already instances of Change. This solves the duplicate Id issue #649 (Berkeley Martinez)\n\n * Expose path to the built-in favicon file (Miroslav Bajtoš)\n\n * Add API docs for `loopback.static`. (Miroslav Bajtoš)\n\n * Add test for `remoting.rest.supportedTypes` (Miroslav Bajtoš)\n\n * Revert \"rest handler options\" (Miroslav Bajtoš)\n\n * REST handler options. (Guilherme Cirne)\n\n * The elapsed time in milliseconds can be 0 (less than 1 ms) (Raymond Feng)\n\n\n2014-10-27, Version 2.7.0\n=========================\n\n * Bump version (Raymond Feng)\n\n * User: custom email headers in verify (Juan Pizarro)\n\n * Add realm support (Raymond Feng)\n\n * Make sure GET /:id/exists returns 200 {exists: true|false} https://github.com/strongloop/loopback/issues/679 (Raymond Feng)\n\n * Adjust id handling to deal with 0 and null (Chris S)\n\n * Force principalId to be a string. (Chris S)\n\n\n2014-10-23, Version 2.6.0\n=========================\n\n * User: fix `confirm` permissions (Miroslav Bajtoš)\n\n * Use === to compare with 0 (Rob Halff)\n\n * add laxbreak option (Rob Halff)\n\n * use singlequotes (Rob Halff)\n\n * split jshint task for test & lib (Rob Halff)\n\n * allow comma first style and increase line length (Rob Halff)\n\n * add missing semicolons (Rob Halff)\n\n * Support per-model and per-handler remoting options (Fabien Franzen)\n\n * Fix JSdoc for registerResolver (Rand McKinney)\n\n * lib/application: improve URL building algo (Miroslav Bajtoš)\n\n * Fix findById callback signature (Rand McKinney)\n\n * JSdoc fixes (Rand McKinney)\n\n * Fix places using undefined variables (Miroslav Bajtoš)\n\n * Clean up jsdoc comments (crandmck)\n\n * models: move Change LDL def into a json file (Miroslav Bajtoš)\n\n * models: move Checkpoint LDL def into a json file (Miroslav Bajtoš)\n\n * models: move Role LDL def into a json file (Miroslav Bajtoš)\n\n * models: move RoleMapping def into its own files (Miroslav Bajtoš)\n\n * models: move ACL LDL def into a json file (Miroslav Bajtoš)\n\n * models: move Scope def into its own files (Miroslav Bajtoš)\n\n * models: move AccessToken LDL def into a json file (Miroslav Bajtoš)\n\n * models: move Application LDL def into a json file (Miroslav Bajtoš)\n\n * models: move Email LDL def into `email.json` (Miroslav Bajtoš)\n\n * models: move User LDL def into `user.json` (Miroslav Bajtoš)\n\n * test: run more tests in the browser (Miroslav Bajtoš)\n\n * test: verify exported models (Miroslav Bajtoš)\n\n * test: remove infinite timeout (Miroslav Bajtoš)\n\n * Auto-load and register built-in `Checkpoint` model (Miroslav Bajtoš)\n\n * Skip static ACL entries that don't match the property (Raymond Feng)\n\n * Dismantle `lib/models`. (Miroslav Bajtoš)\n\n * Register built-in models in a standalone file (Miroslav Bajtoš)\n\n\n2014-10-10, Version 2.4.1\n=========================\n\n * models/change: fix `id` property definition (Miroslav Bajtoš)\n\n * Added class properties jsdoc. (Rand McKinney)\n\n * Fixed up JS Doc (Rand McKinney)\n\n * Update contribution guidelines (Ryan Graham)\n\n * Document ACL class properties (Rand McKinney)\n\n * Add properties JSdoc. (Rand McKinney)\n\n * Move looback remote connector to npm module (Krishna Raman)\n\n * Update strong-remoting version (Ritchie Martori)\n\n * Document user class properties (Ritchie Martori)\n\n * Add Model.disableRemoteMethod() (Ritchie Martori)\n\n\n2014-09-12, Version 2.2.0\n=========================\n\n * Bump versions (Raymond Feng)\n\n * PersistedModel: add remote method aliases (Miroslav Bajtoš)\n\n * Fix last commit, which misplaced an ACL. Move the ACL inside \"acls\". Signed-off-by: Carey Richard Murphey <rich@murphey.org> (zxvv)\n\n * Add an ACL to User, to allow everyone to execute User.passwordReset(). (zxvv)\n\n * package: add \"web\" keyword (Miroslav Bajtoš)\n\n * Fix require (Fabien Franzen)\n\n * Fix coercion for remoting on vanilla models (Ritchie Martori)\n\n * user#login include server crash fix (Alexander Ryzhikov)\n\n * Update model.js (Rand McKinney)\n\n * Restrict: only hasManyThrough relation can have additional properties (Clark Wang)\n\n * Restrict that only hasManyThrough can have additional properties (Clark Wang)\n\n * Add tests for hasManyThrough link with data (Clark Wang)\n\n * Support data field as body for link operation (Clark Wang)\n\n * Tiny fix: correct url format (Fabien Franzen)\n\n * Fix embedsMany/findById to return proper 404 response (Fabien Franzen)\n\n * registry: warn when dataSource is not specified (Miroslav Bajtoš)\n\n * Only validate dataSource when defined (Fixes #482) (Ritchie Martori)\n\n * Fix tests (Fabien Franzen)\n\n * Enable remoting for embedsOne relation (Jaka Hudoklin)\n\n * Allow 'where' argument for scoped count API (Fabien Franzen)\n\n * Account for undefined before/afterListeners (Fabien Franzen)\n\n * added test and fixed changing passed in object within ctor (britztopher)\n\n * adding the ability to use single or multiple email transports in datasources.json file (britztopher)\n\n * added the ability to use an array of transports or just a single trnasport (britztopher)\n\n\n2014-08-18, Version 2.1.3\n=========================\n\n * Bump version (Raymond Feng)\n\n * Make sure AccessToken extends from PersistedModel (Raymond Feng)\n\n * add count to relations and scopes (Jaka Hudoklin)\n\n * Remove `req.resume` from `app.enableAuth` (Miroslav Bajtoš)\n\n * Fix accessToken property docs (Ritchie Martori)\n\n\n2014-08-08, Version 2.1.1\n=========================\n\n * Bump version (Raymond Feng)\n\n * Make sure scoped methods are remoted (Raymond Feng)\n\n * Pass in remotingContext for ACL (Raymond Feng)\n\n * Fix reference to app (Raymond Feng)\n\n * Don't assume relation.modelTo in case of polymorphic belongsTo (Fabien Franzen)\n\n\n2014-08-07, Version 2.1.0\n=========================\n\n * Bump version (Raymond Feng)\n\n * Fix doc for the EXECUTE (Raymond Feng)\n\n * Fix \"callbacl\" by \"callback\" in doc (Steve Grosbois)\n\n * Inherit hooks when nesting (Fabien Franzen)\n\n * Changed options.path to options.http.path (Fabien Franzen)\n\n * filterMethod can also be a direct callback (Fabien Franzen)\n\n * filterMethod option (fn) to filter nested remote methods (Fabien Franzen)\n\n * Fix test to be more specific (Fabien Franzen)\n\n * Implement Model.nestRemoting (Fabien Franzen)\n\n * Allow custom relation path (http) - enable hasOne remoting access (Fabien Franzen)\n\n * Expose Model.exists over HTTP HEAD (Raymond Feng)\n\n * Return data source for app.dataSource() (Raymond Feng)\n\n * Fix typo in README (Ritchie Martori)\n\n * Integration test: referencesMany (Fabien Franzen)\n\n * Integration test: embedsMany (Fabien Franzen)\n\n * Fix jsdoc for remoteMethod() (Rand McKinney)\n\n * Map exists to HEAD for REST (Raymond Feng)\n\n * Build the email verification url from app context (Raymond Feng)\n\n\n2014-07-27, Version 2.0.2\n=========================\n\n * Fix https://github.com/strongloop/loopback/issues/413 (Raymond Feng)\n\n * Update test case to remove usage of deprecated express apis (Raymond Feng)\n\n\n2014-07-26, Version 2.0.1\n=========================\n\n * Bump version (Raymond Feng)\n\n * updated LB module diagram (altsang)\n\n * Update package.json (Al Tsang)\n\n * Updates for 2.0 (crandmck)\n\n * Update module diagram again (crandmck)\n\n * Update module diagram (crandmck)\n\n * Emit a 'modelRemoted' event by app.model() (Raymond Feng)\n\n * Fix remoting types for related models (Raymond Feng)\n\n * Fix for email transports (Raymond Feng)\n\n * Remove the link to obsolete wiki page to favor loopback.io (Raymond Feng)\n\n\n2014-07-22, Version 2.0.0\n=========================\n\n * Enhance the base model assertions (Raymond Feng)\n\n * Report error for User.confirm() (Raymond Feng)\n\n * Set up the base model based on the connector types (Raymond Feng)\n\n * express-middleware: improve error message (Miroslav Bajtoš)\n\n * Remove `app.docs()` (Miroslav Bajtoš)\n\n * Remove `loopback.compat.usePluralNamesForRemoting` (Miroslav Bajtoš)\n\n * Validate username uniqueness (Jaka Hudoklin)\n\n * Add descriptions for custom methods on user model (Raymond Feng)\n\n * Move remoting metadata from juggler to loopback (Raymond Feng)\n\n * Upgrade to nodemailer 1.0.1 (Raymond Feng)\n\n * Enhance the error message (Raymond Feng)\n\n\n2014-07-16, Version 2.0.0-beta7\n===============================\n\n * Bump version (Raymond Feng)\n\n * Remove unused dep (Raymond Feng)\n\n * Bump version and update deps (Raymond Feng)\n\n * Upgrade to loopback-datasource-juggler@1.7.0 (Raymond Feng)\n\n * Refactor modelBuilder to registry and set up default model (Raymond Feng)\n\n * Add a test case for credentials/challenges (Raymond Feng)\n\n * Fix credentials/challenges types (Raymond Feng)\n\n * Update modules for examples (Raymond Feng)\n\n * Split out aliases for deleteById and destroyAll functions for jsdoc. (crandmck)\n\n * Remove unused deps (Raymond Feng)\n\n * Refactor email verification tests into a new group (Raymond Feng)\n\n * Fix the typo (Raymond Feng)\n\n * Add an option to honor emailVerified (Raymond Feng)\n\n * Update module list in README (Raymond Feng)\n\n * Refine the test cases for relation REST APIs (Raymond Feng)\n\n * test: add check of Model remote methods (Miroslav Bajtoš)\n\n * Adjust the REST mapping for add/remove (Raymond Feng)\n\n * Add a test case for hasMany through add/remove remoting (Raymond Feng)\n\n * Fix the typo and add Bearer token support (Raymond Feng)\n\n * Update README (Raymond Feng)\n\n * Fix misleading token middleware documentation (Aleksandr Tsertkov)\n\n\n2014-07-15, Version 2.0.0-beta6\n===============================\n\n * lib/application: publish Change models to REST API (Miroslav Bajtoš)\n\n * models/change: fix typo (Miroslav Bajtoš)\n\n * checkpoint: fix `current()` (Miroslav Bajtoš)\n\n\n2014-07-03, Version 2.0.0-beta5\n===============================\n\n * app: update `url` on `listening` event (Miroslav Bajtoš)\n\n * Fix \"ReferenceError: loopback is not defined\" in registry.memory(). (Guilherme Cirne)\n\n * Invalid Access Token return 401 (Karl Mikkelsen)\n\n * Bump version and update deps (Raymond Feng)\n\n * Update debug setting (Raymond Feng)\n\n * Mark `app.boot` as deprecated. (Miroslav Bajtoš)\n\n * Update link to doc (Rand McKinney)\n\n\n2014-06-26, Version 2.0.0-beta4\n===============================\n\n * package: upgrade juggler to 2.0.0-beta2 (Miroslav Bajtoš)\n\n * Fix loopback in PhantomJS, fix karma tests (Miroslav Bajtoš)\n\n * Allow peer to use beta2 of datasource-juggler (and future) (Laurent)\n\n * Remove `app.boot` (Miroslav Bajtoš)\n\n * Update juggler dep (Raymond Feng)\n\n * Remove relationNameFor (Raymond Feng)\n\n * Fix a slowdown caused by mutation of an incoming accessToken option. (Samuel Reed)\n\n * Fix remote method definition in client-server example (Ritchie Martori)\n\n * package: the next version will be a minor version (Miroslav Bajtoš)\n\n * lib/registry: `getModel` throws, add `findModel` (Miroslav Bajtoš)\n\n * lib/application: Remove forgotten `loopback` ref (Miroslav Bajtoš)\n\n * Allow customization of ACL http status (Karl Mikkelsen)\n\n * Expose loopback as `app.loopback` (Miroslav Bajtoš)\n\n * Remove loopback-explorer from dev deps (Miroslav Bajtoš)\n\n * registry: export DataSource class (Miroslav Bajtoš)\n\n * registry: fix non-unique default dataSources (Miroslav Bajtoš)\n\n * lib/registry fix jsdoc comments (Miroslav Bajtoš)\n\n * test: add debug logs (Miroslav Bajtoš)\n\n * refactor: extract runtime and registry (Miroslav Bajtoš)\n\n * Remove assertIsModel and isDataSource (Miroslav Bajtoš)\n\n * Add createModelFromConfig and configureModel() (Miroslav Bajtoš)\n\n * Make app.get/app.set available in browser (Miroslav Bajtoš)\n\n * package: upgrade Mocha to 1.20 (Miroslav Bajtoš)\n\n * test: fix ACL integration tests (Miroslav Bajtoš)\n\n * JSDoc fixes (crandmck)\n\n * Add a test case (Raymond Feng)\n\n * Set the role id to be generated (Raymond Feng)\n\n * Add loopback.version back (Miroslav Bajtoš)\n\n * Tidy up app.model() to remove duplicate & recusrive call (Raymond Feng)\n\n * Register existing model to app.models during app.model() (Raymond Feng)\n\n * JSDoc cleanup (crandmck)\n\n * Bump version so that we can republish (Raymond Feng)\n\n * Bump version (Raymond Feng)\n\n * Use constructor to reference the model class (Raymond Feng)\n\n * Allow the creation of access token to be overriden (Raymond Feng)\n\n * Fixup JSDocs; note: updateOrCreate function alias pulled out on separate line for docs (crandmck)\n\n * lib/loopback: fix jsdoc comments (Miroslav Bajtoš)\n\n * Rename DataModel to PersistedModel (Miroslav Bajtoš)\n\n * Added middleware and API doc headings (crandmck)\n\n * Update JSDoc (crandmck)\n\n * Update docs.json (Rand McKinney)\n\n * Removed old .md files from API docs (Rand McKinney)\n\n * Delete api-model.md (Rand McKinney)\n\n * Delete api-datasource.md (Rand McKinney)\n\n * Delete api-geopoint.md (Rand McKinney)\n\n * Remove duplicate doc content (Rand McKinney)\n\n * Add note about unavailable args to remote hooks. (Rand McKinney)\n\n * Undo incorrect changes I made -- per Ritchie (Rand McKinney)\n\n * Update strong-remoting to 1.5 (Ritchie Martori)\n\n * Remove \"user\" as arg to beforeRemote(..) (Rand McKinney)\n\n * Exclude express-middleware from browser bundle (Miroslav Bajtoš)\n\n * !fixup only set ctx.accessType when sharedMethod is available (Ritchie Martori)\n\n * Refactor ACL to allow for `methodNames` / aliases (Ritchie Martori)\n\n * test: Remove forgotten call of `console.log()` (Miroslav Bajtoš)\n\n * Update README and the module diagram (Raymond Feng)\n\n * Clean up express middleware dependencies (Raymond Feng)\n\n * Update strong-remoting dep (Raymond Feng)\n\n * Rename express-wrapper to express-middleware (Raymond Feng)\n\n * Clean up the tests (Raymond Feng)\n\n * Upgrade to Express 4.x (Raymond Feng)\n\n * Deprecate app.boot, remove app.installMiddleware (Miroslav Bajtoš)\n\n\n2014-05-28, Version 2.0.0-beta3\n===============================\n\n * package.json: fix malformed json (Miroslav Bajtoš)\n\n * 2.0.0-beta2 (Ritchie Martori)\n\n * 2.0.0-beta1 (Ritchie Martori)\n\n * Add RC version (Ritchie Martori)\n\n * Depend on juggler@1.6.0 (Ritchie Martori)\n\n * !fixup Mark DAO methods as delegate (Ritchie Martori)\n\n * Ensure changes are created in sync (Ritchie Martori)\n\n * Remove un-rectify-able changes (Ritchie Martori)\n\n * Rework change conflict detection (Ritchie Martori)\n\n * - Use the RemoteObjects class to find remote objects instead of creating a cache  - Use the SharedClass class to build the remote connector  - Change default base model from Model to DataModel  - Fix DataModel errors not logging correct method names  - Use the strong-remoting 1.4 resolver API to resolve dynamic remote methods (relation api)  - Remove use of fn object for storing remoting meta data (Ritchie Martori)\n\n * In progress: rework remoting meta-data (Ritchie Martori)\n\n * Add test for conflicts where both deleted (Ritchie Martori)\n\n * Rework replication test (Ritchie Martori)\n\n * bump juggler version (Ritchie Martori)\n\n * Change#getModel(), Doc cleanup, Conflict event (Ritchie Martori)\n\n * Add error logging for missing data (Ritchie Martori)\n\n * Fix issues when using MongoDB for replication (Ritchie Martori)\n\n * !fixup Test cleanup (Ritchie Martori)\n\n * Move replication implementation to DataModel (Ritchie Martori)\n\n * All tests passing (Ritchie Martori)\n\n * !fixup use DataModel instead of Model for all data based models (Ritchie Martori)\n\n * fixup! unskip failing tests (Ritchie Martori)\n\n * !fixup RemoteConnector tests (Ritchie Martori)\n\n * Add missing test/model file (Ritchie Martori)\n\n * Refactor DataModel remoting (Ritchie Martori)\n\n * !fixup .replicate() argument handling (Ritchie Martori)\n\n * Fixes for e2e replication / remote connector tests (Ritchie Martori)\n\n * Add replication e2e tests (Ritchie Martori)\n\n * fixup! Assert model exists (Ritchie Martori)\n\n * fixup! rename Change.track => rectifyModelChanges (Ritchie Martori)\n\n * Add model tests (Ritchie Martori)\n\n * Add replication example (Ritchie Martori)\n\n * Add Checkpoint model and Model replication methods (Ritchie Martori)\n\n * Add Change model (Ritchie Martori)\n\n\n2014-07-16, Version 1.10.0\n==========================\n\n * Remove unused dep (Raymond Feng)\n\n * Bump version and update deps (Raymond Feng)\n\n * Upgrade to loopback-datasource-juggler@1.7.0 (Raymond Feng)\n\n * Refactor modelBuilder to registry and set up default model (Raymond Feng)\n\n * Add a test case for credentials/challenges (Raymond Feng)\n\n * Fix credentials/challenges types (Raymond Feng)\n\n * Update modules for examples (Raymond Feng)\n\n * Split out aliases for deleteById and destroyAll functions for jsdoc. (crandmck)\n\n * Remove unused deps (Raymond Feng)\n\n * Refactor email verification tests into a new group (Raymond Feng)\n\n * Fix the typo (Raymond Feng)\n\n * Add an option to honor emailVerified (Raymond Feng)\n\n * Update module list in README (Raymond Feng)\n\n * Refine the test cases for relation REST APIs (Raymond Feng)\n\n * test: add check of Model remote methods (Miroslav Bajtoš)\n\n * Adjust the REST mapping for add/remove (Raymond Feng)\n\n * Add a test case for hasMany through add/remove remoting (Raymond Feng)\n\n * Fix the typo and add Bearer token support (Raymond Feng)\n\n * Update README (Raymond Feng)\n\n * Fix misleading token middleware documentation (Aleksandr Tsertkov)\n\n * app: update `url` on `listening` event (Miroslav Bajtoš)\n\n\n2014-06-27, Version 1.9.1\n=========================\n\n * Fix \"ReferenceError: loopback is not defined\" in registry.memory(). (Guilherme Cirne)\n\n * Invalid Access Token return 401 (Karl Mikkelsen)\n\n\n2014-06-25, Version 1.9.0\n=========================\n\n * Bump version and update deps (Raymond Feng)\n\n * Update debug setting (Raymond Feng)\n\n * Mark `app.boot` as deprecated. (Miroslav Bajtoš)\n\n * Update link to doc (Rand McKinney)\n\n * Update juggler dep (Raymond Feng)\n\n * Remove relationNameFor (Raymond Feng)\n\n * Fix a slowdown caused by mutation of an incoming accessToken option. (Samuel Reed)\n\n * package: the next version will be a minor version (Miroslav Bajtoš)\n\n * lib/application: Remove forgotten `loopback` ref (Miroslav Bajtoš)\n\n * Allow customization of ACL http status (Karl Mikkelsen)\n\n * Expose loopback as `app.loopback` (Miroslav Bajtoš)\n\n * registry: export DataSource class (Miroslav Bajtoš)\n\n * registry: fix non-unique default dataSources (Miroslav Bajtoš)\n\n * lib/registry fix jsdoc comments (Miroslav Bajtoš)\n\n * test: add debug logs (Miroslav Bajtoš)\n\n * refactor: extract runtime and registry (Miroslav Bajtoš)\n\n * Remove assertIsModel and isDataSource (Miroslav Bajtoš)\n\n * Add createModelFromConfig and configureModel() (Miroslav Bajtoš)\n\n * Make app.get/app.set available in browser (Miroslav Bajtoš)\n\n * package: upgrade Mocha to 1.20 (Miroslav Bajtoš)\n\n * test: fix ACL integration tests (Miroslav Bajtoš)\n\n * JSDoc fixes (crandmck)\n\n * Add a test case (Raymond Feng)\n\n * Set the role id to be generated (Raymond Feng)\n\n * Tidy up app.model() to remove duplicate & recusrive call (Raymond Feng)\n\n * Register existing model to app.models during app.model() (Raymond Feng)\n\n * JSDoc cleanup (crandmck)\n\n * Bump version so that we can republish (Raymond Feng)\n\n\n2014-06-09, Version 1.8.6\n=========================\n\n * Bump version (Raymond Feng)\n\n * Use constructor to reference the model class (Raymond Feng)\n\n * Allow the creation of access token to be overriden (Raymond Feng)\n\n * Fixup JSDocs; note: updateOrCreate function alias pulled out on separate line for docs (crandmck)\n\n * Added middleware and API doc headings (crandmck)\n\n * Update JSDoc (crandmck)\n\n * Update docs.json (Rand McKinney)\n\n * Removed old .md files from API docs (Rand McKinney)\n\n * Delete api-model.md (Rand McKinney)\n\n * Delete api-datasource.md (Rand McKinney)\n\n * Delete api-geopoint.md (Rand McKinney)\n\n * Remove duplicate doc content (Rand McKinney)\n\n * Add note about unavailable args to remote hooks. (Rand McKinney)\n\n * Undo incorrect changes I made -- per Ritchie (Rand McKinney)\n\n * Update strong-remoting to 1.5 (Ritchie Martori)\n\n * Remove \"user\" as arg to beforeRemote(..) (Rand McKinney)\n\n * !fixup only set ctx.accessType when sharedMethod is available (Ritchie Martori)\n\n * Refactor ACL to allow for `methodNames` / aliases (Ritchie Martori)\n\n * Update README and the module diagram (Raymond Feng)\n\n * app: implement `connector()` and `connectors` (Miroslav Bajtoš)\n\n * Fix a typo in `app.boot`. (Samuel Reed)\n\n * Make app.datasources unique per app instance (Miroslav Bajtoš)\n\n\n2014-05-27, Version 1.8.5\n=========================\n\n * Bump version (Raymond Feng)\n\n * Add postgresql to the keywords (Raymond Feng)\n\n * updated package.json with SOAP and framework keywords (altsang)\n\n * updated package.json with keywords and updated description (Raymond Feng)\n\n\n2014-05-27, Version 1.8.4\n=========================\n\n * Add more keywords (Raymond Feng)\n\n * Bump version (Raymond Feng)\n\n * app: flatten model config (Miroslav Bajtoš)\n\n * Fix the test for mocha 1.19.0 (Raymond Feng)\n\n * Update dependencies (Raymond Feng)\n\n * Added more keywords (Rand McKinney)\n\n * Update README and the module diagram (Raymond Feng)\n\n * added \"REST API\" keyword (Rand McKinney)\n\n * added 'web' and 'framework' keywords (Rand McKinney)\n\n * Make image URL absolute for npmjs.org. (Rand McKinney)\n\n * Use common syntax for juggler dep (Ritchie Martori)\n\n * Modify `loopback.rest` to include `loopback.token` (Miroslav Bajtoš)\n\n * Relax validation object test (Ritchie Martori)\n\n * Make juggler version a bit more strict to avoid pulling in breaking changes (Ritchie Martori)\n\n * Change module diagram to local png (Rand McKinney)\n\n * Add LoopBack modules diagram (crandmck)\n\n * Update README.md (sumitha)\n\n * Update README.md (Al Tsang)\n\n * added github prefix to path (altsang)\n\n * removed githalytics, added sl-beacon (altsang)\n\n * Update README and license link (Raymond Feng)\n\n * Add CLA (Raymond Feng)\n\n\n2014-05-16, Version 1.8.2\n=========================\n\n * test/geo-point: relax too precise assertions (Miroslav Bajtoš)\n\n * Fix typo \"Unkown\" => \"Unknown\" (Adam Schwartz)\n\n * Support all 1.x versions of datasource-juggler (Miroslav Bajtoš)\n\n * Remove validation methods, now covered in JSDoc. (Rand McKinney)\n\n * Remove docs/api-geopoint.md from docs (Rand McKinney)\n\n * Removed docs/api-datasource.md (Rand McKinney)\n\n * Update README.md (Al Tsang)\n\n * Update README.md (Rand McKinney)\n\n * Move content from wiki on LB modules. (Rand McKinney)\n\n * Add homepage to package.json (Ritchie Martori)\n\n * Fix bug in User#resetPassword (haio)\n\n * Fix client-server example (Ritchie Martori)\n\n * Ensure roleId and principalId to be string in Role#isInRole (haio)\n\n * typo (haio)\n\n * Add more check on principalId (haio)\n\n * Convert principalId to String (haio)\n\n\n2014-04-24, Version 1.8.1\n=========================\n\n * Bump version (Raymond Feng)\n\n * Fix constructor JSDoc (crandmck)\n\n * Remove intermediate section headers from nav (crandmck)\n\n * Rename the method so that it won't conflict with Model.checkAccess (Raymond Feng)\n\n * Fix/remove ctx.user documentation (Ritchie Martori)\n\n * Documentation cleanup (Ritchie Martori)\n\n * Fix save implementation for remoting connector (Ritchie Martori)\n\n * Add basic Remote connector e2e test (Ritchie Martori)\n\n * Bump juggler version (Ritchie Martori)\n\n * Add test for remoting nested hidden properties (Ritchie Martori)\n\n * Fix #229 (Whitespaces removed (Alex Pica)\n\n * Add nodemailer to browser ignores (Ritchie Martori)\n\n * Add an assertion to the returned store object (Raymond Feng)\n\n * Add an integration test for belongsTo remoting (Raymond Feng)\n\n * Depend on strong-remoting 1.3 (Ritchie Martori)\n\n * Support host / port in Remote connector (Ritchie Martori)\n\n * Throw useful errors in DataModel stub methods (Ritchie Martori)\n\n * Move proxy creation from remote connector into base model class (Ritchie Martori)\n\n * Remove reload method body (Ritchie Martori)\n\n * Add Remote connector (Ritchie Martori)\n\n * Initial client-server example (Ritchie Martori)\n\n\n2014-04-04, Version 1.7.4\n=========================\n\n * Clean up JSDoc comments.  Remove doc for deprecated installMiddleware function (crandmck)\n\n * Describe the \"id\" parameter of model's sharedCtor (Miroslav Bajtoš)\n\n * Update and cleanup JSDoc (crandmck)\n\n * Cleanup and update of jsdoc (crandmck)\n\n * Add link to loopback.io (Rand McKinney)\n\n * Update user.js (Doug Toppin)\n\n * Add hidden property documentation (Ritchie Martori)\n\n * test: add hasAndBelongsToMany integration test (Miroslav Bajtoš)\n\n * fix to enable ACL for confirm link sent by email (Doug Toppin)\n\n * Add hidden property support to models (Ritchie Martori)\n\n * Allow app.model() to accept a DataSource instance (Ritchie Martori)\n\n * Make verifications url safe (Ritchie Martori)\n\n * Try to fix  org.pegdown.ParsingTimeoutException (Rand McKinney)\n\n * using base64 caused an occasional token string to contain '+' which resulted in a space being embedded in the token.  'hex' should always produce a url safe string for the token. (Doug Toppin)\n\n * Sending email was missing the from field (Doug Toppin)\n\n\n2014-03-19, Version 1.7.2\n=========================\n\n * Bump version (Raymond Feng)\n\n * Add more comments (Raymond Feng)\n\n * Improve the ACL matching algorithm (Raymond Feng)\n\n\n2014-03-18, Version 1.7.1\n=========================\n\n * Add test for request pausing during authentication (Miroslav Bajtoš)\n\n * Pause the req before checking access (Raymond Feng)\n\n * Remove the generated flag as the id is set by the before hook (Raymond Feng)\n\n * Improvements to JSDoc comments (crandmck)\n\n * Fixes to JSDoc for API docs (crandmck)\n\n * Remove oauth2 models as they will be packaged in a separate module (Raymond Feng)\n\n * Update api-model.md (Rand McKinney)\n\n * Minor doc fix (Ritchie Martori)\n\n * Set the correct status code for User.login (Raymond Feng)\n\n\n2014-02-21, Version 1.7.0\n=========================\n\n * Bump version to 1.7.0 (Raymond Feng)\n\n * Update deps (Raymond Feng)\n\n * Bump version and update deps (Raymond Feng)\n\n * Rewrite test for clear handler cache. (Guilherme Cirne)\n\n * Allows options to be passed to strong-remoting (Raymond Feng)\n\n * Remove coercion from port check (Ritchie Martori)\n\n * The simplest possible solution for clearing the handler cache when registering a model. (Guilherme Cirne)\n\n * Remove outdated test readme (Ritchie Martori)\n\n * Remove unnecessary lines (Alberto Leal)\n\n * Update the license text (Raymond Feng)\n\n * Make sure User/AccessToken relations are set up by default (Raymond Feng)\n\n * Remove unused karma packages (Ritchie Martori)\n\n * Add karma for running browser tests (Ritchie Martori)\n\n * Dual license: MIT + StrongLoop (Raymond Feng)\n\n\n2014-02-12, Version 1.6.2\n=========================\n\n * Bump version and update deps (Raymond Feng)\n\n * Documentation (generated) fix (Aurelien Chivot)\n\n * Use hex encoding for application ids/keys (Raymond Feng)\n\n * Add app.isAuthEnabled. (Miroslav Bajtoš)\n\n * Make app.models unique per app instance (Miroslav Bajtoš)\n\n * Fix incorrect usage of `app` in app.test.js (Miroslav Bajtoš)\n\n * Make sure the configured ACL submodel is used (Raymond Feng)\n\n\n2014-01-30, Version 1.6.1\n=========================\n\n * Add `include=user` param to `User.login` (Miroslav Bajtoš)\n\n * Describe `access_token` param of `User.logout` (Miroslav Bajtoš)\n\n * Remove the generated flag for access token id (Raymond Feng)\n\n * Remove message prefix as debug will print it (Raymond Feng)\n\n * Add debug information for user.login (Raymond Feng)\n\n\n2014-01-27, Version 1.6.0\n=========================\n\n * Update dependencies (Miroslav Bajtoš)\n\n * Add loopback.compat to simplify upgrade to 1.6 (Miroslav Bajtoš)\n\n * Register exported models using singular names (Miroslav Bajtoš)\n\n * User: use User.http.path (Miroslav Bajtoš)\n\n\n2014-01-23, Version 1.5.3\n=========================\n\n * Bump version (Raymond Feng)\n\n * Add a test for autoAttach (Raymond Feng)\n\n * Fix the Role ref to RoleMapping (Raymond Feng)\n\n * Fix the Scope reference to models (Raymond Feng)\n\n * Lookup the email model (Raymond Feng)\n\n * Add lookback.getModelByType() and use it resolve model deps (Raymond Feng)\n\n * Fix user test race condition (Ritchie Martori)\n\n * Fix race condition where MyEmail model was not attached to the correct dataSource in tests (Ritchie Martori)\n\n * Fix the method args (Raymond Feng)\n\n * Fix the typo for the method name (Raymond Feng)\n\n * Small change to text webhook. (Rand McKinney)\n\n * Minor wording change for testing purposes. (Rand McKinney)\n\n * Fix capitalization and punctuation. (Rand McKinney)\n\n * Minor wording cleanup. (Rand McKinney)\n\n * Prevent autoAttach from overriding existing data source (Raymond Feng)\n\n\n2014-01-17, Version 1.5.2\n=========================\n\n * Bump version (Raymond Feng)\n\n * Clean up loopback.js doc and add it to docs.json (Raymond Feng)\n\n * Fix the jsdoc for loopback.getModel() (Raymond Feng)\n\n * Make sure defaultPermission is checked (Raymond Feng)\n\n * Remove the dangling require (Raymond Feng)\n\n * Make ACL model subclassing friendly (Raymond Feng)\n\n * Fix heading levels in docs/ markdown files. (Miroslav Bajtoš)\n\n * Remove docs/rest.md (Miroslav Bajtoš)\n\n * Improve jsdox documentation of app object (Miroslav Bajtoš)\n\n\n2014-01-14, Version 1.5.1\n=========================\n\n * Bump version (Raymond Feng)\n\n * Make sure methods are called in the context of the calling class (Raymond Feng)\n\n * Start to move md to jsdoc (Ritchie Martori)\n\n\n2014-01-14, Version 1.5.0\n=========================\n\n * Replace `on` with `once` in middleware examples (Miroslav Bajtoš)\n\n * Fix incorrect transports (Ritchie Martori)\n\n * Speed up tests accessing User.password (Miroslav Bajtoš)\n\n * Describe loopback.ValidationError in API docs. (Miroslav Bajtoš)\n\n * Implement app.installMiddleware (Miroslav Bajtoš)\n\n * Implement `app.listen` (Miroslav Bajtoš)\n\n * Provide sane default for email connector transports (Ritchie Martori)\n\n * Add an empty transportsIndex to the mail connector by default (Ritchie Martori)\n\n * Add missing assert in user model (Ritchie Martori)\n\n * docs: document remote method `description` (Miroslav Bajtoš)\n\n\n2014-01-07, Version 1.4.2\n=========================\n\n * Bump version (Raymond Feng)\n\n * Add app.restApiRoot setting (Miroslav Bajtoš)\n\n * Fix links so they work on apidocs site. (Rand McKinney)\n\n * Add ValidationError to loopback exports. (Miroslav Bajtoš)\n\n * Add API docs to README (Ritchie Martori)\n\n * Fixed some broken links and added ACL example in createModel() (Rand McKinney)\n\n\n2013-12-20, Version 1.4.1\n=========================\n\n * Explicitly depend on juggler@1.2.11 (Ritchie Martori)\n\n * Add e2e tests for relations (Ritchie Martori)\n\n * Fix destroyAll reference (Ritchie Martori)\n\n * Add reference documentation using sdocs (Ritchie)\n\n * Update README for application model (Raymond Feng)\n\n\n2013-12-18, Version 1.4.0\n=========================\n\n * app.boot() now loads the \"boot\" directory (Ritchie Martori)\n\n * Clean up the test case (Raymond Feng)\n\n * Remove the default values for gateway/port (Raymond Feng)\n\n * Reformat the code using 2 space identation (Raymond Feng)\n\n * Allow cert/key data to be shared by push/feedback (Raymond Feng)\n\n * fixup - Include accessToken in user logout tests (Ritchie Martori)\n\n * Logout now automatically pulls the accessToken from the request (Ritchie Martori)\n\n * Fix tests depending on old behavior of default User ACLs (Ritchie Martori)\n\n * Add default user ACLs (Ritchie Martori)\n\n * Define schema for GCM push-notification settings (Miroslav Bajtoš)\n\n * Improve debug statements for access control (Ritchie Martori)\n\n\n2013-12-13, Version 1.3.4\n=========================\n\n * Dont attempt access checking on models without a check access method (Ritchie Martori)\n\n * App config settings are now available from app.get() (Ritchie Martori)\n\n * Fix user not allowed to delete itself if user (Ritchie Martori)\n\n * Only look at cookies if they are available (Ritchie Martori)\n\n * Remove the empty comment and set default token (Raymond Feng)\n\n * Refactor to the code use wrapper classes (Raymond Feng)\n\n * Enhance getRoles() to support smart roles (Raymond Feng)\n\n * Fix the algorithm for Role.isInRole and ACL.checkAccess (Raymond Feng)\n\n * Various ACL fixes (Ritchie Martori)\n\n * Add user default ACLs (Ritchie Martori)\n\n * Allow requests without auth tokens (Ritchie Martori)\n\n * Fix base class not being actual base class (Ritchie Martori)\n\n * Fix the ACL resolution against rules by matching score (Raymond Feng)\n\n * Add access type checking (Ritchie Martori)\n\n * Add Model.requireToken for disabling token requirement (Ritchie Martori)\n\n * Add Model.requireToken, default swagger to false (Ritchie Martori)\n\n * Add password reset (Ritchie Martori)\n\n\n2013-12-06, Version 1.3.3\n=========================\n\n * Bump version (Raymond Feng)\n\n\n2013-12-06, Version show\n========================\n\n * Bump version (Raymond Feng)\n\n * Make loopback-datasource-juggler a peer dep (Raymond Feng)\n\n * Add blank line before list so it lays out properly. (Rand McKinney)\n\n * Fix list format and minor wording fix. (Rand McKinney)\n\n * Small fix to link text (Rand McKinney)\n\n * Minor formatting and wording fixes. (Rand McKinney)\n\n * docs: describe http mapping of arguments (Miroslav Bajtos)\n\n * SLA-725 support PORT and HOST environment for PaaS support (Ritchie)\n\n\n2013-12-04, Version 1.3.1\n=========================\n\n * Fix the test assertion as the error message is changed. (Raymond Feng)\n\n * Bump version (Raymond Feng)\n\n * Remove superfluous head1 (Rand McKinney)\n\n * Fixed some list formatting issues. (Rand McKinney)\n\n * Fix list formats to play well with wiki markdown macro. (Rand McKinney)\n\n * Minor reformatting. (Rand McKinney)\n\n * Sadly, HTML table format is unusable in documentation wiki.  Revert to lame md format. (Rand McKinney)\n\n * Reformat table for /find operation arguments (Rand McKinney)\n\n * Deleted extra space that foiled bold formatting (Rand McKinney)\n\n * Changed h3's to bold text to avoid generating items in TOC (Rand McKinney)\n\n * Fixed erroneous heading level (Rand McKinney)\n\n * Use loopback.AccessToken as default (Ritchie Martori)\n\n * Fix missing assert (Ritchie Martori)\n\n * Minor formatting fixes to make it play well in wiki (Rand McKinney)\n\n * Initial auth implementation (Ritchie Martori)\n\n * added Google Analytics to README.md to test tracking (altsang)\n\n * Delete types.md (Rand McKinney)\n\n * Delete resources.md (Rand McKinney)\n\n * Delete quickstart.md (Rand McKinney)\n\n * Delete js.md (Rand McKinney)\n\n * Delete java.md (Rand McKinney)\n\n * Delete ios.md (Rand McKinney)\n\n * Delete gettingstarted.md (Rand McKinney)\n\n * Delete concepts.md (Rand McKinney)\n\n * Delete cli.md (Rand McKinney)\n\n * Delete bundled-models.md (Rand McKinney)\n\n * Delete apiexplorer.md (Rand McKinney)\n\n * Delete intro.md (Rand McKinney)\n\n * Add .jshintignore (Miroslav Bajtos)\n\n * Add test for findById returning 404 (Miroslav Bajtos)\n\n * Fix minor autoWiring bugs (Ritchie Martori)\n\n * Add unauthenticated role (Raymond Feng)\n\n * Add checkAccess for subject and token (Raymond Feng)\n\n * Start to support smart roles such as owner (Raymond Feng)\n\n * Add jshint configuration. (Miroslav Bajtos)\n\n * Update rest.md (Rand McKinney)\n\n * Update api.md (Rand McKinney)\n\n * Add status middleware (Ritchie Martori)\n\n * Auto attach all models created (Ritchie Martori)\n\n * Update docs.json (Rand McKinney)\n\n * Add loopback.urlNotFound() middleware. (Miroslav Bajtos)\n\n * Remove .attachTo() from tests (Ritchie Martori)\n\n * Create api-model-remote.md (Rand McKinney)\n\n * Create api-model.md (Rand McKinney)\n\n * Create api-geopoint.md (Rand McKinney)\n\n * Create api-datasource.md (Rand McKinney)\n\n * Create api-app.md (Rand McKinney)\n\n * Debugging odd defineFK behavior (Ritchie Martori)\n\n * Update the doc link (Raymond Feng)\n\n * Initial auto wiring for model dataSources (Ritchie Martori)\n\n * Add public flag checking (Ritchie Martori)\n\n\n2013-11-18, Version 1.3.0\n=========================\n\n * Upgrade nodemailer (Ritchie Martori)\n\n * Bump minor version (Ritchie Martori)\n\n * Add LoopBack forum link (Raymond Feng)\n\n * Remove blanket (Raymond Feng)\n\n * Switch to modelBuilder (Raymond Feng)\n\n * Allow ACLs for methods/relations (Raymond Feng)\n\n * Allows LDL level ACLs (Raymond Feng)\n\n * Update dependencies (Raymond Feng)\n\n * Fix the permission resolution (Raymond Feng)\n\n * Simplify check permission (Raymond Feng)\n\n * Fix the permission check (Raymond Feng)\n\n * Add oauth2 related models (Raymond Feng)\n\n * Add a stub to register role resolvers (Raymond Feng)\n\n * Add tests for isInRole and getRoles (Raymond Feng)\n\n * Add constants and more tests (Raymond Feng)\n\n * Define the models/relations for ACL (Raymond Feng)\n\n * Start to build the ACL models (Raymond Feng)\n\n * Update acl/role models (Raymond Feng)\n\n * Update ACL model (Raymond Feng)\n\n * Update AccessToken and User relationship (Ritchie Martori)\n\n * Added AccessToken created property (Ritchie Martori)\n\n * Update session / token documentation (Ritchie Martori)\n\n * Add loopback.token() middleware (Ritchie Martori)\n\n * Rename Session => AccessToken (Ritchie)\n\n * Bump verison (Ritchie Martori)\n\n * Fix bundle model name casing (Ritchie Martori)\n\n * Remove old node versions from travis (Ritchie Martori)\n\n * Add explicit Strong-remoting dep version (Ritchie Martori)\n\n * Add travis (Ritchie Martori)\n\n * Bump version (Ritchie Martori)\n\n * Update \"hasMany\" example (Ritchie Martori)\n\n * Code review fixes based on feedback from https://github.com/strongloop/loopback/pull/57 (Ritchie Martori)\n\n * Automatically convert strings to connectors if they are LoopBack connectors (Ritchie Martori)\n\n * Update api.md (Rand McKinney)\n\n * Update docs.json (Rand McKinney)\n\n * Create types.md (Rand McKinney)\n\n * Create bundled-models.md (Rand McKinney)\n\n * Update java.md (Rand McKinney)\n\n * Add app.dataSource() method (Ritchie)\n\n * Add app.boot() (Ritchie Martori)\n\n * README updates (Ritchie Martori)\n\n * Remove the proxy as it is now handled by the juggler (Raymond Feng)\n\n * Add MySQL connector (Raymond Feng)\n\n * Add belongsTo and hasAndBelongsToMany (Raymond Feng)\n\n * Clean up the model (Raymond Feng)\n\n * Added link to Doxygen API docs. (Rand McKinney)\n\n * Refactor email model into mail connector (Ritchie Martori)\n\n * Update Application model for the push notification (Raymond Feng)\n\n * Fix missing assert module (Ritchie Martori)\n\n * Fix the test as DAO now ignores undefined value for query (Raymond Feng)\n\n * Simplified LB architecture diagram (crandmck)\n\n * reorg and rewriting of first part of LoopBack guide, with new diagram (crandmck)\n\n * Fix the id and property access (Raymond Feng)\n\n * Update remote method example (Ritchie)\n\n * Update intro.md (Rand McKinney)\n\n * Update rest.md (Rand McKinney)\n\n * more cleanup (altsang)\n\n * remove >>>>>> from bad merge (altsang)\n\n * merged in Schoons' changes to mobile clients section (altsang)\n\n * revised per Ritchie's comments (altsang)\n\n * Revise Mobile Clients copy. (Michael Schoonmaker)\n\n * added Matt S' section on Mobile Clients (altsang)\n\n * filled out Big Picture (altsang)\n\n * revised shell -> node.js api (altsang)\n\n * Fix the preposition (Raymond Feng)\n\n * One more fix based on the comment (Raymond Feng)\n\n * Drop sure (Raymond Feng)\n\n * Add the missing article (Raymond Feng)\n\n * Add section for api explorer to docs (Raymond Feng)\n\n * added apiexplorer placeholder and big picture (altsang)\n\n * removed 'the' before StrongLoop Suite (altsang)\n\n * Remove redundant version in docs and testing docs webhook (Ritchie Martori)\n\n * removed version text (altsang)\n\n * Add keywords to package.json (Raymond Feng)\n\n * Add repo (Raymond Feng)\n\n * Finalize package.json for sls-1.0.0 (Raymond Feng)\n\n * Update docs for api->project rename. (Michael Schoonmaker)\n\n * Use a pure JS bcrypt (Ritchie)\n\n * Added little boxes to Getting Started. (Michael Schoonmaker)\n\n * Update assets mapping (Raymond Feng)\n\n * Update concepts doc with a new diagram (Raymond Feng)\n\n * Simplify readme (Ritchie Martori)\n\n * Add placeholders for client apis (Ritchie Martori)\n\n * Add command line docs (Ritchie Martori)\n\n * Add getting started link (Ritchie Martori)\n\n * Update the Quick Start (Ritchie Martori)\n\n * Fix package.json to remove duplicate mocha deps (Raymond Feng)\n\n * Tidy up package.json for LoopBack 1.0.0 (Raymond Feng)\n\n * Update model docs further. (Michael Schoonmaker)\n\n * Update license (Raymond Feng)\n\n * Updated model docs (Ritchie Martori)\n\n * Concepts overhaul in progress (Ritchie Martori)\n\n * Update the rest doc with more samples, fix the curl encoding (Raymond Feng)\n\n * Remove the todos example and fix doc example (Ritchie Martori)\n\n * doc/concepts: fixed link to strong-remoting docs (Miroslav Bajtos)\n\n * Update the internal prefix (Raymond Feng)\n\n * Update findOne (Raymond Feng)\n\n * Update REST doc based on the PR feedback (Raymond Feng)\n\n * Update REST doc (Raymond Feng)\n\n * Update the docs to fix into width of 80 (Raymond Feng)\n\n * intro edits and TOC adjustments (Al Tsang)\n\n * Fix the test case (Raymond Feng)\n\n\n2013-08-27, Version 0.2.1\n=========================\n\n * Doc edits (Ritchie Martori)\n\n * Update concepts (Raymond Feng)\n\n * Add more description about the filter arg for find() (Raymond Feng)\n\n * Update the concepts.md and link to related guides (Raymond Feng)\n\n * Update rest.md (Raymond Feng)\n\n * Add quickstart (Ritchie Martori)\n\n * Start to add rest.md (Raymond Feng)\n\n * adjusting concept headers, cleaning up intro, more instructions on getting started (Al Tsang)\n\n * Use findById to look up the instance by id (Raymond Feng)\n\n * Update the list of shared methods (Raymond Feng)\n\n * Make sure User.setup calls Model.setup to support shared ctor (Raymond Feng)\n\n * Add LICENSE (Raymond Feng)\n\n * Added code coverage blanket.js (cgole)\n\n * took google docs TOC and put into sdocs (Al Tsang)\n\n * Added placeholder docs (Ritchie Martori)\n\n * Use strong-task-emitter (Raymond Feng)\n\n * Rename 'loopback-data' to 'loopback-datasource-juggler' (Raymond Feng)\n\n * Fix login query (Ritchie Martori)\n\n * Implement required and update invlaid id schemas (Ritchie Martori)\n\n * Remove auth middleware and passport until adding in acl and strategies (Ritchie Martori)\n\n * Clean up log out methods (Ritchie Martori)\n\n * Swagger integration (Ritchie)\n\n * Fix hasMany / relational methods. Update docs. (Ritchie)\n\n * Add root true to remote methods (Ritchie)\n\n * Fix bad connector path (Ritchie)\n\n * Fix the test case (Raymond Feng)\n\n * Rename adapter to connector (Raymond Feng)\n\n * Add more docs and apis to application model (Raymond Feng)\n\n * Add a deleteById test (Raymond Feng)\n\n * Rename sl-remoting to strong-remoting (Ritchie Martori)\n\n * Add more functions and tests for Application model (Raymond Feng)\n\n * More readme cleanup (Ritchie)\n\n * README cleanup (Ritchie)\n\n * Fix renaming manually (Ritchie)\n\n * Manually merge application (Ritchie)\n\n * Manually merge rest adapter (Ritchie)\n\n * Add fields documentation (Ritchie)\n\n * More cleanup for test/README.md (Ritchie Martori)\n\n * Cleanup test markdown (Ritchie Martori)\n\n * Add memory docs and test (Ritchie Martori)\n\n * Remove remote option object (Ritchie Martori)\n\n * Rename jugglingdb to loopback-data (Raymond Feng)\n\n * Add renamed files (Raymond Feng)\n\n * rename asteroid to loopback (Raymond Feng)\n\n * Fix model remoting issue. (Ritchie Martori)\n\n * Fix inheritance bug (Ritchie Martori)\n\n * Remove updateAttribute as remote method (Ritchie Martori)\n\n * Fix login bug. (Ritchie Martori)\n\n * Added bcrypt for password hashing (Ritchie Martori)\n\n * Refactor Model into class. Make createModel() just sugar. (Ritchie Martori)\n\n * Remove data argument name from user tests (Ritchie Martori)\n\n * Validate uniqueness and format of User email. (Ritchie Martori)\n\n * Add user.logout() sugar method and update logout docs (Ritchie Martori)\n\n * Create 64 byte session ids (Ritchie Martori)\n\n * Tests README (Ritchie Martori)\n\n * Experiment application model (Raymond Feng)\n\n * Updated generated test docs (Ritchie Martori)\n\n * Update docs and add asteroid.memory() sugar api (Ritchie Martori)\n\n * Add exports to models (Raymond Feng)\n\n * Updating models (Raymond Feng)\n\n * Add basic email verification (Ritchie Martori)\n\n * Initial users (Ritchie Martori)\n\n * Add default user properties (Ritchie Martori)\n\n * Add initial User model (Ritchie Martori)\n\n * Remove app.modelBuilder() (Ritchie Martori)\n\n * Add more user model docs (Ritchie Martori)\n\n * Update README.md (cgole)\n\n * Fix type in docs (Ritchie Martori)\n\n * Add normalized properties to Models (Ritchie Martori)\n\n * Add schema skeletons for built-in models (Raymond Feng)\n\n * Fix service() & services() (Raymond Feng)\n\n * Add service method (Ritchie Martori)\n\n * Add more info to the models (Raymond Feng)\n\n * Add more information to the logical models (Raymond Feng)\n\n * Only build a sl remoting handler when a model is added to the app. (Ritchie Martori)\n\n * Add user model docs. (Ritchie Martori)\n\n * Bump version (Ritchie Martori)\n\n * Add geo point tests (Ritchie Martori)\n\n * Rename long to lng (Ritchie Martori)\n\n * Add geo point (Ritchie Martori)\n\n * model.find => model.findById, model.all => model.find (Ritchie Martori)\n\n\n2013-06-24, Version 0.8.0\n=========================\n\n * First release!\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "# Lines starting with '#' are comments.\n# Each line is a file pattern followed by one or more owners,\n# the last matching pattern has the most precendence.\n\n# Current maintainers\n\n* @bajtos @fabien @clarkorz @ebarault @zbarbuto @nitro404\n\n# Alumni\n\n_ @lehni\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "### Contributing ###\n\nThank you for your interest in `loopback`, an open source project\nadministered by StrongLoop.\n\nContributing to `loopback` is easy. In a few simple steps:\n\n  * Ensure that your effort is aligned with the project's roadmap by\n    talking to the maintainers, especially if you are going to spend a\n    lot of time on it.\n\n  * Make something better or fix a bug.\n\n  * Adhere to code style outlined in the [Google C++ Style Guide][] and\n    [Google Javascript Style Guide][].\n\n  * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback)\n\n  * Submit a pull request through Github.\n\n\n### Contributor License Agreement ###\n\n```\n  Individual Contributor License Agreement\n\n  By signing this Individual Contributor License Agreement\n  (\"Agreement\"), and making a Contribution (as defined below) to\n  StrongLoop, Inc. (\"StrongLoop\"), You (as defined below) accept and\n  agree to the following terms and conditions for Your present and\n  future Contributions submitted to StrongLoop. Except for the license\n  granted in this Agreement to StrongLoop and recipients of software\n  distributed by StrongLoop, You reserve all right, title, and interest\n  in and to Your Contributions.\n\n  1. Definitions\n\n     \"You\" or \"Your\" shall mean the copyright owner or the individual\n     authorized by the copyright owner that is entering into this\n     Agreement with StrongLoop.\n\n     \"Contribution\" shall mean any original work of authorship,\n     including any modifications or additions to an existing work, that\n     is intentionally submitted by You to StrongLoop for inclusion in,\n     or documentation of, any of the products owned or managed by\n     StrongLoop (\"Work\"). For purposes of this definition, \"submitted\"\n     means any form of electronic, verbal, or written communication\n     sent to StrongLoop or its representatives, including but not\n     limited to communication or electronic mailing lists, source code\n     control systems, and issue tracking systems that are managed by,\n     or on behalf of, StrongLoop for the purpose of discussing and\n     improving the Work, but excluding communication that is\n     conspicuously marked or otherwise designated in writing by You as\n     \"Not a Contribution.\"\n\n  2. You Grant a Copyright License to StrongLoop\n\n     Subject to the terms and conditions of this Agreement, You hereby\n     grant to StrongLoop and recipients of software distributed by\n     StrongLoop, a perpetual, worldwide, non-exclusive, no-charge,\n     royalty-free, irrevocable copyright license to reproduce, prepare\n     derivative works of, publicly display, publicly perform,\n     sublicense, and distribute Your Contributions and such derivative\n     works under any license and without any restrictions.\n\n  3. You Grant a Patent License to StrongLoop\n\n     Subject to the terms and conditions of this Agreement, You hereby\n     grant to StrongLoop and to recipients of software distributed by\n     StrongLoop a perpetual, worldwide, non-exclusive, no-charge,\n     royalty-free, irrevocable (except as stated in this Section)\n     patent license to make, have made, use, offer to sell, sell,\n     import, and otherwise transfer the Work under any license and\n     without any restrictions. The patent license You grant to\n     StrongLoop under this Section applies only to those patent claims\n     licensable by You that are necessarily infringed by Your\n     Contributions(s) alone or by combination of Your Contributions(s)\n     with the Work to which such Contribution(s) was submitted. If any\n     entity institutes a patent litigation against You or any other\n     entity (including a cross-claim or counterclaim in a lawsuit)\n     alleging that Your Contribution, or the Work to which You have\n     contributed, constitutes direct or contributory patent\n     infringement, any patent licenses granted to that entity under\n     this Agreement for that Contribution or Work shall terminate as\n     of the date such litigation is filed.\n\n  4. You Have the Right to Grant Licenses to StrongLoop\n\n     You represent that You are legally entitled to grant the licenses\n     in this Agreement.\n\n     If Your employer(s) has rights to intellectual property that You\n     create, You represent that You have received permission to make\n     the Contributions on behalf of that employer, that Your employer\n     has waived such rights for Your Contributions, or that Your\n     employer has executed a separate Corporate Contributor License\n     Agreement with StrongLoop.\n\n  5. The Contributions Are Your Original Work\n\n     You represent that each of Your Contributions are Your original\n     works of authorship (see Section 8 (Submissions on Behalf of\n     Others) for submission on behalf of others). You represent that to\n     Your knowledge, no other person claims, or has the right to claim,\n     any right in any intellectual property right related to Your\n     Contributions.\n\n     You also represent that You are not legally obligated, whether by\n     entering into an agreement or otherwise, in any way that conflicts\n     with the terms of this Agreement.\n\n     You represent that Your Contribution submissions include complete\n     details of any third-party license or other restriction (including,\n     but not limited to, related patents and trademarks) of which You\n     are personally aware and which are associated with any part of\n     Your Contributions.\n\n  6. You Don't Have an Obligation to Provide Support for Your Contributions\n\n     You are not expected to provide support for Your Contributions,\n     except to the extent You desire to provide support. You may provide\n     support for free, for a fee, or not at all.\n\n  6. No Warranties or Conditions\n\n     StrongLoop acknowledges that unless required by applicable law or\n     agreed to in writing, You provide Your Contributions on an \"AS IS\"\n     BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER\n     EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES\n     OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR\n     FITNESS FOR A PARTICULAR PURPOSE.\n\n  7. Submission on Behalf of Others\n\n     If You wish to submit work that is not Your original creation, You\n     may submit it to StrongLoop separately from any Contribution,\n     identifying the complete details of its source and of any license\n     or other restriction (including, but not limited to, related\n     patents, trademarks, and license agreements) of which You are\n     personally aware, and conspicuously marking the work as\n     \"Submitted on Behalf of a Third-Party: [named here]\".\n\n  8. Agree to Notify of Change of Circumstances\n\n     You agree to notify StrongLoop of any facts or circumstances of\n     which You become aware that would make these representations\n     inaccurate in any respect. Email us at callback@strongloop.com.\n```\n\n[Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html\n[Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nmodule.exports = function(grunt) {\n  // Do not report warnings from unit-tests exercising deprecated paths\n  process.env.NO_DEPRECATION = 'loopback';\n\n  grunt.loadNpmTasks('grunt-mocha-test');\n\n  // Project configuration.\n  grunt.initConfig({\n    // Metadata.\n    pkg: grunt.file.readJSON('package.json'),\n    banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +\n      '<%= grunt.template.today(\"yyyy-mm-dd\") %>\\n' +\n      '<%= pkg.homepage ? \"* \" + pkg.homepage + \"\\\\n\" : \"\" %>' +\n      '* Copyright (c) <%= grunt.template.today(\"yyyy\") %> <%= pkg.author.name %>;' +\n      ' Licensed <%= _.pluck(pkg.licenses, \"type\").join(\", \") %> */\\n',\n    // Task configuration.\n    uglify: {\n      options: {\n        banner: '<%= banner %>',\n      },\n      dist: {\n        files: {\n          'dist/loopback.min.js': ['dist/loopback.js'],\n        },\n      },\n    },\n    eslint: {\n      gruntfile: {\n        src: 'Gruntfile.js',\n      },\n      lib: {\n        src: ['lib/**/*.js'],\n      },\n      common: {\n        src: ['common/**/*.js'],\n      },\n      server: {\n        src: ['server/**/*.js'],\n      },\n      test: {\n        src: ['test/**/*.js'],\n      },\n    },\n    watch: {\n      gruntfile: {\n        files: '<%= eslint.gruntfile.src %>',\n        tasks: ['eslint:gruntfile'],\n      },\n      browser: {\n        files: ['<%= eslint.browser.src %>'],\n        tasks: ['eslint:browser'],\n      },\n      common: {\n        files: ['<%= eslint.common.src %>'],\n        tasks: ['eslint:common'],\n      },\n      lib: {\n        files: ['<%= eslint.lib.src %>'],\n        tasks: ['eslint:lib'],\n      },\n      server: {\n        files: ['<%= eslint.server.src %>'],\n        tasks: ['eslint:server'],\n      },\n      test: {\n        files: ['<%= eslint.test.src %>'],\n        tasks: ['eslint:test'],\n      },\n    },\n    browserify: {\n      dist: {\n        files: {\n          'dist/loopback.js': ['index.js'],\n        },\n        options: {\n          ignore: ['nodemailer', 'passport', 'bcrypt'],\n          standalone: 'loopback',\n        },\n      },\n    },\n    mochaTest: {\n      'unit': {\n        src: 'test/*.js',\n        options: {\n          reporter: 'dot',\n          require: require.resolve('./test/helpers/use-english.js'),\n        },\n      },\n      'unit-xml': {\n        src: 'test/*.js',\n        options: {\n          reporter: 'xunit',\n          captureFile: 'xunit.xml',\n        },\n      },\n    },\n    karma: {\n      'unit-once': {\n        configFile: 'test/karma.conf.js',\n        browsers: ['ChromeDocker'],\n        singleRun: true,\n        reporters: ['dots', 'junit'],\n\n        // increase the timeout for slow build slaves (e.g. Travis-ci)\n        browserNoActivityTimeout: 30000,\n\n        // CI friendly test output\n        junitReporter: {\n          outputFile: 'karma-xunit.xml',\n        },\n\n        browserify: {\n          // Disable sourcemaps to prevent\n          // Fatal error: Maximum call stack size exceeded\n          debug: false,\n          // Disable watcher, grunt will exit after the first run\n          watch: false,\n        },\n      },\n      unit: {\n        configFile: 'test/karma.conf.js',\n      },\n      e2e: {\n        options: {\n          // base path, that will be used to resolve files and exclude\n          basePath: '',\n\n          // frameworks to use\n          frameworks: ['mocha', 'browserify'],\n\n          // list of files / patterns to load in the browser\n          files: [\n            'test/e2e/remote-connector.e2e.js',\n            'test/e2e/replication.e2e.js',\n          ],\n\n          // list of files to exclude\n          exclude: [\n\n          ],\n\n          // test results reporter to use\n          // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'\n          reporters: ['dots'],\n\n          // web server port\n          port: 9876,\n\n          // cli runner port\n          runnerPort: 9100,\n\n          // enable / disable colors in the output (reporters and logs)\n          colors: true,\n\n          // level of logging\n          // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n          logLevel: 'warn',\n\n          // enable / disable watching file and executing tests whenever any file changes\n          autoWatch: true,\n\n          // Start these browsers, currently available:\n          // - Chrome\n          // - ChromeCanary\n          // - Firefox\n          // - Opera\n          // - Safari (only Mac)\n          // - PhantomJS\n          // - IE (only Windows)\n          browsers: [\n            'Chrome',\n          ],\n\n          // If browser does not capture in given timeout [ms], kill it\n          captureTimeout: 60000,\n\n          // Continuous Integration mode\n          // if true, it capture browsers, run tests and exit\n          singleRun: false,\n\n          // Browserify config (all optional)\n          browserify: {\n            // extensions: ['.coffee'],\n            ignore: [\n              'nodemailer',\n              'passport',\n              'passport-local',\n              'superagent',\n              'supertest',\n              'bcrypt',\n            ],\n            // transform: ['coffeeify'],\n            // debug: true,\n            // noParse: ['jquery'],\n            watch: true,\n          },\n\n          // Add browserify to preprocessors\n          preprocessors: {'test/e2e/*': ['browserify']},\n        },\n      },\n    },\n\n  });\n\n  // These plugins provide necessary tasks.\n  grunt.loadNpmTasks('grunt-browserify');\n  grunt.loadNpmTasks('grunt-contrib-uglify');\n  grunt.loadNpmTasks('grunt-eslint');\n  grunt.loadNpmTasks('grunt-contrib-watch');\n  grunt.loadNpmTasks('grunt-karma');\n\n  grunt.registerTask('e2e-server', function() {\n    const done = this.async();\n    const app = require('./test/fixtures/e2e/app');\n    app.listen(0, function() {\n      process.env.PORT = this.address().port;\n      done();\n    });\n  });\n\n  grunt.registerTask('skip-karma', function() {\n    console.log(`*** SKIPPING PHANTOM-JS BASED TESTS ON ${process.platform}` +\n      ` ${process.arch} ***`);\n  });\n\n  grunt.registerTask('e2e', ['e2e-server', 'karma:e2e']);\n\n  // Default task.\n  grunt.registerTask('default', ['browserify']);\n\n  grunt.registerTask('test', [\n    'eslint',\n    process.env.JENKINS_HOME ? 'mochaTest:unit-xml' : 'mochaTest:unit',\n    process.env.JENKINS_HOME && (/^win/.test(process.platform) ||\n      /^s390x/.test(process.arch) || /^ppc64/.test(process.arch)) ?\n      'skip-karma' : 'karma:unit-once',\n  ]);\n\n  // alias for sl-ci-run and `npm test`\n  grunt.registerTask('mocha-and-karma', ['test']);\n};\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) IBM Corp. 2013,2018. All Rights Reserved.\nNode module: loopback\nThis project is licensed under the MIT License, full text below.\n\n--------\n\nMIT license\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# LoopBack\n\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/strongloop/loopback?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![Module LTS Adopted'](https://img.shields.io/badge/Module%20LTS-Adopted-brightgreen.svg?style=flat)](http://github.com/CloudNativeJS/ModuleLTS)\n[![IBM Support](https://img.shields.io/badge/IBM%20Support-Frameworks-brightgreen.svg?style=flat)](http://ibm.biz/node-support)\n\n**⚠️ LoopBack 3 has reached end of life. We are no longer accepting pull requests or providing \nsupport for community users. The only exception is fixes for critical bugs and security \nvulnerabilities provided as part of support for IBM API Connect customers.\nWe urge all LoopBack 3 users to migrate their applications to LoopBack 4 as soon as possible. \nLearn more about\n<a href=\"https://loopback.io/doc/en/contrib/Long-term-support.html\">LoopBack's long term support policy.</a>\nwill be provided or accepted. (See\n[Module Long Term Support Policy](#module-long-term-support-policy) below.)**\n\nWe urge all LoopBack 3 users to migrate their applications to LoopBack 4 as\nsoon as possible. Refer to our\n[Migration Guide](https://loopback.io/doc/en/lb4/migration-overview.html)\nfor more information on how to upgrade.\n\n## Overview\n\nLoopBack is a highly-extensible, open-source Node.js framework that enables you to:\n\n  * Create dynamic end-to-end REST APIs with little or no coding.\n  * Access data from Oracle, MySQL, PostgreSQL, MS SQL Server, MongoDB, SOAP and other REST APIs.\n  * Incorporate model relationships and access controls for complex APIs.\n  * Use built-in push, geolocation, and file services for mobile apps.\n  * Easily create client apps using Android, iOS, and JavaScript SDKs.\n  * Run your application on-premises or in the cloud.\n\nLoopBack consists of:\n\n  * A library of Node.js modules.\n  * [Yeoman](http://yeoman.io/) generators for scaffolding applications.\n  * Client SDKs for iOS, Android, and web clients.\n\nLoopBack tools include:\n  * Command-line tool `loopback-cli` to create applications, models, data sources, and so on.\n\nFor more details, see [https://loopback.io/](https://loopback.io/).\n\n\n## Module Long Term Support Policy\n\nLoopBack 3.x has reached End-of-Life.\n\nThis module adopts the [Module Long Term Support (LTS)](http://github.com/CloudNativeJS/ModuleLTS) policy, with the following End Of Life (EOL) dates:\n\n| Version    | Status          | Published | EOL                  |\n| ---------- | --------------- | --------- | -------------------- |\n| LoopBack 4 | Current         | Oct 2018  | Apr 2023 _(minimum)_ |\n| LoopBack 3 | End-of-Life     | Dec 2016  | Dec 2020             |\n| LoopBack 2 | End-of-Life     | Jul 2014  | Apr 2019             |\n\nLearn more about our LTS plan in [docs](https://loopback.io/doc/en/contrib/Long-term-support.html).\n\n## LoopBack modules\n\nThe LoopBack framework is a set of Node.js modules that you can use independently or together.\n\n![LoopBack modules](https://github.com/strongloop/loopback/raw/master/docs/assets/lb-modules.png \"LoopBack modules\")\n\n### Core\n* [loopback](https://github.com/strongloop/loopback)\n* [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler)\n* [strong-remoting](https://github.com/strongloop/strong-remoting)\n\n### Connectors\n* [loopback-connector-mongodb](https://github.com/strongloop/loopback-connector-mongodb)\n* [loopback-connector-mysql](https://github.com/strongloop/loopback-connector-mysql)\n* [loopback-connector-postgresql](https://github.com/strongloop/loopback-connector-postgresql)\n* [loopback-connector-rest](https://github.com/strongloop/loopback-connector-rest)\n\n### Enterprise Connectors\n* [loopback-connector-oracle](https://github.com/strongloop/loopback-connector-oracle)\n* [loopback-connector-mssql](https://github.com/strongloop/loopback-connector-mssql)\n* [loopback-connector-soap](https://github.com/strongloop/loopback-connector-soap)\n* [loopback-connector-atg](https://github.com/strongloop/loopback-connector-atg)\n\n### Community Connectors\n\nThe LoopBack community has created and supports a number of additional connectors.  See [Community connectors](https://loopback.io/doc/en/lb2/Community-connectors.html) for details.\n\n### Components\n* [loopback-component-push](https://github.com/strongloop/loopback-component-push)\n* [loopback-component-storage](https://github.com/strongloop/loopback-component-storage)\n* [loopback-component-passport](https://github.com/strongloop/loopback-component-passport)\n\n### Client SDKs\n* [loopback-sdk-ios](https://github.com/strongloop/loopback-sdk-ios)\n* [loopback-sdk-android](https://github.com/strongloop/loopback-sdk-android)\n* [loopback-sdk-angular](https://github.com/strongloop/loopback-sdk-angular)\n  * [loopback-sdk-angular-cli](https://github.com/strongloop/loopback-sdk-angular-cli)\n  * [grunt-loopback-sdk-angular](https://github.com/strongloop/grunt-loopback-sdk-angular)\n\n### Tools\n* [loopback-explorer](https://github.com/strongloop/loopback-explorer)\n* [loopback-workspace](https://github.com/strongloop/loopback-workspace)\n* [generator-loopback](https://github.com/strongloop/generator-loopback)\n\n### Examples\n\nStrongLoop provides a number of example applications that illustrate various key LoopBack features. In some cases, they have accompanying step-by-step instructions (tutorials).\n\nSee [examples at loopback.io](https://loopback.io/examples/) for details.\n\n## Resources\n\n  * [Documentation](https://loopback.io/doc/).\n  * [API documentation](https://apidocs.strongloop.com/loopback).\n  * [LoopBack Announcements](https://groups.google.com/forum/#!forum/loopbackjs-announcements)\n  * [LoopBack Google Group](https://groups.google.com/forum/#!forum/loopbackjs).\n  * [GitHub issues](https://github.com/strongloop/loopback/issues).\n  * [Gitter chat](https://gitter.im/strongloop/loopback).\n\n## Contributing\n\nContributions to the LoopBack project are welcome! See [Contributing to LoopBack](https://loopback.io/doc/en/contrib/index.html) for more information.\n\n## Reporting issues\n\nOne of the easiest ways to contribute to LoopBack is to report an issue. See [Reporting issues](https://loopback.io/doc/en/contrib/Reporting-issues.html) for more information.\n\n[![Analytics](https://sl-beacon.appspot.com/UA-37775386-1/github/loopback/readme?pixel)](https://github.com/strongloop/loopback)\n"
  },
  {
    "path": "common/models/README.md",
    "content": "# Application\n\nApplication model represents the metadata for a client application that has its\nown identity and associated configuration with the LoopBack server.\n\n## Each application has the following basic properties:\n\n* id: Automatically generated id\n* name: Name of the application (required)\n* description: Description of the application (optional)\n* icon: URL of the icon\n* status: Status of the application, such as production/sandbox/disabled\n* created: Timestamp of the record being created\n* modified: Timestamp of the record being modified\n\n## An application has the following properties linking to users:\n\n* owner: The user id of the developer who registers the application\n* collaborators: A array of users ids who have permissions to work on this app\n\n## oAuth 2.0 settings\n\n* url: The application url\n* callbackUrls: An array of preregistered callback urls for oAuth 2.0\n* permissions: An array of oAuth 2.0 scopes that can be requested by the application\n\n## Security keys\n\nThe following keys are automatically generated by the application creation\nprocess. They can be reset upon request.\n\n* clientKey: Secret for mobile clients\n* javaScriptKey: Secret for JavaScript clients\n* restApiKey: Secret for REST APIs\n* windowsKey: Secret for Windows applications\n* masterKey: Secret for REST APIS. It bypasses model level permissions\n\n## Push notification settings\n\nThe application can be configured to support multiple methods of push notifications.\n\n* pushSettings\n\n\n     pushSettings: {\n        apns: {\n          certData: config.apnsCertData,\n          keyData: config.apnsKeyData,\n          production: false, // Development mode\n          pushOptions: {\n            // Extra options can go here for APN\n          },\n          feedbackOptions: {\n            batchFeedback: true,\n            interval: 300\n          }\n        },\n        gcm: {\n          serverApiKey: config.gcmServerApiKey\n        }\n      }\n\n\n## Authentication schemes\n\n* authenticationEnabled\n* anonymousAllowed\n* authenticationSchemes\n\n### Authentication scheme settings\n\n* scheme: Name of the authentication scheme, such as local, facebook, google,\ntwitter, linkedin, github\n* credential: Scheme-specific credentials\n\n## APIs for Application model\n\nIn addition to the CRUD methods, the Application model also has the following\napis:\n\n### Register a new application\n\nYou can register a new application by providing the owner user id, application\nname, and other properties in the options object.\n\n    Application.register('rfeng', 'MyApp1',\n        {description: 'My first loopback application'},\n        function (err, result) {\n            var app = result;\n        ...\n    });\n\n### Reset keys\n\nYou can reset keys for a given application by id.\n\n    Application.resetKeys(appId, function (err, result) {\n        var app = result;\n        ...\n    });\n\n### Authenticate by appId and key\n\nYou can authenticate an application by id and one of the keys. If successful,\nit calls back with the key name in the result argument. Otherwise, the\nkeyName is null.\n\n    Application.authenticate(appId, clientKey, function (err, keyName) {\n            assert.equal(keyName, 'clientKey');\n            ...\n    });\n\n\n"
  },
  {
    "path": "common/models/access-token.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Module Dependencies.\n */\n\n'use strict';\nconst g = require('../../lib/globalize');\nconst loopback = require('../../lib/loopback');\nconst assert = require('assert');\nconst uid = require('uid2');\nconst DEFAULT_TOKEN_LEN = 64;\n\n/**\n * Token based authentication and access control.\n *\n * **Default ACLs**\n *\n *  - DENY EVERYONE `*`\n *  - ALLOW EVERYONE create\n *\n * @property {String} id Generated token ID.\n * @property {Number} ttl Time to live in seconds, 2 weeks by default.\n * @property {Date} created When the token was created.\n * @property {Object} settings Extends the `Model.settings` object.\n * @property {Number} settings.accessTokenIdLength Length of the base64-encoded string access token. Default value is 64.\n * Increase the length for a more secure access token.\n *\n * @class AccessToken\n * @inherits {PersistedModel}\n */\n\nmodule.exports = function(AccessToken) {\n  /**\n   * Anonymous Token\n   *\n   * ```js\n   * assert(AccessToken.ANONYMOUS.id === '$anonymous');\n   * ```\n   */\n\n  AccessToken.ANONYMOUS = new AccessToken({id: '$anonymous'});\n\n  /**\n   * Create a cryptographically random access token id.\n   *\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {String} token\n   */\n\n  AccessToken.createAccessTokenId = function(fn) {\n    uid(this.settings.accessTokenIdLength || DEFAULT_TOKEN_LEN, function(err, guid) {\n      if (err) {\n        fn(err);\n      } else {\n        fn(null, guid);\n      }\n    });\n  };\n\n  /*!\n   * Hook to create accessToken id.\n   */\n  AccessToken.observe('before save', function(ctx, next) {\n    if (!ctx.instance || ctx.instance.id) {\n      // We are running a partial update or the instance already has an id\n      return next();\n    }\n\n    AccessToken.createAccessTokenId(function(err, id) {\n      if (err) return next(err);\n      ctx.instance.id = id;\n      next();\n    });\n  });\n\n  /**\n   * Extract the access token id from the HTTP request\n   * @param {Request} req HTTP request object\n   * @options {Object} [options] Each option array is used to add additional keys to find an `accessToken` for a `request`.\n   * @property {Array} [cookies] Array of cookie names.\n   * @property {Array} [headers] Array of header names.\n   * @property {Array} [params] Array of param names.\n   * @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request\n   * @property {Boolean} [bearerTokenBase64Encoded] Defaults to `true`. For `Bearer` token based `Authorization` headers,\n   * decode the value from `Base64`. If set to `false`, the decoding will be skipped and the token id will be the raw value\n   * parsed from the header.\n   * @return {String} The access token\n   */\n  AccessToken.getIdForRequest = function(req, options) {\n    options = options || {};\n    let params = options.params || [];\n    let headers = options.headers || [];\n    let cookies = options.cookies || [];\n    let i = 0;\n    let length, id;\n\n    // https://github.com/strongloop/loopback/issues/1326\n    if (options.searchDefaultTokenKeys !== false) {\n      params = params.concat(['access_token']);\n      headers = headers.concat(['X-Access-Token', 'authorization']);\n      cookies = cookies.concat(['access_token', 'authorization']);\n    }\n\n    for (length = params.length; i < length; i++) {\n      const param = params[i];\n      // replacement for deprecated req.param()\n      id = req.params && req.params[param] !== undefined ? req.params[param] :\n        req.body && req.body[param] !== undefined ? req.body[param] :\n          req.query && req.query[param] !== undefined ? req.query[param] :\n            undefined;\n\n      if (typeof id === 'string') {\n        return id;\n      }\n    }\n\n    for (i = 0, length = headers.length; i < length; i++) {\n      id = req.header(headers[i]);\n\n      if (typeof id === 'string') {\n        // Add support for oAuth 2.0 bearer token\n        // http://tools.ietf.org/html/rfc6750\n\n        // To prevent Error: Model::findById requires the id argument\n        // with loopback-datasource-juggler 2.56.0+\n        if (id === '') continue;\n\n        if (id.indexOf('Bearer ') === 0) {\n          id = id.substring(7);\n          if (options.bearerTokenBase64Encoded) {\n            // Decode from base64\n            const buf = new Buffer(id, 'base64');\n            id = buf.toString('utf8');\n          }\n        } else if (/^Basic /i.test(id)) {\n          id = id.substring(6);\n          id = (new Buffer(id, 'base64')).toString('utf8');\n          // The spec says the string is user:pass, so if we see both parts\n          // we will assume the longer of the two is the token, so we will\n          // extract \"a2b2c3\" from:\n          //   \"a2b2c3\"\n          //   \"a2b2c3:\"   (curl http://a2b2c3@localhost:3000/)\n          //   \"token:a2b2c3\" (curl http://token:a2b2c3@localhost:3000/)\n          //   \":a2b2c3\"\n          const parts = /^([^:]*):(.*)$/.exec(id);\n          if (parts) {\n            id = parts[2].length > parts[1].length ? parts[2] : parts[1];\n          }\n        }\n        return id;\n      }\n    }\n\n    if (req.signedCookies) {\n      for (i = 0, length = cookies.length; i < length; i++) {\n        id = req.signedCookies[cookies[i]];\n\n        if (typeof id === 'string') {\n          return id;\n        }\n      }\n    }\n    return null;\n  };\n\n  /**\n   * Resolve and validate the access token by id\n   * @param {String} id Access token\n   * @callback {Function} cb Callback function\n   * @param {Error} err Error information\n   * @param {Object} Resolved access token object\n   */\n  AccessToken.resolve = function(id, cb) {\n    this.findById(id, function(err, token) {\n      if (err) {\n        cb(err);\n      } else if (token) {\n        token.validate(function(err, isValid) {\n          if (err) {\n            cb(err);\n          } else if (isValid) {\n            cb(null, token);\n          } else {\n            const e = new Error(g.f('Invalid Access Token'));\n            e.status = e.statusCode = 401;\n            e.code = 'INVALID_TOKEN';\n            cb(e);\n          }\n        });\n      } else {\n        cb();\n      }\n    });\n  };\n\n  /**\n   * Find a token for the given `ServerRequest`.\n   *\n   * @param {ServerRequest} req\n   * @param {Object} [options] Options for finding the token\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {AccessToken} token\n   */\n  AccessToken.findForRequest = function(req, options, cb) {\n    if (cb === undefined && typeof options === 'function') {\n      cb = options;\n      options = {};\n    }\n\n    const id = this.getIdForRequest(req, options);\n\n    if (id) {\n      this.resolve(id, cb);\n    } else {\n      process.nextTick(cb);\n    }\n  };\n\n  /**\n   * Validate the token.\n   *\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {Boolean} isValid\n   */\n  AccessToken.prototype.validate = function(cb) {\n    try {\n      assert(\n        this.created && typeof this.created.getTime === 'function',\n        'token.created must be a valid Date',\n      );\n      assert(this.ttl !== 0, 'token.ttl must be not be 0');\n      assert(this.ttl, 'token.ttl must exist');\n      assert(this.ttl >= -1, 'token.ttl must be >= -1');\n\n      const AccessToken = this.constructor;\n      const userRelation = AccessToken.relations.user; // may not be set up\n      let User = userRelation && userRelation.modelTo;\n\n      // redefine user model if accessToken's principalType is available\n      if (this.principalType) {\n        User = AccessToken.registry.findModel(this.principalType);\n        if (!User) {\n          process.nextTick(function() {\n            return cb(null, false);\n          });\n        }\n      }\n\n      const now = Date.now();\n      const created = this.created.getTime();\n      const elapsedSeconds = (now - created) / 1000;\n      const secondsToLive = this.ttl;\n      const eternalTokensAllowed = !!(User && User.settings.allowEternalTokens);\n      const isEternalToken = secondsToLive === -1;\n      const isValid = isEternalToken ?\n        eternalTokensAllowed :\n        elapsedSeconds < secondsToLive;\n\n      if (isValid) {\n        process.nextTick(function() {\n          cb(null, isValid);\n        });\n      } else {\n        this.destroy(function(err) {\n          cb(err, isValid);\n        });\n      }\n    } catch (e) {\n      process.nextTick(function() {\n        cb(e);\n      });\n    }\n  };\n};\n"
  },
  {
    "path": "common/models/access-token.json",
    "content": "{\n  \"name\": \"AccessToken\",\n  \"properties\": {\n    \"id\": {\n      \"type\": \"string\",\n      \"id\": true\n    },\n    \"ttl\": {\n      \"type\": \"number\",\n      \"ttl\": true,\n      \"default\": 1209600,\n      \"description\": \"time to live in seconds (2 weeks by default)\"\n    },\n    \"scopes\": {\n      \"type\": [\"string\"],\n      \"description\": \"Array of scopes granted to this access token.\"\n    },\n    \"created\": {\n      \"type\": \"Date\",\n      \"defaultFn\": \"now\"\n    }\n  },\n  \"relations\": {\n    \"user\": {\n      \"type\": \"belongsTo\",\n      \"model\": \"User\",\n      \"foreignKey\": \"userId\"\n    }\n  },\n  \"acls\": [\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\",\n      \"permission\": \"DENY\"\n    }\n  ]\n}\n"
  },
  {
    "path": "common/models/acl.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n/*!\n Schema ACL options\n Object level permissions, for example, an album owned by a user\n Factors to be authorized against:\n * model name: Album\n * model instance properties: userId of the album, friends, shared\n * methods\n * app and/or user ids/roles\n ** loggedIn\n ** roles\n ** userId\n ** appId\n ** none\n ** everyone\n ** relations: owner/friend/granted\n Class level permissions, for example, Album\n * model name: Album\n * methods\n URL/Route level permissions\n * url pattern\n * application id\n * ip addresses\n * http headers\n Map to oAuth 2.0 scopes\n */\n\nconst g = require('../../lib/globalize');\nconst loopback = require('../../lib/loopback');\nconst utils = require('../../lib/utils');\nconst async = require('async');\nconst extend = require('util')._extend;\nconst assert = require('assert');\nconst debug = require('debug')('loopback:security:acl');\n\nconst ctx = require('../../lib/access-context');\nconst AccessContext = ctx.AccessContext;\nconst Principal = ctx.Principal;\nconst AccessRequest = ctx.AccessRequest;\n\nconst Role = loopback.Role;\nassert(Role, 'Role model must be defined before ACL model');\n\n/**\n * A Model for access control meta data.\n *\n * System grants permissions to principals (users/applications, can be grouped\n * into roles).\n *\n * Protected resource: the model data and operations\n * (model/property/method/relation/…)\n *\n * For a given principal, such as client application and/or user, is it allowed\n * to access (read/write/execute)\n * the protected resource?\n *\n * @header ACL\n * @property {String} model Name of the model.\n * @property {String} property Name of the property, method, scope, or relation.\n * @property {String} accessType Type of access being granted: one of READ, WRITE, or EXECUTE.\n * @property {String} permission Type of permission granted. One of:\n *\n *  - ALARM: Generate an alarm, in a system-dependent way, the access specified in the permissions component of the ACL entry.\n *  - ALLOW: Explicitly grants access to the resource.\n *  - AUDIT: Log, in a system-dependent way, the access specified in the permissions component of the ACL entry.\n *  - DENY: Explicitly denies access to the resource.\n * @property {String} principalType Type of the principal; one of: APPLICATION, USER, ROLE.\n * @property {String} principalId ID of the principal - such as appId, userId or roleId.\n * @property {Object} settings Extends the `Model.settings` object.\n * @property {String} settings.defaultPermission Default permission setting: ALLOW, DENY, ALARM, or AUDIT. Default is ALLOW.\n * Set to DENY to prohibit all API access by default.\n *\n * @class ACL\n * @inherits PersistedModel\n */\n\nmodule.exports = function(ACL) {\n  ACL.ALL = AccessContext.ALL;\n\n  ACL.DEFAULT = AccessContext.DEFAULT; // Not specified\n  ACL.ALLOW = AccessContext.ALLOW; // Allow\n  ACL.ALARM = AccessContext.ALARM; // Warn - send an alarm\n  ACL.AUDIT = AccessContext.AUDIT; // Audit - record the access\n  ACL.DENY = AccessContext.DENY; // Deny\n\n  ACL.READ = AccessContext.READ; // Read operation\n  ACL.REPLICATE = AccessContext.REPLICATE; // Replicate (pull) changes\n  ACL.WRITE = AccessContext.WRITE; // Write operation\n  ACL.EXECUTE = AccessContext.EXECUTE; // Execute operation\n\n  ACL.USER = Principal.USER;\n  ACL.APP = ACL.APPLICATION = Principal.APPLICATION;\n  ACL.ROLE = Principal.ROLE;\n  ACL.SCOPE = Principal.SCOPE;\n\n  ACL.DEFAULT_SCOPE = ctx.DEFAULT_SCOPES[0];\n\n  /**\n   * Calculate the matching score for the given rule and request\n   * @param {ACL} rule The ACL entry\n   * @param {AccessRequest} req The request\n   * @returns {Number}\n   */\n  ACL.getMatchingScore = function getMatchingScore(rule, req) {\n    const props = ['model', 'property', 'accessType'];\n    let score = 0;\n\n    for (let i = 0; i < props.length; i++) {\n      // Shift the score by 4 for each of the properties as the weight\n      score = score * 4;\n      const ruleValue = rule[props[i]] || ACL.ALL;\n      const requestedValue = req[props[i]] || ACL.ALL;\n      const isMatchingMethodName = props[i] === 'property' &&\n        req.methodNames.indexOf(ruleValue) !== -1;\n\n      let isMatchingAccessType = ruleValue === requestedValue;\n      if (props[i] === 'accessType' && !isMatchingAccessType) {\n        switch (ruleValue) {\n          case ACL.EXECUTE:\n            // EXECUTE should match READ, REPLICATE and WRITE\n            isMatchingAccessType = true;\n            break;\n          case ACL.WRITE:\n            // WRITE should match REPLICATE too\n            isMatchingAccessType = requestedValue === ACL.REPLICATE;\n            break;\n        }\n      }\n\n      if (isMatchingMethodName || isMatchingAccessType) {\n        // Exact match\n        score += 3;\n      } else if (ruleValue === ACL.ALL) {\n        // Wildcard match\n        score += 2;\n      } else if (requestedValue === ACL.ALL) {\n        score += 1;\n      } else {\n        // Doesn't match at all\n        return -1;\n      }\n    }\n\n    // Weigh against the principal type into 4 levels\n    // - user level (explicitly allow/deny a given user)\n    // - app level (explicitly allow/deny a given app)\n    // - role level (role based authorization)\n    // - other\n    // user > app > role > ...\n    score = score * 4;\n    switch (rule.principalType) {\n      case ACL.USER:\n        score += 4;\n        break;\n      case ACL.APP:\n        score += 3;\n        break;\n      case ACL.ROLE:\n        score += 2;\n        break;\n      default:\n        score += 1;\n    }\n\n    // Weigh against the roles\n    // everyone < authenticated/unauthenticated < related < owner < ...\n    score = score * 8;\n    if (rule.principalType === ACL.ROLE) {\n      switch (rule.principalId) {\n        case Role.OWNER:\n          score += 4;\n          break;\n        case Role.RELATED:\n          score += 3;\n          break;\n        case Role.AUTHENTICATED:\n        case Role.UNAUTHENTICATED:\n          score += 2;\n          break;\n        case Role.EVERYONE:\n          score += 1;\n          break;\n        default:\n          score += 5;\n      }\n    }\n    score = score * 4;\n    score += AccessContext.permissionOrder[rule.permission || ACL.ALLOW] - 1;\n    return score;\n  };\n\n  /**\n   * Get matching score for the given `AccessRequest`.\n   * @param {AccessRequest} req The request\n   * @returns {Number} score\n   */\n\n  ACL.prototype.score = function(req) {\n    return this.constructor.getMatchingScore(this, req);\n  };\n\n  /*!\n   * Resolve permission from the ACLs\n   * @param {Object[]) acls The list of ACLs\n   * @param {AccessRequest} req The access request\n   * @returns {AccessRequest} result The resolved access request\n   */\n  ACL.resolvePermission = function resolvePermission(acls, req) {\n    if (!(req instanceof AccessRequest)) {\n      req.registry = this.registry;\n      req = new AccessRequest(req);\n    }\n    // Sort by the matching score in descending order\n    acls = acls.sort(function(rule1, rule2) {\n      return ACL.getMatchingScore(rule2, req) - ACL.getMatchingScore(rule1, req);\n    });\n    let permission = ACL.DEFAULT;\n    let score = 0;\n\n    for (let i = 0; i < acls.length; i++) {\n      const candidate = acls[i];\n      score = ACL.getMatchingScore(candidate, req);\n      if (score < 0) {\n        // the highest scored ACL did not match\n        break;\n      }\n      if (!req.isWildcard()) {\n        // We should stop from the first match for non-wildcard\n        permission = candidate.permission;\n        break;\n      } else {\n        if (req.exactlyMatches(candidate)) {\n          permission = candidate.permission;\n          break;\n        }\n        // For wildcard match, find the strongest permission\n        const candidateOrder = AccessContext.permissionOrder[candidate.permission];\n        const permissionOrder = AccessContext.permissionOrder[permission];\n        if (candidateOrder > permissionOrder) {\n          permission = candidate.permission;\n          break;\n        }\n      }\n    }\n\n    if (debug.enabled) {\n      debug('The following ACLs were searched: ');\n      acls.forEach(function(acl) {\n        acl.debug();\n        debug('with score:', acl.score(req));\n      });\n    }\n    const res = new AccessRequest({\n      model: req.model,\n      property: req.property,\n      accessType: req.accessType,\n      permission: permission || ACL.DEFAULT,\n      registry: this.registry});\n\n    // Elucidate permission status if DEFAULT\n    res.settleDefaultPermission();\n\n    return res;\n  };\n\n  /*!\n   * Get the static ACLs from the model definition\n   * @param {String} model The model name\n   * @param {String} property The property/method/relation name\n   *\n   * @return {Object[]} An array of ACLs\n   */\n  ACL.getStaticACLs = function getStaticACLs(model, property) {\n    const modelClass = this.registry.findModel(model);\n    const staticACLs = [];\n    if (modelClass && modelClass.settings.acls) {\n      modelClass.settings.acls.forEach(function(acl) {\n        let prop = acl.property;\n        // We support static ACL property with array of string values.\n        if (Array.isArray(prop) && prop.indexOf(property) >= 0)\n          prop = property;\n        if (!prop || prop === ACL.ALL || property === prop) {\n          staticACLs.push(new ACL({\n            model: model,\n            property: prop || ACL.ALL,\n            principalType: acl.principalType,\n            principalId: acl.principalId, // TODO: Should it be a name?\n            accessType: acl.accessType || ACL.ALL,\n            permission: acl.permission,\n          }));\n        }\n      });\n    }\n    const prop = modelClass && (\n      // regular property\n      modelClass.definition.properties[property] ||\n      // relation/scope\n      (modelClass._scopeMeta && modelClass._scopeMeta[property]) ||\n      // static method\n      modelClass[property] ||\n      // prototype method\n      modelClass.prototype[property]);\n    if (prop && prop.acls) {\n      prop.acls.forEach(function(acl) {\n        staticACLs.push(new ACL({\n          model: modelClass.modelName,\n          property: property,\n          principalType: acl.principalType,\n          principalId: acl.principalId,\n          accessType: acl.accessType,\n          permission: acl.permission,\n        }));\n      });\n    }\n    return staticACLs;\n  };\n\n  /**\n   * Check if the given principal is allowed to access the model/property\n   * @param {String} principalType The principal type.\n   * @param {String} principalId The principal ID.\n   * @param {String} model The model name.\n   * @param {String} property The property/method/relation name.\n   * @param {String} accessType The access type.\n   * @callback {Function} callback Callback function.\n   * @param {String|Error} err The error object.\n   * @param {AccessRequest} result The resolved access request.\n   */\n  ACL.checkPermission = function checkPermission(principalType, principalId,\n    model, property, accessType,\n    callback) {\n    if (!callback) callback = utils.createPromiseCallback();\n    if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) {\n      principalId = principalId.toString();\n    }\n    property = property || ACL.ALL;\n    const propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]};\n    accessType = accessType || ACL.ALL;\n    const accessTypeQuery = (accessType === ACL.ALL) ? undefined :\n      {inq: [accessType, ACL.ALL, ACL.EXECUTE]};\n\n    const req = new AccessRequest({model, property, accessType, registry: this.registry});\n\n    let acls = this.getStaticACLs(model, property);\n\n    // resolved is an instance of AccessRequest\n    let resolved = this.resolvePermission(acls, req);\n\n    if (resolved && resolved.permission === ACL.DENY) {\n      debug('Permission denied by statically resolved permission');\n      debug(' Resolved Permission: %j', resolved);\n      process.nextTick(function() {\n        callback(null, resolved);\n      });\n      return callback.promise;\n    }\n\n    const self = this;\n    this.find({where: {principalType: principalType, principalId: principalId,\n      model: model, property: propertyQuery, accessType: accessTypeQuery}},\n    function(err, dynACLs) {\n      if (err) {\n        return callback(err);\n      }\n      acls = acls.concat(dynACLs);\n      // resolved is an instance of AccessRequest\n      resolved = self.resolvePermission(acls, req);\n      return callback(null, resolved);\n    });\n    return callback.promise;\n  };\n\n  ACL.prototype.debug = function() {\n    if (debug.enabled) {\n      debug('---ACL---');\n      debug('model %s', this.model);\n      debug('property %s', this.property);\n      debug('principalType %s', this.principalType);\n      debug('principalId %s', this.principalId);\n      debug('accessType %s', this.accessType);\n      debug('permission %s', this.permission);\n    }\n  };\n\n  // NOTE Regarding ACL.isAllowed() and ACL.prototype.isAllowed()\n  // Extending existing logic, including from ACL.checkAccessForContext() method,\n  // ACL instance with missing property `permission` are not promoted to\n  // permission = ACL.DEFAULT config. Such ACL instances will hence always be\n  // inefective\n\n  /**\n   * Test if ACL's permission is ALLOW\n   * @param {String} permission The permission to test, expects one of 'ALLOW', 'DENY', 'DEFAULT'\n   * @param {String} defaultPermission The default permission to apply if not providing a finite one in the permission parameter\n   * @returns {Boolean} true if ACL permission is ALLOW\n   */\n  ACL.isAllowed = function(permission, defaultPermission) {\n    if (permission === ACL.DEFAULT) {\n      permission = defaultPermission || ACL.ALLOW;\n    }\n    return permission !== loopback.ACL.DENY;\n  };\n\n  /**\n   * Test if ACL's permission is ALLOW\n   * @param {String} defaultPermission The default permission to apply if missing in ACL instance\n   * @returns {Boolean} true if ACL permission is ALLOW\n   */\n  ACL.prototype.isAllowed = function(defaultPermission) {\n    return this.constructor.isAllowed(this.permission, defaultPermission);\n  };\n\n  /**\n   * Check if the request has the permission to access.\n   * @options {AccessContext|Object} context\n   * An AccessContext instance or a plain object with the following properties.\n   * @property {Object[]} principals An array of principals.\n   * @property {String|Model} model The model name or model class.\n   * @property {*} modelId The model instance ID.\n   * @property {String} property The property/method/relation name.\n   * @property {String} accessType The access type:\n   * READ, REPLICATE, WRITE, or EXECUTE.\n   * @callback {Function} callback Callback function\n   * @param {String|Error} err The error object.\n   * @param {AccessRequest} result The resolved access request.\n   */\n  ACL.checkAccessForContext = function(context, callback) {\n    if (!callback) callback = utils.createPromiseCallback();\n    const self = this;\n    self.resolveRelatedModels();\n    const roleModel = self.roleModel;\n\n    if (!(context instanceof AccessContext)) {\n      context.registry = this.registry;\n      context = new AccessContext(context);\n    }\n\n    let authorizedRoles = {};\n    const remotingContext = context.remotingContext;\n    const model = context.model;\n    const modelDefaultPermission = model && model.settings.defaultPermission;\n    const property = context.property;\n    const accessType = context.accessType;\n    const modelName = context.modelName;\n\n    const methodNames = context.methodNames;\n    const propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])};\n\n    const accessTypeQuery = (accessType === ACL.ALL) ?\n      undefined :\n      (accessType === ACL.REPLICATE) ?\n        {inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL]} :\n        {inq: [accessType, ACL.ALL]};\n\n    const req = new AccessRequest({\n      model: modelName,\n      property,\n      accessType,\n      permission: ACL.DEFAULT,\n      methodNames,\n      registry: this.registry});\n\n    if (!context.isScopeAllowed()) {\n      req.permission = ACL.DENY;\n      debug('--Denied by scope config--');\n      debug('Scopes allowed:', context.accessToken.scopes || ctx.DEFAULT_SCOPES);\n      debug('Scope required:', context.getScopes());\n      context.debug();\n      callback(null, req);\n      return callback.promise;\n    }\n\n    const effectiveACLs = [];\n    const staticACLs = self.getStaticACLs(model.modelName, property);\n\n    const query = {\n      where: {\n        model: {inq: [model.modelName, ACL.ALL]},\n        property: propertyQuery,\n        accessType: accessTypeQuery,\n      },\n    };\n\n    this.find(query, function(err, acls) {\n      if (err) return callback(err);\n      const inRoleTasks = [];\n\n      acls = acls.concat(staticACLs);\n\n      acls.forEach(function(acl) {\n        // Check exact matches\n        for (let i = 0; i < context.principals.length; i++) {\n          const p = context.principals[i];\n          const typeMatch = p.type === acl.principalType;\n          const idMatch = String(p.id) === String(acl.principalId);\n          if (typeMatch && idMatch) {\n            effectiveACLs.push(acl);\n            return;\n          }\n        }\n\n        // Check role matches\n        if (acl.principalType === ACL.ROLE) {\n          inRoleTasks.push(function(done) {\n            roleModel.isInRole(acl.principalId, context,\n              function(err, inRole) {\n                if (!err && inRole) {\n                  effectiveACLs.push(acl);\n                  // add the role to authorizedRoles if allowed\n                  if (acl.isAllowed(modelDefaultPermission))\n                    authorizedRoles[acl.principalId] = true;\n                }\n                done(err, acl);\n              });\n          });\n        }\n      });\n\n      async.parallel(inRoleTasks, function(err, results) {\n        if (err) return callback(err, null);\n\n        // resolved is an instance of AccessRequest\n        const resolved = self.resolvePermission(effectiveACLs, req);\n        debug('---Resolved---');\n        resolved.debug();\n\n        // set authorizedRoles in remotingContext options argument if\n        // resolved AccessRequest permission is ALLOW, else set it to empty object\n        authorizedRoles = resolved.isAllowed() ? authorizedRoles : {};\n        saveAuthorizedRolesToRemotingContext(remotingContext, authorizedRoles);\n        return callback(null, resolved);\n      });\n    });\n    return callback.promise;\n  };\n\n  function saveAuthorizedRolesToRemotingContext(remotingContext, authorizedRoles) {\n    const options = remotingContext && remotingContext.args && remotingContext.args.options;\n    // authorizedRoles key/value map is added to the options argument only if\n    // the latter exists and is an object. This means that the feature's availability\n    // will depend on the app configuration\n    if (options && typeof options === 'object') { // null is object too\n      options.authorizedRoles = authorizedRoles;\n    }\n  }\n\n  /**\n   * Check if the given access token can invoke the method\n   * @param {AccessToken} token The access token\n   * @param {String} model The model name\n   * @param {*} modelId The model id\n   * @param {String} method The method name\n   * @callback {Function} callback Callback function\n   * @param {String|Error} err The error object\n   * @param {Boolean} allowed is the request allowed\n   */\n  ACL.checkAccessForToken = function(token, model, modelId, method, callback) {\n    assert(token, 'Access token is required');\n    if (!callback) callback = utils.createPromiseCallback();\n    const context = new AccessContext({\n      registry: this.registry,\n      accessToken: token,\n      model: model,\n      property: method,\n      method: method,\n      modelId: modelId,\n    });\n\n    this.checkAccessForContext(context, function(err, accessRequest) {\n      if (err) callback(err);\n      else callback(null, accessRequest.isAllowed());\n    });\n    return callback.promise;\n  };\n\n  ACL.resolveRelatedModels = function() {\n    if (!this.roleModel) {\n      const reg = this.registry;\n      this.roleModel = reg.getModelByType('Role');\n      this.roleMappingModel = reg.getModelByType('RoleMapping');\n      this.userModel = reg.getModelByType('User');\n      this.applicationModel = reg.getModelByType('Application');\n    }\n  };\n\n  /**\n   * Resolve a principal by type/id\n   * @param {String} type Principal type - ROLE/APP/USER\n   * @param {String|Number} id Principal id or name\n   * @callback {Function} callback Callback function\n   * @param {String|Error} err The error object\n   * @param {Object} result An instance of principal (Role, Application or User)\n   */\n  ACL.resolvePrincipal = function(type, id, cb) {\n    cb = cb || utils.createPromiseCallback();\n    type = type || ACL.ROLE;\n    this.resolveRelatedModels();\n\n    switch (type) {\n      case ACL.ROLE:\n        this.roleModel.findOne({where: {or: [{name: id}, {id: id}]}}, cb);\n        break;\n      case ACL.USER:\n        this.userModel.findOne(\n          {where: {or: [{username: id}, {email: id}, {id: id}]}}, cb,\n        );\n        break;\n      case ACL.APP:\n        this.applicationModel.findOne(\n          {where: {or: [{name: id}, {email: id}, {id: id}]}}, cb,\n        );\n        break;\n      default:\n        // try resolving a user model with a name matching the principalType\n        const userModel = this.registry.findModel(type);\n        if (userModel) {\n          userModel.findOne(\n            {where: {or: [{username: id}, {email: id}, {id: id}]}},\n            cb,\n          );\n        } else {\n          process.nextTick(function() {\n            const err = new Error(g.f('Invalid principal type: %s', type));\n            err.statusCode = 400;\n            err.code = 'INVALID_PRINCIPAL_TYPE';\n            cb(err);\n          });\n        }\n    }\n    return cb.promise;\n  };\n\n  /**\n   * Check if the given principal is mapped to the role\n   * @param {String} principalType Principal type\n   * @param {String|*} principalId Principal id/name\n   * @param {String|*} role Role id/name\n   * @callback {Function} callback Callback function\n   * @param {String|Error} err The error object\n   * @param {Boolean} isMapped is the ACL mapped to the role\n   */\n  ACL.isMappedToRole = function(principalType, principalId, role, cb) {\n    cb = cb || utils.createPromiseCallback();\n    const self = this;\n    this.resolvePrincipal(principalType, principalId,\n      function(err, principal) {\n        if (err) return cb(err);\n        if (principal != null) {\n          principalId = principal.id;\n        }\n        principalType = principalType || 'ROLE';\n        self.resolvePrincipal('ROLE', role, function(err, role) {\n          if (err || !role) return cb(err, role);\n          self.roleMappingModel.findOne({\n            where: {\n              roleId: role.id,\n              principalType: principalType,\n              principalId: String(principalId),\n            },\n          }, function(err, result) {\n            if (err) return cb(err);\n            return cb(null, !!result);\n          });\n        });\n      });\n    return cb.promise;\n  };\n};\n"
  },
  {
    "path": "common/models/acl.json",
    "content": "{\n  \"name\": \"ACL\",\n  \"properties\": {\n    \"model\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the model\"\n    },\n    \"property\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the property, method, scope, or relation\"\n    },\n    \"accessType\": \"string\",\n    \"permission\": \"string\",\n    \"principalType\": \"string\",\n    \"principalId\": \"string\"\n  }\n}\n"
  },
  {
    "path": "common/models/application.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst utils = require('../../lib/utils');\n\n/*!\n * Application management functions\n */\n\nconst crypto = require('crypto');\n\nfunction generateKey(hmacKey, algorithm, encoding) {\n  hmacKey = hmacKey || 'loopback';\n  algorithm = algorithm || 'sha1';\n  encoding = encoding || 'hex';\n  const hmac = crypto.createHmac(algorithm, hmacKey);\n  const buf = crypto.randomBytes(32);\n  hmac.update(buf);\n  const key = hmac.digest(encoding);\n  return key;\n}\n\n/**\n * Manage client applications and organize their users.\n *\n * @property {String} id  Generated ID.\n * @property {String} name Name; required.\n * @property {String} description Text description\n * @property {String} icon String Icon image URL.\n * @property {String} owner User ID of the developer who registers the application.\n * @property {String} email E-mail address\n * @property {Boolean} emailVerified Whether the e-mail is verified.\n * @property {String} url OAuth 2.0  application URL.\n * @property {String}[] callbackUrls The OAuth 2.0 code/token callback URL.\n * @property {String} status Status of the application; Either `production`, `sandbox` (default), or `disabled`.\n * @property {Date} created Date Application object was created.  Default: current date.\n * @property {Date} modified Date Application object was modified.  Default: current date.\n *\n * @property {Object} pushSettings.apns APNS configuration, see the options\n *   below and also\n *   https://github.com/argon/node-apn/blob/master/doc/apn.markdown\n * @property {Boolean} pushSettings.apns.production Whether to use production Apple Push Notification Service (APNS) servers to send push notifications.\n * If true, uses `gateway.push.apple.com:2195` and `feedback.push.apple.com:2196`.\n * If false, uses `gateway.sandbox.push.apple.com:2195` and `feedback.sandbox.push.apple.com:2196`\n * @property {String} pushSettings.apns.certData The certificate data loaded from the cert.pem file (APNS).\n * @property {String} pushSettings.apns.keyData The key data loaded from the key.pem file (APNS).\n * @property {String} pushSettings.apns.pushOptions.gateway (APNS).\n * @property {Number} pushSettings.apns.pushOptions.port (APNS).\n * @property {String} pushSettings.apns.feedbackOptions.gateway  (APNS).\n * @property {Number} pushSettings.apns.feedbackOptions.port (APNS).\n * @property {Boolean} pushSettings.apns.feedbackOptions.batchFeedback (APNS).\n * @property {Number} pushSettings.apns.feedbackOptions.interval (APNS).\n * @property {String} pushSettings.gcm.serverApiKey: Google Cloud Messaging API key.\n *\n * @property {Boolean} authenticationEnabled\n * @property {Boolean} anonymousAllowed\n * @property {Array} authenticationSchemes List of authentication schemes\n *  (see below).\n * @property {String} authenticationSchemes.scheme Scheme name.\n *   Supported values: `local`, `facebook`, `google`,\n *   `twitter`, `linkedin`, `github`.\n * @property {Object} authenticationSchemes.credential\n *   Scheme-specific credentials.\n *\n * @class Application\n * @inherits {PersistedModel}\n */\n\nmodule.exports = function(Application) {\n  /*!\n   * A hook to generate keys before creation\n   * @param next\n   */\n  Application.observe('before save', function(ctx, next) {\n    if (!ctx.instance) {\n      // Partial update - don't generate new keys\n      // NOTE(bajtos) This also means that an atomic updateOrCreate\n      // will not generate keys when a new record is creatd\n      return next();\n    }\n\n    const app = ctx.instance;\n    app.created = app.modified = new Date();\n    if (!app.id) {\n      app.id = generateKey('id', 'md5');\n    }\n    app.clientKey = generateKey('client');\n    app.javaScriptKey = generateKey('javaScript');\n    app.restApiKey = generateKey('restApi');\n    app.windowsKey = generateKey('windows');\n    app.masterKey = generateKey('master');\n    next();\n  });\n\n  /**\n   * Register a new application\n   * @param {String} owner Owner's user ID.\n   * @param {String} name  Name of the application\n   * @param {Object} options  Other options\n   * @callback {Function} callback  Callback function\n   * @param {Error} err\n   * @promise\n   */\n  Application.register = function(owner, name, options, cb) {\n    assert(owner, 'owner is required');\n    assert(name, 'name is required');\n\n    if (typeof options === 'function' && !cb) {\n      cb = options;\n      options = {};\n    }\n    cb = cb || utils.createPromiseCallback();\n\n    const props = {owner: owner, name: name};\n    for (const p in options) {\n      if (!(p in props)) {\n        props[p] = options[p];\n      }\n    }\n    this.create(props, cb);\n    return cb.promise;\n  };\n\n  /**\n   * Reset keys for the application instance\n   * @callback {Function} callback\n   * @param {Error} err\n   */\n  Application.prototype.resetKeys = function(cb) {\n    this.clientKey = generateKey('client');\n    this.javaScriptKey = generateKey('javaScript');\n    this.restApiKey = generateKey('restApi');\n    this.windowsKey = generateKey('windows');\n    this.masterKey = generateKey('master');\n    this.modified = new Date();\n    this.save(cb);\n  };\n\n  /**\n   * Reset keys for a given application by the appId\n   * @param {Any} appId\n   * @callback {Function} callback\n   * @param {Error} err\n   * @promise\n   */\n  Application.resetKeys = function(appId, cb) {\n    cb = cb || utils.createPromiseCallback();\n    this.findById(appId, function(err, app) {\n      if (err) {\n        if (cb) cb(err, app);\n        return;\n      }\n      app.resetKeys(cb);\n    });\n    return cb.promise;\n  };\n\n  /**\n   * Authenticate the application id and key.\n   *\n   * @param {Any} appId\n   * @param {String} key\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {String} matched The matching key; one of:\n   * - clientKey\n   * - javaScriptKey\n   * - restApiKey\n   * - windowsKey\n   * - masterKey\n   * @promise\n   */\n  Application.authenticate = function(appId, key, cb) {\n    cb = cb || utils.createPromiseCallback();\n\n    this.findById(appId, function(err, app) {\n      if (err || !app) {\n        cb(err, null);\n        return cb.promise;\n      }\n      let result = null;\n      const keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey'];\n      for (let i = 0; i < keyNames.length; i++) {\n        if (app[keyNames[i]] === key) {\n          result = {\n            application: app,\n            keyType: keyNames[i],\n          };\n          break;\n        }\n      }\n      cb(null, result);\n    });\n    return cb.promise;\n  };\n};\n"
  },
  {
    "path": "common/models/application.json",
    "content": "{\n  \"name\": \"Application\",\n  \"properties\": {\n    \"id\": {\n      \"type\": \"string\",\n      \"id\": true\n    },\n    \"realm\": {\n      \"type\": \"string\"\n    },\n    \"name\": {\n      \"type\": \"string\",\n      \"required\": true\n    },\n    \"description\": \"string\",\n    \"icon\": {\n      \"type\": \"string\",\n      \"description\": \"The icon image url\"\n    },\n\n    \"owner\": {\n      \"type\": \"string\",\n      \"description\": \"The user id of the developer who registers the application\"\n    },\n    \"collaborators\": {\n      \"type\": [\"string\"],\n      \"description\": \"A list of users ids who have permissions to work on this app\"\n    },\n\n    \"email\": \"string\",\n    \"emailVerified\": \"boolean\",\n\n    \"url\": {\n      \"type\": \"string\",\n      \"description\": \"The application URL for OAuth 2.0\"\n    },\n    \"callbackUrls\": {\n      \"type\": [\"string\"],\n      \"description\": \"OAuth 2.0 code/token callback URLs\"\n    },\n    \"permissions\": {\n      \"type\": [\"string\"],\n      \"description\": \"A list of permissions required by the application\"\n    },\n\n    \"clientKey\": \"string\",\n    \"javaScriptKey\": \"string\",\n    \"restApiKey\": \"string\",\n    \"windowsKey\": \"string\",\n    \"masterKey\": \"string\",\n\n    \"pushSettings\": {\n      \"apns\": {\n        \"production\": {\n          \"type\": \"boolean\",\n          \"description\": [\n            \"Production or development mode. It denotes what default APNS\",\n            \"servers to be used to send notifications.\",\n            \"See API documentation for more details.\"\n          ]\n        },\n\n        \"certData\": {\n          \"type\": \"string\",\n          \"description\": \"The certificate data loaded from the cert.pem file\"\n        },\n        \"keyData\": {\n          \"type\": \"string\",\n          \"description\": \"The key data loaded from the key.pem file\"\n        },\n\n        \"pushOptions\": {\n          \"type\": {\n            \"gateway\": \"string\",\n            \"port\": \"number\"\n          }\n        },\n\n        \"feedbackOptions\": {\n          \"type\": {\n            \"gateway\": \"string\",\n            \"port\": \"number\",\n            \"batchFeedback\": \"boolean\",\n            \"interval\": \"number\"\n          }\n        }\n      },\n\n      \"gcm\": {\n        \"serverApiKey\": \"string\"\n      }\n    },\n\n    \"authenticationEnabled\": {\n      \"type\": \"boolean\",\n      \"default\": true\n    },\n    \"anonymousAllowed\": {\n      \"type\": \"boolean\",\n      \"default\": true\n    },\n    \"authenticationSchemes\": [\n      {\n        \"scheme\": {\n          \"type\": \"string\",\n          \"description\": \"See the API docs for the list of supported values.\"\n        },\n        \"credential\": {\n          \"type\": \"object\",\n          \"description\": \"Scheme-specific credentials\"\n        }\n      }\n    ],\n\n    \"status\": {\n      \"type\": \"string\",\n      \"default\": \"sandbox\",\n      \"description\": \"Status of the application, production/sandbox/disabled\"\n    },\n\n    \"created\": {\n      \"type\": \"date\",\n      \"defaultFn\": \"now\"\n    },\n    \"modified\": {\n      \"type\": \"date\",\n      \"defaultFn\": \"now\"\n    }\n  }\n}\n"
  },
  {
    "path": "common/models/change.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Module Dependencies.\n */\n\n'use strict';\nconst g = require('../../lib/globalize');\nconst PersistedModel = require('../../lib/loopback').PersistedModel;\nconst loopback = require('../../lib/loopback');\nconst utils = require('../../lib/utils');\nconst crypto = require('crypto');\nconst CJSON = {stringify: require('canonical-json')};\nconst async = require('async');\nconst assert = require('assert');\nconst debug = require('debug')('loopback:change');\n\n/**\n * Change list entry.\n *\n * @property {String} id Hash of the modelName and ID.\n * @property {String} rev The current model revision.\n * @property {String} prev The previous model revision.\n * @property {Number} checkpoint The current checkpoint at time of the change.\n * @property {String} modelName Model name.\n * @property {String} modelId Model ID.\n * @property {Object} settings Extends the `Model.settings` object.\n * @property {String} settings.hashAlgorithm Algorithm used to create cryptographic hash, used as argument\n * to [crypto.createHash](http://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm).  Default is sha1.\n * @property {Boolean} settings.ignoreErrors By default, when changes are rectified, an error will throw an exception.\n * However, if this setting is true, then errors will not throw exceptions.\n * @class Change\n * @inherits {PersistedModel}\n */\n\nmodule.exports = function(Change) {\n  /*!\n   * Constants\n   */\n\n  Change.UPDATE = 'update';\n  Change.CREATE = 'create';\n  Change.DELETE = 'delete';\n  Change.UNKNOWN = 'unknown';\n\n  /*!\n   * Conflict Class\n   */\n\n  Change.Conflict = Conflict;\n\n  /*!\n   * Setup the extended model.\n   */\n\n  Change.setup = function() {\n    PersistedModel.setup.call(this);\n    const Change = this;\n\n    Change.getter.id = function() {\n      const hasModel = this.modelName && this.modelId;\n      if (!hasModel) return null;\n\n      return Change.idForModel(this.modelName, this.modelId);\n    };\n  };\n  Change.setup();\n\n  /**\n   * Track the recent change of the given modelIds.\n   *\n   * @param  {String}   modelName\n   * @param  {Array}    modelIds\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {Array} changes Changes that were tracked\n   */\n\n  Change.rectifyModelChanges = function(modelName, modelIds, callback) {\n    const Change = this;\n    const errors = [];\n\n    callback = callback || utils.createPromiseCallback();\n\n    const tasks = modelIds.map(function(id) {\n      return function(cb) {\n        Change.findOrCreateChange(modelName, id, function(err, change) {\n          if (err) return next(err);\n          change.rectify(next);\n        });\n\n        function next(err) {\n          if (err) {\n            err.modelName = modelName;\n            err.modelId = id;\n            errors.push(err);\n          }\n          cb();\n        }\n      };\n    });\n\n    async.parallel(tasks, function(err) {\n      if (err) return callback(err);\n      if (errors.length) {\n        const desc = errors\n          .map(function(e) {\n            return '#' + e.modelId + ' - ' + e.toString();\n          })\n          .join('\\n');\n\n        const msg = g.f('Cannot rectify %s changes:\\n%s', modelName, desc);\n        err = new Error(msg);\n        err.details = {errors: errors};\n        return callback(err);\n      }\n      callback();\n    });\n    return callback.promise;\n  };\n\n  /**\n   * Get an identifier for a given model.\n   *\n   * @param  {String} modelName\n   * @param  {String} modelId\n   * @return {String}\n   */\n\n  Change.idForModel = function(modelName, modelId) {\n    return this.hash([modelName, modelId].join('-'));\n  };\n\n  /**\n   * Find or create a change for the given model.\n   *\n   * @param  {String}   modelName\n   * @param  {String}   modelId\n   * @callback  {Function} callback\n   * @param {Error} err\n   * @param {Change} change\n   * @end\n   */\n\n  Change.findOrCreateChange = function(modelName, modelId, callback) {\n    assert(this.registry.findModel(modelName), modelName + ' does not exist');\n    callback = callback || utils.createPromiseCallback();\n    const id = this.idForModel(modelName, modelId);\n    const Change = this;\n\n    this.findById(id, function(err, change) {\n      if (err) return callback(err);\n      if (change) {\n        callback(null, change);\n      } else {\n        const ch = new Change({\n          id: id,\n          modelName: modelName,\n          modelId: modelId,\n        });\n        ch.debug('creating change');\n        Change.updateOrCreate(ch, callback);\n      }\n    });\n    return callback.promise;\n  };\n\n  /**\n   * Update (or create) the change with the current revision.\n   *\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {Change} change\n   */\n\n  Change.prototype.rectify = function(cb) {\n    const change = this;\n    const currentRev = this.rev;\n\n    change.debug('rectify change');\n\n    cb = cb || utils.createPromiseCallback();\n\n    const model = this.getModelCtor();\n    const id = this.getModelId();\n\n    model.findById(id, function(err, inst) {\n      if (err) return cb(err);\n\n      if (inst) {\n        inst.fillCustomChangeProperties(change, function() {\n          const rev = Change.revisionForInst(inst);\n          prepareAndDoRectify(rev);\n        });\n      } else {\n        prepareAndDoRectify(null);\n      }\n    });\n\n    return cb.promise;\n\n    function prepareAndDoRectify(rev) {\n      // avoid setting rev and prev to the same value\n      if (currentRev === rev) {\n        change.debug('rev and prev are equal (not updating anything)');\n        return cb(null, change);\n      }\n\n      // FIXME(@bajtos) Allow callers to pass in the checkpoint value\n      // (or even better - a memoized async function to get the cp value)\n      // That will enable `rectifyAll` to cache the checkpoint value\n      change.constructor.getCheckpointModel().current(\n        function(err, checkpoint) {\n          if (err) return cb(err);\n          doRectify(checkpoint, rev);\n        },\n      );\n    }\n\n    function doRectify(checkpoint, rev) {\n      if (rev) {\n        if (currentRev === rev) {\n          change.debug('ASSERTION FAILED: Change currentRev==rev ' +\n            'should have been already handled');\n          return cb(null, change);\n        } else {\n          change.rev = rev;\n          change.debug('updated revision (was ' + currentRev + ')');\n          if (change.checkpoint !== checkpoint) {\n            // previous revision is updated only across checkpoints\n            change.prev = currentRev;\n            change.debug('updated prev');\n          }\n        }\n      } else {\n        change.rev = null;\n        change.debug('updated revision (was ' + currentRev + ')');\n        if (change.checkpoint !== checkpoint) {\n          // previous revision is updated only across checkpoints\n          if (currentRev) {\n            change.prev = currentRev;\n          } else if (!change.prev) {\n            change.debug('ERROR - could not determine prev');\n            change.prev = Change.UNKNOWN;\n          }\n          change.debug('updated prev');\n        }\n      }\n\n      if (change.checkpoint != checkpoint) {\n        debug('update checkpoint to', checkpoint);\n        change.checkpoint = checkpoint;\n      }\n\n      if (change.prev === Change.UNKNOWN) {\n        // this occurs when a record of a change doesn't exist\n        // and its current revision is null (not found)\n        change.remove(cb);\n      } else {\n        change.save(cb);\n      }\n    }\n  };\n\n  /**\n   * Get a change's current revision based on current data.\n   * @callback  {Function} callback\n   * @param {Error} err\n   * @param {String} rev The current revision\n   */\n\n  Change.prototype.currentRevision = function(cb) {\n    cb = cb || utils.createPromiseCallback();\n    const model = this.getModelCtor();\n    const id = this.getModelId();\n    model.findById(id, function(err, inst) {\n      if (err) return cb(err);\n      if (inst) {\n        cb(null, Change.revisionForInst(inst));\n      } else {\n        cb(null, null);\n      }\n    });\n    return cb.promise;\n  };\n\n  /**\n   * Create a hash of the given `string` with the `options.hashAlgorithm`.\n   * **Default: `sha1`**\n   *\n   * @param  {String} str The string to be hashed\n   * @return {String}     The hashed string\n   */\n\n  Change.hash = function(str) {\n    return crypto\n      .createHash(Change.settings.hashAlgorithm || 'sha1')\n      .update(str)\n      .digest('hex');\n  };\n\n  /**\n   * Get the revision string for the given object\n   * @param  {Object} inst The data to get the revision string for\n   * @return {String}      The revision string\n   */\n\n  Change.revisionForInst = function(inst) {\n    assert(inst, 'Change.revisionForInst() requires an instance object.');\n    return this.hash(CJSON.stringify(inst));\n  };\n\n  /**\n   * Get a change's type. Returns one of:\n   *\n   * - `Change.UPDATE`\n   * - `Change.CREATE`\n   * - `Change.DELETE`\n   * - `Change.UNKNOWN`\n   *\n   * @return {String} the type of change\n   */\n\n  Change.prototype.type = function() {\n    if (this.rev && this.prev) {\n      return Change.UPDATE;\n    }\n    if (this.rev && !this.prev) {\n      return Change.CREATE;\n    }\n    if (!this.rev && this.prev) {\n      return Change.DELETE;\n    }\n    return Change.UNKNOWN;\n  };\n\n  /**\n   * Compare two changes.\n   * @param  {Change} change\n   * @return {Boolean}\n   */\n\n  Change.prototype.equals = function(change) {\n    if (!change) return false;\n    const thisRev = this.rev || null;\n    const thatRev = change.rev || null;\n    return thisRev === thatRev;\n  };\n\n  /**\n   * Does this change conflict with the given change.\n   * @param  {Change} change\n   * @return {Boolean}\n   */\n\n  Change.prototype.conflictsWith = function(change) {\n    if (!change) return false;\n    if (this.equals(change)) return false;\n    if (Change.bothDeleted(this, change)) return false;\n    if (this.isBasedOn(change)) return false;\n    return true;\n  };\n\n  /**\n   * Are both changes deletes?\n   * @param  {Change} a\n   * @param  {Change} b\n   * @return {Boolean}\n   */\n\n  Change.bothDeleted = function(a, b) {\n    return a.type() === Change.DELETE &&\n      b.type() === Change.DELETE;\n  };\n\n  /**\n   * Determine if the change is based on the given change.\n   * @param  {Change} change\n   * @return {Boolean}\n   */\n\n  Change.prototype.isBasedOn = function(change) {\n    return this.prev === change.rev;\n  };\n\n  /**\n   * Determine the differences for a given model since a given checkpoint.\n   *\n   * The callback will contain an error or `result`.\n   *\n   * **result**\n   *\n   * ```js\n   * {\n *   deltas: Array,\n *   conflicts: Array\n * }\n   * ```\n   *\n   * **deltas**\n   *\n   * An array of changes that differ from `remoteChanges`.\n   *\n   * **conflicts**\n   *\n   * An array of changes that conflict with `remoteChanges`.\n   *\n   * @param  {String}   modelName\n   * @param  {Number}   since         Compare changes after this checkpoint\n   * @param  {Change[]} remoteChanges A set of changes to compare\n   * @callback  {Function} callback\n   * @param {Error} err\n   * @param {Object} result See above.\n   */\n\n  Change.diff = function(modelName, since, remoteChanges, callback) {\n    callback = callback || utils.createPromiseCallback();\n\n    if (!Array.isArray(remoteChanges) || remoteChanges.length === 0) {\n      callback(null, {deltas: [], conflicts: []});\n      return callback.promise;\n    }\n    const remoteChangeIndex = {};\n    const modelIds = [];\n    remoteChanges.forEach(function(ch) {\n      modelIds.push(ch.modelId);\n      remoteChangeIndex[ch.modelId] = new Change(ch);\n    });\n\n    // normalize `since`\n    since = Number(since) || 0;\n    this.find({\n      where: {\n        modelName: modelName,\n        modelId: {inq: modelIds},\n      },\n    }, function(err, allLocalChanges) {\n      if (err) return callback(err);\n      const deltas = [];\n      const conflicts = [];\n      const localModelIds = [];\n\n      const localChanges = allLocalChanges.filter(function(c) {\n        return c.checkpoint >= since;\n      });\n\n      localChanges.forEach(function(localChange) {\n        localChange = new Change(localChange);\n        localModelIds.push(localChange.modelId);\n        const remoteChange = remoteChangeIndex[localChange.modelId];\n        if (remoteChange && !localChange.equals(remoteChange)) {\n          if (remoteChange.conflictsWith(localChange)) {\n            remoteChange.debug('remote conflict');\n            localChange.debug('local conflict');\n            conflicts.push(localChange);\n          } else {\n            remoteChange.debug('remote delta');\n            deltas.push(remoteChange);\n          }\n        }\n      });\n\n      modelIds.forEach(function(id) {\n        if (localModelIds.indexOf(id) !== -1) return;\n\n        const d = remoteChangeIndex[id];\n        const oldChange = allLocalChanges.filter(function(c) {\n          return c.modelId === id;\n        })[0];\n\n        if (oldChange) {\n          d.prev = oldChange.rev;\n        } else {\n          d.prev = null;\n        }\n\n        deltas.push(d);\n      });\n\n      callback(null, {\n        deltas: deltas,\n        conflicts: conflicts,\n      });\n    });\n    return callback.promise;\n  };\n\n  /**\n   * Correct all change list entries.\n   * @param {Function} cb\n   */\n\n  Change.rectifyAll = function(cb) {\n    debug('rectify all');\n    const Change = this;\n    // this should be optimized\n    this.find(function(err, changes) {\n      if (err) return cb(err);\n      async.each(\n        changes,\n        function(c, next) { c.rectify(next); },\n        cb,\n      );\n    });\n  };\n\n  /**\n   * Get the checkpoint model.\n   * @return {Checkpoint}\n   */\n\n  Change.getCheckpointModel = function() {\n    let checkpointModel = this.Checkpoint;\n    if (checkpointModel) return checkpointModel;\n    // FIXME(bajtos) This code creates multiple different models with the same\n    // model name, which is not a valid supported usage of juggler's API.\n    this.Checkpoint = checkpointModel = loopback.Checkpoint.extend('checkpoint');\n    assert(this.dataSource, 'Cannot getCheckpointModel(): ' + this.modelName +\n      ' is not attached to a dataSource');\n    checkpointModel.attachTo(this.dataSource);\n    return checkpointModel;\n  };\n\n  Change.prototype.debug = function() {\n    if (debug.enabled) {\n      const args = Array.prototype.slice.call(arguments);\n      args[0] = args[0] + ' %s';\n      args.push(this.modelName);\n      debug.apply(this, args);\n      debug('\\tid', this.id);\n      debug('\\trev', this.rev);\n      debug('\\tprev', this.prev);\n      debug('\\tcheckpoint', this.checkpoint);\n      debug('\\tmodelName', this.modelName);\n      debug('\\tmodelId', this.modelId);\n      debug('\\ttype', this.type());\n    }\n  };\n\n  /**\n   * Get the `Model` class for `change.modelName`.\n   * @return {Model}\n   */\n\n  Change.prototype.getModelCtor = function() {\n    return this.constructor.settings.trackModel;\n  };\n\n  Change.prototype.getModelId = function() {\n    // TODO(ritch) get rid of the need to create an instance\n    const Model = this.getModelCtor();\n    const id = this.modelId;\n    const m = new Model();\n    m.setId(id);\n    return m.getId();\n  };\n\n  Change.prototype.getModel = function(callback) {\n    const Model = this.constructor.settings.trackModel;\n    const id = this.getModelId();\n    Model.findById(id, callback);\n  };\n\n  /**\n   * When two changes conflict a conflict is created.\n   *\n   * **Note**: call `conflict.fetch()` to get the `target` and `source` models.\n   *\n   * @param {*} modelId\n   * @param {PersistedModel} SourceModel\n   * @param {PersistedModel} TargetModel\n   * @property {ModelClass} source The source model instance\n   * @property {ModelClass} target The target model instance\n   * @class Change.Conflict\n   */\n\n  function Conflict(modelId, SourceModel, TargetModel) {\n    this.SourceModel = SourceModel;\n    this.TargetModel = TargetModel;\n    this.SourceChange = SourceModel.getChangeModel();\n    this.TargetChange = TargetModel.getChangeModel();\n    this.modelId = modelId;\n  }\n\n  /**\n   * Fetch the conflicting models.\n   *\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {PersistedModel} source\n   * @param {PersistedModel} target\n   */\n\n  Conflict.prototype.models = function(cb) {\n    const conflict = this;\n    const SourceModel = this.SourceModel;\n    const TargetModel = this.TargetModel;\n    let source, target;\n\n    async.parallel([\n      getSourceModel,\n      getTargetModel,\n    ], done);\n\n    function getSourceModel(cb) {\n      SourceModel.findById(conflict.modelId, function(err, model) {\n        if (err) return cb(err);\n        source = model;\n        cb();\n      });\n    }\n\n    function getTargetModel(cb) {\n      TargetModel.findById(conflict.modelId, function(err, model) {\n        if (err) return cb(err);\n        target = model;\n        cb();\n      });\n    }\n\n    function done(err) {\n      if (err) return cb(err);\n      cb(null, source, target);\n    }\n  };\n\n  /**\n   * Get the conflicting changes.\n   *\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {Change} sourceChange\n   * @param {Change} targetChange\n   */\n\n  Conflict.prototype.changes = function(cb) {\n    const conflict = this;\n    let sourceChange, targetChange;\n\n    async.parallel([\n      getSourceChange,\n      getTargetChange,\n    ], done);\n\n    function getSourceChange(cb) {\n      const SourceModel = conflict.SourceModel;\n      SourceModel.findLastChange(conflict.modelId, function(err, change) {\n        if (err) return cb(err);\n        sourceChange = change;\n        cb();\n      });\n    }\n\n    function getTargetChange(cb) {\n      const TargetModel = conflict.TargetModel;\n      TargetModel.findLastChange(conflict.modelId, function(err, change) {\n        if (err) return cb(err);\n        targetChange = change;\n        cb();\n      });\n    }\n\n    function done(err) {\n      if (err) return cb(err);\n      cb(null, sourceChange, targetChange);\n    }\n  };\n\n  /**\n   * Resolve the conflict.\n   *\n   * Set the source change's previous revision to the current revision of the\n   * (conflicting) target change. Since the changes are no longer conflicting\n   * and appear as if the source change was based on the target, they will be\n   * replicated normally as part of the next replicate() call.\n   *\n   * This is effectively resolving the conflict using the source version.\n   *\n   * @callback {Function} callback\n   * @param {Error} err\n   */\n\n  Conflict.prototype.resolve = function(cb) {\n    const conflict = this;\n    conflict.TargetModel.findLastChange(\n      this.modelId,\n      function(err, targetChange) {\n        if (err) return cb(err);\n        conflict.SourceModel.updateLastChange(\n          conflict.modelId,\n          {prev: targetChange.rev},\n          cb,\n        );\n      },\n    );\n  };\n\n  /**\n   * Resolve the conflict using the instance data in the source model.\n   *\n   * @callback {Function} callback\n   * @param {Error} err\n   */\n  Conflict.prototype.resolveUsingSource = function(cb) {\n    this.resolve(function(err) {\n      // don't forward any cb arguments from resolve()\n      cb(err);\n    });\n  };\n\n  /**\n   * Resolve the conflict using the instance data in the target model.\n   *\n   * @callback {Function} callback\n   * @param {Error} err\n   */\n  Conflict.prototype.resolveUsingTarget = function(cb) {\n    const conflict = this;\n\n    conflict.models(function(err, source, target) {\n      if (err) return done(err);\n      if (target === null) {\n        return conflict.SourceModel.deleteById(conflict.modelId, done);\n      }\n      const inst = new conflict.SourceModel(\n        target.toObject(),\n        {persisted: true},\n      );\n      inst.save(done);\n    });\n\n    function done(err) {\n      // don't forward any cb arguments from internal calls\n      cb(err);\n    }\n  };\n\n  /**\n   * Return a new Conflict instance with swapped Source and Target models.\n   *\n   * This is useful when resolving a conflict in one-way\n   * replication, where the source data must not be changed:\n   *\n   * ```js\n   * conflict.swapParties().resolveUsingTarget(cb);\n   * ```\n   *\n   * @returns {Conflict} A new Conflict instance.\n   */\n  Conflict.prototype.swapParties = function() {\n    const Ctor = this.constructor;\n    return new Ctor(this.modelId, this.TargetModel, this.SourceModel);\n  };\n\n  /**\n   * Resolve the conflict using the supplied instance data.\n   *\n   * @param {Object} data The set of changes to apply on the model\n   * instance. Use `null` value to delete the source instance instead.\n   * @callback {Function} callback\n   * @param {Error} err\n   */\n\n  Conflict.prototype.resolveManually = function(data, cb) {\n    const conflict = this;\n    if (!data) {\n      return conflict.SourceModel.deleteById(conflict.modelId, done);\n    }\n\n    conflict.models(function(err, source, target) {\n      if (err) return done(err);\n      const inst = source || new conflict.SourceModel(target);\n      inst.setAttributes(data);\n      inst.save(function(err) {\n        if (err) return done(err);\n        conflict.resolve(done);\n      });\n    });\n\n    function done(err) {\n      // don't forward any cb arguments from internal calls\n      cb(err);\n    }\n  };\n\n  /**\n   * Determine the conflict type.\n   *\n   * Possible results are\n   *\n   *  - `Change.UPDATE`: Source and target models were updated.\n   *  - `Change.DELETE`: Source and or target model was deleted.\n   *  - `Change.UNKNOWN`: the conflict type is uknown or due to an error.\n   *\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {String} type The conflict type.\n   */\n\n  Conflict.prototype.type = function(cb) {\n    const conflict = this;\n    this.changes(function(err, sourceChange, targetChange) {\n      if (err) return cb(err);\n      const sourceChangeType = sourceChange.type();\n      const targetChangeType = targetChange.type();\n      if (sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) {\n        return cb(null, Change.UPDATE);\n      }\n      if (sourceChangeType === Change.DELETE || targetChangeType === Change.DELETE) {\n        return cb(null, Change.DELETE);\n      }\n      return cb(null, Change.UNKNOWN);\n    });\n  };\n};\n"
  },
  {
    "path": "common/models/change.json",
    "content": "{\n  \"name\": \"Change\",\n  \"trackChanges\": false,\n  \"properties\": {\n    \"id\": {\n      \"type\": \"string\",\n      \"id\": true\n    },\n    \"rev\": {\n      \"type\": \"string\"\n    },\n    \"prev\": {\n      \"type\": \"string\"\n    },\n    \"checkpoint\": {\n      \"type\": \"number\"\n    },\n    \"modelName\": {\n      \"type\": \"string\"\n    },\n    \"modelId\": {\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "common/models/checkpoint.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/**\n * Module Dependencies.\n */\n\n'use strict';\nconst assert = require('assert');\n\n/**\n * Checkpoint list entry.\n *\n * @property id {Number} the sequencial identifier of a checkpoint\n * @property time {Number} the time when the checkpoint was created\n * @property sourceId {String}  the source identifier\n *\n * @class Checkpoint\n * @inherits {PersistedModel}\n */\n\nmodule.exports = function(Checkpoint) {\n  // Workaround for https://github.com/strongloop/loopback/issues/292\n  Checkpoint.definition.rawProperties.time.default =\n    Checkpoint.definition.properties.time.default = function() {\n      return new Date();\n    };\n\n  /**\n   * Get the current checkpoint id\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {Number} checkpoint The current checkpoint seq\n   */\n  Checkpoint.current = function(cb) {\n    const Checkpoint = this;\n    Checkpoint._getSingleton(function(err, cp) {\n      cb(err, cp.seq);\n    });\n  };\n\n  Checkpoint._getSingleton = function(cb) {\n    const query = {limit: 1}; // match all instances, return only one\n    const initialData = {seq: 1};\n    this.findOrCreate(query, initialData, cb);\n  };\n\n  /**\n   * Increase the current checkpoint if it already exists otherwise initialize it\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {Object} checkpoint The current checkpoint\n   */\n  Checkpoint.bumpLastSeq = function(cb) {\n    const Checkpoint = this;\n    Checkpoint._getSingleton(function(err, cp) {\n      if (err) return cb(err);\n      const originalSeq = cp.seq;\n      cp.seq++;\n      // Update the checkpoint but only if it was not changed under our hands\n      Checkpoint.updateAll({id: cp.id, seq: originalSeq}, {seq: cp.seq}, function(err, info) {\n        if (err) return cb(err);\n        // possible outcomes\n        // 1) seq was updated to seq+1 - exactly what we wanted!\n        // 2) somebody else already updated seq to seq+1 and our call was a no-op.\n        //   That should be ok, checkpoints are time based, so we reuse the one created just now\n        //  3) seq was bumped more than once, so we will be using a value that is behind the latest seq.\n        //    @bajtos is not entirely sure if this is ok, but since it wasn't handled by the current implementation either,\n        //    he thinks we can keep it this way.\n        cb(null, cp);\n      });\n    });\n  };\n};\n"
  },
  {
    "path": "common/models/checkpoint.json",
    "content": "{\n  \"name\": \"Checkpoint\",\n  \"properties\": {\n    \"seq\": {\n      \"type\": \"number\"\n    },\n    \"time\": {\n      \"type\": \"date\"\n    },\n    \"sourceId\": {\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "common/models/email.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('../../lib/globalize');\n\n/**\n* Email model.  Extends LoopBack base [Model](#model-new-model).\n* @property {String} to Email addressee.  Required.\n* @property {String} from Email sender address.  Required.\n* @property {String} subject Email subject string.  Required.\n* @property {String} text Text body of email.\n* @property {String} html HTML body of email.\n*\n* @class Email\n* @inherits {Model}\n*/\n\nmodule.exports = function(Email) {\n  /**\n   * Send an email with the given `options`.\n   *\n   * Example Options:\n   *\n   * ```js\n   * {\n   *   from: \"Fred Foo <foo@blurdybloop.com>\", // sender address\n   *   to: \"bar@blurdybloop.com, baz@blurdybloop.com\", // list of receivers\n   *   subject: \"Hello\", // Subject line\n   *   text: \"Hello world\", // plaintext body\n   *   html: \"<b>Hello world</b>\" // html body\n   * }\n   * ```\n   *\n   * See https://github.com/andris9/Nodemailer for other supported options.\n   *\n   * @options {Object} options See below\n   * @prop {String} from Senders's email address\n   * @prop {String} to List of one or more recipient email addresses (comma-delimited)\n   * @prop {String} subject Subject line\n   * @prop {String} text Body text\n   * @prop {String} html Body HTML (optional)\n   * @param {Function} callback Called after the e-mail is sent or the sending failed\n   */\n\n  Email.send = function() {\n    throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector'));\n  };\n\n  /**\n   * A shortcut for Email.send(this).\n   */\n  Email.prototype.send = function() {\n    throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector'));\n  };\n};\n"
  },
  {
    "path": "common/models/email.json",
    "content": "{\n  \"name\": \"Email\",\n  \"base\": \"Model\",\n  \"properties\": {\n    \"to\": {\"type\": \"String\", \"required\": true},\n    \"from\": {\"type\": \"String\", \"required\": true},\n    \"subject\": {\"type\": \"String\", \"required\": true},\n    \"text\": {\"type\": \"String\"},\n    \"html\": {\"type\": \"String\"}\n  }\n}\n"
  },
  {
    "path": "common/models/key-value-model.js",
    "content": "// Copyright IBM Corp. 2016,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('../../lib/globalize');\n\n/**\n * Data model for key-value databases.\n *\n * @class KeyValueModel\n * @inherits {Model}\n */\n\nmodule.exports = function(KeyValueModel) {\n  /**\n   * Return the value associated with a given key.\n   *\n   * @param {String} key Key to use when searching the database.\n   * @options {Object} options\n   * @callback {Function} callback\n   * @param {Error} err Error object.\n   * @param {Any} result Value associated with the given key.\n   * @promise\n   *\n   * @header KeyValueModel.get(key, cb)\n   */\n  KeyValueModel.get = function(key, options, callback) {\n    throwNotAttached(this.modelName, 'get');\n  };\n\n  /**\n   * Persist a value and associate it with the given key.\n   *\n   * @param {String} key Key to associate with the given value.\n   * @param {Any} value Value to persist.\n   * @options {Number|Object} options Optional settings for the key-value\n   *   pair. If a Number is provided, it is set as the TTL (time to live) in ms\n   *   (milliseconds) for the key-value pair.\n   * @property {Number} ttl TTL for the key-value pair in ms.\n   * @callback {Function} callback\n   * @param {Error} err Error object.\n   * @promise\n   *\n   * @header KeyValueModel.set(key, value, cb)\n   */\n  KeyValueModel.set = function(key, value, options, callback) {\n    throwNotAttached(this.modelName, 'set');\n  };\n\n  /**\n   * Set the TTL (time to live) in ms (milliseconds) for a given key. TTL is the\n   * remaining time before a key-value pair is discarded from the database.\n   *\n   * @param {String} key Key to use when searching the database.\n   * @param {Number} ttl TTL in ms to set for the key.\n   * @options {Object} options\n   * @callback {Function} callback\n   * @param {Error} err Error object.\n   * @promise\n   *\n   * @header KeyValueModel.expire(key, ttl, cb)\n   */\n  KeyValueModel.expire = function(key, ttl, options, callback) {\n    throwNotAttached(this.modelName, 'expire');\n  };\n\n  /**\n   * Return the TTL (time to live) for a given key. TTL is the remaining time\n   * before a key-value pair is discarded from the database.\n   *\n   * @param {String} key Key to use when searching the database.\n   * @options {Object} options\n   * @callback {Function} callback\n   * @param {Error} error\n   * @param {Number} ttl Expiration time for the key-value pair. `undefined` if\n   *   TTL was not initially set.\n   * @promise\n   *\n   * @header KeyValueModel.ttl(key, cb)\n   */\n  KeyValueModel.ttl = function(key, options, callback) {\n    throwNotAttached(this.modelName, 'ttl');\n  };\n\n  /**\n   * Return all keys in the database.\n   *\n   * **WARNING**: This method is not suitable for large data sets as all\n   * key-values pairs are loaded into memory at once. For large data sets,\n   * use `iterateKeys()` instead.\n   *\n   * @param {Object} filter An optional filter object with the following\n   * @param {String} filter.match Glob string used to filter returned\n   *   keys (i.e. `userid.*`). All connectors are required to support `*` and\n   *   `?`, but may also support additional special characters specific to the\n   *   database.\n   * @param {Object} options\n   * @callback {Function} callback\n   * @promise\n   *\n   * @header KeyValueModel.keys(filter, cb)\n   */\n  KeyValueModel.keys = function(filter, options, callback) {\n    throwNotAttached(this.modelName, 'keys');\n  };\n\n  /**\n   * Asynchronously iterate all keys in the database. Similar to `.keys()` but\n   * instead allows for iteration over large data sets without having to load\n   * everything into memory at once.\n   *\n   * Callback example:\n   * ```js\n   * // Given a model named `Color` with two keys `red` and `blue`\n   * var iterator = Color.iterateKeys();\n   * it.next(function(err, key) {\n   *   // key contains `red`\n   *   it.next(function(err, key) {\n   *     // key contains `blue`\n   *   });\n   * });\n   * ```\n   *\n   * Promise example:\n   * ```js\n   * // Given a model named `Color` with two keys `red` and `blue`\n   * var iterator = Color.iterateKeys();\n   * Promise.resolve().then(function() {\n   *   return it.next();\n   * })\n   * .then(function(key) {\n   *   // key contains `red`\n   *   return it.next();\n   * });\n   * .then(function(key) {\n   *   // key contains `blue`\n   * });\n   * ```\n   *\n   * @param {Object} filter An optional filter object with the following\n   * @param {String} filter.match Glob string to use to filter returned\n   *   keys (i.e. `userid.*`). All connectors are required to support `*` and\n   *   `?`. They may also support additional special characters that are\n   *   specific to the backing database.\n   * @param {Object} options\n   * @returns {AsyncIterator} An Object implementing `next(cb) -> Promise`\n   *   function that can be used to iterate all keys.\n   *\n   * @header KeyValueModel.iterateKeys(filter)\n   */\n  KeyValueModel.iterateKeys = function(filter, options) {\n    throwNotAttached(this.modelName, 'iterateKeys');\n  };\n\n  /*!\n   * Set up remoting metadata for this model.\n   *\n   * **Notes**:\n   * - The method is called automatically by `Model.extend` and/or\n   *   `app.registry.createModel`\n   * - In general, base models use call this to ensure remote methods are\n   *   inherited correctly, see bug at\n   *   https://github.com/strongloop/loopback/issues/2350\n   */\n  KeyValueModel.setup = function() {\n    KeyValueModel.base.setup.apply(this, arguments);\n\n    this.remoteMethod('get', {\n      accepts: {\n        arg: 'key', type: 'string', required: true,\n        http: {source: 'path'},\n      },\n      returns: {arg: 'value', type: 'any', root: true},\n      http: {path: '/:key', verb: 'get'},\n      rest: {after: convertNullToNotFoundError},\n    });\n\n    this.remoteMethod('set', {\n      accepts: [\n        {arg: 'key', type: 'string', required: true,\n          http: {source: 'path'}},\n        {arg: 'value', type: 'any', required: true,\n          http: {source: 'body'}},\n        {arg: 'ttl', type: 'number',\n          http: {source: 'query'},\n          description: 'time to live in milliseconds'},\n      ],\n      http: {path: '/:key', verb: 'put'},\n    });\n\n    this.remoteMethod('expire', {\n      accepts: [\n        {arg: 'key', type: 'string', required: true,\n          http: {source: 'path'}},\n        {arg: 'ttl', type: 'number', required: true,\n          http: {source: 'form'}},\n      ],\n      http: {path: '/:key/expire', verb: 'put'},\n    });\n\n    this.remoteMethod('ttl', {\n      accepts: {\n        arg: 'key', type: 'string', required: true,\n        http: {source: 'path'},\n      },\n      returns: {arg: 'value', type: 'any', root: true},\n      http: {path: '/:key/ttl', verb: 'get'},\n    });\n\n    this.remoteMethod('keys', {\n      accepts: {\n        arg: 'filter', type: 'object', required: false,\n        http: {source: 'query'},\n      },\n      returns: {arg: 'keys', type: ['string'], root: true},\n      http: {path: '/keys', verb: 'get'},\n    });\n  };\n};\n\nfunction throwNotAttached(modelName, methodName) {\n  throw new Error(g.f(\n    'Cannot call %s.%s(). ' +\n      'The %s method has not been setup. ' +\n      'The {{KeyValueModel}} has not been correctly attached ' +\n      'to a {{DataSource}}!',\n    modelName, methodName, methodName,\n  ));\n}\n\nfunction convertNullToNotFoundError(ctx, cb) {\n  if (ctx.result !== null) return cb();\n\n  const modelName = ctx.method.sharedClass.name;\n  const id = ctx.getArgByName('id');\n  const msg = g.f('Unknown \"%s\" {{key}} \"%s\".', modelName, id);\n  const error = new Error(msg);\n  error.statusCode = error.status = 404;\n  error.code = 'KEY_NOT_FOUND';\n  cb(error);\n}\n"
  },
  {
    "path": "common/models/key-value-model.json",
    "content": "{\n  \"name\": \"KeyValueModel\",\n  \"base\": \"Model\"\n}\n"
  },
  {
    "path": "common/models/role-mapping.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../lib/loopback');\nconst utils = require('../../lib/utils');\n\n/**\n * The `RoleMapping` model extends from the built in `loopback.Model` type.\n *\n * @property {String} id Generated ID.\n * @property {String} name Name of the role.\n * @property {String} Description Text description.\n *\n * @class RoleMapping\n * @inherits {PersistedModel}\n */\n\nmodule.exports = function(RoleMapping) {\n  // Principal types\n  RoleMapping.USER = 'USER';\n  RoleMapping.APP = RoleMapping.APPLICATION = 'APP';\n  RoleMapping.ROLE = 'ROLE';\n\n  RoleMapping.resolveRelatedModels = function() {\n    if (!this.userModel) {\n      const reg = this.registry;\n      this.roleModel = reg.getModelByType('Role');\n      this.userModel = reg.getModelByType('User');\n      this.applicationModel = reg.getModelByType('Application');\n    }\n  };\n\n  /**\n   * Get the application principal\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {Application} application\n   */\n  RoleMapping.prototype.application = function(callback) {\n    callback = callback || utils.createPromiseCallback();\n    this.constructor.resolveRelatedModels();\n\n    if (this.principalType === RoleMapping.APPLICATION) {\n      const applicationModel = this.constructor.applicationModel;\n      applicationModel.findById(this.principalId, callback);\n    } else {\n      process.nextTick(function() {\n        callback(null, null);\n      });\n    }\n    return callback.promise;\n  };\n\n  /**\n   * Get the user principal\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {User} user\n   */\n  RoleMapping.prototype.user = function(callback) {\n    callback = callback || utils.createPromiseCallback();\n    this.constructor.resolveRelatedModels();\n    let userModel;\n\n    if (this.principalType === RoleMapping.USER) {\n      userModel = this.constructor.userModel;\n      userModel.findById(this.principalId, callback);\n      return callback.promise;\n    }\n\n    // try resolving a user model that matches principalType\n    userModel = this.constructor.registry.findModel(this.principalType);\n    if (userModel) {\n      userModel.findById(this.principalId, callback);\n    } else {\n      process.nextTick(function() {\n        callback(null, null);\n      });\n    }\n    return callback.promise;\n  };\n\n  /**\n   * Get the child role principal\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {User} childUser\n   */\n  RoleMapping.prototype.childRole = function(callback) {\n    callback = callback || utils.createPromiseCallback();\n    this.constructor.resolveRelatedModels();\n\n    if (this.principalType === RoleMapping.ROLE) {\n      const roleModel = this.constructor.roleModel;\n      roleModel.findById(this.principalId, callback);\n    } else {\n      process.nextTick(function() {\n        callback(null, null);\n      });\n    }\n    return callback.promise;\n  };\n};\n"
  },
  {
    "path": "common/models/role-mapping.json",
    "content": "{\n  \"name\": \"RoleMapping\",\n  \"description\": \"Map principals to roles\",\n  \"properties\": {\n    \"id\": {\n      \"type\": \"string\",\n      \"id\": true,\n      \"generated\": true\n    },\n    \"principalType\": {\n      \"type\": \"string\",\n      \"description\": \"The principal type, such as USER, APPLICATION, ROLE, or user model name in case of multiple user models\"\n    },\n    \"principalId\": {\n      \"type\": \"string\",\n      \"index\": true\n    }\n  },\n  \"relations\": {\n    \"role\": {\n      \"type\": \"belongsTo\",\n      \"model\": \"Role\",\n      \"foreignKey\": \"roleId\"\n    }\n  }\n}\n"
  },
  {
    "path": "common/models/role.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../lib/loopback');\nconst debug = require('debug')('loopback:security:role');\nconst assert = require('assert');\nconst async = require('async');\nconst utils = require('../../lib/utils');\nconst ctx = require('../../lib/access-context');\nconst AccessContext = ctx.AccessContext;\nconst Principal = ctx.Principal;\nconst RoleMapping = loopback.RoleMapping;\n\nassert(RoleMapping, 'RoleMapping model must be defined before Role model');\n\n/**\n * The Role model\n * @class Role\n * @header Role object\n */\nmodule.exports = function(Role) {\n  Role.resolveRelatedModels = function() {\n    if (!this.userModel) {\n      const reg = this.registry;\n      this.roleMappingModel = reg.getModelByType('RoleMapping');\n      this.userModel = reg.getModelByType('User');\n      this.applicationModel = reg.getModelByType('Application');\n    }\n  };\n\n  // Set up the connection to users/applications/roles once the model\n  Role.once('dataSourceAttached', function(roleModel) {\n    ['users', 'applications', 'roles'].forEach(function(rel) {\n      /**\n       * Fetch all users assigned to this role\n       * @function Role.prototype#users\n       * @param {object} [query] query object passed to model find call\n       * @callback {Function} [callback] The callback function\n       * @param {String|Error} err The error string or object\n       * @param {Array} list The list of users.\n       * @promise\n       */\n      /**\n       * Fetch all applications assigned to this role\n       * @function Role.prototype#applications\n       * @param {object} [query] query object passed to model find call\n       * @callback {Function} [callback] The callback function\n       * @param {String|Error} err The error string or object\n       * @param {Array} list The list of applications.\n       * @promise\n       */\n      /**\n       * Fetch all roles assigned to this role\n       * @function Role.prototype#roles\n       * @param {object} [query] query object passed to model find call\n       * @callback {Function} [callback] The callback function\n       * @param {String|Error} err The error string or object\n       * @param {Array} list The list of roles.\n       * @promise\n       */\n      Role.prototype[rel] = function(query, callback) {\n        if (!callback) {\n          if (typeof query === 'function') {\n            callback = query;\n            query = {};\n          } else {\n            callback = utils.createPromiseCallback();\n          }\n        }\n        query = query || {};\n        query.where = query.where || {};\n\n        roleModel.resolveRelatedModels();\n        const relsToModels = {\n          users: roleModel.userModel,\n          applications: roleModel.applicationModel,\n          roles: roleModel,\n        };\n\n        const ACL = loopback.ACL;\n        const relsToTypes = {\n          users: ACL.USER,\n          applications: ACL.APP,\n          roles: ACL.ROLE,\n        };\n\n        let principalModel = relsToModels[rel];\n        let principalType = relsToTypes[rel];\n\n        // redefine user model and user type if user principalType is custom (available and not \"USER\")\n        const isCustomUserPrincipalType = rel === 'users' &&\n          query.where.principalType &&\n          query.where.principalType !== RoleMapping.USER;\n\n        if (isCustomUserPrincipalType) {\n          const registry = this.constructor.registry;\n          principalModel = registry.findModel(query.where.principalType);\n          principalType = query.where.principalType;\n        }\n        // make sure we don't keep principalType in userModel query\n        delete query.where.principalType;\n\n        if (principalModel) {\n          listByPrincipalType(this, principalModel, principalType, query, callback);\n        } else {\n          process.nextTick(function() {\n            callback(null, []);\n          });\n        }\n        return callback.promise;\n      };\n    });\n\n    /**\n     * Fetch all models assigned to this role\n     * @private\n     * @param {object} Context role context\n     * @param {*} model model type to fetch\n     * @param {String} [principalType] principalType used in the rolemapping for model\n     * @param {object} [query] query object passed to model find call\n     * @param  {Function} [callback] callback function called with `(err, models)` arguments.\n     */\n    function listByPrincipalType(context, model, principalType, query, callback) {\n      if (callback === undefined && typeof query === 'function') {\n        callback = query;\n        query = {};\n      }\n      query = query || {};\n\n      roleModel.roleMappingModel.find({\n        where: {roleId: context.id, principalType: principalType},\n      }, function(err, mappings) {\n        if (err) {\n          return callback(err);\n        }\n        const ids = mappings.map(function(m) {\n          return m.principalId;\n        });\n        query.where = query.where || {};\n        query.where.id = {inq: ids};\n        model.find(query, function(err, models) {\n          callback(err, models);\n        });\n      });\n    }\n  });\n\n  // Special roles\n  Role.OWNER = '$owner'; // owner of the object\n  Role.RELATED = '$related'; // any User with a relationship to the object\n  Role.AUTHENTICATED = '$authenticated'; // authenticated user\n  Role.UNAUTHENTICATED = '$unauthenticated'; // authenticated user\n  Role.EVERYONE = '$everyone'; // everyone\n\n  /**\n   * Add custom handler for roles.\n   * @param {String} role Name of role.\n   * @param {Function} resolver Function that determines\n   * if a principal is in the specified role.\n   * Should provide a callback or return a promise.\n   */\n  Role.registerResolver = function(role, resolver) {\n    if (!Role.resolvers) {\n      Role.resolvers = {};\n    }\n    Role.resolvers[role] = resolver;\n  };\n\n  Role.registerResolver(Role.OWNER, function(role, context, callback) {\n    if (!context || !context.model || !context.modelId) {\n      process.nextTick(function() {\n        if (callback) callback(null, false);\n      });\n      return;\n    }\n    const modelClass = context.model;\n    const modelId = context.modelId;\n    const user = context.getUser();\n    const userId = user && user.id;\n    const principalType = user && user.principalType;\n    const opts = {accessToken: context.accessToken};\n    Role.isOwner(modelClass, modelId, userId, principalType, opts, callback);\n  });\n\n  function isUserClass(modelClass) {\n    if (!modelClass) return false;\n    const User = modelClass.modelBuilder.models.User;\n    if (!User) return false;\n    return modelClass == User || modelClass.prototype instanceof User;\n  }\n\n  /*!\n   * Check if two user IDs matches\n   * @param {*} id1\n   * @param {*} id2\n   * @returns {boolean}\n   */\n  function matches(id1, id2) {\n    if (id1 === undefined || id1 === null || id1 === '' ||\n      id2 === undefined || id2 === null || id2 === '') {\n      return false;\n    }\n    // The id can be a MongoDB ObjectID\n    return id1 === id2 || id1.toString() === id2.toString();\n  }\n\n  /**\n   * Check if a given user ID is the owner the model instance.\n   * @param {Function} modelClass The model class\n   * @param {*} modelId The model ID\n   * @param {*} userId The user ID\n   * @param {String} principalType The user principalType (optional)\n   * @options {Object} options\n   * @property {accessToken} The access token used to authorize the current user.\n   * @callback {Function} [callback] The callback function\n   * @param {String|Error} err The error string or object\n   * @param {Boolean} isOwner True if the user is an owner.\n   * @promise\n   */\n  Role.isOwner = function isOwner(modelClass, modelId, userId, principalType, options, callback) {\n    const _this = this;\n\n    if (!callback && typeof options === 'function') {\n      callback = options;\n      options = {};\n    } else if (!callback && typeof principalType === 'function') {\n      callback = principalType;\n      principalType = undefined;\n      options = {};\n    }\n    principalType = principalType || Principal.USER;\n\n    assert(modelClass, 'Model class is required');\n    if (!callback) callback = utils.createPromiseCallback();\n\n    debug('isOwner(): %s %s userId: %s principalType: %s',\n      modelClass && modelClass.modelName, modelId, userId, principalType);\n\n    // Resolve isOwner false if userId is missing\n    if (!userId) {\n      debug('isOwner(): no user id was set, returning false');\n      process.nextTick(function() {\n        callback(null, false);\n      });\n      return callback.promise;\n    }\n\n    // At this stage, principalType is valid in one of 2 following condition:\n    // 1. the app has a single user model and principalType is 'USER'\n    // 2. the app has multiple user models and principalType is not 'USER'\n    // multiple user models\n    const isMultipleUsers = _isMultipleUsers();\n    const isPrincipalTypeValid =\n      (!isMultipleUsers && principalType === Principal.USER) ||\n      (isMultipleUsers && principalType !== Principal.USER);\n\n    debug('isOwner(): isMultipleUsers?', isMultipleUsers,\n      'isPrincipalTypeValid?', isPrincipalTypeValid);\n\n    // Resolve isOwner false if principalType is invalid\n    if (!isPrincipalTypeValid) {\n      process.nextTick(function() {\n        callback(null, false);\n      });\n      return callback.promise;\n    }\n\n    // Is the modelClass User or a subclass of User?\n    if (isUserClass(modelClass)) {\n      const userModelName = modelClass.modelName;\n      // matching ids is enough if principalType is USER or matches given user model name\n      if (principalType === Principal.USER || principalType === userModelName) {\n        process.nextTick(function() {\n          callback(null, matches(modelId, userId));\n        });\n        return callback.promise;\n      }\n      // otherwise continue with the regular owner resolution\n    }\n\n    modelClass.findById(modelId, options, function(err, inst) {\n      if (err || !inst) {\n        debug('Model not found for id %j', modelId);\n        return callback(err, false);\n      }\n      debug('Model found: %j', inst);\n\n      const ownerRelations = modelClass.settings.ownerRelations;\n      if (!ownerRelations) {\n        return legacyOwnershipCheck(inst);\n      } else {\n        return checkOwnership(inst);\n      }\n    });\n    return callback.promise;\n\n    // NOTE Historically, for principalType USER, we were resolving isOwner()\n    // as true if the model has \"userId\" or \"owner\" property matching\n    // id of the current user (principalId), even though there was no\n    // belongsTo relation set up.\n    // Additionaly, the original implementation did not support the\n    // possibility for a model to have multiple related users: when\n    // testing belongsTo relations, the first related user failing the\n    // ownership check induced the whole isOwner() to resolve as false.\n    // This behaviour will be pruned at next LoopBack major release.\n    function legacyOwnershipCheck(inst) {\n      const ownerId = inst.userId || inst.owner;\n      if (principalType === Principal.USER && ownerId && 'function' !== typeof ownerId) {\n        return callback(null, matches(ownerId, userId));\n      }\n\n      // Try to follow belongsTo\n      for (const r in modelClass.relations) {\n        const rel = modelClass.relations[r];\n        // relation should be belongsTo and target a User based class\n        const belongsToUser = rel.type === 'belongsTo' && isUserClass(rel.modelTo);\n        if (!belongsToUser) {\n          continue;\n        }\n\n        // checking related user\n        const relatedUser = rel.modelTo;\n        const userModelName = relatedUser.modelName;\n        const isMultipleUsers = _isMultipleUsers(relatedUser);\n        // a relation can be considered for isOwner resolution if:\n        // 1. the app has a single user model and principalType is 'USER'\n        // 2. the app has multiple user models and principalType is the related user model name\n        if ((!isMultipleUsers && principalType === Principal.USER) ||\n            (isMultipleUsers && principalType === userModelName)) {\n          debug('Checking relation %s to %s: %j', r, userModelName, rel);\n          inst[r](processRelatedUser);\n          return;\n        }\n      }\n      debug('No matching belongsTo relation found for model %j - user %j principalType %j',\n        modelId, userId, principalType);\n      callback(null, false);\n\n      function processRelatedUser(err, user) {\n        if (err || !user) return callback(err, false);\n\n        debug('User found: %j', user.id);\n        callback(null, matches(user.id, userId));\n      }\n    }\n\n    function checkOwnership(inst) {\n      const ownerRelations = inst.constructor.settings.ownerRelations;\n      // collecting related users\n      const relWithUsers = [];\n      for (const r in modelClass.relations) {\n        const rel = modelClass.relations[r];\n        // relation should be belongsTo and target a User based class\n        if (rel.type !== 'belongsTo' || !isUserClass(rel.modelTo)) {\n          continue;\n        }\n\n        // checking related user\n        const relatedUser = rel.modelTo;\n        const userModelName = relatedUser.modelName;\n        const isMultipleUsers = _isMultipleUsers(relatedUser);\n        // a relation can be considered for isOwner resolution if:\n        // 1. the app has a single user model and principalType is 'USER'\n        // 2. the app has multiple user models and principalType is the related user model name\n        // In addition, if an array of relations if provided with the ownerRelations option,\n        // then the given relation name is further checked against this array\n        if ((!isMultipleUsers && principalType === Principal.USER) ||\n            (isMultipleUsers && principalType === userModelName)) {\n          debug('Checking relation %s to %s: %j', r, userModelName, rel);\n          if (ownerRelations === true) {\n            relWithUsers.push(r);\n          } else if (Array.isArray(ownerRelations) && ownerRelations.indexOf(r) !== -1) {\n            relWithUsers.push(r);\n          }\n        }\n      }\n      if (relWithUsers.length === 0) {\n        debug('No matching belongsTo relation found for model %j and user: %j principalType: %j',\n          modelId, userId, principalType);\n        return callback(null, false);\n      }\n\n      // check related users: someSeries is used to avoid spamming the db\n      async.someSeries(relWithUsers, processRelation, callback);\n\n      function processRelation(r, cb) {\n        inst[r](function processRelatedUser(err, user) {\n          if (err || !user) return cb(err, false);\n\n          debug('User found: %j (through %j)', user.id, r);\n          cb(null, matches(user.id, userId));\n        });\n      }\n    }\n\n    // A helper function to check if the app user config is multiple users or\n    // single user. It can be used with or without a reference user model.\n    // In case no user model is provided, we use the registry to get any of the\n    // user model by type. The relation with AccessToken is used to check\n    // if polymorphism is used, and thus if multiple users.\n    function _isMultipleUsers(userModel) {\n      const oneOfUserModels = userModel || _this.registry.getModelByType('User');\n      const accessTokensRel = oneOfUserModels.relations.accessTokens;\n      return !!(accessTokensRel && accessTokensRel.polymorphic);\n    }\n  };\n\n  Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) {\n    if (!context) {\n      process.nextTick(function() {\n        if (callback) callback(null, false);\n      });\n      return;\n    }\n    Role.isAuthenticated(context, callback);\n  });\n\n  /**\n   * Check if the user ID is authenticated\n   * @param {Object} context The security context.\n   *\n   * @callback {Function} callback Callback function.\n   * @param {Error} err Error object.\n   * @param {Boolean} isAuthenticated True if the user is authenticated.\n   * @promise\n   */\n  Role.isAuthenticated = function isAuthenticated(context, callback) {\n    if (!callback) callback = utils.createPromiseCallback();\n    process.nextTick(function() {\n      if (callback) callback(null, context.isAuthenticated());\n    });\n    return callback.promise;\n  };\n\n  Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) {\n    process.nextTick(function() {\n      if (callback) callback(null, !context || !context.isAuthenticated());\n    });\n  });\n\n  Role.registerResolver(Role.EVERYONE, function(role, context, callback) {\n    process.nextTick(function() {\n      if (callback) callback(null, true); // Always true\n    });\n  });\n\n  /**\n   * Check if a given principal is in the specified role.\n   *\n   * @param {String} role The role name.\n   * @param {Object} context The context object.\n   *\n   * @callback {Function} callback Callback function.\n   * @param {Error} err Error object.\n   * @param {Boolean} isInRole True if the principal is in the specified role.\n   * @promise\n   */\n  Role.isInRole = function(role, context, callback) {\n    context.registry = this.registry;\n    if (!(context instanceof AccessContext)) {\n      context = new AccessContext(context);\n    }\n\n    if (!callback) {\n      callback = utils.createPromiseCallback();\n      // historically, isInRole is returning the Role instance instead of true\n      // we are preserving that behaviour for callback-based invocation,\n      // but fixing it when invoked in Promise mode\n      callback.promise = callback.promise.then(function(isInRole) {\n        return !!isInRole;\n      });\n    }\n\n    this.resolveRelatedModels();\n\n    debug('isInRole(): %s', role);\n    context.debug();\n\n    const resolver = Role.resolvers[role];\n    if (resolver) {\n      debug('Custom resolver found for role %s', role);\n\n      const promise = resolver(role, context, callback);\n      if (promise && typeof promise.then === 'function') {\n        promise.then(\n          function(result) { callback(null, result); },\n          callback,\n        );\n      }\n      return callback.promise;\n    }\n\n    if (context.principals.length === 0) {\n      debug('isInRole() returns: false');\n      process.nextTick(function() {\n        if (callback) callback(null, false);\n      });\n      return callback.promise;\n    }\n\n    const inRole = context.principals.some(function(p) {\n      const principalType = p.type || undefined;\n      const principalId = p.id || undefined;\n\n      // Check if it's the same role\n      return principalType === RoleMapping.ROLE && principalId === role;\n    });\n\n    if (inRole) {\n      debug('isInRole() returns: %j', inRole);\n      process.nextTick(function() {\n        if (callback) callback(null, true);\n      });\n      return callback.promise;\n    }\n\n    const roleMappingModel = this.roleMappingModel;\n    this.findOne({where: {name: role}}, function(err, result) {\n      if (err) {\n        if (callback) callback(err);\n        return;\n      }\n      if (!result) {\n        if (callback) callback(null, false);\n        return;\n      }\n      debug('Role found: %j', result);\n\n      // Iterate through the list of principals\n      async.some(context.principals, function(p, done) {\n        const principalType = p.type || undefined;\n        let principalId = p.id || undefined;\n        const roleId = result.id.toString();\n        const principalIdIsString = typeof principalId === 'string';\n\n        if (principalId !== null && principalId !== undefined && !principalIdIsString) {\n          principalId = principalId.toString();\n        }\n\n        if (principalType && principalId) {\n          roleMappingModel.findOne({where: {roleId: roleId,\n            principalType: principalType, principalId: principalId}},\n          function(err, result) {\n            debug('Role mapping found: %j', result);\n            done(!err && result); // The only arg is the result\n          });\n        } else {\n          process.nextTick(function() {\n            done(false);\n          });\n        }\n      }, function(inRole) {\n        debug('isInRole() returns: %j', inRole);\n        if (callback) callback(null, inRole);\n      });\n    });\n    return callback.promise;\n  };\n\n  /**\n   * List roles for a given principal.\n   * @param {Object} context The security context.\n   *\n   * @callback {Function} callback Callback function.\n   * @param {Error} err Error object.\n   * @param {String[]} roles An array of role IDs\n   * @promise\n   */\n  Role.getRoles = function(context, options, callback) {\n    if (!callback) {\n      if (typeof options === 'function') {\n        callback = options;\n        options = {};\n      } else {\n        callback = utils.createPromiseCallback();\n      }\n    }\n    if (!options) options = {};\n\n    context.registry = this.registry;\n    if (!(context instanceof AccessContext)) {\n      context = new AccessContext(context);\n    }\n    const roles = [];\n    this.resolveRelatedModels();\n\n    const addRole = function(role) {\n      if (role && roles.indexOf(role) === -1) {\n        roles.push(role);\n      }\n    };\n\n    const self = this;\n    // Check against the smart roles\n    const inRoleTasks = [];\n    Object.keys(Role.resolvers).forEach(function(role) {\n      inRoleTasks.push(function(done) {\n        self.isInRole(role, context, function(err, inRole) {\n          if (debug.enabled) {\n            debug('In role %j: %j', role, inRole);\n          }\n          if (!err && inRole) {\n            addRole(role);\n            done();\n          } else {\n            done(err, null);\n          }\n        });\n      });\n    });\n\n    const roleMappingModel = this.roleMappingModel;\n    context.principals.forEach(function(p) {\n      // Check against the role mappings\n      const principalType = p.type || undefined;\n      let principalId = p.id == null ? undefined : p.id;\n\n      if (typeof principalId !== 'string' && principalId != null) {\n        principalId = principalId.toString();\n      }\n\n      // Add the role itself\n      if (principalType === RoleMapping.ROLE && principalId) {\n        addRole(principalId);\n      }\n\n      if (principalType && principalId) {\n        // Please find() treat undefined matches all values\n        inRoleTasks.push(function(done) {\n          const filter = {where: {principalType: principalType, principalId: principalId}};\n          if (options.returnOnlyRoleNames === true) {\n            filter.include = ['role'];\n          }\n          roleMappingModel.find(filter, function(err, mappings) {\n            debug('Role mappings found: %s %j', err, mappings);\n            if (err) {\n              if (done) done(err);\n              return;\n            }\n            mappings.forEach(function(m) {\n              let role;\n              if (options.returnOnlyRoleNames === true) {\n                role = m.toJSON().role.name;\n              } else {\n                role = m.roleId;\n              }\n              addRole(role);\n            });\n            if (done) done();\n          });\n        });\n      }\n    });\n\n    async.parallel(inRoleTasks, function(err, results) {\n      debug('getRoles() returns: %j %j', err, roles);\n      if (callback) callback(err, roles);\n    });\n    return callback.promise;\n  };\n\n  Role.validatesUniquenessOf('name', {message: 'already exists'});\n};\n"
  },
  {
    "path": "common/models/role.json",
    "content": "{\n  \"name\": \"Role\",\n  \"properties\": {\n\n    \"id\": {\n      \"type\": \"string\",\n      \"id\": true,\n      \"generated\": true\n    },\n    \"name\": {\n      \"type\": \"string\",\n      \"required\": true\n    },\n    \"description\": \"string\",\n    \"created\": {\n      \"type\":\"date\",\n      \"defaultFn\": \"now\"\n    },\n    \"modified\": {\n      \"type\":\"date\",\n      \"defaultFn\": \"now\"\n    }\n  },\n  \"relations\": {\n    \"principals\": {\n      \"type\": \"hasMany\",\n      \"model\": \"RoleMapping\",\n      \"foreignKey\": \"roleId\"\n    }\n  }\n}\n"
  },
  {
    "path": "common/models/scope.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst loopback = require('../../lib/loopback');\n\n/**\n * Resource owner grants/delegates permissions to client applications\n *\n * For a protected resource, does the client application have the authorization\n * from the resource owner (user or system)?\n *\n * Scope has many resource access entries\n *\n * @class Scope\n */\n\nmodule.exports = function(Scope) {\n  Scope.resolveRelatedModels = function() {\n    if (!this.aclModel) {\n      const reg = this.registry;\n      this.aclModel = reg.getModelByType(loopback.ACL);\n    }\n  };\n\n  /**\n   * Check if the given scope is allowed to access the model/property\n   * @param {String} scope The scope name\n   * @param {String} model The model name\n   * @param {String} property The property/method/relation name\n   * @param {String} accessType The access type\n   * @callback {Function} callback\n   * @param {String|Error} err The error object\n   * @param {AccessRequest} result The access permission\n   */\n  Scope.checkPermission = function(scope, model, property, accessType, callback) {\n    this.resolveRelatedModels();\n    const aclModel = this.aclModel;\n    assert(aclModel,\n      'ACL model must be defined before Scope.checkPermission is called');\n\n    this.findOne({where: {name: scope}}, function(err, scope) {\n      if (err) {\n        if (callback) callback(err);\n      } else {\n        aclModel.checkPermission(\n          aclModel.SCOPE, scope.id, model, property, accessType, callback,\n        );\n      }\n    });\n  };\n};\n"
  },
  {
    "path": "common/models/scope.json",
    "content": "{\n  \"name\": \"Scope\",\n  \"description\": [\n    \"Schema for Scope which represents the permissions that are granted\",\n    \"to client applications by the resource owner\"\n  ],\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"required\": true\n    },\n    \"description\": \"string\"\n  }\n}\n"
  },
  {
    "path": "common/models/user.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Module Dependencies.\n */\n\n'use strict';\nconst g = require('../../lib/globalize');\nconst isEmail = require('isemail');\nconst loopback = require('../../lib/loopback');\nconst utils = require('../../lib/utils');\nconst path = require('path');\nconst qs = require('querystring');\nconst SALT_WORK_FACTOR = 10;\nconst crypto = require('crypto');\n// bcrypt's max length is 72 bytes;\n// See https://github.com/kelektiv/node.bcrypt.js/blob/45f498ef6dc6e8234e58e07834ce06a50ff16352/src/node_blf.h#L59\nconst MAX_PASSWORD_LENGTH = 72;\nlet bcrypt;\ntry {\n  // Try the native module first\n  bcrypt = require('bcrypt');\n  // Browserify returns an empty object\n  if (bcrypt && typeof bcrypt.compare !== 'function') {\n    bcrypt = require('bcryptjs');\n  }\n} catch (err) {\n  // Fall back to pure JS impl\n  bcrypt = require('bcryptjs');\n}\n\nconst DEFAULT_TTL = 1209600; // 2 weeks in seconds\nconst DEFAULT_RESET_PW_TTL = 15 * 60; // 15 mins in seconds\nconst DEFAULT_MAX_TTL = 31556926; // 1 year in seconds\nconst assert = require('assert');\n\nconst debug = require('debug')('loopback:user');\n\n/**\n * Built-in User model.\n * Extends LoopBack [PersistedModel](#persistedmodel-new-persistedmodel).\n *\n * Default `User` ACLs.\n *\n * - DENY EVERYONE `*`\n * - ALLOW EVERYONE `create`\n * - ALLOW OWNER `deleteById`\n * - ALLOW EVERYONE `login`\n * - ALLOW EVERYONE `logout`\n * - ALLOW OWNER `findById`\n * - ALLOW OWNER `updateAttributes`\n *\n * @property {String} username Must be unique.\n * @property {String} password Hidden from remote clients.\n * @property {String} email Must be valid email.\n * @property {Boolean} emailVerified Set when a user's email has been verified via `confirm()`.\n * @property {String} verificationToken Set when `verify()` is called.\n * @property {String} realm The namespace the user belongs to. See [Partitioning users with realms](http://loopback.io/doc/en/lb2/Partitioning-users-with-realms.html) for details.\n * @property {Object} settings Extends the `Model.settings` object.\n * @property {Boolean} settings.emailVerificationRequired Require the email verification\n * process before allowing a login.\n * @property {Number} settings.ttl Default time to live (in seconds) for the `AccessToken` created by `User.login() / user.createAccessToken()`.\n * Default is `1209600` (2 weeks)\n * @property {Number} settings.maxTTL The max value a user can request a token to be alive / valid for.\n * Default is `31556926` (1 year)\n * @property {Boolean} settings.realmRequired Require a realm when logging in a user.\n * @property {String} settings.realmDelimiter When set a realm is required.\n * @property {Number} settings.resetPasswordTokenTTL Time to live for password reset `AccessToken`. Default is `900` (15 minutes).\n * @property {Number} settings.saltWorkFactor The `bcrypt` salt work factor. Default is `10`.\n * @property {Boolean} settings.caseSensitiveEmail Enable case sensitive email.\n *\n * @class User\n * @inherits {PersistedModel}\n */\n\nmodule.exports = function(User) {\n  /**\n   * Create access token for the logged in user. This method can be overridden to\n   * customize how access tokens are generated\n   *\n   * Supported flavours:\n   *\n   * ```js\n   * createAccessToken(ttl, cb)\n   * createAccessToken(ttl, options, cb);\n   * createAccessToken(options, cb);\n   * // recent addition:\n   * createAccessToken(data, options, cb);\n   * ```\n   *\n   * @options {Number|Object} [ttl|data] Either the requested ttl,\n   * or an object with token properties to set (see below).\n   * @property {Number} [ttl] The requested ttl\n   * @property {String[]} [scopes] The access scopes granted to the token.\n   * @param {Object} [options] Additional options including remoting context\n   * @callback {Function} cb The callback function\n   * @param {String|Error} err The error string or object\n   * @param {AccessToken} token The generated access token object\n   * @promise\n   *\n   */\n  User.prototype.createAccessToken = function(ttl, options, cb) {\n    if (cb === undefined && typeof options === 'function') {\n      // createAccessToken(ttl, cb)\n      cb = options;\n      options = undefined;\n    }\n\n    cb = cb || utils.createPromiseCallback();\n\n    let tokenData;\n    if (typeof ttl !== 'object') {\n      // createAccessToken(ttl[, options], cb)\n      tokenData = {ttl};\n    } else if (options) {\n      // createAccessToken(data, options, cb)\n      tokenData = ttl;\n    } else {\n      // createAccessToken(options, cb);\n      tokenData = {};\n    }\n\n    const userSettings = this.constructor.settings;\n    tokenData.ttl = Math.min(tokenData.ttl || userSettings.ttl, userSettings.maxTTL);\n    this.accessTokens.create(tokenData, options, cb);\n    return cb.promise;\n  };\n\n  function splitPrincipal(name, realmDelimiter) {\n    const parts = [null, name];\n    if (!realmDelimiter) {\n      return parts;\n    }\n    const index = name.indexOf(realmDelimiter);\n    if (index !== -1) {\n      parts[0] = name.substring(0, index);\n      parts[1] = name.substring(index + realmDelimiter.length);\n    }\n    return parts;\n  }\n\n  /**\n   * Normalize the credentials\n   * @param {Object} credentials The credential object\n   * @param {Boolean} realmRequired\n   * @param {String} realmDelimiter The realm delimiter, if not set, no realm is needed\n   * @returns {Object} The normalized credential object\n   */\n  User.normalizeCredentials = function(credentials, realmRequired, realmDelimiter) {\n    const query = {};\n    credentials = credentials || {};\n    if (!realmRequired) {\n      if (credentials.email) {\n        query.email = credentials.email;\n      } else if (credentials.username) {\n        query.username = credentials.username;\n      }\n    } else {\n      if (credentials.realm) {\n        query.realm = credentials.realm;\n      }\n      let parts;\n      if (credentials.email) {\n        parts = splitPrincipal(credentials.email, realmDelimiter);\n        query.email = parts[1];\n        if (parts[0]) {\n          query.realm = parts[0];\n        }\n      } else if (credentials.username) {\n        parts = splitPrincipal(credentials.username, realmDelimiter);\n        query.username = parts[1];\n        if (parts[0]) {\n          query.realm = parts[0];\n        }\n      }\n    }\n    return query;\n  };\n\n  /**\n   * Login a user by with the given `credentials`.\n   *\n   * ```js\n   *    User.login({username: 'foo', password: 'bar'}, function (err, token) {\n   *      console.log(token.id);\n   *    });\n   * ```\n   *\n   * If the `emailVerificationRequired` flag is set for the inherited user model\n   * and the email has not yet been verified then the method will return a 401\n   * error that will contain the user's id. This id can be used to call the\n   * `api/verify` remote method to generate a new email verification token and\n   * send back the related email to the user.\n   *\n   * @param {Object} credentials username/password or email/password\n   * @param {String[]|String} [include] Optionally set it to \"user\" to include\n   * the user info\n   * @callback {Function} callback Callback function\n   * @param {Error} err Error object\n   * @param {AccessToken} token Access token if login is successful\n   * @promise\n   */\n\n  User.login = function(credentials, include, fn) {\n    const self = this;\n    if (typeof include === 'function') {\n      fn = include;\n      include = undefined;\n    }\n\n    fn = fn || utils.createPromiseCallback();\n\n    include = (include || '');\n    if (Array.isArray(include)) {\n      include = include.map(function(val) {\n        return val.toLowerCase();\n      });\n    } else {\n      include = include.toLowerCase();\n    }\n\n    let realmDelimiter;\n    // Check if realm is required\n    const realmRequired = !!(self.settings.realmRequired ||\n      self.settings.realmDelimiter);\n    if (realmRequired) {\n      realmDelimiter = self.settings.realmDelimiter;\n    }\n    const query = self.normalizeCredentials(credentials, realmRequired,\n      realmDelimiter);\n\n    if (realmRequired) {\n      if (!query.realm) {\n        const err1 = new Error(g.f('{{realm}} is required'));\n        err1.statusCode = 400;\n        err1.code = 'REALM_REQUIRED';\n        fn(err1);\n        return fn.promise;\n      } else if (typeof query.realm !== 'string') {\n        const err5 = new Error(g.f('Invalid realm'));\n        err5.statusCode = 400;\n        err5.code = 'INVALID_REALM';\n        fn(err5);\n        return fn.promise;\n      }\n    }\n    if (!query.email && !query.username) {\n      const err2 = new Error(g.f('{{username}} or {{email}} is required'));\n      err2.statusCode = 400;\n      err2.code = 'USERNAME_EMAIL_REQUIRED';\n      fn(err2);\n      return fn.promise;\n    }\n    if (query.username && typeof query.username !== 'string') {\n      const err3 = new Error(g.f('Invalid username'));\n      err3.statusCode = 400;\n      err3.code = 'INVALID_USERNAME';\n      fn(err3);\n      return fn.promise;\n    } else if (query.email && typeof query.email !== 'string') {\n      const err4 = new Error(g.f('Invalid email'));\n      err4.statusCode = 400;\n      err4.code = 'INVALID_EMAIL';\n      fn(err4);\n      return fn.promise;\n    }\n\n    self.findOne({where: query}, function(err, user) {\n      const defaultError = new Error(g.f('login failed'));\n      defaultError.statusCode = 401;\n      defaultError.code = 'LOGIN_FAILED';\n\n      function tokenHandler(err, token) {\n        if (err) return fn(err);\n        if (Array.isArray(include) ? include.indexOf('user') !== -1 : include === 'user') {\n          // NOTE(bajtos) We can't set token.user here:\n          //  1. token.user already exists, it's a function injected by\n          //     \"AccessToken belongsTo User\" relation\n          //  2. ModelBaseClass.toJSON() ignores own properties, thus\n          //     the value won't be included in the HTTP response\n          // See also loopback#161 and loopback#162\n          token.__data.user = user;\n        }\n        fn(err, token);\n      }\n\n      if (err) {\n        debug('An error is reported from User.findOne: %j', err);\n        fn(defaultError);\n      } else if (user) {\n        user.hasPassword(credentials.password, function(err, isMatch) {\n          if (err) {\n            debug('An error is reported from User.hasPassword: %j', err);\n            fn(defaultError);\n          } else if (isMatch) {\n            if (self.settings.emailVerificationRequired && !user.emailVerified) {\n              // Fail to log in if email verification is not done yet\n              debug('User email has not been verified');\n              err = new Error(g.f('login failed as the email has not been verified'));\n              err.statusCode = 401;\n              err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED';\n              err.details = {\n                userId: user.id,\n              };\n              fn(err);\n            } else {\n              if (user.createAccessToken.length === 2) {\n                user.createAccessToken(credentials.ttl, tokenHandler);\n              } else {\n                user.createAccessToken(credentials.ttl, credentials, tokenHandler);\n              }\n            }\n          } else {\n            debug('The password is invalid for user %s', query.email || query.username);\n            fn(defaultError);\n          }\n        });\n      } else {\n        debug('No matching record is found for user %s', query.email || query.username);\n        fn(defaultError);\n      }\n    });\n    return fn.promise;\n  };\n\n  /**\n   * Logout a user with the given accessToken id.\n   *\n   * ```js\n   *    User.logout('asd0a9f8dsj9s0s3223mk', function (err) {\n  *      console.log(err || 'Logged out');\n  *    });\n   * ```\n   *\n   * @param {String} accessTokenID\n   * @callback {Function} callback\n   * @param {Error} err\n   * @promise\n   */\n\n  User.logout = function(tokenId, fn) {\n    fn = fn || utils.createPromiseCallback();\n\n    let err;\n    if (!tokenId) {\n      err = new Error(g.f('{{accessToken}} is required to logout'));\n      err.statusCode = 401;\n      process.nextTick(fn, err);\n      return fn.promise;\n    }\n\n    this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {\n      if (err) {\n        fn(err);\n      } else if ('count' in info && info.count === 0) {\n        err = new Error(g.f('Could not find {{accessToken}}'));\n        err.statusCode = 401;\n        fn(err);\n      } else {\n        fn();\n      }\n    });\n    return fn.promise;\n  };\n\n  User.observe('before delete', function(ctx, next) {\n    // Do nothing when the access control was disabled for this user model.\n    if (!ctx.Model.relations.accessTokens) return next();\n\n    const AccessToken = ctx.Model.relations.accessTokens.modelTo;\n    const pkName = ctx.Model.definition.idName() || 'id';\n    ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {\n      if (err) return next(err);\n\n      const ids = list.map(function(u) { return u[pkName]; });\n      ctx.where = {};\n      ctx.where[pkName] = {inq: ids};\n\n      AccessToken.destroyAll({userId: {inq: ids}}, next);\n    });\n  });\n\n  /**\n   * Compare the given `password` with the users hashed password.\n   *\n   * @param {String} password The plain text password\n   * @callback {Function} callback Callback function\n   * @param {Error} err Error object\n   * @param {Boolean} isMatch Returns true if the given `password` matches record\n   * @promise\n   */\n\n  User.prototype.hasPassword = function(plain, fn) {\n    fn = fn || utils.createPromiseCallback();\n    if (this.password && plain) {\n      bcrypt.compare(plain, this.password, function(err, isMatch) {\n        if (err) return fn(err);\n        fn(null, isMatch);\n      });\n    } else {\n      fn(null, false);\n    }\n    return fn.promise;\n  };\n\n  /**\n   * Change this user's password.\n   *\n   * @param {*} userId Id of the user changing the password\n   * @param {string} oldPassword Current password, required in order\n   *   to strongly verify the identity of the requesting user\n   * @param {string} newPassword The new password to use.\n   * @param {object} [options]\n   * @callback {Function} callback\n   * @param {Error} err Error object\n   * @promise\n   */\n  User.changePassword = function(userId, oldPassword, newPassword, options, cb) {\n    if (cb === undefined && typeof options === 'function') {\n      cb = options;\n      options = undefined;\n    }\n    cb = cb || utils.createPromiseCallback();\n\n    // Make sure to use the constructor of the (sub)class\n    // where the method is invoked from (`this` instead of `User`)\n    this.findById(userId, options, (err, inst) => {\n      if (err) return cb(err);\n\n      if (!inst) {\n        const err = new Error(`User ${userId} not found`);\n        Object.assign(err, {\n          code: 'USER_NOT_FOUND',\n          statusCode: 401,\n        });\n        return cb(err);\n      }\n\n      inst.changePassword(oldPassword, newPassword, options, cb);\n    });\n\n    return cb.promise;\n  };\n\n  /**\n   * Change this user's password (prototype/instance version).\n   *\n   * @param {string} oldPassword Current password, required in order\n   *   to strongly verify the identity of the requesting user\n   * @param {string} newPassword The new password to use.\n   * @param {object} [options]\n   * @callback {Function} callback\n   * @param {Error} err Error object\n   * @promise\n   */\n  User.prototype.changePassword = function(oldPassword, newPassword, options, cb) {\n    if (cb === undefined && typeof options === 'function') {\n      cb = options;\n      options = undefined;\n    }\n    cb = cb || utils.createPromiseCallback();\n\n    this.hasPassword(oldPassword, (err, isMatch) => {\n      if (err) return cb(err);\n      if (!isMatch) {\n        const err = new Error('Invalid current password');\n        Object.assign(err, {\n          code: 'INVALID_PASSWORD',\n          statusCode: 400,\n        });\n        return cb(err);\n      }\n\n      this.setPassword(newPassword, options, cb);\n    });\n    return cb.promise;\n  };\n\n  /**\n   * Set this user's password after a password-reset request was made.\n   *\n   * @param {*} userId Id of the user changing the password\n   * @param {string} newPassword The new password to use.\n   * @param {Object} [options] Additional options including remoting context\n   * @callback {Function} callback\n   * @param {Error} err Error object\n   * @promise\n   */\n  User.setPassword = function(userId, newPassword, options, cb) {\n    assert(userId != null && userId !== '', 'userId is a required argument');\n    assert(!!newPassword, 'newPassword is a required argument');\n\n    if (cb === undefined && typeof options === 'function') {\n      cb = options;\n      options = undefined;\n    }\n    cb = cb || utils.createPromiseCallback();\n\n    // Make sure to use the constructor of the (sub)class\n    // where the method is invoked from (`this` instead of `User`)\n    this.findById(userId, options, (err, inst) => {\n      if (err) return cb(err);\n\n      if (!inst) {\n        const err = new Error(`User ${userId} not found`);\n        Object.assign(err, {\n          code: 'USER_NOT_FOUND',\n          statusCode: 401,\n        });\n        return cb(err);\n      }\n\n      inst.setPassword(newPassword, options, cb);\n    });\n\n    return cb.promise;\n  };\n\n  /**\n   * Set this user's password. The callers of this method\n   * must ensure the client making the request is authorized\n   * to change the password, typically by providing the correct\n   * current password or a password-reset token.\n   *\n   * @param {string} newPassword The new password to use.\n   * @param {Object} [options] Additional options including remoting context\n   * @callback {Function} callback\n   * @param {Error} err Error object\n   * @promise\n   */\n  User.prototype.setPassword = function(newPassword, options, cb) {\n    assert(!!newPassword, 'newPassword is a required argument');\n\n    if (cb === undefined && typeof options === 'function') {\n      cb = options;\n      options = undefined;\n    }\n    cb = cb || utils.createPromiseCallback();\n\n    try {\n      this.constructor.validatePassword(newPassword);\n    } catch (err) {\n      cb(err);\n      return cb.promise;\n    }\n\n    // We need to modify options passed to patchAttributes, but we don't want\n    // to modify the original options object passed to us by setPassword caller\n    options = Object.assign({}, options);\n\n    // patchAttributes() does not allow callers to modify the password property\n    // unless \"options.setPassword\" is set.\n    options.setPassword = true;\n\n    const delta = {password: newPassword};\n    this.patchAttributes(delta, options, (err, updated) => cb(err));\n\n    return cb.promise;\n  };\n\n  /**\n   * Returns default verification options to use when calling User.prototype.verify()\n   * from remote method /user/:id/verify.\n   *\n   * NOTE: the User.getVerifyOptions() method can also be used to ease the\n   * building of identity verification options.\n   *\n   * ```js\n   * var verifyOptions = MyUser.getVerifyOptions();\n   * user.verify(verifyOptions);\n   * ```\n   *\n   * This is the full list of possible params, with example values\n   *\n   * ```js\n   * {\n   *   type: 'email',\n   *   mailer: {\n   *     send(verifyOptions, options, cb) {\n   *       // send the email\n   *       cb(err, result);\n   *     }\n   *   },\n   *   to: 'test@email.com',\n   *   from: 'noreply@email.com'\n   *   subject: 'verification email subject',\n   *   text: 'Please verify your email by opening this link in a web browser',\n   *   headers: {'Mime-Version': '1.0'},\n   *   template: 'path/to/template.ejs',\n   *   templateFn: function(verifyOptions, options, cb) {\n   *     cb(null, 'some body template');\n   *   }\n   *   redirect: '/',\n   *   verifyHref: 'http://localhost:3000/api/user/confirm',\n   *   host: 'localhost'\n   *   protocol: 'http'\n   *   port: 3000,\n   *   restApiRoot= '/api',\n   *   generateVerificationToken: function (user, options, cb) {\n   *     cb(null, 'random-token');\n   *   }\n   * }\n   * ```\n   *\n   * NOTE: param `to` internally defaults to user's email but can be overriden for\n   * test purposes or advanced customization.\n   *\n   * Static default params can be modified in your custom user model json definition\n   * using `settings.verifyOptions`. Any default param can be programmatically modified\n   * like follows:\n   *\n   * ```js\n   * customUserModel.getVerifyOptions = function() {\n   *   const base = MyUser.base.getVerifyOptions();\n   *   return Object.assign({}, base, {\n   *     // custom values\n   *   });\n   * }\n   * ```\n   *\n   * Usually you should only require to modify a subset of these params\n   * See `User.verify()` and `User.prototype.verify()` doc for params reference\n   * and their default values.\n   */\n\n  User.getVerifyOptions = function() {\n    const defaultOptions = {\n      type: 'email',\n      from: 'noreply@example.com',\n    };\n    return Object.assign({}, this.settings.verifyOptions || defaultOptions);\n  };\n\n  /**\n   * Verify a user's identity by sending them a confirmation message.\n   * NOTE: Currently only email verification is supported\n   *\n   * ```js\n   * var verifyOptions = {\n   *   type: 'email',\n   *   from: 'noreply@example.com'\n   *   template: 'verify.ejs',\n   *   redirect: '/',\n   *   generateVerificationToken: function (user, options, cb) {\n   *     cb('random-token');\n   *   }\n   * };\n   *\n   * user.verify(verifyOptions);\n   * ```\n   *\n   * NOTE: the User.getVerifyOptions() method can also be used to ease the\n   * building of identity verification options.\n   *\n   * ```js\n   * var verifyOptions = MyUser.getVerifyOptions();\n   * user.verify(verifyOptions);\n   * ```\n   *\n   * @options {Object} verifyOptions\n   * @property {String} type Must be `'email'` in the current implementation.\n   * @property {Function} mailer A mailer function with a static `.send() method.\n   *  The `.send()` method must accept the verifyOptions object, the method's\n   *  remoting context options object and a callback function with `(err, email)`\n   *  as parameters.\n   *  Defaults to provided `userModel.email` function, or ultimately to LoopBack's\n   *  own mailer function.\n   * @property {String} to Email address to which verification email is sent.\n   *  Defaults to user's email. Can also be overriden to a static value for test\n   *  purposes.\n   * @property {String} from Sender email address\n   *  For example `'noreply@example.com'`.\n   * @property {String} subject Subject line text.\n   *  Defaults to `'Thanks for Registering'` or a local equivalent.\n   * @property {String} text Text of email.\n   *  Defaults to `'Please verify your email by opening this link in a web browser:`\n   *  followed by the verify link.\n   * @property {Object} headers Email headers. None provided by default.\n   * @property {String} template Relative path of template that displays verification\n   *  page. Defaults to `'../../templates/verify.ejs'`.\n   * @property {Function} templateFn A function generating the email HTML body\n   *  from `verify()` options object and generated attributes like `options.verifyHref`.\n   *  It must accept the verifyOptions object, the method's remoting context options\n   *  object and a callback function with `(err, html)` as parameters.\n   *  A default templateFn function is provided, see `createVerificationEmailBody()`\n   *  for implementation details.\n   * @property {String} redirect Page to which user will be redirected after\n   *  they verify their email. Defaults to `'/'`.\n   * @property {String} verifyHref The link to include in the user's verify message.\n   *  Defaults to an url analog to:\n   *  `http://host:port/restApiRoot/userRestPath/confirm?uid=userId&redirect=/``\n   * @property {String} host The API host. Defaults to app's host or `localhost`.\n   * @property {String} protocol The API protocol. Defaults to `'http'`.\n   * @property {Number} port The API port. Defaults to app's port or `3000`.\n   * @property {String} restApiRoot The API root path. Defaults to app's restApiRoot\n   *  or `'/api'`\n   * @property {Function} generateVerificationToken A function to be used to\n   *  generate the verification token.\n   *  It must accept the verifyOptions object, the method's remoting context options\n   *  object and a callback function with `(err, hexStringBuffer)` as parameters.\n   *  This function should NOT add the token to the user object, instead simply\n   *  execute the callback with the token! User saving and email sending will be\n   *  handled in the `verify()` method.\n   *  A default token generation function is provided, see `generateVerificationToken()`\n   *  for implementation details.\n   * @callback {Function} cb Callback function.\n   * @param {Object} options remote context options.\n   * @param {Error} err Error object.\n   * @param {Object} object Contains email, token, uid.\n   * @promise\n   */\n\n  User.prototype.verify = function(verifyOptions, options, cb) {\n    if (cb === undefined && typeof options === 'function') {\n      cb = options;\n      options = undefined;\n    }\n    cb = cb || utils.createPromiseCallback();\n\n    const user = this;\n    const userModel = this.constructor;\n    const registry = userModel.registry;\n    verifyOptions = Object.assign({}, verifyOptions);\n    // final assertion is performed once all options are assigned\n    assert(typeof verifyOptions === 'object',\n      'verifyOptions object param required when calling user.verify()');\n\n    // Shallow-clone the options object so that we don't override\n    // the global default options object\n    verifyOptions = Object.assign({}, verifyOptions);\n\n    // Set a default template generation function if none provided\n    verifyOptions.templateFn = verifyOptions.templateFn || createVerificationEmailBody;\n\n    // Set a default token generation function if none provided\n    verifyOptions.generateVerificationToken = verifyOptions.generateVerificationToken ||\n      User.generateVerificationToken;\n\n    // Set a default mailer function if none provided\n    verifyOptions.mailer = verifyOptions.mailer || userModel.email ||\n      registry.getModelByType(loopback.Email);\n\n    const pkName = userModel.definition.idName() || 'id';\n    verifyOptions.redirect = verifyOptions.redirect || '/';\n    const defaultTemplate = path.join(__dirname, '..', '..', 'templates', 'verify.ejs');\n    verifyOptions.template = path.resolve(verifyOptions.template || defaultTemplate);\n    verifyOptions.user = user;\n    verifyOptions.protocol = verifyOptions.protocol || 'http';\n\n    const app = userModel.app;\n    verifyOptions.host = verifyOptions.host || (app && app.get('host')) || 'localhost';\n    verifyOptions.port = verifyOptions.port || (app && app.get('port')) || 3000;\n    verifyOptions.restApiRoot = verifyOptions.restApiRoot || (app && app.get('restApiRoot')) || '/api';\n\n    const displayPort = (\n      (verifyOptions.protocol === 'http' && verifyOptions.port == '80') ||\n      (verifyOptions.protocol === 'https' && verifyOptions.port == '443')\n    ) ? '' : ':' + verifyOptions.port;\n\n    if (!verifyOptions.verifyHref) {\n      const confirmMethod = userModel.sharedClass.findMethodByName('confirm');\n      if (!confirmMethod) {\n        throw new Error(\n          'Cannot build user verification URL, ' +\n          'the default confirm method is not public. ' +\n          'Please provide the URL in verifyOptions.verifyHref.',\n        );\n      }\n\n      const urlPath = joinUrlPath(\n        verifyOptions.restApiRoot,\n        userModel.http.path,\n        confirmMethod.http.path,\n      );\n\n      verifyOptions.verifyHref =\n        verifyOptions.protocol +\n        '://' +\n        verifyOptions.host +\n        displayPort +\n        urlPath +\n        '?' + qs.stringify({\n          uid: '' + verifyOptions.user[pkName],\n          redirect: verifyOptions.redirect,\n        });\n    }\n\n    verifyOptions.to = verifyOptions.to || user.email;\n    verifyOptions.subject = verifyOptions.subject || g.f('Thanks for Registering');\n    verifyOptions.headers = verifyOptions.headers || {};\n\n    // assert the verifyOptions params that might have been badly defined\n    assertVerifyOptions(verifyOptions);\n\n    // argument \"options\" is passed depending on verifyOptions.generateVerificationToken function requirements\n    const tokenGenerator = verifyOptions.generateVerificationToken;\n    if (tokenGenerator.length == 3) {\n      tokenGenerator(user, options, addTokenToUserAndSave);\n    } else {\n      tokenGenerator(user, addTokenToUserAndSave);\n    }\n\n    function addTokenToUserAndSave(err, token) {\n      if (err) return cb(err);\n      user.verificationToken = token;\n      user.save(options, function(err) {\n        if (err) return cb(err);\n        sendEmail(user);\n      });\n    }\n\n    // TODO - support more verification types\n    function sendEmail(user) {\n      verifyOptions.verifyHref +=\n        verifyOptions.verifyHref.indexOf('?') === -1 ? '?' : '&';\n      verifyOptions.verifyHref += 'token=' + user.verificationToken;\n\n      verifyOptions.verificationToken = user.verificationToken;\n      verifyOptions.text = verifyOptions.text || g.f('Please verify your email by opening ' +\n        'this link in a web browser:\\n\\t%s', verifyOptions.verifyHref);\n      verifyOptions.text = verifyOptions.text.replace(/\\{href\\}/g, verifyOptions.verifyHref);\n\n      // argument \"options\" is passed depending on templateFn function requirements\n      const templateFn = verifyOptions.templateFn;\n      if (templateFn.length == 3) {\n        templateFn(verifyOptions, options, setHtmlContentAndSend);\n      } else {\n        templateFn(verifyOptions, setHtmlContentAndSend);\n      }\n\n      function setHtmlContentAndSend(err, html) {\n        if (err) return cb(err);\n\n        verifyOptions.html = html;\n\n        // Remove verifyOptions.template to prevent rejection by certain\n        // nodemailer transport plugins.\n        delete verifyOptions.template;\n\n        // argument \"options\" is passed depending on Email.send function requirements\n        const Email = verifyOptions.mailer;\n        if (Email.send.length == 3) {\n          Email.send(verifyOptions, options, handleAfterSend);\n        } else {\n          Email.send(verifyOptions, handleAfterSend);\n        }\n\n        function handleAfterSend(err, email) {\n          if (err) return cb(err);\n          cb(null, {email: email, token: user.verificationToken, uid: user[pkName]});\n        }\n      }\n    }\n\n    return cb.promise;\n  };\n\n  function assertVerifyOptions(verifyOptions) {\n    assert(verifyOptions.type, 'You must supply a verification type (verifyOptions.type)');\n    assert(verifyOptions.type === 'email', 'Unsupported verification type');\n    assert(verifyOptions.to, 'Must include verifyOptions.to when calling user.verify() ' +\n      'or the user must have an email property');\n    assert(verifyOptions.from, 'Must include verifyOptions.from when calling user.verify()');\n    assert(typeof verifyOptions.templateFn === 'function',\n      'templateFn must be a function');\n    assert(typeof verifyOptions.generateVerificationToken === 'function',\n      'generateVerificationToken must be a function');\n    assert(verifyOptions.mailer, 'A mailer function must be provided');\n    assert(typeof verifyOptions.mailer.send === 'function', 'mailer.send must be a function ');\n  }\n\n  function createVerificationEmailBody(verifyOptions, options, cb) {\n    const template = loopback.template(verifyOptions.template);\n    const body = template(verifyOptions);\n    cb(null, body);\n  }\n\n  /**\n   * A default verification token generator which accepts the user the token is\n   * being generated for and a callback function to indicate completion.\n   * This one uses the crypto library and 64 random bytes (converted to hex)\n   * for the token. When used in combination with the user.verify() method this\n   * function will be called with the `user` object as it's context (`this`).\n   *\n   * @param {object} user The User this token is being generated for.\n   * @param {object} options remote context options.\n   * @param {Function} cb The generator must pass back the new token with this function call.\n   */\n  User.generateVerificationToken = function(user, options, cb) {\n    crypto.randomBytes(64, function(err, buf) {\n      cb(err, buf && buf.toString('hex'));\n    });\n  };\n\n  /**\n   * Confirm the user's identity.\n   *\n   * @param {Any} userId\n   * @param {String} token The validation token\n   * @param {String} redirect URL to redirect the user to once confirmed\n   * @callback {Function} callback\n   * @param {Error} err\n   * @promise\n   */\n  User.confirm = function(uid, token, redirect, fn) {\n    fn = fn || utils.createPromiseCallback();\n    this.findById(uid, function(err, user) {\n      if (err) {\n        fn(err);\n      } else {\n        if (user && user.verificationToken === token) {\n          user.verificationToken = null;\n          user.emailVerified = true;\n          user.save(function(err) {\n            if (err) {\n              fn(err);\n            } else {\n              fn();\n            }\n          });\n        } else {\n          if (user) {\n            err = new Error(g.f('Invalid token: %s', token));\n            err.statusCode = 400;\n            err.code = 'INVALID_TOKEN';\n          } else {\n            err = new Error(g.f('User not found: %s', uid));\n            err.statusCode = 404;\n            err.code = 'USER_NOT_FOUND';\n          }\n          fn(err);\n        }\n      }\n    });\n    return fn.promise;\n  };\n\n  /**\n   * Create a short lived access token for temporary login. Allows users\n   * to change passwords if forgotten.\n   *\n   * @options {Object} options\n   * @prop {String} email The user's email address\n   * @property {String} realm The user's realm (optional)\n   * @callback {Function} callback\n   * @param {Error} err\n   * @promise\n   */\n\n  User.resetPassword = function(options, cb) {\n    cb = cb || utils.createPromiseCallback();\n    const UserModel = this;\n    const ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;\n    options = options || {};\n    if (typeof options.email !== 'string') {\n      const err = new Error(g.f('Email is required'));\n      err.statusCode = 400;\n      err.code = 'EMAIL_REQUIRED';\n      cb(err);\n      return cb.promise;\n    }\n\n    try {\n      if (options.password) {\n        UserModel.validatePassword(options.password);\n      }\n    } catch (err) {\n      return cb(err);\n    }\n    const where = {\n      email: options.email,\n    };\n    if (options.realm) {\n      where.realm = options.realm;\n    }\n    UserModel.findOne({where: where}, function(err, user) {\n      if (err) {\n        return cb(err);\n      }\n      if (!user) {\n        err = new Error(g.f('Email not found'));\n        err.statusCode = 404;\n        err.code = 'EMAIL_NOT_FOUND';\n        return cb(err);\n      }\n      // create a short lived access token for temp login to change password\n      // TODO(ritch) - eventually this should only allow password change\n      if (UserModel.settings.emailVerificationRequired && !user.emailVerified) {\n        err = new Error(g.f('Email has not been verified'));\n        err.statusCode = 401;\n        err.code = 'RESET_FAILED_EMAIL_NOT_VERIFIED';\n        return cb(err);\n      }\n\n      if (UserModel.settings.restrictResetPasswordTokenScope) {\n        const tokenData = {\n          ttl: ttl,\n          scopes: ['reset-password'],\n        };\n        user.createAccessToken(tokenData, options, onTokenCreated);\n      } else {\n        // We need to preserve backwards-compatibility with\n        // user-supplied implementations of \"createAccessToken\"\n        // that may not support \"options\" argument (we have such\n        // examples in our test suite).\n        user.createAccessToken(ttl, onTokenCreated);\n      }\n\n      function onTokenCreated(err, accessToken) {\n        if (err) {\n          return cb(err);\n        }\n        cb();\n        UserModel.emit('resetPasswordRequest', {\n          email: options.email,\n          accessToken: accessToken,\n          user: user,\n          options: options,\n        });\n      }\n    });\n\n    return cb.promise;\n  };\n\n  /*!\n   * Hash the plain password\n   */\n  User.hashPassword = function(plain) {\n    this.validatePassword(plain);\n    const salt = bcrypt.genSaltSync(this.settings.saltWorkFactor || SALT_WORK_FACTOR);\n    return bcrypt.hashSync(plain, salt);\n  };\n\n  User.validatePassword = function(plain) {\n    let err;\n    if (!plain || typeof plain !== 'string') {\n      err = new Error(g.f('Invalid password.'));\n      err.code = 'INVALID_PASSWORD';\n      err.statusCode = 422;\n      throw err;\n    }\n\n    // Bcrypt only supports up to 72 bytes; the rest is silently dropped.\n    const len = Buffer.byteLength(plain, 'utf8');\n    if (len > MAX_PASSWORD_LENGTH) {\n      err = new Error(g.f('The password entered was too long. Max length is %d (entered %d)',\n        MAX_PASSWORD_LENGTH, len));\n      err.code = 'PASSWORD_TOO_LONG';\n      err.statusCode = 422;\n      throw err;\n    }\n  };\n\n  User._invalidateAccessTokensOfUsers = function(userIds, options, cb) {\n    if (typeof options === 'function' && cb === undefined) {\n      cb = options;\n      options = {};\n    }\n\n    if (!Array.isArray(userIds) || !userIds.length)\n      return process.nextTick(cb);\n\n    const accessTokenRelation = this.relations.accessTokens;\n    if (!accessTokenRelation)\n      return process.nextTick(cb);\n\n    const AccessToken = accessTokenRelation.modelTo;\n    const query = {userId: {inq: userIds}};\n    const tokenPK = AccessToken.definition.idName() || 'id';\n    if (options.accessToken && tokenPK in options.accessToken) {\n      query[tokenPK] = {neq: options.accessToken[tokenPK]};\n    }\n    // add principalType in AccessToken.query if using polymorphic relations\n    // between AccessToken and User\n    const relatedUser = AccessToken.relations.user;\n    const isRelationPolymorphic = relatedUser && relatedUser.polymorphic &&\n      !relatedUser.modelTo;\n    if (isRelationPolymorphic) {\n      query.principalType = this.modelName;\n    }\n    AccessToken.deleteAll(query, options, cb);\n  };\n\n  /*!\n   * Setup an extended user model.\n   */\n\n  User.setup = function() {\n    // We need to call the base class's setup method\n    User.base.setup.call(this);\n    const UserModel = this;\n\n    // max ttl\n    this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL;\n    this.settings.ttl = this.settings.ttl || DEFAULT_TTL;\n\n    UserModel.setter.email = function(value) {\n      if (!UserModel.settings.caseSensitiveEmail && typeof value === 'string') {\n        this.$email = value.toLowerCase();\n      } else {\n        this.$email = value;\n      }\n    };\n\n    UserModel.setter.password = function(plain) {\n      if (typeof plain !== 'string') {\n        return;\n      }\n      if ((plain.indexOf('$2a$') === 0 || plain.indexOf('$2b$') === 0) && plain.length === 60) {\n        // The password is already hashed. It can be the case\n        // when the instance is loaded from DB\n        this.$password = plain;\n      } else {\n        this.$password = this.constructor.hashPassword(plain);\n      }\n    };\n\n    // Make sure emailVerified is not set by creation\n    UserModel.beforeRemote('create', function(ctx, user, next) {\n      const body = ctx.req.body;\n      if (body && body.emailVerified) {\n        body.emailVerified = false;\n      }\n      next();\n    });\n\n    UserModel.remoteMethod(\n      'login',\n      {\n        description: 'Login a user with username/email and password.',\n        accepts: [\n          {arg: 'credentials', type: 'object', required: true, http: {source: 'body'}},\n          {arg: 'include', type: ['string'], http: {source: 'query'},\n            description: 'Related objects to include in the response. ' +\n            'See the description of return value for more details.'},\n        ],\n        returns: {\n          arg: 'accessToken', type: 'object', root: true,\n          description:\n            g.f('The response body contains properties of the {{AccessToken}} created on login.\\n' +\n            'Depending on the value of `include` parameter, the body may contain ' +\n            'additional properties:\\n\\n' +\n            '  - `user` - `U+007BUserU+007D` - Data of the currently logged in user. ' +\n            '{{(`include=user`)}}\\n\\n'),\n        },\n        http: {verb: 'post'},\n      },\n    );\n\n    UserModel.remoteMethod(\n      'logout',\n      {\n        description: 'Logout a user with access token.',\n        accepts: [\n          {arg: 'access_token', type: 'string', http: function(ctx) {\n            const req = ctx && ctx.req;\n            const accessToken = req && req.accessToken;\n            const tokenID = accessToken ? accessToken.id : undefined;\n\n            return tokenID;\n          }, description: 'Do not supply this argument, it is automatically extracted ' +\n            'from request headers.',\n          },\n        ],\n        http: {verb: 'all'},\n      },\n    );\n\n    UserModel.remoteMethod(\n      'prototype.verify',\n      {\n        description: 'Trigger user\\'s identity verification with configured verifyOptions',\n        accepts: [\n          {arg: 'verifyOptions', type: 'object', http: ctx => this.getVerifyOptions()},\n          {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n        ],\n        http: {verb: 'post'},\n      },\n    );\n\n    UserModel.remoteMethod(\n      'confirm',\n      {\n        description: 'Confirm a user registration with identity verification token.',\n        accepts: [\n          {arg: 'uid', type: 'string', required: true},\n          {arg: 'token', type: 'string', required: true},\n          {arg: 'redirect', type: 'string'},\n        ],\n        http: {verb: 'get', path: '/confirm'},\n      },\n    );\n\n    UserModel.remoteMethod(\n      'resetPassword',\n      {\n        description: 'Reset password for a user with email.',\n        accepts: [\n          {arg: 'options', type: 'object', required: true, http: {source: 'body'}},\n        ],\n        http: {verb: 'post', path: '/reset'},\n      },\n    );\n\n    UserModel.remoteMethod(\n      'changePassword',\n      {\n        description: 'Change a user\\'s password.',\n        accepts: [\n          {arg: 'id', type: 'any', http: getUserIdFromRequestContext},\n          {arg: 'oldPassword', type: 'string', required: true, http: {source: 'form'}},\n          {arg: 'newPassword', type: 'string', required: true, http: {source: 'form'}},\n          {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n        ],\n        http: {verb: 'POST', path: '/change-password'},\n      },\n    );\n\n    const setPasswordScopes = UserModel.settings.restrictResetPasswordTokenScope ?\n      ['reset-password'] : undefined;\n\n    UserModel.remoteMethod(\n      'setPassword',\n      {\n        description: 'Reset user\\'s password via a password-reset token.',\n        accepts: [\n          {arg: 'id', type: 'any', http: getUserIdFromRequestContext},\n          {arg: 'newPassword', type: 'string', required: true, http: {source: 'form'}},\n          {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n        ],\n        accessScopes: setPasswordScopes,\n        http: {verb: 'POST', path: '/reset-password'},\n      },\n    );\n\n    function getUserIdFromRequestContext(ctx) {\n      const token = ctx.req.accessToken;\n      if (!token) return;\n\n      const hasPrincipalType = 'principalType' in token;\n      if (hasPrincipalType && token.principalType !== UserModel.modelName) {\n        // We have multiple user models related to the same access token model\n        // and the token used to authorize reset-password request was created\n        // for a different user model.\n        const err = new Error(g.f('Access Denied'));\n        err.statusCode = 403;\n        throw err;\n      }\n\n      return token.userId;\n    }\n\n    UserModel.afterRemote('confirm', function(ctx, inst, next) {\n      if (ctx.args.redirect !== undefined) {\n        if (!ctx.res) {\n          return next(new Error(g.f('The transport does not support HTTP redirects.')));\n        }\n        ctx.res.location(ctx.args.redirect);\n        ctx.res.status(302);\n      }\n      next();\n    });\n\n    // default models\n    assert(loopback.Email, 'Email model must be defined before User model');\n    UserModel.email = loopback.Email;\n\n    assert(loopback.AccessToken, 'AccessToken model must be defined before User model');\n    UserModel.accessToken = loopback.AccessToken;\n\n    UserModel.validate('email', emailValidator, {\n      message: g.f('Must provide a valid email'),\n    });\n\n    // Realm users validation\n    if (UserModel.settings.realmRequired && UserModel.settings.realmDelimiter) {\n      UserModel.validatesUniquenessOf('email', {\n        message: 'Email already exists',\n        scopedTo: ['realm'],\n      });\n      UserModel.validatesUniquenessOf('username', {\n        message: 'User already exists',\n        scopedTo: ['realm'],\n      });\n    } else {\n      // Regular(Non-realm) users validation\n      UserModel.validatesUniquenessOf('email', {message: 'Email already exists'});\n      UserModel.validatesUniquenessOf('username', {message: 'User already exists'});\n    }\n\n    return UserModel;\n  };\n\n  /*!\n   * Setup the base user.\n   */\n\n  User.setup();\n\n  // --- OPERATION HOOKS ---\n  //\n  // Important: Operation hooks are inherited by subclassed models,\n  // therefore they must be registered outside of setup() function\n\n  // Access token to normalize email credentials\n  User.observe('access', function normalizeEmailCase(ctx, next) {\n    if (!ctx.Model.settings.caseSensitiveEmail && ctx.query.where &&\n        ctx.query.where.email && typeof(ctx.query.where.email) === 'string') {\n      ctx.query.where.email = ctx.query.where.email.toLowerCase();\n    }\n    next();\n  });\n\n  User.observe('before save', function rejectInsecurePasswordChange(ctx, next) {\n    const UserModel = ctx.Model;\n    if (!UserModel.settings.rejectPasswordChangesViaPatchOrReplace) {\n      // In legacy password flow, any DAO method can change the password\n      return next();\n    }\n\n    if (ctx.isNewInstance) {\n      // The password can be always set when creating a new User instance\n      return next();\n    }\n    const data = ctx.data || ctx.instance;\n    const isPasswordChange = 'password' in data;\n\n    // This is the option set by `setPassword()` API\n    // when calling `this.patchAttritubes()` to change user's password\n    if (ctx.options.setPassword) {\n      // Verify that only the password is changed and nothing more or less.\n      if (Object.keys(data).length > 1 || !isPasswordChange) {\n        // This is a programmer's error, use the default status code 500\n        return next(new Error(\n          'Invalid use of \"options.setPassword\". Only \"password\" can be ' +\n          'changed when using this option.',\n        ));\n      }\n\n      return next();\n    }\n\n    if (!isPasswordChange) {\n      return next();\n    }\n\n    const err = new Error(\n      'Changing user password via patch/replace API is not allowed. ' +\n      'Use changePassword() or setPassword() instead.',\n    );\n    err.statusCode = 401;\n    err.code = 'PASSWORD_CHANGE_NOT_ALLOWED';\n    next(err);\n  });\n\n  User.observe('before save', function prepareForTokenInvalidation(ctx, next) {\n    if (ctx.isNewInstance) return next();\n    if (!ctx.where && !ctx.instance) return next();\n\n    const pkName = ctx.Model.definition.idName() || 'id';\n    let where = ctx.where;\n    if (!where) {\n      where = {};\n      where[pkName] = ctx.instance[pkName];\n    }\n\n    ctx.Model.find({where: where}, ctx.options, function(err, userInstances) {\n      if (err) return next(err);\n      ctx.hookState.originalUserData = userInstances.map(function(u) {\n        const user = {};\n        user[pkName] = u[pkName];\n        user.email = u.email;\n        user.password = u.password;\n        return user;\n      });\n      let emailChanged;\n      if (ctx.instance) {\n        // Check if map does not return an empty array\n        // Fix server crashes when try to PUT a non existent id\n        if (ctx.hookState.originalUserData.length > 0) {\n          emailChanged = ctx.instance.email !== ctx.hookState.originalUserData[0].email;\n        } else {\n          emailChanged = true;\n        }\n\n        if (emailChanged && ctx.Model.settings.emailVerificationRequired) {\n          ctx.instance.emailVerified = false;\n        }\n      } else if (ctx.data.email) {\n        emailChanged = ctx.hookState.originalUserData.some(function(data) {\n          return data.email != ctx.data.email;\n        });\n        if (emailChanged && ctx.Model.settings.emailVerificationRequired) {\n          ctx.data.emailVerified = false;\n        }\n      }\n\n      next();\n    });\n  });\n\n  User.observe('after save', function invalidateOtherTokens(ctx, next) {\n    if (!ctx.instance && !ctx.data) return next();\n    if (!ctx.hookState.originalUserData) return next();\n\n    const pkName = ctx.Model.definition.idName() || 'id';\n    const newEmail = (ctx.instance || ctx.data).email;\n    const newPassword = (ctx.instance || ctx.data).password;\n\n    if (!newEmail && !newPassword) return next();\n\n    if (ctx.options.preserveAccessTokens) return next();\n\n    const userIdsToExpire = ctx.hookState.originalUserData.filter(function(u) {\n      return (newEmail && u.email !== newEmail) ||\n        (newPassword && u.password !== newPassword);\n    }).map(function(u) {\n      return u[pkName];\n    });\n    ctx.Model._invalidateAccessTokensOfUsers(userIdsToExpire, ctx.options, next);\n  });\n};\n\nfunction emailValidator(err, done) {\n  const value = this.email;\n  if (value == null)\n    return;\n  if (typeof value !== 'string')\n    return err('string');\n  if (value === '') return;\n  if (!isEmail.validate(value))\n    return err('email');\n}\n\nfunction joinUrlPath(args) {\n  let result = arguments[0];\n  for (let ix = 1; ix < arguments.length; ix++) {\n    const next = arguments[ix];\n    result += result[result.length - 1] === '/' && next[0] === '/' ?\n      next.slice(1) : next;\n  }\n  return result;\n}\n"
  },
  {
    "path": "common/models/user.json",
    "content": "{\n  \"name\": \"User\",\n  \"properties\": {\n    \"realm\": {\n      \"type\": \"string\"\n    },\n    \"username\": {\n      \"type\": \"string\"\n    },\n    \"password\": {\n      \"type\": \"string\",\n      \"required\": true\n    },\n    \"email\": {\n      \"type\": \"string\",\n      \"required\": true\n    },\n    \"emailVerified\": \"boolean\",\n    \"verificationToken\": \"string\"\n  },\n  \"options\": {\n    \"caseSensitiveEmail\": true\n  },\n  \"hidden\": [\"password\", \"verificationToken\"],\n  \"acls\": [\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\",\n      \"permission\": \"DENY\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"create\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$owner\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"deleteById\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"login\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"logout\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$owner\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"findById\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$owner\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"patchAttributes\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$owner\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"replaceById\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"verify\",\n      \"accessType\": \"EXECUTE\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"confirm\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"resetPassword\",\n      \"accessType\": \"EXECUTE\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$authenticated\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"changePassword\",\n      \"accessType\": \"EXECUTE\"\n    },\n    {\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$authenticated\",\n      \"permission\": \"ALLOW\",\n      \"property\": \"setPassword\",\n      \"accessType\": \"EXECUTE\"\n    }\n  ],\n  \"relations\": {\n    \"accessTokens\": {\n      \"type\": \"hasMany\",\n      \"model\": \"AccessToken\",\n      \"foreignKey\": \"userId\",\n      \"options\": {\n        \"disableInclude\": true\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/api-explorer-details.md",
    "content": "<!-- NOTE: This file is not currently included into the docs.  Need to (a) decide if this info is important and if so (b) decide where to put it.\n\n-->\n\n# REST API specs\n\nLoopBack API Explorer is built on top of the popular\n[Swagger Framework](https://github.com/wordnik/swagger-core/wiki). There are two\ncomponents involved.\n\n1. LoopBack builds up formal specifications of the REST APIs using the knowledge of\nmodel definitions, JavaScript method declarations, and remote mappings. The\nspecifications are served over the following endpoints.\n\n2. The wonderful Web UI is brought you by [Swagger UI](https://github.com/strongloop/swagger-ui).\nSwagger UI is a collection of HTML, Javascript, and CSS assets that dynamically\ngenerate beautiful documentation and sandbox from the REST API specifications.\n\n## Resource Listing\nThe first part is a listing of the REST APIs.\n\n- http://localhost:3000/swagger/resources\n\n```javascript\n    {\n      \"swaggerVersion\": \"1.1\",\n      \"basePath\": \"http://localhost:3000\",\n      \"apis\": [\n        {\n          \"path\": \"/swagger/ammo\"\n        },\n        {\n          \"path\": \"/swagger/customers\"\n        },\n        {\n          \"path\": \"/swagger/inventory\"\n        },\n        {\n          \"path\": \"/swagger/locations\"\n        },\n        {\n          \"path\": \"/swagger/weapons\"\n        }\n      ]\n    }\n```\n\n## Resource Operations\nThe second part describes all operations of a given model.\n\n- http://localhost:3000/swagger/locations\n\n```javascript\n    {\n      \"swaggerVersion\": \"1.1\",\n      \"basePath\": \"http://localhost:3000\",\n      \"apis\": [\n        {\n          \"path\": \"/locations\",\n          \"operations\": [\n            {\n              \"httpMethod\": \"POST\",\n              \"nickname\": \"locations_create\",\n              \"responseClass\": \"object\",\n              \"parameters\": [\n                {\n                  \"paramType\": \"body\",\n                  \"name\": \"data\",\n                  \"description\": \"Model instance data\",\n                  \"dataType\": \"object\",\n                  \"required\": false,\n                  \"allowMultiple\": false\n                }\n              ],\n              \"errorResponses\": [],\n              \"summary\": \"Create a new instance of the model and persist it into the data source\",\n              \"notes\": \"\"\n            }\n          ]\n        },\n        ...\n        {\n          \"path\": \"/locations/{id}\",\n          \"operations\": [\n            {\n              \"httpMethod\": \"GET\",\n              \"nickname\": \"locations_findById\",\n              \"responseClass\": \"any\",\n              \"parameters\": [\n                {\n                  \"paramType\": \"path\",\n                  \"name\": \"id\",\n                  \"description\": \"Model id\",\n                  \"dataType\": \"any\",\n                  \"required\": true,\n                  \"allowMultiple\": false\n                }\n              ],\n              \"errorResponses\": [],\n              \"summary\": \"Find a model instance by id from the data source\",\n              \"notes\": \"\"\n            }\n          ]\n        },\n        ...\n      ]\n    }\n```\n"
  },
  {
    "path": "docs.json",
    "content": "{\n  \"title\": \"LoopBack Documentation\",\n  \"content\": [\n    \"lib/application.js\",\n    \"lib/server-app.js\",\n    \"lib/loopback.js\",\n    \"lib/registry.js\",\n    \"lib/access-context.js\",\n{ \"title\": \"Base models\", \"depth\": 2 },\n    \"lib/model.js\",\n    \"lib/persisted-model.js\",\n{ \"title\": \"Middleware\", \"depth\": 2 },\n    \"server/middleware/favicon.js\",\n    \"server/middleware/rest.js\",\n    \"server/middleware/static.js\",\n    \"server/middleware/status.js\",\n    \"server/middleware/token.js\",\n    \"server/middleware/url-not-found.js\",\n{ \"title\": \"Built-in models\", \"depth\": 2 },\n    \"common/models/access-token.js\",\n    \"common/models/acl.js\",\n    \"common/models/application.js\",\n    \"common/models/change.js\",\n    \"common/models/email.js\",\n    \"common/models/key-value-model.js\",\n    \"common/models/role.js\",\n    \"common/models/role-mapping.js\",\n    \"common/models/scope.js\",\n    \"common/models/user.js\"\n  ],\n  \"assets\": \"/docs/assets\"\n}\n"
  },
  {
    "path": "example/client-server/client.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('../../lib/globalize');\nconst loopback = require('../../');\nconst client = loopback();\nconst CartItem = require('./models').CartItem;\nconst remote = loopback.createDataSource({\n  connector: loopback.Remote,\n  url: 'http://localhost:3000',\n});\n\nclient.model(CartItem);\nCartItem.attachTo(remote);\n\n// call the remote method\nCartItem.sum(1, function(err, total) {\n  g.log('result:%s', err || total);\n});\n\n// call a built in remote method\nCartItem.find(function(err, items) {\n  console.log(items);\n});\n"
  },
  {
    "path": "example/client-server/models.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../');\n\nconst CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', {\n  tax: {type: Number, default: 0.1},\n  price: Number,\n  item: String,\n  qty: {type: Number, default: 0},\n  cartId: Number,\n});\n\nCartItem.sum = function(cartId, callback) {\n  this.find({where: {cartId: cartId}}, function(err, items) {\n    if (err) return callback(err);\n    const total = items\n      .map(function(item) {\n        return item.total();\n      })\n      .reduce(function(cur, prev) {\n        return prev + cur;\n      }, 0);\n\n    callback(null, total);\n  });\n};\n\nCartItem.remoteMethod('sum',\n  {\n    accepts: {arg: 'cartId', type: 'number'},\n    returns: {arg: 'total', type: 'number'},\n  });\n\nCartItem.prototype.total = function() {\n  return this.price * this.qty * (1 + this.tax);\n};\n"
  },
  {
    "path": "example/client-server/server.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../');\nconst server = module.exports = loopback();\nconst CartItem = require('./models').CartItem;\nconst memory = loopback.createDataSource({\n  connector: loopback.Memory,\n});\n\nserver.use(loopback.rest());\nserver.model(CartItem);\n\nCartItem.attachTo(memory);\n\n// test data\nCartItem.create([\n  {item: 'red hat', qty: 6, price: 19.99, cartId: 1},\n  {item: 'green shirt', qty: 1, price: 14.99, cartId: 1},\n  {item: 'orange pants', qty: 58, price: 9.99, cartId: 1},\n]);\n\nCartItem.sum(1, function(err, total) {\n  console.log(total);\n});\n\nserver.listen(3000);\n"
  },
  {
    "path": "example/colors/app.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('../../lib/globalize');\nconst loopback = require('../../');\nconst app = loopback();\n\napp.use(loopback.rest());\n\nconst schema = {\n  name: String,\n};\n\napp.dataSource('db', {connector: 'memory'});\nconst Color = app.registry.createModel('color', schema);\napp.model(Color, {dataSource: 'db'});\n\nColor.create({name: 'red'});\nColor.create({name: 'green'});\nColor.create({name: 'blue'});\n\napp.listen(3000);\n\ng.log('a list of colors is available at {{http://localhost:3000/colors}}');\n"
  },
  {
    "path": "example/mobile-models/app.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('../../lib/globalize');\nconst models = require('../../lib/models');\nconst loopback = require('../../');\nconst app = loopback();\n\napp.use(loopback.rest());\n\nconst dataSource = loopback.createDataSource('db', {connector: loopback.Memory});\n\nconst Application = models.Application(dataSource);\n\napp.model(Application);\n\nconst data = {\n  pushSettings: [{\n    'platform': 'apns',\n    'apns': {\n      'pushOptions': {\n        'gateway': 'gateway.sandbox.push.apple.com',\n        'cert': 'credentials/apns_cert_dev.pem',\n        'key': 'credentials/apns_key_dev.pem',\n      },\n\n      'feedbackOptions': {\n        'gateway': 'feedback.sandbox.push.apple.com',\n        'cert': 'credentials/apns_cert_dev.pem',\n        'key': 'credentials/apns_key_dev.pem',\n        'batchFeedback': true,\n        'interval': 300,\n      },\n    },\n  }],\n};\n\nApplication.create(data, function(err, data) {\n  g.log('Created: %s', data.toObject());\n});\n\nApplication.register('rfeng', 'MyApp', {description: g.f('My first mobile application')},\n  function(err, result) {\n    console.log(result.toObject());\n\n    result.resetKeys(function(err, result) {\n      console.log(result.toObject());\n    });\n  });\n"
  },
  {
    "path": "example/replication/app.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../');\nconst app = loopback();\nconst db = app.dataSource('db', {connector: 'memory'});\nconst Color = app.registry.createModel('color', {}, {trackChanges: true});\napp.model(Color, {dataSource: 'db'});\nconst Color2 = app.registry.createModel('color2', {}, {trackChanges: true});\napp.model(Color2, {dataSource: 'db'});\nconst target = Color2;\nconst source = Color;\nconst SPEED = process.env.SPEED || 100;\nlet conflicts;\n\nconst steps = [\n\n  createSomeInitialSourceData,\n\n  replicateSourceToTarget,\n  list.bind(this, source, 'current SOURCE data'),\n  list.bind(this, target, 'current TARGET data'),\n\n  updateSomeTargetData,\n\n  replicateSourceToTarget,\n  list.bind(this, source, 'current SOURCE data '),\n  list.bind(this, target, 'current TARGET data (includes conflicting update)'),\n\n  updateSomeSourceDataCausingAConflict,\n\n  replicateSourceToTarget,\n  list.bind(this, source, 'current SOURCE data (now has a conflict)'),\n  list.bind(this, target, 'current TARGET data (includes conflicting update)'),\n\n  resolveAllConflicts,\n\n  replicateSourceToTarget,\n  list.bind(this, source, 'current SOURCE data (conflict resolved)'),\n  list.bind(this, target, 'current TARGET data (conflict resolved)'),\n\n  createMoreSourceData,\n\n  replicateSourceToTarget,\n  list.bind(this, source, 'current SOURCE data'),\n  list.bind(this, target, 'current TARGET data'),\n\n  createEvenMoreSourceData,\n\n  replicateSourceToTarget,\n  list.bind(this, source, 'current SOURCE data'),\n  list.bind(this, target, 'current TARGET data'),\n\n  deleteAllSourceData,\n\n  replicateSourceToTarget,\n  list.bind(this, source, 'current SOURCE data (empty)'),\n  list.bind(this, target, 'current TARGET data (empty)'),\n\n  createSomeNewSourceData,\n\n  replicateSourceToTarget,\n  list.bind(this, source, 'current SOURCE data'),\n  list.bind(this, target, 'current TARGET data'),\n];\n\nrun(steps);\n\nfunction createSomeInitialSourceData() {\n  Color.create([\n    {name: 'red'},\n    {name: 'blue'},\n    {name: 'green'},\n  ]);\n}\n\nfunction replicateSourceToTarget() {\n  Color.replicate(0, Color2, {}, function(err, replicationConflicts) {\n    conflicts = replicationConflicts;\n  });\n}\n\nfunction resolveAllConflicts() {\n  if (conflicts.length) {\n    conflicts.forEach(function(conflict) {\n      conflict.resolve();\n    });\n  }\n}\n\nfunction updateSomeTargetData() {\n  Color2.findById(1, function(err, color) {\n    color.name = 'conflict';\n    color.save();\n  });\n}\n\nfunction createMoreSourceData() {\n  Color.create({name: 'orange'});\n}\n\nfunction createEvenMoreSourceData() {\n  Color.create({name: 'black'});\n}\n\nfunction updateSomeSourceDataCausingAConflict() {\n  Color.findById(1, function(err, color) {\n    color.name = 'red!!!!';\n    color.save();\n  });\n}\n\nfunction deleteAllSourceData() {\n  Color.destroyAll();\n}\n\nfunction createSomeNewSourceData() {\n  Color.create([\n    {name: 'violet'},\n    {name: 'amber'},\n    {name: 'olive'},\n  ]);\n}\n\nfunction list(model, msg) {\n  console.log(msg);\n  model.find(function(err, items) {\n    items.forEach(function(item) {\n      console.log(' -', item.name);\n    });\n    console.log();\n  });\n}\n\nfunction run(steps) {\n  setInterval(function() {\n    const step = steps.shift();\n    if (step) {\n      console.log(step.name);\n      step();\n    }\n  }, SPEED);\n}\n"
  },
  {
    "path": "example/simple-data-source/app.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('../../lib/globalize');\nconst loopback = require('../../');\nconst app = loopback();\n\napp.use(loopback.rest());\n\nconst dataSource = app.dataSource('db', {adapter: 'memory'});\n\nconst Color = dataSource.define('color', {\n  'name': String,\n});\n\nColor.create({name: 'red'});\nColor.create({name: 'green'});\nColor.create({name: 'blue'});\n\nColor.all(function() {\n  console.log(arguments);\n});\n\napp.listen(3000);\n\ng.log('a list of colors is available at {{http://localhost:3000/colors}}');\n"
  },
  {
    "path": "index.js",
    "content": "// Copyright IBM Corp. 2013,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n/**\n * loopback ~ public api\n */\n\nconst loopback = module.exports = require('./lib/loopback');\nconst datasourceJuggler = require('loopback-datasource-juggler');\n\n/**\n * Connectors\n */\n\nloopback.Connector = require('./lib/connectors/base-connector');\nloopback.Memory = require('./lib/connectors/memory');\nloopback.Mail = require('./lib/connectors/mail');\nloopback.Remote = require('loopback-connector-remote');\n\n/**\n * Types\n */\n\nloopback.GeoPoint = require('loopback-datasource-juggler/lib/geo').GeoPoint;\nloopback.DateString = require('loopback-datasource-juggler/lib/date-string');\nloopback.ValidationError = loopback.Model.ValidationError;\n"
  },
  {
    "path": "intl/cs/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"Nebyl nalezen žádný záznam změny pro {0} s ID {1}\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"Ověření vyžaduje, aby byl definován model {0}.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Přístup odepřen\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"Je vyžadován e-mail.\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"Odstraňte související položku podle ID pro {0}.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"Metadata vzdálené komunikace pro {0}.{1} {{\\\"isStatic\\\"}} se neshodují s novým stylem založeném na názvu metody.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"Seznam barev je dostupný na adrese {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"Tělo odezvy obsahuje vlastnosti {{AccessToken}} vytvořené při přihlášení.\\nV závislosti na hodnotě parametru `include` může tělo obsahovat další vlastnosti:\\n\\n  - `user` - `U+007BUserU+007D` - Data aktuálně přihlášeného uživatele. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t PŘEDMĚT:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"VytvořenO: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Aktualizujte související položku podle ID pro {0}.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t OD: {0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} byl odebrán ve verzi 3.0. Další podrobnosti naleznete v části {1}.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"Nelze najít {0} s ID {1}\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Očekávaná logická hodnota, obdrženo: {0}\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Neplatný typ činitele: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"Vlastnost vztahů konfigurace `{0}` musí být objekt\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Model nebyl nalezen: model `{0}` rozšiřuje neznámý model `{1}`.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Ověřte svůj e-mail otevřením tohoto odkazu ve webovém prohlížeči:\\n\\t {0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"Zadané heslo je příliš dlouhé. Maximální délka je {0} (zadáno {1})\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"Přihlášení se nezdařilo, protože e-mail nebyl ověřen\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Neznámá fáze {{middleware}} {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} používá nastavení modelu {1}, které již není dostupné.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Neplatný token: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Děkujeme za registraci\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Varování: Pro odeslání e-mailu nebyl zadán žádný přenos e-mailu. Nastavení přenosu pro odesílání poštovních zpráv.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"Nelze volat {0}.{1}(). Metoda {2} nebyla nastavena. {{PersistedModel}} nebyl správně připojen ke {{DataSource}}!\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"Volba {{DataSource}} {{\\\"defaultForType\\\"}} se již nepodporuje\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"E-mail nebyl nalezen\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Odesílání pošty:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"E-mail nebyl ověřen\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"Aktualizujte {0} tohoto modelu.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Cizí klíč pro {0}.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Nelze vytvořit zdroj dat {0}: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"Musíte připojit model {{Email}} ke konektoru {{Mail}}\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Zkontrolujte existenci vztahu {0} k položce podle ID.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Odeberte vztah {0} k položce podle ID.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"Přihlášení se nezdařilo\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"Nelze najít model s {{id}} {0}\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"Vztah `{0}` neexistuje pro model `{1}`\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Nelze použít hromadné aktualizace, konektor správně nehlásí počet aktualizovaných záznamů.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t TEXT:{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Najdete související položku podle ID pro {0}.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Chybí data pro změnu: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"Nebyl nalezen {{accessToken}}.\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"Vlastnost voleb konfigurace `{0}` musí být objekt\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"Vlastnost acls konfigurace `{0}` musí být pole objektů\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"Dotazy {0} z {1}.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Cizí klíč pro {0}\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"Moje první mobilní aplikace\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Neplatný přístupový token\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Vyžadována autorizace\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} je vyžadován pro odhlášení\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"Model nebyl nalezen: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"Načtení vztahu hasOne {0}.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"Vlastnost `{0}` nelze překonfigurovat pro `{1}`\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Neplatné heslo.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"Neznámé ID \\\"{0}\\\" \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"Přenos nepodporuje přesměrování HTTP.\",\r\n  \"86254879d01a60826a851066987703f2\": \"Přidejte související položku podle ID pro {0}.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} nebo {{email}} je povinné\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Neplatná vzdálená metoda: `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"Hromadná aktualizace se nezdařila, konektor upravil neočekávaný počet záznamů: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"Podřízené modely `{0}` nebudou dědit nově definované vzdálené metody {1}.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"Konfiguraci `{0}` chybí vlastnost {{`dataSource`}}.\\nPoužijte `null` nebo `false` k označení modelů, které nejsou připojeny k žádnému zdroji dat.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Načte vztah belongsTo {0}.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"Uživatel nebyl nalezen: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"Neznámý \\\"{0}\\\" {{key}} \\\"{1}\\\".\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} je povinný\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Odstraní všechny {0} tohoto modelu.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"Nelze odstranit {0} změn: \\n {1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"Middleware {0} byl odebrán ve verzi 3.0. Další podrobnosti naleznete v části {1}.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"Metadata vzdálené komunikace {{\\\"isStatic\\\"}} jsou zamítnuta. Místo toho zadejte {{\\\"prototype.name\\\"}} v názvu metody pro {{isStatic=false}}.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"Je třeba zadat platný e-mail\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"Je třeba zadat {{id}} nebo {{data}}\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} byl odebrán, místo toho použijte nový modul {{loopback-boot}}\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Vytvoří novou instanci v {0} tohoto modelu.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"Vypočte {0} z {1}.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"Ignorování neobjektového nastavení \\\"methods\\\" \\\"{0}\\\".\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"Neznámé \\\"{0}\\\" {{id}} \\\"{1}\\\".\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"Hromadná aktualizace se nezdařila, konektor odstranil neočekávaný počet záznamů: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"Nelze použít hromadné aktualizace, konektor správně nehlásí počet odstraněných záznamů.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"Nelze volat {0}.{1}(). Metoda {2} nebyla nastavena. {{KeyValueModel}} nebyl správně připojen ke {{DataSource}}!\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t K:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t PŘENOS:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"výsledek: {0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Konflikt\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Odstraní {0} tohoto modelu.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Přepracujte aplikaci tak, aby používala oficiální řešení pro vložení argumentu \\\"options\\\" z kontextu požadavku,\\nviz {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/de/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"Kein Änderungssatz gefunden für {0} mit ID {1}\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"Für die Authentifizierung muss Modell {0} definiert sein.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Zugriff verweigert\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"E-Mail ist erforderlich\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"Zugehöriges Element nach ID für {0} löschen.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"Die Remote-Anbindungs-Metadaten {{\\\"isStatic\\\"}} für {0}.{1} entsprechen nicht dem namensbasierten Stil der neuen Methode.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"eine Liste mit Farben ist verfügbar unter {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"Der Antworthauptteil enthält Eigenschaften des bei der Anmeldung erstellten {{AccessToken}}.\\nAbhängig vom Wert des Parameters 'include' kann der Hauptteil zusätzliche Eigenschaften enthalten:\\n\\n  - user - U+007BUserU+007D - Daten des derzeit angemeldeten Benutzers. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t BETREFF:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Erstellt: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Zugehöriges Element nach ID für {0} aktualisieren.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t VON:{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} wurde in Version 3.0 entfernt. Siehe {1} für weitere Details.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"{0} mit ID {1} konnte nicht gefunden werden\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Erwartet wurde boolescher Wert, {0} empfangen\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Ungültiger Prinzipaltyp: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"Die relations-Eigenschaft der Konfiguration '{0}' muss ein Objekt sein\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Modell nicht gefunden: Modell '{0}' bietet das unbekannte Modell '{1}' an.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Bestätigen Sie Ihre E-Mail-Adresse, indem Sie diesen Link in einem Web-Browser öffnen:\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"Das eingegebene Kennwort war zu lang. Maximale Länge: {0} (eingegeben: {1})\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"Anmeldung fehlgeschlagen, da die E-Mail-Adresse nicht bestätigt wurde\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Unbekannte {{middleware}}-Phase {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} verwendet Modelleinstellung {1}, die nicht mehr verfügbar ist.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Ungültiges Token: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Vielen Dank für die Registrierung\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Warnung: Keine E-Mail-Transportmethode für das Senden von E-Mails angegeben. Richten Sie eine Transportmethode für das Senden von E-Mails ein.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"{0}.{1}() kann nicht aufgerufen werden. Die Methode {2} wurde nicht konfiguriert. {{PersistedModel}} wurde nicht ordnungsgemäß an eine {{DataSource}} angehängt!\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"{{DataSource}}-Option {{\\\"defaultForType\\\"}} wird nicht mehr unterstützt\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"E-Mail nicht gefunden\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"E-Mail senden:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"E-Mail-Adresse wurde nicht bestätigt\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"{0} von diesem Modell aktualisieren.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Fremdschlüssel für {0}.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Kann Datenquelle {0} nicht erstellen: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"Sie müssen das {{Email}}-Modell mit einem {{Mail}}-Konnektor verbinden\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Vorhandensein von {0}-Beziehung zu einem Element nach ID prüfen.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"{0}-Beziehung zu einem Element nach ID entfernen.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"Anmeldung fehlgeschlagen\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"Modell mit {{id}} {0} konnte nicht gefunden werden\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"Beziehung '{0} ist für Modell {1} nicht vorhanden\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Massenaktualisierungen können nicht angewendet werden, der Konnektor meldet die Anzahl aktualisierter Datensätze nicht richtig.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t TEXT:{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Zugehöriges Element nach ID für {0} suchen.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Fehlende Daten für Änderung: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"{{accessToken}} konnte nicht gefunden werden\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"Die options-Eigenschaft der Konfiguration '{0}' muss ein Objekt sein\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"Die acls-Eigenschaft der Konfiguration '{0}' muss eine Reihe von Objekten sein\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"Abfrage von {0} von {1}.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Fremdschlüssel für {0}\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"Meine erste mobile Anwendung\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Ungültiges Zugriffstoken\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Berechtigung erforderlich\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} muss sich abmelden\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"Modell nicht gefunden: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"Ruft hasOne-Beziehung {0} ab.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"Eigenschaft '{0}' kann für {1} nicht rekonfiguriert werden\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Ungültiges Kennwort.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"\\\"{0}\\\" unbekannt, ID \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"Die Transportmethode unterstützt keine HTTP-Umleitungen.\",\r\n  \"86254879d01a60826a851066987703f2\": \"Zugehöriges Element nach ID für {0} hinzufügen.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} oder {{email}} ist erforderlich\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Ungültige Remote-Methode: '{0}'\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"Massenaktualisierung fehlgeschlagen, der Konnektor hat eine unerwartete Anzahl an Datensätzen geändert: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"Untergeordnete Modelle von `{0}` übernehmen nicht die neu definierten Remote-Methoden {1}.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"Der Konfiguration von {0} fehlt die {{`dataSource`}}-Eigenschaft.\\nVerwenden Sie 'null' oder 'false', um Modelle zu kennzeichnen, die mit keiner Datenquelle verbunden sind.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Ruft belongsTo-Beziehung {0} ab.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"Benutzer nicht gefunden: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"\\\"{0}\\\" unbekannt, {{key}} \\\"{1}\\\".\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} ist erforderlich\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Löscht alle {0} von diesem Modell.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"{0} Änderungen können nicht behoben werden:\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"Middleware {0} wurde in Version 3.0 entfernt. Siehe {1} für weitere Details.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"Metadaten {{\\\"isStatic\\\"}} für Remote-Anbindung sind veraltet. Bitte geben Sie {{\\\"prototype.name\\\"}} anstelle von {{isStatic=false}} beim Methodennamen an.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"Eine gültige E-Mail-Adresse muss angegeben werden\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"muss {{id}} oder {{data}} angeben\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} wurde entfernt, verwenden Sie stattdessen das neue Modul {{loopback-boot}}\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Erstellt eine neue Instanz in {0} von diesem Modell.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"Zählt {0} von {1}.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"Nicht-Objekt-Einstellung \\\"{0}\\\" von \\\"methods\\\" wird ignoriert.\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"\\\"{0}\\\" unbekannt, {{id}} \\\"{1}\\\".\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"Massenaktualisierung fehlgeschlagen, der Konnektor hat eine unerwartete Anzahl an Datensätzen gelöscht: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"Massenaktualisierungen können nicht angewendet werden, der Konnektor meldet die Anzahl gelöschter Datensätze nicht richtig.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"{0}.{1}() kann nicht aufgerufen werden. Die Methode {2} wurde nicht konfiguriert. {{KeyValueModel}} wurde nicht ordnungsgemäß an eine {{DataSource}} angehängt!\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t AN:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t TRANSPORTMETHODE:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"Ergebnis:{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Konflikt\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Löscht {0} von diesem Modell.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Überarbeiten Sie Ihre App, damit sie die offizielle Lösung für eine Injection des Arguments \\\"options\\\" aus dem Anforderungskontext verwendet,\\nsiehe {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/en/messages.json",
    "content": "{\n  \"03f79fa268fe199de2ce4345515431c1\": \"No change record found for {0} with id {1}\",\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"Authentication requires model {0} to be defined.\",\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Access Denied\",\n  \"0caffe1d763c8cca6a61814abe33b776\": \"Email is required\",\n  \"0da38687fed24275c1547e815914a8e3\": \"Delete a related item by id for {0}.\",\n  \"0e21aad369dd09e1965c11949303cefd\": \"Remoting metadata for {0}.{1} {{\\\"isStatic\\\"}} does not match new method name-based style.\",\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"a list of colors is available at {{http://localhost:3000/colors}}\",\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"The response body contains properties of the {{AccessToken}} created on login.\\nDepending on the value of `include` parameter, the body may contain additional properties:\\n\\n  - `user` - `U+007BUserU+007D` - Data of the currently logged in user. {{(`include=user`)}}\\n\\n\",\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t SUBJECT:{0}\",\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Created: {0}\",\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Update a related item by id for {0}.\",\n  \"275f22ab95671f095640ca99194b7635\": \"\\t FROM:{0}\",\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} was removed in version 3.0. See {1} for more details.\",\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"could not find {0} with id {1}\",\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Expected boolean, got {0}\",\n  \"320c482401afa1207c04343ab162e803\": \"Invalid principal type: {0}\",\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"The relations property of `{0}` configuration must be an object\",\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Model not found: model `{0}` is extending an unknown model `{1}`.\",\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Please verify your email by opening this link in a web browser:\\n\\t{0}\",\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"The password entered was too long. Max length is {0} (entered {1})\",\n  \"3aae63bb7e8e046641767571c1591441\": \"login failed as the email has not been verified\",\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Unknown {{middleware}} phase {0}\",\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} is using model setting {1} which is no longer available.\",\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Invalid token: {0}\",\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Thanks for Registering\",\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Warning: No email transport specified for sending email. Setup a transport to send mail messages.\",\n  \"4203ab415ec66a78d3164345439ba76e\": \"Cannot call {0}.{1}(). The {2} method has not been setup. The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!\",\n  \"42a36bac5cf03c4418d664500c81047a\": \"{{DataSource}} option {{\\\"defaultForType\\\"}} is no longer supported\",\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"Email not found\",\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Sending Mail:\",\n  \"4b494de07f524703ac0879addbd64b13\": \"Email has not been verified\",\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"Update {0} of this model.\",\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Foreign key for {0}.\",\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Cannot create data source {0}: {1}\",\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"You must connect the {{Email}} Model to a {{Mail}} connector\",\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Check the existence of {0} relation to an item by id.\",\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Remove the {0} relation to an item by id.\",\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"login failed\",\n  \"5fa3afb425819ebde958043e598cb664\": \"could not find a model with {{id}} {0}\",\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"Relation `{0}` does not exist for model `{1}`\",\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Cannot apply bulk updates, the connector does not correctly report the number of updated records.\",\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t TEXT:{0}\",\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Find a related item by id for {0}.\",\n  \"6bc376432cd9972cf991aad3de371e78\": \"Missing data for change: {0}\",\n  \"705c2d456a3e204c4af56e671ec3225c\": \"Could not find {{accessToken}}\",\n  \"734a7bebb65e10899935126ba63dd51f\": \"The options property of `{0}` configuration must be an object\",\n  \"779467f467862836e19f494a37d6ab77\": \"The acls property of `{0}` configuration must be an array of objects\",\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"Queries {0} of {1}.\",\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Foreign key for {0}\",\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"My first mobile application\",\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Invalid Access Token\",\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Authorization Required\",\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} is required to logout\",\n  \"80a32e80cbed65eba2103201a7c94710\": \"Model not found: {0}\",\n  \"830cb6c862f8f364e9064cea0026f701\": \"Fetches hasOne relation {0}.\",\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"Property `{0}` cannot be reconfigured for `{1}`\",\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Invalid password.\",\n  \"855ecd4a64885ba272d782435f72a4d4\": \"Unknown \\\"{0}\\\" id \\\"{1}\\\".\",\n  \"860d1a0b8bd340411fb32baa72867989\": \"The transport does not support HTTP redirects.\",\n  \"86254879d01a60826a851066987703f2\": \"Add a related item by id for {0}.\",\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} or {{email}} is required\",\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Invalid remote method: `{0}`\",\n  \"8bab6720ecc58ec6412358c858a53484\": \"Bulk update failed, the connector has modified unexpected number of records: {0}\",\n  \"8ecab7f534de38360bd1b1c88e880123\": \"Child models of `{0}` will not inherit newly defined remote methods {1}.\",\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"The configuration of `{0}` is missing {{`dataSource`}} property.\\nUse `null` or `false` to mark models not attached to any data source.\",\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Fetches belongsTo relation {0}.\",\n  \"a50d10fc6e0959b220e085454c40381e\": \"User not found: {0}\",\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"Unknown \\\"{0}\\\" {{key}} \\\"{1}\\\".\",\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} is required\",\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Deletes all {0} of this model.\",\n  \"c2b5d51f007178170ca3952d59640ca4\": \"Cannot rectify {0} changes:\\n{1}\",\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"{0} middleware was removed in version 3.0. See {1} for more details.\",\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"Remoting metadata {{\\\"isStatic\\\"}} is deprecated. Please specify {{\\\"prototype.name\\\"}} in method name instead for {{isStatic=false}}.\",\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"Must provide a valid email\",\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"must specify an {{id}} or {{data}}\",\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead\",\n  \"d6f43b266533b04d442bdb3955622592\": \"Creates a new instance in {0} of this model.\",\n  \"da13d3cdf21330557254670dddd8c5c7\": \"Counts {0} of {1}.\",\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"Ignoring non-object \\\"methods\\\" setting of \\\"{0}\\\".\",\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"Unknown \\\"{0}\\\" {{id}} \\\"{1}\\\".\",\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"Bulk update failed, the connector has deleted unexpected number of records: {0}\",\n  \"ea63d226b6968e328bdf6876010786b5\": \"Cannot apply bulk updates, the connector does not correctly report the number of deleted records.\",\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"Cannot call {0}.{1}(). The {2} method has not been setup. The {{KeyValueModel}} has not been correctly attached to a {{DataSource}}!\",\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t TO:{0}\",\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t TRANSPORT:{0}\",\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"result:{0}\",\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Conflict\",\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Deletes {0} of this model.\",\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Please rework your app to use the offical solution for injecting \\\"options\\\" argument from request context,\\nsee {0}\"\n}\n"
  },
  {
    "path": "intl/es/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"No se ha encontrado ningún registro de cambio para {0} con el id {1}\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"La autenticación requiere la definición del modelo {0}.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Acceso denegado\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"El correo electrónico es obligatorio\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"Suprimir un elemento relacionado por id para {0}.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"Los metadatos de interacción remota para {0}.{1} {{\\\"isStatic\\\"}} no coinciden con el estilo basado en nombre del nuevo método.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"una lista de colores está disponible en {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"El cuerpo de respuesta contiene propiedades de la {{AccessToken}} creada durante el inicio de la sesión.\\nDependiendo del valor del parámetro `include`, el cuerpo puede contener propiedades adicionales:\\n\\n  - `user` - `U+007BUserU+007D` - Datos del usuario conectado actualmente. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t ASUNTO:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Creado: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Actualizar un elemento relacionado por id para {0}.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t DESDE:{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} se ha eliminado en la versión 3.0. Consulte {1} para obtener detalles.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"no se ha encontrado {0} con el ID {1}\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Se esperaba un booleano, se ha obtenido {0}\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Tipo de principal no válido: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"La configuración de la propiedad relations de `{0}` debe ser un objeto\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Modelo no encontrado: el modelo `{0}` está ampliando un modelo desconocido `{1}`.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Verifique su correo electrónico abriendo este enlace en un navegador:\\n\\t {0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"La contraseña especificada es demasiado larga. La longitud máxima es {0} (se ha especificado {1})\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"el inicio de sesión ha fallado porque el correo electrónico no ha sido verificado\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Fase de {{middleware}} desconocida {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} utiliza el valor de modelo {1}, que ya no está disponible.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"La señal no es válida: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Gracias por registrarse\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Aviso: No se ha especificado ningún transporte de correo electrónico para enviar correo electrónico. Configure un transporte para enviar mensajes de correo.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"No se puede llamar a {0}.{1}(). El método {2} no se ha configurado. {{PersistedModel}} no se ha conectado correctamente a un {{DataSource}}.\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"La opción de {{DataSource}} {{\\\"defaultForType\\\"}} ya no está soportada\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"Correo electrónico no encontrado\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Enviando correo:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"No se ha verificado el correo electrónico\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"Actualizar {0} de este modelo.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Clave foránea para {0}.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"No se puede crear el origen de datos {0}: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"Debe conectar el modelo de {{Email}} a un conector de {{Mail}}\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Comprobar la existencia de la relación {0} con un elemento por id.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Eliminar la relación {0} con un elemento por id.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"el inicio de sesión ha fallado\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"no se ha encontrado un modelo con {{id}} {0}\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"La relación `{0}` no existe para el modelo `{1}`\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"No pueden aplicarse actualizaciones masivas, el conector no notifica correctamente el número de registros actualizados.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t TEXTO:{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Buscar un elemento relacionado por id para {0}.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Faltan datos para el cambio: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"No se ha encontrado {{accessToken}}\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"La configuración de la propiedad de options de `{0}` debe ser un objeto\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"La configuración de la propiedad acls de `{0}` debe ser una matriz de objetos\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"{0} consultas de {1}.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Clave foránea para {0}\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"Mi primera aplicación móvil\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Señal de acceso no válida\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Autorización necesaria\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"Es necesario {{accessToken}} para cerrar la sesión\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"No se ha encontrado el modelo: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"Capta la relación hasOne {0}.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"La propiedad `{0}` no puede reconfigurarse para `{1}`\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Contraseña no válida.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"Id de \\\"{0}\\\" desconocido \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"El transporte no admite redirecciones HTTP.\",\r\n  \"86254879d01a60826a851066987703f2\": \"Añadir un elemento relacionado por id para {0}.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} o {{email}} es obligatorio\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Método remoto no válido: `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"La actualización masiva ha fallado, el conector ha modificado un número de registros inesperado: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"Los modelos hijo de `{0}` no heredarán los métodos remotos definidos recientemente {1}.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"En la configuración de `{0}` falta la propiedad {{`dataSource`}}.\\nUtilice `null` o `false` para marcar los modelos no conectados a ningún origen de datos.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Capta la relación belongsTo {0}.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"No se ha encontrado el usuario: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"{{key}} de \\\"{0}\\\" desconocido \\\"{1}\\\".\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} es obligatorio\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Suprime todos los {0} de este modelo.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"No se pueden rectificar los cambios de {0}:\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"El middleware {0} se ha eliminado en la versión 3.0. Consulte {1} para obtener detalles.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"Los metadatos de interacción remota {{\\\"isStatic\\\"}} están en desuso. Especifique {{\\\"prototype.name\\\"}} en el nombre de método en lugar de {{isStatic=false}}.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"Debe proporcionar un correo electrónico válido\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"debe especificar un {{id}} o {{data}}\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} se ha eliminado, utilice el nuevo módulo {{loopback-boot}} en su lugar\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Crea una nueva instancia en {0} de este modelo.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"Recuentos {0} de {1}.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"Se ignora el valor \\\"methods\\\" no de objeto de \\\"{0}\\\".\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"{{id}} de \\\"{0}\\\" desconocido \\\"{1}\\\".\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"La actualización masiva ha fallado, el conector ha suprimido un número de registros inesperado: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"No pueden aplicarse actualizaciones masivas, el conector no notifica correctamente el número de registros suprimidos.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"No se puede llamar a {0}.{1}(). El método {2} no se ha configurado. {{KeyValueModel}} no se ha conectado correctamente a un {{DataSource}}.\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t A:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t TRANSPORTE:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"resultado:{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Conflicto\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Suprime {0} de este modelo.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Reconfigure su aplicación para que utilice la solución oficial para inyectar el argumento \\\"options\\\" del contexto de solicitud, \\nconsulte {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/fr/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"Aucun enregistrement de changement trouvé pour {0} avec l'id {1}\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"L'authentification exige que le modèle {0} soit défini.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Accès refusé\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"L'adresse électronique est obligatoire\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"Supprimez un élément lié par id pour {0}.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"Les métadonnées remoting pour {0}.{1} {{\\\"isStatic\\\"}} ne correspondent pas au style basé sur le nom de la nouvelle méthode.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"une liste de couleurs est disponible sur {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"Le corps de réponse contient les propriétés de {{AccessToken}} créées lors de la connexion.\\nEn fonction de la valeur du paramètre `include`, le corps peut contenir des propriétés supplémentaires :\\n\\n  - `user` - `U+007BUserU+007D` - Données de l'utilisateur connecté. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t OBJET :{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Création de : {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Mettez à jour un élément lié par id pour {0}.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t DE :{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} a été supprimé dans la version 3.0. Pour plus de détails, voir {1}.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"impossible de trouver {0} avec l'id {1}\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Valeur booléenne attendue, {0} obtenu\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Type de principal non valide : {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"La propriété relations de la configuration `{0}` doit être un objet\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Modèle introuvable : le modèle `{0}` étend un modèle inconnu `{1}`.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Vérifiez votre courrier électronique en ouvrant ce lien dans un navigateur Web :\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"Le mot de passe saisi était trop long. La longueur maximale est {0} ({1} caractères saisis)\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"la connexion a échoué car l'adresse électronique n'a pas été vérifiée\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Phase {{middleware}} inconnue {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} utilise le paramètre de modèle {1} qui n'est plus disponible.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Jeton non valide : {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Merci pour votre inscription\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Avertissement : Aucun transport de courrier électronique n'est spécifié pour l'envoi d'un message électronique. Configurez un transport pour envoyer des messages électroniques.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"Impossible d'appeler {0}.{1}(). La méthode {2} n'a pas été configurée. {{PersistedModel}} n'a pas été associé correctement à {{DataSource}} !\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"L'option {{DataSource}} {{\\\"defaultForType\\\"}} n'est plus prise en charge\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"Adresse électronique introuvable\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Envoi d'un message électronique :\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"Le courrier électronique n'a pas été vérifié\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"Mettez à jour {0} de ce modèle.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Clé externe pour {0}.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Impossible de créer la source de données {0} : {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"Vous devez connecter le modèle {{Email}} à un connecteur {{Mail}}\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Vérifiez l'existence de la relation {0} à un élément par id.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Supprimez la relation {0} à un élément par id.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"échec de la connexion\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"impossible de trouver un modèle avec {{id}} {0}\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"La relation `{0}` n'existe pas pour le modèle `{1}`\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Impossible d'appliquer des mises à jour en bloc ; le connecteur ne signale pas correctement le nombre d'enregistrements mis à jour.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t TEXTE :{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Recherchez un élément lié par id pour {0}.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Données manquantes pour le changement : {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"{{accessToken}} introuvable\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"La propriété options de la configuration `{0}` doit être un objet\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"La propriété acls de la configuration `{0}` doit être un tableau d'objets\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"Demandes {0} de {1}.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Clé externe pour {0}\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"Ma première application mobile\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Jeton d'accès non valide\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Autorisation requise\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} est nécessaire pour la déconnexion\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"Modèle introuvable : {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"Extrait la relation hasOne {0}.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"La propriété `{0}` ne peut pas être reconfigurée pour `{1}`\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Mot de passe non valide.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"ID \\\"{0}\\\" inconnu \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"Le transport ne prend pas en charge les réacheminements HTTP.\",\r\n  \"86254879d01a60826a851066987703f2\": \"Ajoutez un élément lié par id pour {0}.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} ou {{email}} est obligatoire\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Méthode distante non valide : `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"La mise à jour en bloc a échoué ; le connecteur a modifié un nombre inattendu d'enregistrements : {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"Les modèles enfant de `{0}` n'hériteront pas des méthodes distantes nouvellement définies {1}.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML :{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"La propriété {{`dataSource`}} est manquante dans la configuration de `{0}`.\\nUtilisez `null` ou `false` pour marquer les modèles non associés à une source de données.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Extrait la relation belongsTo {0}.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"Utilisateur introuvable : {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"\\\"{0}\\\" {{key}} \\\"{1}\\\" inconnu.\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} est obligatoire\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Supprime tous les {0} de ce modèle.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"Impossible de rectifier les modifications {0} :\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"Le middleware {0} a été supprimé dans la version 3.0. Pour plus de détails, voir {1}.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"Les métadonnées Remoting {{\\\"isStatic\\\"}} sont obsolètes. Spécifiez {{\\\"prototype.name\\\"}} dans le nom de la méthode plutôt pour {{isStatic=false}}.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"Obligation de fournir une adresse électronique valide\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"obligation de spécifier {{id}} ou {{data}}\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} a été supprimé ; utilisez à la place le nouveau module {{loopback-boot}}\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Crée une instance dans {0} de ce modèle.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"Compte {0} de {1}.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"Le paramètre \\\"methods\\\" non objet de \\\"{0}\\\" est ignoré.\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"\\\"{0}\\\" {{id}} \\\"{1}\\\" inconnu.\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"La mise à jour en bloc a échoué ; le connecteur a supprimé un nombre inattendu d'enregistrements : {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"Impossible d'appliquer des mises à jour en bloc ; le connecteur ne signale pas correctement le nombre d'enregistrements supprimés.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"Impossible d'appeler {0}.{1}(). La méthode {2} n'a pas été configurée. {{KeyValueModel}} n'a pas été associé correctement à {{DataSource}} !\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t A :{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t TRANSPORT :{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"résultat :{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Conflit\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Supprime {0} de ce modèle.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Transformez votre application pour utiliser la solution officielle d'injection de l'argument \\\"options\\\" à partir du contexte de demande. \\nVoir {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/it/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"Nessun record di modifica trovato per {0} con id {1}\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"L'autenticazione richiede che sia definito il modello {0}.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Accesso negato\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"L'email è obbligatoria\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"Eliminare un elemento correlato in base all'ID per {0}.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"L'esecuzione della copia in remoto dei metadata per {0}.{1} {{\\\"isStatic\\\"}} non corrisponde al nuovo stile basato sul nome del metodo.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"un elenco dei colori è disponibile all'indirizzo {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"Il corpo della risposta contiene proprietà del {{AccessToken}} creato all'accesso.\\nIn base al valore del parametro `include`, il corpo può contenere ulteriori proprietà:\\n\\n  - `user` - `U+007BUserU+007D` - Dati dell'utente attualmente collegato.. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t OGGETTO:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Creato: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Aggiornare un elemento correlato in base all'ID per {0}.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t DA:{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} è stato rimosso nella versione 3.0. Consultare {1} per ulteriori dettagli.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"impossibile trovare {0} con id {1}\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Previsto valore booleano, ricevuto {0}\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Tipo principal non valido: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"La proprietà relations della configurazione `{0}` deve essere un oggetto\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Modello non trovato: il modello `{0}` estende un modello sconosciuto `{1}`.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Verificare la e-mail aprendo questo link in un browser web:\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"La password immessa è troppo lunga. La lunghezza massima è {0} (immessa {1})\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"login non riuscito perché l'email non è stata verificata\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Fase {{middleware}} sconosciuta {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} utilizza l'impostazione del modello {1} che non è più disponibile.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Token non valido: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Grazie per essersi registrati\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Avvertenza: nessun trasporto email specificato per l'invio della email. Configurare un trasporto per inviare messaggi email.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"Impossibile chiamare {0}.{1}(). Il metodo {2} non è stato configurato. {{PersistedModel}} non è stato correttamente collegato ad una {{DataSource}}!\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"L'opzione di {{DataSource}} {{\\\"defaultForType\\\"}} non è più supportata\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"Email non trovata\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Invio email:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"La e-mail non è stata verificata\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"Aggiornare {0} di questo modello.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Chiave esterna per {0}.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Impossibile creare l'origine dati {0}: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"È necessario collegare il modello {{Email}} ad un connettore {{Mail}}\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Verificare l'esistenza della relazione {0} ad un elemento in base all'ID.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Rimuovere la relazione {0} ad un elemento in base all'ID.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"login non riuscito\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"impossibile trovare un modello con {{id}} {0}\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"La relazione `{0}` non esiste per il modello `{1}`\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Impossibile applicare gli aggiornamenti in massa, il connettore non indica correttamente il numero di record aggiornati.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t TESTO:{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Trovare un elemento correlato in base all'ID per {0}.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Dati mancanti per la modifica: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"Impossibile trovare {{accessToken}}\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"La proprietà options della configurazione `{0}` deve essere un oggetto\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"La proprietà acls della configurazione `{0}` deve essere un array di oggetti\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"Query {0} di {1}.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Chiave esterna per {0}\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"Prima applicazione mobile personale\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Token di accesso non valido\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Autorizzazione richiesta\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} deve scollegarsi\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"Modello non trovato: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"Recupera la relazione hasOne {0}.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"Impossibile riconfigurare la proprietà `{0}` per `{1}`\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Password non valida.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"ID sconosciuto \\\"{0}\\\" \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"Il trasporto non supporta i reindirizzamenti HTTP.\",\r\n  \"86254879d01a60826a851066987703f2\": \"Aggiungere un elemento correlato in base all'ID per {0}.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"Sono richiesti {{username}} o {{email}}\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Metodo remoto non valido: `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"Aggiornamento in massa non riuscito, il connettore ha modificato un numero non previsto di record: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"I modelli child di `{0}` non erediteranno i metodi remoti definiti di recente {1}.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"La configurazione di `{0}` non contiene la proprietà {{`dataSource`}}.\\nUtilizzare `null` o `false` per contrassegnare i modelli non collegati ad alcuna origine dati.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Recupera la relazione belongsTo {0}.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"Utente non trovato: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"{{key}} \\\"{0}\\\" sconosciuto \\\"{1}\\\".\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} è obbligatorio\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Elimina tutti i {0} di questo modello.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"Impossibile correggere {0} modifiche:\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"Middleware {0} è stato rimosso nella versione 3.0. Consultare {1} per ulteriori dettagli.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"L'impostazione dei metadati {{\\\"isStatic\\\"}} in remoto è un'operazione obsoleta. Specificare {{\\\"prototype.name\\\"}} nel nome del metodo invece di {{isStatic=false}}.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"È necessario fornire una email valida\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"è necessario specificare {{id}} o {{data}}\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} è stato rimosso, utilizzare il nuovo modulo {{loopback-boot}}\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Crea una nuova istanza di questo modello in {0}.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"{0} di {1}.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"L'impostazione \\\"methods\\\" non oggetto di \\\"{0}\\\" viene ignorata.\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"{{id}} \\\"{0}\\\" sconosciuto \\\"{1}\\\".\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"Aggiornamento in massa non riuscito, il connettore ha eliminato un numero non previsto di record: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"Impossibile applicare gli aggiornamenti in massa, il connettore non indica correttamente il numero di record eliminati.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"Impossibile chiamare {0}.{1}(). Il metodo {2} non è stato configurato. {{KeyValueModel}} non è stato correttamente collegato ad una {{DataSource}}!\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t A:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t TRASPORTO:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"risultato:{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Conflitto\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Elimina {0} di questo modello.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Impostare l'app in modo da utilizzare la soluzione ufficiale per inserire l'argomento \\\"options\\\" dal contesto della richiesta,\\nconsultare {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/ja/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"ID {1} の {0} の変更レコードが見つかりませんでした\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"認証では、モデル {0} を定義する必要があります。\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"アクセス拒否\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"E メールは必須です\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"ID を指定して {0} の関連項目を削除します。\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"{0}.{1} のリモート・メタデータ {{\\\"isStatic\\\"}} は、新規メソッドの名前ベースの方式と一致しません。\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"カラー・リストは {{http://localhost:3000/colors}} で利用できます\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"応答本文には、ログイン時に作成された {{AccessToken}} のプロパティーが含まれます。\\n`include` パラメーターの値によっては、本文に追加のプロパティーが含まれる場合があります:\\n\\n  - `user` - `U+007BUserU+007D` - 現在ログインしているユーザーのデータ。 {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t 件名:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"作成済み: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"ID を指定して {0} の関連項目を更新します。\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t 送信元:{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} は、バージョン 3.0 で削除されました。 詳しくは、{1} を参照してください。\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"ID {1} の {0} が見つかりませんでした\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"ブール値が必要ですが、{0} が取得されました\",\r\n  \"320c482401afa1207c04343ab162e803\": \"無効なプリンシパル・タイプ: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"`{0}` 構成の関係プロパティーはオブジェクトでなければなりません\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"モデルが見つかりません: モデル `{0}` は不明のモデル `{1}` を拡張しています。\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Web ブラウザーで次のリンクを開いて、E メールを検証してください: \\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"入力したパスワードが長すぎます。最大長は {0} です ({1} が入力されました)\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"E メールが検証されていないため、ログインに失敗しました\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"不明な {{middleware}} フェーズ {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} は、使用できなくなったモデル設定 {1} を使用しています。\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"無効なトークン: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"ご登録いただき、ありがとうございます。\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"警告: E メール送信用の E メール・トランスポートが指定されていません。 メール・メッセージを送信するためのトランスポートをセットアップしてください。\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"{0}.{1}() を呼び出せません。 {2} メソッドがセットアップされていません。 {{PersistedModel}} は {{DataSource}} に正しく付加されていません。\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"{{DataSource}} オプション {{\\\"defaultForType\\\"}} はサポートされなくなりました\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"E メールが見つかりません\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"メールの送信:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"E メールが検証されていません\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"このモデルの {0} を更新します。\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"{0} の外部キー。\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"データ・ソース {0}: {1} を作成できません\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"{{Email}} モデルを {{Mail}} コネクターに接続する必要があります\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"ID を指定して項目との {0} 関係があることを確認します。\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"ID を指定して項目との {0} 関係を削除します。\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"ログインに失敗しました\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"{{id}} {0} のモデルが見つかりませんでした\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"モデル `{1}` には関係 `{0}` が存在しません\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"一括更新を適用できません。コネクターは更新されたレコードの数を正しく報告していません。\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t テキスト:{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"ID を指定して {0} の関連項目を検索します。\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"変更用のデータがありません: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"{{accessToken}} が見つかりませんでした\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"`{0}` 構成のオプション・プロパティーはオブジェクトでなければなりません\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"`{0}` 構成の ACL プロパティーはオブジェクトの配列でなければなりません\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"{1} の {0} を照会します。\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"{0} の外部キー\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"最初のモバイル・アプリケーション\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"無効なアクセス・トークン\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"許可が必要です\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"ログアウトするには {{accessToken}} が必要です\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"モデルが見つかりません: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"hasOne 関係 {0} をフェッチします。\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"`{1}` のプロパティー `{0}` を再構成できません\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"パスワードが無効です。\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"\\\"{0}\\\" ID \\\"{1}\\\" が不明です。\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"トランスポートでは HTTP リダイレクトはサポートされません。\",\r\n  \"86254879d01a60826a851066987703f2\": \"ID を指定して {0} の関連項目を追加します。\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} または {{email}} が必要です\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"無効なリモート・メソッド: `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"一括更新が失敗しました。コネクターは予期しない数のレコードを変更しました: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"`{0}` の子モデルは、新しく定義されたリモート・メソッド {1} を継承しません。\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"`{0}` の構成は {{`dataSource`}} プロパティーがありません。\\nどのデータ・ソースにも付加されていないモデルにマークを付けるには `null` または `false` を使用します。\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"belongsTo 関係 {0} をフェッチします。\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"ユーザーが見つかりません: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"\\\"{0}\\\" {{key}} \\\"{1}\\\" が不明です。\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} は必須です\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"このモデルのすべての {0} を削除します。\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"{0} の変更を修正できません:\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"{0} ミドルウェアは、バージョン 3.0 で削除されました。 詳しくは、{1} を参照してください。\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"リモート・メタデータ {{\\\"isStatic\\\"}} は非推奨です。 メソッド名では {{isStatic=false}} の代わりに {{\\\"prototype.name\\\"}} を指定してください。\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"有効な E メールを指定する必要があります\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"{{id}} または {{data}} を指定する必要があります\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} は削除されました。代わりに新規モジュール {{loopback-boot}} を使用してください\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"このモデルの {0} に新規インスタンスを作成します。\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"{1} の {0} をカウントします。\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"\\\"{0}\\\" の非オブジェクト「メソッド」設定を無視します。\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"\\\"{0}\\\" {{id}} \\\"{1}\\\" が不明です。\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"一括更新が失敗しました。コネクターは予期しない数のレコードを削除しました: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"一括更新を適用できません。コネクターは削除されたレコードの数を正しく報告していません。\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"{0}.{1}() を呼び出せません。 {2} メソッドがセットアップされていません。 {{KeyValueModel}} は {{DataSource}} に正しく付加されていません。\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t 宛先:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t トランスポート:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"結果:{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"競合\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"このモデルの {0} を削除します。\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"要求コンテキストからの \\\"options\\\" 引数を注入するための公式な解決策を使用するようにアプリケーションを作り直してください。\\n{0} を参照してください。\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/ko/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"ID가 {1}인 {0}에 대한 변경 레코드를 찾을 수 없음\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"인증을 위해 {0} 모델이 정의되어야 함\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"액세스 거부\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"이메일은 필수입니다.\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"{0}에 대해 ID로 관련 항목을 삭제하십시오.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"{0}.{1} {{\\\"isStatic\\\"}}의 원격 메타데이터가 새 메소드 이름 기반 스타일과 일치하지 않습니다.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"색상 목록은 {{http://localhost:3000/colors}}에 있음\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"응답 본문에 로그인 시 작성한 {{AccessToken}} 특성이 포함됩니다.\\n`include` 매개변수 값에 따라 본문에 추가 특성이 포함될 수 있습니다. \\n\\n  - `user` - `U+007BUserU+007D` - 현재 로그인된 사용자의 데이터. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t 제목:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"작성 날짜: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"{0}에 대해 ID로 관련 항목을 업데이트하십시오.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t 발신인:{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"버전 3.0에서 {0}이(가) 제거되었습니다. 자세한 정보는 {1}을(를) 참조하십시오.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"ID {1}(으)로 {0}을(를) 찾을 수 없음\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"예상 부울, 실제 {0}\",\r\n  \"320c482401afa1207c04343ab162e803\": \"올바르지 않은 프린시펄 유형: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"`{0}` 구성의 관계 특성은 오브젝트여야 함\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"모델을 찾을 수 없음: 모델 `{0}`은(는) 알 수 없는 모델 `{1}`의 확장입니다.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"웹 브라우저에서 이 링크를 열어 이메일을 확인하십시오.\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"입력한 비밀번호가 너무 깁니다. 최대 길이는 {0}입니다({1}자 입력함).\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"이메일이 확인되지 않아서 로그인에 실패했습니다.\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"알 수 없는 {{middleware}} 단계 {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0}에서 더 이상 사용할 수 없는 모델 설정 {1}을(를) 사용하고 있습니다.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"올바르지 않은 토큰: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"등록해 주셔서 감사합니다.\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"경고: 이메일 발송을 위해 이메일 전송이 지정되지 않았습니다. 메일 메시지를 보내려면 전송을 설정하십시오.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"{0}.{1}()을(를) 호출할 수 없습니다. {2} 메소드가 설정되지 않았습니다. {{PersistedModel}}이(가) {{DataSource}}에 재대로 첨부되지 않았습니다!\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"{{DataSource}} 옵션 {{\\\"defaultForType\\\"}}이(가) 더 이상 지원되지 않음\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"이메일을 찾을 수 없음\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"메일 발송 중:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"이메일이 확인되지 않았습니다.\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"이 모델의 {0}을(를) 업데이트하십시오.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"{0}의 외부 키입니다.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"데이터 소스 {0}을(를) 작성할 수 없음: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"{{Email}} 모델을 {{Mail}} 커넥터에 연결해야 합니다.\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"ID로 항목에 대한 {0} 관계의 존재를 확인하십시오.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"ID로 항목에 대한 {0} 관계를 제거하십시오.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"로그인 실패\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"{{id}} {0}인 모델을 찾을 수 없음\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"모델 `{1}`에 대해 관계 `{0}`이(가) 없습니다.\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"벌크 업데이트를 적용할 수 없습니다. 커넥터가 업데이트된 레코드 수를 제대로 보고하지 않습니다.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t 텍스트:{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"{0}에 대해 ID로 관련 항목을 찾으십시오.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"변경을 위한 데이터 누락: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"{{accessToken}}을(를) 찾을 수 없음\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"`{0}` 구성의 옵션 특성은 오브젝트여야 함\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"`{0}` 구성의 acls 특성은 오브젝트 배열이어야 함\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"{1}의 {0}을(를) 조회합니다.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"{0}의 외부 키\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"내 첫 번째 모바일 애플리케이션\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"올바르지 않은 액세스 토큰\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"권한 필수\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}}이(가) 로그아웃해야 함\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"모델을 찾을 수 없음: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"페치에 하나의 관계 {0}이(가) 있습니다.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"`{1}`에 대해 `{0}` 특성을 다시 구성할 수 없음\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"올바르지 않은 비밀번호입니다.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"알 수 없는 \\\"{0}\\\" ID \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"전송에서 HTTP 경로 재지원을 지원하지 않습니다.\",\r\n  \"86254879d01a60826a851066987703f2\": \"{0}에 대해 ID로 관련 항목을 추가하십시오.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} 또는 {{email}}은(는) 필수입니다.\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"올바르지 않은 원격 메소드: `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"벌크 업데이트에 실패했습니다. 커넥터가 예상치 못한 수의 레코드를 수정했습니다. {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"'{0}'의 하위 모델은 새로 정의된 원격 메소드 {1}을(를) 상속하지 않습니다.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"`{0}`의 구성에 {{`dataSource`}} 특성이 누락되었습니다.\\n데이터 소스에 첨부되지 않은 모델을 표시하려면 `null` 또는 `false`를 사용하십시오.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"페치가 관계 {0}에 속합니다.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"사용자를 찾을 수 없음: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"알 수 없는 \\\"{0}\\\" {{key}} \\\"{1}\\\".\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}}은(는) 필수입니다.\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"이 모델의 모든 {0}을(를) 삭제합니다.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"{0} 변경사항을 교정할 수 없음:\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"버전 3.0에서 {0} 미들웨어가 제거되었습니다. 자세한 정보는 {1}을(를) 참조하십시오.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"원격 메타데이터 {{\\\"isStatic\\\"}}이(가) 더 이상 사용되지 않습니다. {{isStatic=false}}인 경우 메소드 이름에 대신 {{\\\"prototype.name\\\"}}을(를) 지정하십시오.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"올바른 이메일을 제공해야 함\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"{{id}} 또는 {{data}}을(를) 지정해야 함\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}}이(가) 제거되었습니다. 대신 새 모듈 {{loopback-boot}}을(를) 사용하십시오.\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"이 모델의 {0}에서 새 인스턴스를 작성합니다.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"{1}의 {0}을(를) 계수합니다.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"\\\"{0}\\\"의 비오브젝트 \\\"methods\\\" 설정 무시\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"알 수 없는 \\\"{0}\\\" {{id}} \\\"{1}\\\".\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"벌크 업데이트에 실패했습니다. 커넥터가 예상치 못한 수의 레코드를 삭제했습니다. {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"벌크 업데이트를 적용할 수 없습니다. 커넥터가 삭제된 레코드 수를 제대로 보고하지 않습니다.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"{0}.{1}()을(를) 호출할 수 없습니다. {2} 메소드가 설정되지 않았습니다. {{KeyValueModel}}이(가) {{DataSource}}에 재대로 첨부되지 않았습니다!\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t 수신인:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t 전송:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"결과: {0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"충돌\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"이 모델의 {0}을(를) 삭제합니다.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"요청 컨텍스트의 \\\"options\\\" 인수 삽입에 정식 솔루션을 사용하도록 앱을 다시 작업하십시오.\\n{0} 참조\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/nl/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"Geen wijzigingsrecord gevonden voor {0} met ID {1}\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"Voor verificatie moet model {0} worden gedefinieerd.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Toegang geweigerd\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"E-mail is vereist\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"Gerelateerd item wissen op basis van ID voor {0}.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"Het niet-lokaal zetten van metagegevens voor {0}.{1} {{\\\"isStatic\\\"}} komt niet overeen met nieuwe op naam gebaseerde stijl van de methode.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"een lijst van kleuren is beschikbaar op {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"De lopende tekst van het antwoord bevat eigenschappen van het {{AccessToken}} dat is gemaakt bij aanmelding.\\nAfhankelijk van de waarde van de parameter 'include' kan de lopende tekst aanvullende eigenschappen bevatten:\\n\\n  - 'user' - 'U+007BUserU+007D' - Gegevens van de aangemelde gebruiker. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t ONDERWERP: {0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Gemaakt: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Gerelateerd item bijwerken op basis van ID voor {0}.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t VAN: {0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} is versie 3.0 verwijderd. Zie {1} voor meer informatie.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"kan {0} met ID {1} niet vinden\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Booleaanse waarde verwacht, {0} ontvangen\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Ongeldig type principal: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"De relaties-eigenschap van de '{0}'-configuratie moet een object zijn\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Model niet gevonden: model '{0}' is een uitbreiding van onbekend model '{1}'.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Controleer uw e-mail door deze link te openen in een webbrowser:\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"Het opgegeven wachtwoord is te lang. Max lengte is {0} (opgegeven {1})\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"Aanmelding mislukt omdat e-mail niet is gecontroleerd\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Onbekende {{middleware}}-fase {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} werkt met modelinstelling {1}, maar deze is niet meer beschikbaar.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Ongeldig token: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Hartelijk dank voor uw registratie\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Waarschuwing: Geen e-mailtransport opgegeven voor verzending van e-mail. Configureer een transport om e-mailberichten te verzenden.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"Kan {0}.{1}() niet aanroepen. De methode {2} is niet geconfigureerd. De {{PersistedModel}} is niet correct gekoppeld aan een {{DataSource}}!\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"{{DataSource}}-optie {{\\\"defaultForType\\\"}} wordt niet meer ondersteund\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"E-mail is niet gevonden\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Mail verzenden:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"E-mail is niet geverifieerd\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"{0} van dit model bijwerken.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Externe sleutel voor {0}.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Probleem bij maken van gegevensbron {0}: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"U moet verbinding maken tussen het model {{Email}} en een {{Mail}}-connector\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Bestaan van {0}-relatie met item controleren op basis van ID.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Verwijder de {0}-relatie met een item op basis van ID.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"Aanmelden is mislukt\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"geen model gevonden met {{id}} {0}\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"Relatie '{0}' voor model '{1}' bestaat niet\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Bulkupdates kunnen niet worden toegepast, de connector meldt niet het juiste aantal bijgewerkte records.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t TEKST: {0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Gerelateerd item zoeken op basis van ID voor {0}.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Ontbrekende gegevens voor wijziging: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"{{accessToken}} is niet gevonden\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"De opties-eigenschap van de '{0}'-configuratie moet een object zijn\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"De acls-eigenschap van de '{0}'-configuratie moet een array objecten zijn\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"Query's {0} van {1}.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Externe sleutel voor {0}\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"Mijn eerste mobiele toepassing\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Ongeldig toegangstoken\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Verplichte verificatie\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} is vereist voor afmelding\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"Model is niet gevonden: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"Haalt hasOne-relatie {0} op.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"Eigenschap '{0}' mag niet opnieuw worden geconfigureerd voor '{1}'\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Ongeldig wachtwoord.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"Onbekend \\\"{0}\\\"-ID \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"Transport biedt geen ondersteuning voor HTTP-omleidingen.\",\r\n  \"86254879d01a60826a851066987703f2\": \"Gerelateerd item toevoegen op basis van ID voor {0}.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} of {{email}} is verplicht\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Ongeldige niet-lokale methode: '{0}'\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"Bulkupdate is mislukt; connector heeft een onverwacht aantal records gewijzigd: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"Onderliggende modellen van '{0}' nemen de nieuw gedefinieerde niet-lokale methoden {1} niet over.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML: {0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"De eigenschap {{`dataSource`}} ontbreekt in de configuratie van '{0}'.\\nGebruik 'null' of 'false' om modellen te markeren die niet gekoppeld zijn aan een gegevensbron.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Haalt belongsTo-relatie {0} op.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"Gebruiker is niet gevonden: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"Onbekend \\\"{0}\\\" {{key}} \\\"{1}\\\".\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} is verplicht\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Verwijdert alle {0} van dit model.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"Wijzigingen van {0} kunnen niet worden hersteld:\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"{0} middleware is versie 3.0 verwijderd. Zie {1} voor meer informatie.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"Extern plaatsen (remoting) van metagegevens {{\\\"isStatic\\\"}} is gedeprecieerd. Geef {{\\\"prototype.name\\\"}} op in naam van methode in plaats van {{isStatic=false}}.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"U moet een geldig e-mailadres opgeven\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"U moet een {{id}} of {{data}} opgeven\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} is verwijderd; gebruik in plaats daarvan de nieuwe module {{loopback-boot}}\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Maakt een nieuwe instance in {0} van dit model.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"Aantal {0} van {1}.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"Niet-object \\\"methods\\\"-instelling \\\"{0}\\\" wordt genegeerd.\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"Onbekend \\\"{0}\\\" {{id}} \\\"{1}\\\".\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"Bulkupdate is mislukt; connector heeft een onverwacht aantal records gewist: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"Bulkupdates kunnen niet worden toegepast, de connector meldt niet het juiste aantal gewiste records.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"Kan {0}.{1}() niet aanroepen. De methode {2} is niet geconfigureerd. De {{KeyValueModel}} is niet correct gekoppeld aan een {{DataSource}}!\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t AAN: {0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t TRANSPORT: {0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"resultaat:{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Conflict\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Verwijdert {0} van dit model.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Herzie uw app zodanig dat deze gebruikmaakt van de officiële oplossing voor het injecteren van het argument \\\"options\\\" vanuit de aanvraagcontext. \\nZie {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/pl/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"Nie znaleziono rekordu zmiany dla elementu {0} o identyfikatorze {1}\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"Uwierzytelnianie wymaga zdefiniowania modelu {0}.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Odmowa dostępu\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"Adres e-mail jest wymagany\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"Usuń pokrewny element wg identyfikatora dla {0}.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"Zdalne metadane dla {0}.{1} {{\\\"isStatic\\\"}} nie pasują do opartego na nazwie stylu nowej metody.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"lista kolorów jest dostępna pod adresem {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"Treść odpowiedzi zawiera właściwości elementu {{AccessToken}} utworzonego przy logowaniu.\\nW zależności od wartości parametru `include`, treść może zawierać dodatkowe właściwości:\\n\\n  - `user` - `U+007BUserU+007D` — dane aktualnie zalogowanego użytkownika. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t TEMAT:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Utworzono: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Zaktualizuj pokrewny element wg identyfikatora dla {0}.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t OD:{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"Element {0} został usunięty w wersji 3.0. Więcej informacji na ten temat zawiera sekcja {1}.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"nie można znaleźć elementu {0} o identyfikatorze {1}\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Oczekiwano wartości boolowskiej, otrzymano {0}\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Niepoprawny typ elementu głównego: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"Właściwość relacji konfiguracji `{0}` musi być obiektem\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Nie znaleziono modelu: model `{0}` rozszerza nieznany model `{1}`.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Zweryfikuj swój adres e-mail, otwierając ten odsyłacz w przeglądarce WWW:\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"Wprowadzone hasło było zbyt długie. Maksymalna liczba znaków: {0} (wprowadzono: {1}).\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"logowanie nie powiodło się, ponieważ adres e-mail nie został zweryfikowany\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Nieznana faza {{middleware}} {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} używa ustawienia modelu {1}, które nie jest już dostępne.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Niepoprawny znacznik: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Dziękujemy za zarejestrowanie\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Ostrzeżenie: nie określono transportu poczty elektronicznej na potrzeby wysyłania wiadomości e-mail. Skonfiguruj transport w celu wysyłania wiadomości e-mail.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"Nie można wywołać metody {0}.{1}(). Metoda {2} nie została skonfigurowana. Model {{PersistedModel}} nie został poprawnie przyłączony do źródła danych {{DataSource}}!\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"Opcja {{\\\"defaultForType\\\"}} źródła danych {{DataSource}} nie jest już obsługiwana\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"Nie znaleziono adresu e-mail\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Wysyłanie poczty:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"Adres e-mail nie został zweryfikowany\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"Zaktualizuj element {0} tego modelu.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Klucz obcy dla {0}.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Nie można utworzyć źródła danych {0}: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"Należy połączyć model {{Email}} z konektorem {{Mail}}\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Sprawdź istnienie relacji {0} z elementem wg identyfikatora.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Usuń relację {0} z elementem wg identyfikatora.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"logowanie nie powiodło się\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"nie można znaleźć modelu o identyfikatorze {{id}} {0}\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"Relacja `{0}` nie istnieje dla modelu `{1}'\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Nie można zastosować aktualizacji masowej, ponieważ konektor nie raportuje poprawnie liczby zaktualizowanych rekordów.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t TEKST:{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Znajdź pokrewny element wg identyfikatora dla {0}.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Brak danych do zmiany: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"Nie można znaleźć obiektu {{accessToken}}\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"Właściwość options konfiguracji `{0}` musi być obiektem\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"Właściwość acls konfiguracji `{0}` musi być tablicą obiektów\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"Odpytuje element {0} w {1}.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Klucz obcy dla {0}\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"Moja pierwsza aplikacja dla urządzeń przenośnych\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Niepoprawny znacznik dostępu\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Wymagana autoryzacja\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} jest wymagany do wylogowania się\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"Nie znaleziono modelu: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"Pobiera relację hasOne {0}.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"Nie można zrekonfigurować właściwości `{0}` dla `{1}`\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Niepoprawne hasło.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"Nieznany identyfikator \\\"{0}\\\" \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"transport nie obsługuje przekierowań HTTP.\",\r\n  \"86254879d01a60826a851066987703f2\": \"Dodaj pokrewny element wg identyfikatora dla {0}.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"Wymagana jest {{username}} lub {{email}}\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Niepoprawna metoda zdalna: `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"Aktualizacja masowa nie powiodła się, konektor zmodyfikował nieoczekiwaną liczbę rekordów: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"Elementy potomne obiektu `{0}` nie odziedziczą nowo zdefiniowanych metod zdalnych {1}.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"W konfiguracji elementu `{0}` brakuje właściwości {{`dataSource`}}.\\nUżyj wartości `null`, aby oznaczyć modele, które nie są przyłączone do żadnego źródła danych.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Pobiera relację belongsTo {0}.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"Nie znaleziono użytkownika: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"Nieznany klucz {{key}} \\\"{0}\\\" \\\"{1}\\\".\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"Właściwość {{realm}} jest wymagana.\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Usuwa wszystkie {0} tego modelu.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"Nie można skorygować {0} zmian: \\n {1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"Warstwa pośrednia {0} została usunięta w wersji 3.0. Więcej informacji na ten temat zawiera sekcja {1}.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"Metadane zdalnego dostępu {{\\\"isStatic\\\"}} są nieaktualne. Określ właściwość {{\\\"prototype.name\\\"}} w nazwie metody zamiast właściwości {{isStatic=false}}.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"Należy podać poprawny adres e-mail\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"należy określić {{id}} lub {{data}}\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"Moduł {{`app.boot`}} został usunięty; zamiast niego użyj nowego modułu {{loopback-boot}}\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Tworzy nową instancję w elemencie {0} tego modelu.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"Zlicza elementy {0} w {1}.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"Ignorowanie niebędącego obiektem ustawienia \\\"methods\\\" elementu \\\"{0}\\\".\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"Nieznany identyfikator {{id}} \\\"{0}\\\" \\\"{1}\\\".\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"Aktualizacja masowa nie powiodła się, konektor usunął nieoczekiwaną liczbę rekordów: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"Nie można zastosować aktualizacji masowej, ponieważ konektor nie raportuje poprawnie liczby usuniętych rekordów.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"Nie można wywołać metody {0}.{1}(). Metoda {2} nie została skonfigurowana. Model {{KeyValueModel}} nie został poprawnie przyłączony do źródła danych {{DataSource}}!\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t DO:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t TRANSPORT:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"wynik:{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Konflikt\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Usuwa element {0} tego modelu.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Zmodyfikuj aplikację w celu użycia oficjalnego rozwiązania do wstrzykiwania argumentu \\\"options\\\" z kontekstu żądania,\\npatrz sekcja {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/pt/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"Nenhum registro de mudança localizado para {0} com o ID {1}\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"Autenticação requer que modelo {0} seja definido.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Acesso Negado\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"E-mail é necessário\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"Excluir um item relacionado por ID para {0}.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"Os metadados remotos para {0}.{1} {{\\\"isStatic\\\"}} não correspondem ao novo estilo baseado em nome do método. \",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"uma lista de cores está disponível em {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"O corpo de resposta contém propriedades do {{AccessToken}} criado no login.\\nDependendo do valor do parâmetro `include`, o corpo poderá conter propriedades adicionais:\\n\\n  - `user` - `U+007BUserU+007D` - Dados do usuário com login efetuado atualmente. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t ASSUNTO:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Criado: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Atualizar um item relacionado por ID para {0}.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t DE:{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} foi removido na versão 3.0. Consulte {1} para obter mais detalhes.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"não foi possível localizar {0} com ID {1}\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Booleano esperado, obteve {0}\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Tipo principal inválido: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"A propriedade de relações da configuração de `{0}` deve ser um objeto\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Modelo não localizado: modelo `{0}` está estendendo um modelo `{1}` desconhecido.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Verifique seu e-mail abrindo este link em um navegador da web:\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"A senha inserida é muito longa. O comprimento máximo é de {0} ({1} inserido)\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"login com falha pois o e-mail não foi verificado\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Fase {0} do {{middleware}} desconhecida\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} está usando a configuração do modelo {1} que não está mais disponível.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Token inválido: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Obrigado por se Registrar\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Aviso: Nenhum transporte de e-mail especificado para enviar e-mail. Configure um transporte para enviar mensagens de e-mail.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"Não é possível chamar {0}.{1}(). O método {2} não foi configurado. O {{PersistedModel}} não foi conectado corretamente a uma {{DataSource}}!\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"Opção {{\\\"defaultForType\\\"}} de {{DataSource}} não é mais suportada\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"E-mail não encontrado\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Enviando E-mail:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"E-mail não foi verificado\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"Atualizar {0} deste modelo.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Chave estrangeira para {0}.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Não é possível criar origem de dados {0}: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"Deve-se conectar o Modelo de {{Email}} em um conector de {{Mail}}\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Verifique a existência da relação de {0} com um item por ID.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Remova a relação de {0} com um item por ID.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"falha de login\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"não foi possível localizar um modelo com {{id}} {0}\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"Relação `{0}` não existe para o modelo `{1}`\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Não é possível aplicar atualizações em massa, o conector não relata o número de registros de atualização corretamente.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t TEXTO:{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Localize um item relacionado por ID para {0}.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Dados ausentes para a mudança: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"Não foi possível localizar o {{accessToken}}\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"A propriedade de opções da configuração de `{0}` deve ser um objeto\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"A propriedade acls da configuração de `{0}` deve ser uma matriz de objetos\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"{0} consultas de {1}.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Chave estrangeira para {0}\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"Meu primeiro aplicativo móvel\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Token de Acesso Inválido\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Autorização Necessária\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} é necessário para efetuar logout\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"Modelo não localizado: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"Busca relação {0} de hasOne.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"A propriedade `{0}` não pode ser reconfigurada para `{1}`\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Senha inválida.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"ID \\\"{1}\\\" de \\\"{0}\\\" desconhecido.\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"O transporte não suporta redirecionamentos de HTTP.\",\r\n  \"86254879d01a60826a851066987703f2\": \"Inclua um item relacionado por ID para {0}.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} ou {{email}} é necessário\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Método remoto inválido: `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"Atualização em massa falhou, o conector modificou um número inesperado de registros: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"Modelos filhos de `{0}` não herdarão métodos remotos recém-definidos {1}.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"A configuração de `{0}` não possui a propriedade {{`dataSource`}}.\\nUse `null` ou `false` para marcar modelos não conectados a nenhuma origem de dados.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Busca relação {0} de belongsTo.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"Usuário não localizado: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"\\\"{0}\\\" {{key}} \\\"{1}\\\" desconhecido.\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} é obrigatório\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Exclui todos os {0} deste modelo.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"Não é possível retificar mudanças de {0}:\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"{0} middleware foi removido na versão 3.0. Consulte {1} para obter mais detalhes.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"Metadados {{\\\"isStatic\\\"}} remotos estão descontinuados. Especifique {{\\\"prototype.name\\\"}} no nome do método em vez de para {{isStatic=false}}.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"Deve-se fornecer um e-mail válido\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"deve-se especificar um {{id}} ou {{data}}\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} foi removido, use o novo módulo {{loopback-boot}} no lugar\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Cria uma nova instância no {0} deste modelo.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"{0} contagens de {1}.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"Ignorando configuração de \\\"methods\\\" de não objeto de \\\"{0}\\\".\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"\\\"{0}\\\" {{id}} \\\"{1}\\\" desconhecido.\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"Atualização em massa falhou, o conector excluiu um número inesperado de registros: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"Não é possível aplicar atualizações em massa, o conector não relata o número de registros excluídos corretamente.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"Não é possível chamar {0}.{1}(). O método {2} não foi configurado. O {{KeyValueModel}} não foi conectado corretamente a uma {{DataSource}}!\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t PARA:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t TRANSPORTE:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"resultado:{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Conflito\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Exclui {0} deste modelo.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Retrabalhe seu aplicativo para usar a solução oficial para injetar o argumento \\\"options\\\" do contexto da solicitação,\\nconsulte {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/ru/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"Не удалось найти записи изменений для {0} с ИД {1}\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"Для идентификации необходимо определить модель {0}.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Доступ запрещен\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"Необходимо указать адрес электронной почты\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"Удалить связанный элемент по ИД для {0}.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"Метаданные удаленного соединения для {0}.{1} {{\\\"isStatic\\\"}} не соответствуют новому стилю на основе имени метода.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"Список цветов доступен по адресу {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"Тело ответа содержит свойства маркера {{AccessToken}}, созданного при входе в систему.\\nВ зависимости от значения параметра `include`, тело может содержать дополнительные свойства:\\n\\n  - `user` - `U+007BUserU+007D` - данные пользователя, вошедшего в систему. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t ТЕМА:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Кем создано: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"Изменить связанный элемент по ИД для {0}.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t От кого:{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0} удален в версии 3.0. Дополнительные сведения приведены в {1}.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"не удалось найти {0} с ИД {1}\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Ожидался тип boolean, получен тип {0}\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Недопустимый тип субъекта: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"Свойство relations конфигурации `{0}` должно быть объектом\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Модель не найдена: модель `{0}` расширяет неизвестную модель `{1}`.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Проверьте адрес электронной почты. Для этого откройте ссылку в веб-браузере:\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"Введен слишком длинный пароль. Максимальная длина составляет {0} символов (введено {1} символов)\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"Вход в систему не выполнен, так как не проверен адрес электронной почты\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Неизвестный этап {{middleware}} {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} использует параметр модели {1}, который больше не доступен.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Недопустимый маркер: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Спасибо за регистрацию\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Предупреждение: не указан транспортный протокол для отправки электронной почты. Настройте транспортный протокол для отправки сообщений электронной почты.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"Не удалось вызвать {0}. {1} (). Метод {2} не настроен. Модель {{PersistedModel}} неправильно подключена к источнику данных {{DataSource}}!\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"Опция {{DataSource}} {{\\\"defaultForType\\\"}} больше не поддерживается\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"Не найден адрес электронной почты\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Отправка почты:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"Не проверен адрес электронной почты\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"Обновить {0} этой модели.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"Внешний ключ для {0}.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Не удалось создать источник данных {0}: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"Необходимо подключить модель {{Email}} к коннектору {{Mail}}\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Проверьте существование связи {0} с элементом по ИД.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Удалить связь {0} с элементом по ИД.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"Вход в систему не выполнен\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"Не удалось найти модель с {{id}} {0}\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"Связь `{0}` не существует для модели `{1}`\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Не удалось применить обновления большого объема данных, коннектор неправильно сообщает о числе обновленных записей.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t ТЕКСТ: {0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"Найти связанный элемент по ИД для {0}.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Отсутствуют данные для изменения: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"Не удалось найти {{accessToken}}\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"Свойство options конфигурации `{0}` должно быть объектом\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"Свойство acls конфигурации `{0}` должно быть массивом объектов\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"{0} запросов из {1}.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"Внешний ключ для {0}\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"Мое первое мобильное приложение\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Недопустимый маркер доступа\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Требуется авторизация\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} требуется для завершения сеанса\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"Модель не найдена: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"Получает связь hasOne {0}.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"Не удается повторно выполнить конфигурацию свойства `{0}` для `{1}`\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Недопустимый пароль.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"Неизвестный ИД \\\"{0}\\\" \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"Транспортный протокол не поддерживает перенаправление HTTP.\",\r\n  \"86254879d01a60826a851066987703f2\": \"Добавить связанный элемент по ИД для {0}.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"Необходимо указать {{username}} или {{email}}\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Недопустимый удаленный метод: `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"Обновление большого объема данных не выполнено, коннектор изменил непредвиденное число записей: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"Дочерние модели ` {0} ' не будут наследовать только что определенные удаленные методы {1}.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"В конфигурации `{0}` отсутствует свойство {{`dataSource`}}.\\nДля пометки моделей, которые не подключены в источникам данных, используйте значение `null` или `false`.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"Получает связь belongsTo {0}.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"Пользователь не найден: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"Неизвестный {{key}} \\\"{0}\\\" \\\"{1}\\\".\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"Необходимо указать {{realm}}\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Удалить все {0} этой модели.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"Не удалось исправить изменения {0}:\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"Промежуточное ПО {0} удалено в версии 3.0. Дополнительные сведения приведены в {1}.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"Метаданные удаленного соединения {{\\\"isStatic\\\"}} устарели. В имени метода вместо {{isStatic=false}} укажите {{\\\"prototype.name\\\"}}.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"Необходимо указать допустимый адрес электронной почты\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"Необходимо указать {{id}} или {{data}}\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"Модуль {{`app.boot`}} был удален, используйте вместо него новый модуль {{loopback-boot}}\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Создает новый экземпляр в {0} этой модели.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"Считает {0} из {1}.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"Не относящийся к объекту параметр \\\"methods\\\" для \\\"{0}\\\" игнорируется.\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"Неизвестный {{id}} \\\"{0}\\\" \\\"{1}\\\".\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"Обновление большого объема данных не выполнено, коннектор удалил непредвиденное число записей: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"Не удалось применить обновления большого объема данных, коннектор неправильно сообщает о числе удаленных записей.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"Не удалось вызвать {0}.{1}(). Метод {2} не настроен. Модель {{KeyValueModel}} неправильно подключена к источнику данных {{DataSource}}!\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t Кому:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t Транспортный протокол:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"результат:{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Конфликт\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Удалить {0} этой модели.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Доработайте приложение с целью использования официального решения для добавления аргумента \\\"options\\\" из контекста запроса,\\nсм. {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/tr/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"{0} için {1} tanıtıcılı bir değişiklik kaydı bulunamadı\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"Kimlik doğrulaması {0} modelinin tanımlanmasını gerektiriyor.\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"Erişim Verilmedi\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"E-posta zorunludur\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"{0} için ilgili bir öğeyi tanıtıcı temelinde siler.\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"{0}.{1} {{\\\"isStatic\\\"}} ile ilgili uzaktan iletişim meta verisi, yöntem adına dayalı yeni stille eşleşmiyor.\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"renklerin listesine şu adresle erişebilirsiniz: {{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"Yanıt gövdesi, oturum açma sırasında yaratılan {{AccessToken}} belirtecine ilişkin özellikleri içerir.\\n`include` parametresinin değerine bağlı olarak, gövde ek özellikler içerebilir:\\n\\n  - `user` - `U+007BUserU+007D` - Oturum açmış olan kullanıcıya ilişkin veriler. {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t KONU:{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"Yaratıldığı tarih: {0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"{0} için ilgili bir öğeyi tanıtıcı temelinde günceller.\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t KİMDEN:{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"{0}, 3.0 sürümünde kaldırıldı. Daha fazla ayrıntı için bkz. {1}.\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"{1} tanıtıcılı {0} bulunamadı\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"Boole beklenirken {0} alındı\",\r\n  \"320c482401afa1207c04343ab162e803\": \"Geçersiz birincil kullanıcı tipi: {0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"`{0}` yapılandırmasının ilişkiler (relations) özelliği bir nesne olmalıdır\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"Model bulunamadı: `{0}` modeli, bilinmeyen `{1}` modelini genişletiyor.\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"Lütfen bu bağlantıyı bir web tarayıcısında açarak e-postanızı doğrulayın:\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"Girilen parola çok uzundu. Uzunluk üst sınırı: {0} (girilen: {1})\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"e-posta doğrulanmadığından oturum açma başarısız oldu\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"Bilinmeyen {{middleware}} aşaması {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0}, artık kullanılabilir olmayan {1} model ayarını kullanıyor.\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"Geçersiz belirteç: {0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"Kaydolduğunuz için teşekkürler\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"Uyarı: E-posta göndermek için e-posta aktarımı belirtilmedi. Posta iletileri göndermek için aktarım ayarlayın.\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"{0}.{1}() çağrılamıyor. {2} yöntemi ayarlanmamış. {{PersistedModel}}, bir veri kaynağına ({{DataSource}}) doğru olarak eklenmedi!\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"{{DataSource}} seçeneği {{\\\"defaultForType\\\"}} artık desteklenmiyor.\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"E-posta bulunamadı\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"Posta Gönderiliyor:\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"E-posta doğrulanmadı\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"Bu modele ilişkin {0} güncellemesi.\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"{0} için dış anahtar.\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"Veri kaynağı {0} yaratılamıyor: {1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"{{Email}} modelini bir {{Mail}} bağlayıcısına bağlamalısınız\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"Bir öğeye yönelik {0} ilişkisinin var olup olmadığını tanıtıcı temelinde denetler.\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"Bir öğeye yönelik {0} ilişkisini kaldırır.\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"oturum açma başarısız oldu\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"{{id}} {0} tanıtıcılı bir model bulunamadı\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"`{1}` modeli için `{0}` ilişkisi yok\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"Toplu güncelleme uygulanamaz; bağlayıcı, güncellenen kayıtların sayısını doğru olarak bildirmiyor.\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t METİN:{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"{0} için ilgili bir öğeyi tanıtıcı temelinde bulur.\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"Değişiklik için veri eksik: {0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"{{accessToken}} bulunamadı\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"`{0}` yapılandırmasının seçenekler (options) özelliği bir nesne olmalıdır.\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"`{0}` yapılandırmasının erişim denetim listeleri (acls) özelliği bir nesne dizisi olmalıdır.\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"{1} ile ilişkili {0} öğesini sorgular.\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"{0} için dış anahtar\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"İlk mobil uygulamam\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"Geçersiz Erişim Belirteci\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"Yetkilendirme Gerekli\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"Oturumu kapatmak için {{accessToken}} zorunludur\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"Model bulunamadı: {0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"{0} hasOne ilişkisini alır.\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"`{0}` özelliği `{1}` için yeniden yapılandırılamıyor\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"Geçersiz parola.\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"Bilinmeyen \\\"{0}\\\" tanıtıcısı \\\"{1}\\\".\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"Aktarım HTTP yeniden yönlendirmelerini desteklemiyor.\",\r\n  \"86254879d01a60826a851066987703f2\": \"{0} için ilgili bir öğeyi tanıtıcı temelinde ekler.\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} ya da {{email}} zorunludur\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"Uzak yöntem geçersiz: `{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"Toplu güncelleme başarısız oldu, bağlayıcı beklenmeyen sayıda kaydı değiştirdi: {0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"`{0}` alt modelleri, yeni tanımlanan uzak yöntemleri ({1}) devralmayacaktır.\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML:{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"`{0}` yapılandırmasında {{`dataSource`}} özelliği eksik.\\nHiçbir veri kaynağına eklenmemiş modelleri işaretlemek için `null` ya da `false` kullanın.\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"{0} belongsTo ilişkisini alır.\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"Kullanıcı bulunamadı: {0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"Bilinmeyen \\\"{0}\\\" {{key}} \\\"{1}\\\".\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} zorunludur\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"Bu modele ilişkin tüm {0} öğelerini siler.\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"{0} değişiklik düzeltilemiyor:\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"{0} ara katman yazılımı, 3.0 sürümünde kaldırıldı. Daha fazla ayrıntı için bkz. {1}.\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"{{\\\"isStatic\\\"}} uzaktan iletişim meta verisi kullanım dışı bırakıldı. Lütfen, yöntem adında {{isStatic=false}} yerine {{\\\"prototype.name\\\"}} belirtin.\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"Geçerli bir e-posta belirtilmeli\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"bir {{id}} ya da {{data}} belirtmelidir\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"{{`app.boot`}} kaldırıldı, onun yerine yeni {{loopback-boot}} modülünü kullanın\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"Bu modele ilişkin {0} içinde yeni eşgörünüm yaratır.\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"{1} ile ilişkili {0} öğesini sayar.\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"\\\"{0}\\\" öğesinin nesne olmayan \\\"methods\\\" atarı yoksayılıyor.\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"Bilinmeyen \\\"{0}\\\" {{id}} \\\"{1}\\\".\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"Toplu güncelleme başarısız oldu, bağlayıcı beklenmeyen sayıda kaydı sildi: {0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"Toplu güncelleme uygulanamaz; bağlayıcı, silinen kayıtların sayısını doğru olarak bildirmiyor.\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"{0}.{1}() çağrılamıyor. {2} yöntemi ayarlanmamış. {{KeyValueModel}}, bir veri kaynağına ({{DataSource}}) doğru olarak eklenmedi!\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t KİME:{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t AKTARIM:{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"sonuç:{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"Çakışma\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"Bu modele ilişkin {0} öğesini siler.\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"Lütfen istek bağlamından \\\"seçenekler\\\" bağımsız değişkenini eklemek amacıyla resmi çözümü kullanmak için uygulamanız üzerinde yeniden çalışın,\\nbkz. {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/zh-Hans/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"对于标识为 {1} 的 {0}，找不到任何更改记录\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"认证需要定义模型 {0}。\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"拒绝访问\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"电子邮件是必需的\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"按标识删除 {0} 的相关项。\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"{0}.{1} {{\\\"isStatic\\\"}} 的远程处理元数据不匹配新的基于方法名称的样式。\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"颜色列表位于：{{http://localhost:3000/colors}}\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"响应主体包含在登录时创建的 {{AccessToken}} 的属性。\\n根据“include”参数的值，主体可包含其他属性：\\n\\n  - `user` - `U+007BUserU+007D` - 当前已登录用户的数据。 {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t主题：{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"创建时间：{0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"按标识更新 {0} 的相关项。\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t发件人：{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"V3.0 中移除了 {0}。请参阅 {1} 以获取更多详细信息。\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"无法找到标识为 {1} 的 {0}\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"期望布尔值，获取 {0}\",\r\n  \"320c482401afa1207c04343ab162e803\": \"无效的主体类型：{0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"“{0}”配置的关系属性必须是对象。\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"找不到模型：模型“{0}”正在扩展未知的模型“{1}”。\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"请通过在 Web 浏览器中打开此链接来验证您的电子邮件：\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"输入的密码过长。最大长度为 {0}（输入的长度为 {1}）\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"因为尚未验证电子邮件，登录失败\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"未知的 {{middleware}} 阶段 {0}\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} 正在使用目前不再可用的模型设置 {1}。\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"无效的令牌：{0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"感谢您注册\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"警告：未指定用于发送电子邮件的电子邮件传输。设置传输以发送电子邮件消息。\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"无法调用 {0}.{1}()。尚未设置 {2} 方法。{{PersistedModel}} 未正确附加到 {{DataSource}}！\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"不再支持 {{DataSource}} 选项 {{\\\"defaultForType\\\"}}\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"找不到电子邮件\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"正在发送电子邮件：\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"尚未验证电子邮件\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"更新此模型的 {0}。\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"{0} 的外键。\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"无法创建数据源 {0}：{1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"您必须将 {{Email}} 模型连接到 {{Mail}} 连接器\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"按标识检查项的 {0} 关系是否存在。\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"按标识除去项的 {0} 关系。\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"登录失败\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"找不到具有 {{id}} {0} 的模型\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"对于模型“{1}”，关系“{0}”不存在\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"无法应用批量更新，连接器未正确报告更新的记录数。\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t 文本：{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"按标识查找 {0} 的相关项。\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"缺少更改的数据：{0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"无法找到 {{accessToken}}\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"“{0}”配置的选项属性必须是对象。\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"“{0}”配置的 acls 属性必须是对象数组。\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"查询 {1} 的 {0}。\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"{0} 的外键\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"我的第一个移动应用程序\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"无效的访问令牌\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"需要授权\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"{{accessToken}} 需要注销\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"找不到模型：{0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"访存 hasOne 关系 {0}。\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"无法针对“{1}”重新配置属性“{0}”。\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"密码无效。\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"未知的“{0}”标识“{1}”。\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"传输不支持 HTTP 重定向。\",\r\n  \"86254879d01a60826a851066987703f2\": \"按标识添加 {0} 的相关项。\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"{{username}} 或 {{email}} 是必需的\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"无效的远程方法：“{0}”\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"批量更新失败，连接器已修改意外数量的记录：{0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"“{0}”的子模型不会继承最新定义的远程方法 {1}。\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML：{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"“{0}”的配置缺少 {{`dataSource`}} 属性。\\n使用“null”或“false”来标记未附加到任何数据源的模型。\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"访存 belongsTo 关系 {0}。\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"找不到用户：{0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"未知的“{0}”{{key}}“{1}”。\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"{{realm}} 是必需的\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"删除此模型的所有 {0}。\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"无法纠正 {0} 更改：\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"V3.0 中移除了 {0} 中间件。请参阅 {1} 以获取更多详细信息。\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"不推荐使用远程处理元数据 {{\\\"isStatic\\\"}}。而是针对 {{isStatic=false}} 在方法名称中指定 {{\\\"prototype.name\\\"}}。\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"必须提供有效电子邮件\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"必须指定 {{id}} 或 {{data}}\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"已移除 {{`app.boot`}}，请改用新模块 {{loopback-boot}}\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"在此模型的 {0} 中创建新实例。\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"计算 {0} 的数量（{1}）。\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"忽略“{0}”的非对象“方法”设置。\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"未知的“{0}”{{id}}“{1}”。\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"批量更新失败，连接器已删除意外数量的记录：{0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"无法应用批量更新，连接器未正确报告删除的记录数。\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"无法调用 {0}.{1}()。尚未设置 {2} 方法。{{KeyValueModel}} 未正确附加到 {{DataSource}}！\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t 收件人：{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t 传输：{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"结果：{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"冲突\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"删除此模型的 {0}。\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"请重新设计您的应用程序以使用正式解决方案插入来自请求上下文的“options”自变量，\\n请参阅 {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "intl/zh-Hant/messages.json",
    "content": "{\r\n  \"03f79fa268fe199de2ce4345515431c1\": \"對於 id 為 {1} 的 {0}，找不到變更記錄\",\r\n  \"04bd8af876f001ceaf443aad6a9002f9\": \"需要定義模型 {0} 才能鑑別。\",\r\n  \"095afbf2f1f0e5be678f5dac5c54e717\": \"拒絕存取\",\r\n  \"0caffe1d763c8cca6a61814abe33b776\": \"需要電子郵件\",\r\n  \"0da38687fed24275c1547e815914a8e3\": \"依 id 刪除 {0} 的相關項目。\",\r\n  \"0e21aad369dd09e1965c11949303cefd\": \"{0} 的遠端 meta 資料。{1} {{\\\"isStatic\\\"}} 不符合新的方法名稱型樣式。\",\r\n  \"10e01c895dc0b2fecc385f9f462f1ca6\": \"{{http://localhost:3000/colors}} 提供顏色清單\",\r\n  \"1b2a6076dccbe91a56f1672eb3b8598c\": \"回應內文包含登入時建立的 {{AccessToken}} 的內容。\\n根據 `include` 參數的值而定，內文可能包含其他內容：\\n\\n  - `user` - `U+007BUserU+007D` - 目前登入的使用者的資料。 {{(`include=user`)}}\\n\\n\",\r\n  \"1d7833c3ca2f05fdad8fad7537531c40\": \"\\t 主旨：{0}\",\r\n  \"1e85f822b547a75d7d385048030e4ecb\": \"已建立：{0}\",\r\n  \"22fe62fa8d595b72c62208beddaa2a56\": \"依 id 更新 {0} 的相關項目。\",\r\n  \"275f22ab95671f095640ca99194b7635\": \"\\t 寄件者：{0}\",\r\n  \"2860bccdf9ef1e350c1a38932ed12173\": \"3.0 版中已移除 {0}。如需詳細資料，請參閱 {1}。\",\r\n  \"2d3071e3b18681c80a090dc0efbdb349\": \"找不到 id 為 {1} 的 {0}\",\r\n  \"316e5b82c203cf3de31a449ee07d0650\": \"預期為布林，但卻取得 {0}\",\r\n  \"320c482401afa1207c04343ab162e803\": \"無效的主體類型：{0}\",\r\n  \"3438fab56cc7ab92dfd88f0497e523e0\": \"`{0}` 配置的 relations 內容必須是物件\",\r\n  \"3591f1d3e115b46f9f195df5ca548a6a\": \"找不到模型：模型 `{0}` 正在延伸不明模型 `{1}`。\",\r\n  \"35e5252c62d80f8c54a5290d30f4c7d0\": \"請在 Web 瀏覽器中開啟此鏈結來驗證電子郵件：\\n\\t{0}\",\r\n  \"37bcd4b50dfae98734772e39ffb1ea3d\": \"輸入的密碼太長。長度上限為 {0}（輸入了 {1}）\",\r\n  \"3aae63bb7e8e046641767571c1591441\": \"因為尚未驗證電子郵件，所以登入失敗\",\r\n  \"3aecb24fa8bdd3f79d168761ca8a6729\": \"{{middleware}} 階段 {0} 不明\",\r\n  \"3ca45aa6f705c46a4c598a900716f086\": \"{0} 正在使用已不再可用的模型設定 {1}。\",\r\n  \"3caaa84fc103d6d5612173ae6d43b245\": \"無效記號：{0}\",\r\n  \"3d617953470be16d0c2b32f0bcfbb5ee\": \"感謝您登錄\",\r\n  \"3d63008ccfb2af1db2142e8cc2716ace\": \"警告：未指定用於傳送電子郵件的電子郵件傳輸。請設定傳輸來傳送郵件訊息。\",\r\n  \"4203ab415ec66a78d3164345439ba76e\": \"無法呼叫 {0}。{1}()。尚未設定 {2} 方法。{{PersistedModel}} 未正確連接至 {{DataSource}}！\",\r\n  \"42a36bac5cf03c4418d664500c81047a\": \"不再支援 {{DataSource}} 選項 {{\\\"defaultForType\\\"}}\",\r\n  \"44a6c8b1ded4ed653d19ddeaaf89a606\": \"找不到電子郵件\",\r\n  \"4a4f04a4e480fc5d4ee73b84d9a4b904\": \"正在傳送郵件：\",\r\n  \"4b494de07f524703ac0879addbd64b13\": \"尚未驗證電子郵件\",\r\n  \"528325f3cbf1b0ab9a08447515daac9a\": \"更新這個模型的 {0}。\",\r\n  \"543d19bad5e47ee1e9eb8af688e857b4\": \"{0} 的外部索引鍵。\",\r\n  \"57b87ae0e65f6ab7a2e3e6cbdfca49a4\": \"無法建立資料來源 {0}：{1}\",\r\n  \"5858e63efaa0e4ad86b61c0459ea32fa\": \"您必須將 {{Email}} 模型連接至 {{Mail}} 連接器\",\r\n  \"598ff0255ffd1d1b71e8de55dbe2c034\": \"依 id 檢查項目的 {0} 關係是否存在。\",\r\n  \"5a36cc6ba0cc27c754f6c5ed6015ea3c\": \"依 id 移除項目的 {0} 關係。\",\r\n  \"5e81ad3847a290dc650b47618b9cbc7e\": \"登入失敗\",\r\n  \"5fa3afb425819ebde958043e598cb664\": \"找不到 {{id}} 為 {0} 的模型\",\r\n  \"61e5deebaf44d68f4e6a508f30cc31a3\": \"模型 `{1}` 的關係 `{0}` 不存在\",\r\n  \"62e8b0a733417978bab22c8dacf5d7e6\": \"無法套用大量更新，連接器未正確報告已更新的記錄數。\",\r\n  \"63a091ced88001ab6acb58f61ec041c5\": \"\\t 文字：{0}\",\r\n  \"651f0b3cbba001635152ec3d3d954d0a\": \"依 id 尋找 {0} 的相關項目。\",\r\n  \"6bc376432cd9972cf991aad3de371e78\": \"遺漏變更的資料：{0}\",\r\n  \"705c2d456a3e204c4af56e671ec3225c\": \"找不到 {{accessToken}}\",\r\n  \"734a7bebb65e10899935126ba63dd51f\": \"`{0}` 配置的 options 內容必須是物件\",\r\n  \"779467f467862836e19f494a37d6ab77\": \"`{0}` 配置的 acls 內容必須是物件陣列\",\r\n  \"7bc7b301ad9c4fc873029d57fb9740fe\": \"查詢 {0} 個（共 {1} 個）。\",\r\n  \"7c837b88fd0e509bd3fc722d7ddf0711\": \"{0} 的外部索引鍵\",\r\n  \"7d5e7ed0efaedf3f55f380caae0df8b8\": \"我的第一個行動式應用程式\",\r\n  \"7e0fca41d098607e1c9aa353c67e0fa1\": \"存取記號無效\",\r\n  \"7e287fc885d9fdcf42da3a12f38572c1\": \"需要授權\",\r\n  \"7ea04ea91aac3cb7ce0ddd96b7ff1fa4\": \"需要 {{accessToken}} 才能登出\",\r\n  \"80a32e80cbed65eba2103201a7c94710\": \"找不到模型：{0}\",\r\n  \"830cb6c862f8f364e9064cea0026f701\": \"提取 hasOne 關係 {0}。\",\r\n  \"83cbdc2560ba9f09155ccfc63e08f1a1\": \"無法為 `{1}` 重新配置內容 `{0}`\",\r\n  \"855eb8db89b4921c42072832d33d2dc2\": \"密碼無效。\",\r\n  \"855ecd4a64885ba272d782435f72a4d4\": \"\\\"{0}\\\" ID \\\"{1}\\\" 不明。\",\r\n  \"860d1a0b8bd340411fb32baa72867989\": \"傳輸不支援 HTTP 重新導向。\",\r\n  \"86254879d01a60826a851066987703f2\": \"依 id 新增 {0} 的相關項目。\",\r\n  \"895b1f941d026870b3cc8e6af087c197\": \"需要 {{username}} 或 {{email}}\",\r\n  \"8ae418c605b6a45f2651be9b1677c180\": \"無效的遠端方法：`{0}`\",\r\n  \"8bab6720ecc58ec6412358c858a53484\": \"大量更新失敗，連接器已修改超乎預期的記錄數：{0}\",\r\n  \"8ecab7f534de38360bd1b1c88e880123\": \"`{0}` 的子項模型將不會繼承新定義的遠端方法 {1}。\",\r\n  \"93ba9a1d03da3b7696332d3f155c5bb7\": \"\\t HTML：{0}\",\r\n  \"97795efe0c3eb7f35ce8cf8cfe70682b\": \"`{0}` 的配置遺漏 {{`dataSource`}} 內容。\\n請使用 `null` 或 `false` 來標示未連接至任何資料來源的模型。\",\r\n  \"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7\": \"提取 belongsTo 關係 {0}。\",\r\n  \"a50d10fc6e0959b220e085454c40381e\": \"找不到使用者：{0}\",\r\n  \"b6f740aeb6f2eb9bee9cb049dbfe6a28\": \"\\\"{0}\\\" {{key}} \\\"{1}\\\" 不明。\",\r\n  \"ba96498b10c179f9cd75f75c8def4f70\": \"需要 {{realm}}\",\r\n  \"c0057a569ff9d3b509bac61a4b2f605d\": \"刪除這個模型的所有 {0}。\",\r\n  \"c2b5d51f007178170ca3952d59640ca4\": \"無法更正 {0} 個變更：\\n{1}\",\r\n  \"c4ee6d177c974532c3552d2f98eb72ea\": \"3.0 版中已移除 {0} 中介軟體。如需詳細資料，請參閱 {1}。\",\r\n  \"c61a5a02ba3801a892308f70f5d55a14\": \"遠端 meta 資料 {{\\\"isStatic\\\"}} 已淘汰。請在方法名稱中指定 {{\\\"prototype.name\\\"}} 來代替 {{isStatic=false}}。\",\r\n  \"c68a93f0a9524fed4ff64372fc90c55f\": \"必須提供有效的電子郵件\",\r\n  \"cd0412f2f33a4a2a316acc834f3f21a6\": \"必須指定 {{id}} 或 {{data}}\",\r\n  \"d5552322de5605c58b62f47ad26d2716\": \"已移除 {{`app.boot`}}，請改用新的模組 {{loopback-boot}}\",\r\n  \"d6f43b266533b04d442bdb3955622592\": \"在這個模型的 {0} 中建立新實例。\",\r\n  \"da13d3cdf21330557254670dddd8c5c7\": \"計算 {0} 個（共 {1} 個）。\",\r\n  \"dc568bee32deb0f6eaf63e73b20e8ceb\": \"忽略 \\\"{0}\\\" 的非物件 \\\"methods\\\" 設定。\",\r\n  \"e4434de4bb8f5a3cd1d416e4d80d7e0b\": \"\\\"{0}\\\" {{id}} \\\"{1}\\\" 不明。\",\r\n  \"e92aa25b6b864e3454b65a7c422bd114\": \"大量更新失敗，連接器已刪除非預期的記錄數：{0}\",\r\n  \"ea63d226b6968e328bdf6876010786b5\": \"無法套用大量更新，連接器未正確報告已刪除的記錄數。\",\r\n  \"ead044e2b4bce74b4357f8a03fb78ec4\": \"無法呼叫 {0}。{1}()。尚未設定 {2} 方法。{{KeyValueModel}} 未正確連接至 {{DataSource}}！\",\r\n  \"ecb06666ef95e5db27a5ac1d6a17923b\": \"\\t 收件者：{0}\",\r\n  \"f0aed00a3d3d0b97d6594e4b70e0c201\": \"\\t 傳輸：{0}\",\r\n  \"f0bd73df8714cefb925e3b8da2f4c5f6\": \"結果：{0}\",\r\n  \"f1d4ac54357cc0932f385d56814ba7e4\": \"衝突\",\r\n  \"f66ae3cf379b2fce28575a3282defe1a\": \"刪除這個模型的 {0}。\",\r\n  \"f8e26bcca62a47f579562f1cd2c785ff\": \"請重做您的應用程式以使用正式解決方案，從要求環境定義注入 \\\"options\\\" 引數，\\n請參閱 {0}\"\r\n}\r\n\r\n"
  },
  {
    "path": "lib/access-context.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst loopback = require('./loopback');\nconst debug = require('debug')('loopback:security:access-context');\n\nconst DEFAULT_SCOPES = ['DEFAULT'];\n\n/**\n * Access context represents the context for a request to access protected\n * resources\n *\n * NOTE While the method expects an array of principals in the AccessContext instance/object,\n * it also accepts a single principal defined with the following properties:\n * ```js\n * {\n *   // AccessContext instance/object\n *   // ..\n *   principalType: 'somePrincipalType', // APP, ROLE, USER, or custom user model name\n *   principalId: 'somePrincipalId',\n * }\n * ```\n *\n * @class\n * @options {AccessContext|Object} context An AccessContext instance or an object\n * @property {Principal[]} principals An array of principals\n * @property {Function} model The model class\n * @property {String} modelName The model name\n * @property {*} modelId The model id\n * @property {String} property The model property/method/relation name\n * @property {String} method The model method to be invoked\n * @property {String} accessType The access type: READ, REPLICATE, WRITE, or EXECUTE.\n * @property {AccessToken} accessToken The access token resolved for the request\n * @property {RemotingContext} remotingContext The request's remoting context\n * @property {Registry} registry The application or global registry\n * @returns {AccessContext}\n * @constructor\n */\nfunction AccessContext(context) {\n  if (!(this instanceof AccessContext)) {\n    return new AccessContext(context);\n  }\n  context = context || {};\n\n  assert(context.registry,\n    'Application registry is mandatory in AccessContext but missing in provided context');\n  this.registry = context.registry;\n  this.principals = context.principals || [];\n  let model = context.model;\n  model = ('string' === typeof model) ? this.registry.getModel(model) : model;\n  this.model = model;\n  this.modelName = model && model.modelName;\n\n  this.modelId = context.id || context.modelId;\n  this.property = context.property || AccessContext.ALL;\n\n  this.method = context.method;\n  this.sharedMethod = context.sharedMethod;\n  this.sharedClass = this.sharedMethod && this.sharedMethod.sharedClass;\n  if (this.sharedMethod) {\n    this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);\n  } else {\n    this.methodNames = [];\n  }\n\n  if (this.sharedMethod) {\n    this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);\n  }\n\n  this.accessType = context.accessType || AccessContext.ALL;\n\n  assert(loopback.AccessToken,\n    'AccessToken model must be defined before AccessContext model');\n  this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;\n\n  const principalType = context.principalType || Principal.USER;\n  const principalId = context.principalId || undefined;\n  const principalName = context.principalName || undefined;\n  if (principalId != null) {\n    this.addPrincipal(principalType, principalId, principalName);\n  }\n\n  const token = this.accessToken;\n\n  if (token.userId != null) {\n    this.addPrincipal(token.principalType || Principal.USER, token.userId);\n  }\n  if (token.appId != null) {\n    this.addPrincipal(Principal.APPLICATION, token.appId);\n  }\n  this.remotingContext = context.remotingContext;\n}\n\n// Define constant for the wildcard\nAccessContext.ALL = '*';\n\n// Define constants for access types\nAccessContext.READ = 'READ'; // Read operation\nAccessContext.REPLICATE = 'REPLICATE'; // Replicate (pull) changes\nAccessContext.WRITE = 'WRITE'; // Write operation\nAccessContext.EXECUTE = 'EXECUTE'; // Execute operation\n\nAccessContext.DEFAULT = 'DEFAULT'; // Not specified\nAccessContext.ALLOW = 'ALLOW'; // Allow\nAccessContext.ALARM = 'ALARM'; // Warn - send an alarm\nAccessContext.AUDIT = 'AUDIT'; // Audit - record the access\nAccessContext.DENY = 'DENY'; // Deny\n\nAccessContext.permissionOrder = {\n  DEFAULT: 0,\n  ALLOW: 1,\n  ALARM: 2,\n  AUDIT: 3,\n  DENY: 4,\n};\n\n/**\n * Add a principal to the context\n * @param {String} principalType The principal type\n * @param {*} principalId The principal id\n * @param {String} [principalName] The principal name\n * @returns {boolean}\n */\nAccessContext.prototype.addPrincipal = function(principalType, principalId, principalName) {\n  const principal = new Principal(principalType, principalId, principalName);\n  for (let i = 0; i < this.principals.length; i++) {\n    const p = this.principals[i];\n    if (p.equals(principal)) {\n      return false;\n    }\n  }\n  this.principals.push(principal);\n  return true;\n};\n\n/**\n * Get the user id\n * @returns {*}\n */\nAccessContext.prototype.getUserId = function() {\n  const user = this.getUser();\n  return user && user.id;\n};\n\n/**\n * Get the user\n * @returns {*}\n */\nAccessContext.prototype.getUser = function() {\n  const BaseUser = this.registry.getModel('User');\n  for (let i = 0; i < this.principals.length; i++) {\n    const p = this.principals[i];\n    const isBuiltinPrincipal = p.type === Principal.APP ||\n      p.type === Principal.ROLE ||\n      p.type == Principal.SCOPE;\n    if (isBuiltinPrincipal) continue;\n\n    // the principalType must either be 'USER'\n    if (p.type === Principal.USER) {\n      return {id: p.id, principalType: p.type};\n    }\n\n    // or permit to resolve a valid user model\n    const userModel = this.registry.findModel(p.type);\n    if (!userModel) continue;\n    if (userModel.prototype instanceof BaseUser) {\n      return {id: p.id, principalType: p.type};\n    }\n  }\n};\n\n/**\n * Get the application id\n * @returns {*}\n */\nAccessContext.prototype.getAppId = function() {\n  for (let i = 0; i < this.principals.length; i++) {\n    const p = this.principals[i];\n    if (p.type === Principal.APPLICATION) {\n      return p.id;\n    }\n  }\n  return null;\n};\n\n/**\n * Check if the access context has authenticated principals\n * @returns {boolean}\n */\nAccessContext.prototype.isAuthenticated = function() {\n  return this.getUserId() != null || this.getAppId() != null;\n};\n\n/**\n * Get the list of scopes required by the current access context.\n */\nAccessContext.prototype.getScopes = function() {\n  if (!this.sharedMethod)\n    return DEFAULT_SCOPES;\n\n  // For backwards compatibility, methods with no scopes defined\n  // are assigned a single \"DEFAULT\" scope\n  const methodLevel = this.sharedMethod.accessScopes || DEFAULT_SCOPES;\n\n  // TODO add model-level and app-level scopes\n\n  debug('--Context scopes of %s()--', this.sharedMethod.stringName);\n  debug('  method-level: %j', methodLevel);\n\n  return methodLevel;\n};\n\n/**\n * Check if the scope required by the remote method is allowed\n * by the scopes granted to the requesting access token.\n * @return {boolean}\n */\nAccessContext.prototype.isScopeAllowed = function() {\n  if (!this.accessToken) return false;\n\n  // For backwards compatibility, tokens with no scopes are treated\n  // as if they have \"DEFAULT\" scope granted\n  const tokenScopes = this.accessToken.scopes || DEFAULT_SCOPES;\n\n  const resourceScopes = this.getScopes();\n\n  // Scope is allowed when at least one of token's scopes\n  // is found in method's (resource's) scopes.\n  return Array.isArray(tokenScopes) && Array.isArray(resourceScopes) &&\n    resourceScopes.some(s => tokenScopes.indexOf(s) !== -1);\n};\n\n/*!\n * Print debug info for access context.\n */\n\nAccessContext.prototype.debug = function() {\n  if (debug.enabled) {\n    debug('---AccessContext---');\n    if (this.principals && this.principals.length) {\n      debug('principals:');\n      this.principals.forEach(function(principal) {\n        debug('principal: %j', principal);\n      });\n    } else {\n      debug('principals: %j', this.principals);\n    }\n    debug('modelName %s', this.modelName);\n    debug('modelId %s', this.modelId);\n    debug('property %s', this.property);\n    debug('method %s', this.method);\n    debug('accessType %s', this.accessType);\n    debug('accessScopes %j', this.getScopes());\n    if (this.accessToken) {\n      debug('accessToken:');\n      debug('  id %j', this.accessToken.id);\n      debug('  ttl %j', this.accessToken.ttl);\n      debug('  scopes %j', this.accessToken.scopes || DEFAULT_SCOPES);\n    }\n    debug('getUserId() %s', this.getUserId());\n    debug('isAuthenticated() %s', this.isAuthenticated());\n  }\n};\n\n/**\n * This class represents the abstract notion of a principal, which can be used\n * to represent any entity, such as an individual, a corporation, and a login id\n * @param {String} type The principal type\n * @param {*} id The principal id\n * @param {String} [name] The principal name\n * @param {String} modelName The principal model name\n * @returns {Principal}\n * @class\n */\nfunction Principal(type, id, name) {\n  if (!(this instanceof Principal)) {\n    return new Principal(type, id, name);\n  }\n  this.type = type;\n  this.id = id;\n  this.name = name;\n}\n\n// Define constants for principal types\nPrincipal.USER = 'USER';\nPrincipal.APP = Principal.APPLICATION = 'APP';\nPrincipal.ROLE = 'ROLE';\nPrincipal.SCOPE = 'SCOPE';\n\n/**\n * Compare if two principals are equal\n * Returns true if argument principal is equal to this principal.\n * @param {Object} p The other principal\n */\nPrincipal.prototype.equals = function(p) {\n  if (p instanceof Principal) {\n    return this.type === p.type && String(this.id) === String(p.id);\n  }\n  return false;\n};\n\n/**\n * A request to access protected resources.\n *\n * The method can either be called with the following signature or with a single\n * argument: an AccessRequest instance or an object containing all the required properties.\n *\n * @class\n * @options {String|AccessRequest|Object} model|req The model name,<br>\n *    or an AccessRequest instance/object.\n * @param {String} property The property/method/relation name\n * @param {String} accessType The access type\n * @param {String} permission The requested permission\n * @param {String[]} methodNames The names of involved methods\n * @param {Registry} registry The application or global registry\n * @returns {AccessRequest}\n */\nfunction AccessRequest(model, property, accessType, permission, methodNames, registry) {\n  if (!(this instanceof AccessRequest)) {\n    return new AccessRequest(model, property, accessType, permission, methodNames);\n  }\n  if (arguments.length === 1 && typeof model === 'object') {\n    // The argument is an object that contains all required properties\n    const obj = model || {};\n    this.model = obj.model || AccessContext.ALL;\n    this.property = obj.property || AccessContext.ALL;\n    this.accessType = obj.accessType || AccessContext.ALL;\n    this.permission = obj.permission || AccessContext.DEFAULT;\n    this.methodNames = obj.methodNames || [];\n    this.registry = obj.registry;\n  } else {\n    this.model = model || AccessContext.ALL;\n    this.property = property || AccessContext.ALL;\n    this.accessType = accessType || AccessContext.ALL;\n    this.permission = permission || AccessContext.DEFAULT;\n    this.methodNames = methodNames || [];\n    this.registry = registry;\n  }\n  // do not create AccessRequest without a registry\n  assert(this.registry,\n    'Application registry is mandatory in AccessRequest but missing in provided argument(s)');\n}\n\n/**\n * Does the request contain any wildcards?\n *\n * @returns {Boolean}\n */\nAccessRequest.prototype.isWildcard = function() {\n  return this.model === AccessContext.ALL ||\n    this.property === AccessContext.ALL ||\n    this.accessType === AccessContext.ALL;\n};\n\n/**\n * Does the given `ACL` apply to this `AccessRequest`.\n *\n * @param {ACL} acl\n */\n\nAccessRequest.prototype.exactlyMatches = function(acl) {\n  const matchesModel = acl.model === this.model;\n  const matchesProperty = acl.property === this.property;\n  const matchesMethodName = this.methodNames.indexOf(acl.property) !== -1;\n  const matchesAccessType = acl.accessType === this.accessType;\n\n  if (matchesModel && matchesAccessType) {\n    return matchesProperty || matchesMethodName;\n  }\n\n  return false;\n};\n\n/**\n * Settle the accessRequest's permission if DEFAULT\n * In most situations, the default permission can be resolved from the nested model\n * config. An default permission can also be explicitly provided to override it or\n * cope with AccessRequest instances without a nested model (e.g. model is '*')\n *\n * @param {String} defaultPermission (optional) the default permission to apply\n */\n\nAccessRequest.prototype.settleDefaultPermission = function(defaultPermission) {\n  if (this.permission !== 'DEFAULT')\n    return;\n\n  const modelName = this.model;\n  if (!defaultPermission) {\n    const modelClass = this.registry.findModel(modelName);\n    defaultPermission = modelClass && modelClass.settings.defaultPermission;\n  }\n\n  this.permission = defaultPermission || 'ALLOW';\n};\n\n/**\n * Is the request for access allowed?\n *\n * @returns {Boolean}\n */\n\nAccessRequest.prototype.isAllowed = function() {\n  return this.permission !== loopback.ACL.DENY;\n};\n\nAccessRequest.prototype.debug = function() {\n  if (debug.enabled) {\n    debug('---AccessRequest---');\n    debug(' model %s', this.model);\n    debug(' property %s', this.property);\n    debug(' accessType %s', this.accessType);\n    debug(' permission %s', this.permission);\n    debug(' isWildcard() %s', this.isWildcard());\n    debug(' isAllowed() %s', this.isAllowed());\n  }\n};\n\nmodule.exports.AccessContext = AccessContext;\nmodule.exports.Principal = Principal;\nmodule.exports.AccessRequest = AccessRequest;\nmodule.exports.DEFAULT_SCOPES = DEFAULT_SCOPES;\n"
  },
  {
    "path": "lib/application.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Module dependencies.\n */\n\n'use strict';\nconst g = require('./globalize');\nconst DataSource = require('loopback-datasource-juggler').DataSource;\nconst Registry = require('./registry');\nconst assert = require('assert');\nconst fs = require('fs');\nconst extend = require('util')._extend;\nconst RemoteObjects = require('strong-remoting');\nconst classify = require('underscore.string/classify');\nconst camelize = require('underscore.string/camelize');\nconst path = require('path');\nconst util = require('util');\n\n/**\n * The `App` object represents a Loopback application.\n *\n * The App object extends [Express](http://expressjs.com/api.html#express) and\n * supports Express middleware. See\n * [Express documentation](http://expressjs.com/) for details.\n *\n * ```js\n * var loopback = require('loopback');\n * var app = loopback();\n *\n * app.get('/', function(req, res){\n *   res.send('hello world');\n * });\n *\n * app.listen(3000);\n * ```\n *\n * @class LoopBackApplication\n * @header var app = loopback()\n */\nfunction App() {\n  // this is a dummy placeholder for jsdox\n}\n\n/*!\n * Export the app prototype.\n */\n\nconst app = module.exports = {};\n\n/**\n * Lazily load a set of [remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).\n *\n * **NOTE:** Calling `app.remotes()` more than once returns only a single set of remote objects.\n * @returns {RemoteObjects}\n */\n\napp.remotes = function() {\n  if (this._remotes) {\n    return this._remotes;\n  } else {\n    let options = {};\n\n    if (this.get) {\n      options = this.get('remoting');\n    }\n\n    return (this._remotes = RemoteObjects.create(options));\n  }\n};\n\n/*!\n * Remove a route by reference.\n */\n\napp.disuse = function(route) {\n  if (this.stack) {\n    for (let i = 0; i < this.stack.length; i++) {\n      if (this.stack[i].route === route) {\n        this.stack.splice(i, 1);\n      }\n    }\n  }\n};\n\n/**\n * Attach a model to the app. The `Model` will be available on the\n * `app.models` object.\n *\n * Example - Attach an existing model:\n ```js\n * var User = loopback.User;\n * app.model(User);\n *```\n * Example - Attach an existing model, alter some aspects of the model:\n * ```js\n * var User = loopback.User;\n * app.model(User, { dataSource: 'db' });\n *```\n *\n * @param {Object} Model The model to attach.\n * @options {Object} config The model's configuration.\n * @property {String|DataSource} dataSource The `DataSource` to which to attach the model.\n * @property {Boolean} [public] Whether the model should be exposed via REST API.\n * @property {Object} [relations] Relations to add/update.\n * @end\n * @returns {ModelConstructor} the model class\n */\n\napp.model = function(Model, config) {\n  let isPublic = true;\n  const registry = this.registry;\n\n  if (typeof Model === 'string') {\n    const msg = 'app.model(modelName, settings) is no longer supported. ' +\n      'Use app.registry.createModel(modelName, definition) and ' +\n      'app.model(ModelCtor, config) instead.';\n    throw new Error(msg);\n  }\n\n  if (arguments.length > 1) {\n    config = config || {};\n    configureModel(Model, config, this);\n    isPublic = config.public !== false;\n  } else {\n    assert(Model.prototype instanceof Model.registry.getModel('Model'),\n      Model.modelName + ' must be a descendant of loopback.Model');\n  }\n\n  const modelName = Model.modelName;\n  this.models[modelName] =\n    this.models[classify(modelName)] =\n      this.models[camelize(modelName)] = Model;\n\n  this.models().push(Model);\n\n  if (isPublic && Model.sharedClass) {\n    this.remotes().defineObjectType(Model.modelName, function(data) {\n      return new Model(data);\n    });\n    this.remotes().addClass(Model.sharedClass);\n    if (Model.settings.trackChanges && Model.Change) {\n      this.remotes().addClass(Model.Change.sharedClass);\n    }\n    clearHandlerCache(this);\n    this.emit('modelRemoted', Model.sharedClass);\n  }\n\n  const self = this;\n  Model.on('remoteMethodDisabled', function(model, methodName) {\n    clearHandlerCache(self);\n    self.emit('remoteMethodDisabled', model, methodName);\n  });\n  Model.on('remoteMethodAdded', function(model) {\n    clearHandlerCache(self);\n    self.emit('remoteMethodAdded', model);\n  });\n\n  Model.shared = isPublic;\n  Model.app = this;\n  Model.emit('attached', this);\n  return Model;\n};\n\n/**\n * Remove all references to a previously registered Model.\n *\n * The method emits \"modelDeleted\" event as a counter-part to \"modelRemoted\"\n * event.\n *\n * @param {String} modelName The name of the model to remove.\n */\napp.deleteModelByName = function(modelName) {\n  const ModelCtor = this.models[modelName];\n  delete this.models[modelName];\n  delete this.models[classify(modelName)];\n  delete this.models[camelize(modelName)];\n\n  if (ModelCtor) {\n    ModelCtor.removeAllListeners();\n\n    const ix = this._models.indexOf(ModelCtor);\n    if (ix > -1) {\n      this._models.splice(ix, 1);\n    }\n  }\n\n  const remotes = this.remotes();\n  remotes.deleteClassByName(modelName);\n  remotes.deleteTypeByName(modelName);\n\n  if (ModelCtor && ModelCtor.dataSource) {\n    ModelCtor.dataSource.deleteModelByName(modelName);\n  } else {\n    this.registry.modelBuilder.deleteModelByName(modelName);\n  }\n\n  clearHandlerCache(this);\n\n  this.emit('modelDeleted', ModelCtor || modelName);\n};\n\n/**\n * Get the models exported by the app. Returns only models defined using `app.model()`\n *\n * There are two ways to access models:\n *\n * 1.  Call `app.models()` to get a list of all models.\n *\n * ```js\n * var models = app.models();\n *\n * models.forEach(function(Model) {\n *  console.log(Model.modelName); // color\n * });\n * ```\n *\n * 2. Use `app.models` to access a model by name.\n * `app.models` has properties for all defined models.\n *\n * The following example illustrates accessing the `Product` and `CustomerReceipt` models\n * using the `models` object.\n *\n * ```js\n * var loopback = require('loopback');\n *  var app = loopback();\n *  app.boot({\n *   dataSources: {\n *     db: {connector: 'memory'}\n *   }\n * });\n *\n * var productModel = app.registry.createModel('product');\n * app.model(productModel, {dataSource: 'db'});\n * var customerReceiptModel = app.registry.createModel('customer-receipt');\n * app.model(customerReceiptModel, {dataSource: 'db'});\n *\n * // available based on the given name\n * var Product = app.models.Product;\n *\n * // also available as camelCase\n * var product = app.models.product;\n *\n * // multi-word models are avaiable as pascal cased\n * var CustomerReceipt = app.models.CustomerReceipt;\n *\n * // also available as camelCase\n * var customerReceipt = app.models.customerReceipt;\n * ```\n *\n * @returns {Array} Array of model classes.\n */\n\napp.models = function() {\n  return this._models || (this._models = []);\n};\n\n/**\n * Define a DataSource.\n *\n * @param {String} name The data source name\n * @param {Object} config The data source config\n */\napp.dataSource = function(name, config) {\n  try {\n    const ds = dataSourcesFromConfig(name, config, this.connectors, this.registry);\n    this.dataSources[name] =\n    this.dataSources[classify(name)] =\n    this.dataSources[camelize(name)] = ds;\n    ds.app = this;\n    return ds;\n  } catch (err) {\n    if (err.message) {\n      err.message = g.f('Cannot create data source %s: %s',\n        JSON.stringify(name), err.message);\n    }\n    throw err;\n  }\n};\n\n/**\n * Register a connector.\n *\n * When a new data-source is being added via `app.dataSource`, the connector\n * name is looked up in the registered connectors first.\n *\n * Connectors are required to be explicitly registered only for applications\n * using browserify, because browserify does not support dynamic require,\n * which is used by LoopBack to automatically load the connector module.\n *\n * @param {String} name Name of the connector, e.g. 'mysql'.\n * @param {Object} connector Connector object as returned\n *   by `require('loopback-connector-{name}')`.\n */\napp.connector = function(name, connector) {\n  this.connectors[name] =\n  this.connectors[classify(name)] =\n  this.connectors[camelize(name)] = connector;\n};\n\n/**\n * Get all remote objects.\n * @returns {Object} [Remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).\n */\n\napp.remoteObjects = function() {\n  const result = {};\n\n  this.remotes().classes().forEach(function(sharedClass) {\n    result[sharedClass.name] = sharedClass.ctor;\n  });\n\n  return result;\n};\n\n/*!\n * Get a handler of the specified type from the handler cache.\n * @triggers `mounted` events on shared class constructors (models)\n */\n\napp.handler = function(type, options) {\n  const handlers = this._handlers || (this._handlers = {});\n  if (handlers[type]) {\n    return handlers[type];\n  }\n\n  const remotes = this.remotes();\n  const handler = this._handlers[type] = remotes.handler(type, options);\n\n  remotes.classes().forEach(function(sharedClass) {\n    sharedClass.ctor.emit('mounted', app, sharedClass, remotes);\n  });\n\n  return handler;\n};\n\n/**\n * An object to store dataSource instances.\n */\n\napp.dataSources = app.datasources = {};\n\n/**\n * Enable app wide authentication.\n */\n\napp.enableAuth = function(options) {\n  const AUTH_MODELS = ['User', 'AccessToken', 'ACL', 'Role', 'RoleMapping'];\n\n  const remotes = this.remotes();\n  const app = this;\n\n  if (options && options.dataSource) {\n    const appModels = app.registry.modelBuilder.models;\n    AUTH_MODELS.forEach(function(m) {\n      const Model = app.registry.findModel(m);\n      if (!Model) {\n        throw new Error(\n          g.f('Authentication requires model %s to be defined.', m),\n        );\n      }\n\n      if (Model.dataSource || Model.app) return;\n\n      // Find descendants of Model that are attached,\n      // for example \"Customer\" extending \"User\" model\n      for (const name in appModels) {\n        const candidate = appModels[name];\n        const isSubclass = candidate.prototype instanceof Model;\n        const isAttached = !!candidate.dataSource || !!candidate.app;\n        if (isSubclass && isAttached) return;\n      }\n\n      app.model(Model, {\n        dataSource: options.dataSource,\n        public: m === 'User',\n      });\n    });\n  }\n\n  remotes.authorization = function(ctx, next) {\n    const method = ctx.method;\n    const req = ctx.req;\n    const Model = method.ctor;\n    const modelInstance = ctx.instance;\n\n    const modelId = modelInstance && modelInstance.id ||\n      // replacement for deprecated req.param()\n      (req.params && req.params.id !== undefined ? req.params.id :\n        req.body && req.body.id !== undefined ? req.body.id :\n          req.query && req.query.id !== undefined ? req.query.id :\n            undefined);\n\n    const modelName = Model.modelName;\n\n    const modelSettings = Model.settings || {};\n    let errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;\n    if (!req.accessToken) {\n      errStatusCode = 401;\n    }\n\n    if (Model.checkAccess) {\n      Model.checkAccess(\n        req.accessToken,\n        modelId,\n        method,\n        ctx,\n        function(err, allowed) {\n          if (err) {\n            console.log(err);\n            next(err);\n          } else if (allowed) {\n            next();\n          } else {\n            const messages = {\n              403: {\n                message: g.f('Access Denied'),\n                code: 'ACCESS_DENIED',\n              },\n              404: {\n                message: (g.f('could not find %s with id %s', modelName, modelId)),\n                code: 'MODEL_NOT_FOUND',\n              },\n              401: {\n                message: g.f('Authorization Required'),\n                code: 'AUTHORIZATION_REQUIRED',\n              },\n            };\n\n            const e = new Error(messages[errStatusCode].message || messages[403].message);\n            e.statusCode = errStatusCode;\n            e.code = messages[errStatusCode].code || messages[403].code;\n            next(e);\n          }\n        },\n      );\n    } else {\n      next();\n    }\n  };\n\n  this._verifyAuthModelRelations();\n\n  this.isAuthEnabled = true;\n};\n\napp._verifyAuthModelRelations = function() {\n  // Allow unit-tests (but also LoopBack users) to disable the warnings\n  if (this.get('_verifyAuthModelRelations') === false) return;\n\n  const AccessToken = this.registry.findModel('AccessToken');\n  const User = this.registry.findModel('User');\n  this.models().forEach(Model => {\n    if (Model === AccessToken || Model.prototype instanceof AccessToken) {\n      scheduleVerification(Model, verifyAccessTokenRelations);\n    }\n\n    if (Model === User || Model.prototype instanceof User) {\n      scheduleVerification(Model, verifyUserRelations);\n    }\n  });\n\n  function scheduleVerification(Model, verifyFn) {\n    if (Model.dataSource) {\n      verifyFn(Model);\n    } else {\n      Model.on('attached', () => verifyFn(Model));\n    }\n  }\n\n  function verifyAccessTokenRelations(Model) {\n    const belongsToUser = Model.relations && Model.relations.user;\n    if (belongsToUser) return;\n\n    const relationsConfig = Model.settings.relations || {};\n    const userName = (relationsConfig.user || {}).model;\n    if (userName) {\n      console.warn(\n        'The model %j configures \"belongsTo User-like models\" relation ' +\n          'with target model %j. However, the model %j is not attached to ' +\n          'the application and therefore cannot be used by this relation. ' +\n          'This typically happens when the application has a custom ' +\n          'custom User subclass, but does not fix AccessToken relations ' +\n          'to use this new model.\\n' +\n          'Learn more at http://ibm.biz/setup-loopback-auth',\n        Model.modelName, userName, userName,\n      );\n      return;\n    }\n\n    console.warn(\n      'The model %j does not have \"belongsTo User-like model\" relation ' +\n        'configured.\\n' +\n        'Learn more at http://ibm.biz/setup-loopback-auth',\n      Model.modelName,\n    );\n  }\n\n  function verifyUserRelations(Model) {\n    const hasManyTokens = Model.relations && Model.relations.accessTokens;\n\n    if (hasManyTokens) {\n      // display a temp warning message for users using multiple users config\n      if (hasManyTokens.polymorphic) {\n        console.warn(\n          'The app configuration follows the multiple user models setup ' +\n            'as described in http://ibm.biz/setup-loopback-auth',\n          'The built-in role resolver $owner is not currently compatible ' +\n            'with this configuration and should not be used in production.',\n        );\n      }\n      return;\n    }\n\n    const relationsConfig = Model.settings.relations || {};\n    const accessTokenName = (relationsConfig.accessTokens || {}).model;\n    if (accessTokenName) {\n      console.warn(\n        'The model %j configures \"hasMany AccessToken-like models\" relation ' +\n          'with target model %j. However, the model %j is not attached to ' +\n          'the application and therefore cannot be used by this relation. ' +\n          'This typically happens when the application has a custom ' +\n          'AccessToken subclass, but does not fix User relations to use this ' +\n          'new model.\\n' +\n          'Learn more at http://ibm.biz/setup-loopback-auth',\n        Model.modelName, accessTokenName, accessTokenName,\n      );\n      return;\n    }\n\n    console.warn(\n      'The model %j does not have \"hasMany AccessToken-like models\" relation ' +\n        'configured.\\n' +\n        'Learn more at http://ibm.biz/setup-loopback-auth',\n      Model.modelName,\n    );\n  }\n};\n\napp.boot = function(options) {\n  throw new Error(\n    g.f('{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead'),\n  );\n};\n\nfunction dataSourcesFromConfig(name, config, connectorRegistry, registry) {\n  let connectorPath;\n\n  assert(typeof config === 'object',\n    'can not create data source without config object');\n\n  if (typeof config.connector === 'string') {\n    const connectorName = config.connector;\n    if (connectorRegistry[connectorName]) {\n      config.connector = connectorRegistry[connectorName];\n    } else {\n      connectorPath = path.join(__dirname, 'connectors', connectorName + '.js');\n\n      if (fs.existsSync(connectorPath)) {\n        config.connector = require(connectorPath);\n      }\n    }\n    if (config.connector && typeof config.connector === 'object' && !config.connector.name)\n      config.connector.name = connectorName;\n  }\n\n  return registry.createDataSource(name, config);\n}\n\nfunction configureModel(ModelCtor, config, app) {\n  assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'),\n    ModelCtor.modelName + ' must be a descendant of loopback.Model');\n\n  let dataSource = config.dataSource;\n\n  if (dataSource) {\n    if (typeof dataSource === 'string') {\n      dataSource = app.dataSources[dataSource];\n    }\n\n    assert(\n      dataSource instanceof DataSource,\n      ModelCtor.modelName + ' is referencing a dataSource that does not exist: \"' +\n      config.dataSource + '\"',\n    );\n  }\n\n  config = extend({}, config);\n  config.dataSource = dataSource;\n\n  app.registry.configureModel(ModelCtor, config);\n}\n\nfunction clearHandlerCache(app) {\n  app._handlers = undefined;\n}\n\n/**\n * Listen for connections and update the configured port.\n *\n * When there are no parameters or there is only one callback parameter,\n * the server will listen on `app.get('host')` and `app.get('port')`.\n *\n * For example, to listen on host/port configured in app config:\n * ```js\n * app.listen();\n * ```\n *\n * Otherwise all arguments are forwarded to `http.Server.listen`.\n *\n * For example, to listen on the specified port and all hosts, and ignore app config.\n * ```js\n * app.listen(80);\n * ```\n *\n * The function also installs a `listening` callback that calls\n * `app.set('port')` with the value returned by `server.address().port`.\n * This way the port param contains always the real port number, even when\n * listen was called with port number 0.\n *\n * @param {Function} [cb] If specified, the callback is added as a listener\n *   for the server's \"listening\" event.\n * @returns {http.Server} A node `http.Server` with this application configured\n *   as the request handler.\n */\napp.listen = function(cb) {\n  const self = this;\n\n  const server = require('http').createServer(this);\n\n  server.on('listening', function() {\n    self.set('port', this.address().port);\n\n    let listeningOnAll = false;\n    let host = self.get('host');\n    if (!host) {\n      listeningOnAll = true;\n      host = this.address().address;\n      self.set('host', host);\n    } else if (host === '0.0.0.0' || host === '::') {\n      listeningOnAll = true;\n    }\n\n    if (!self.get('url')) {\n      if (listeningOnAll) {\n        // We are replacing it with localhost to build a URL\n        // that can be copied and pasted into the browser.\n        host = 'localhost';\n      }\n      const url = 'http://' + host + ':' + self.get('port') + '/';\n      self.set('url', url);\n    }\n  });\n\n  const useAppConfig =\n    arguments.length === 0 ||\n      (arguments.length == 1 && typeof arguments[0] == 'function');\n\n  if (useAppConfig) {\n    let port = this.get('port');\n    // NOTE(bajtos) port:undefined no longer works on node@6,\n    // we must pass port:0 explicitly\n    if (port === undefined) port = 0;\n    server.listen(port, this.get('host'), cb);\n  } else {\n    server.listen.apply(server, arguments);\n  }\n\n  return server;\n};\n"
  },
  {
    "path": "lib/browser-express.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst EventEmitter = require('events').EventEmitter;\nconst util = require('util');\n\nmodule.exports = browserExpress;\n\nfunction browserExpress() {\n  return new BrowserExpress();\n}\n\nbrowserExpress.errorHandler = {};\n\nfunction BrowserExpress() {\n  this.settings = {};\n}\n\nutil.inherits(BrowserExpress, EventEmitter);\n\nBrowserExpress.prototype.set = function(key, value) {\n  if (arguments.length == 1) {\n    return this.get(key);\n  }\n\n  this.settings[key] = value;\n\n  return this; // fluent API\n};\n\nBrowserExpress.prototype.get = function(key) {\n  return this.settings[key];\n};\n"
  },
  {
    "path": "lib/builtin-models.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst assert = require('assert');\n\nmodule.exports = function(registry) {\n  // NOTE(bajtos) we must use static require() due to browserify limitations\n\n  registry.KeyValueModel = createModel(\n    require('../common/models/key-value-model.json'),\n    require('../common/models/key-value-model.js'),\n  );\n\n  registry.Email = createModel(\n    require('../common/models/email.json'),\n    require('../common/models/email.js'),\n  );\n\n  registry.Application = createModel(\n    require('../common/models/application.json'),\n    require('../common/models/application.js'),\n  );\n\n  registry.AccessToken = createModel(\n    require('../common/models/access-token.json'),\n    require('../common/models/access-token.js'),\n  );\n\n  registry.User = createModel(\n    require('../common/models/user.json'),\n    require('../common/models/user.js'),\n  );\n\n  registry.RoleMapping = createModel(\n    require('../common/models/role-mapping.json'),\n    require('../common/models/role-mapping.js'),\n  );\n\n  registry.Role = createModel(\n    require('../common/models/role.json'),\n    require('../common/models/role.js'),\n  );\n\n  registry.ACL = createModel(\n    require('../common/models/acl.json'),\n    require('../common/models/acl.js'),\n  );\n\n  registry.Scope = createModel(\n    require('../common/models/scope.json'),\n    require('../common/models/scope.js'),\n  );\n\n  registry.Change = createModel(\n    require('../common/models/change.json'),\n    require('../common/models/change.js'),\n  );\n\n  registry.Checkpoint = createModel(\n    require('../common/models/checkpoint.json'),\n    require('../common/models/checkpoint.js'),\n  );\n\n  function createModel(definitionJson, customizeFn) {\n    // Clone the JSON definition to allow applications\n    // to modify model settings while not affecting\n    // settings of new models created in the local registry\n    // of another app.\n    // This is needed because require() always returns the same\n    // object instance it loaded during the first call.\n    definitionJson = cloneDeepJson(definitionJson);\n\n    const Model = registry.createModel(definitionJson);\n    customizeFn(Model);\n    return Model;\n  }\n};\n\n// Because we are cloning objects created by JSON.parse,\n// the cloning algorithm can stay much simpler than a general-purpose\n// \"cloneDeep\" e.g. from lodash.\nfunction cloneDeepJson(obj) {\n  const result = Array.isArray(obj) ? [] : {};\n  assert.equal(Object.getPrototypeOf(result), Object.getPrototypeOf(obj));\n  for (const key in obj) {\n    const value = obj[key];\n    if (typeof value === 'object') {\n      result[key] = cloneDeepJson(value);\n    } else {\n      result[key] = value;\n    }\n  }\n  return result;\n}\n"
  },
  {
    "path": "lib/configure-shared-methods.js",
    "content": "// Copyright IBM Corp. 2017,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst util = require('util');\nconst extend = require('util')._extend;\nconst g = require('./globalize');\n\nmodule.exports = function(modelCtor, remotingConfig, modelConfig) {\n  const settings = {};\n\n  // apply config.json settings\n  const configHasSharedMethodsSettings = remotingConfig &&\n      remotingConfig.sharedMethods &&\n      typeof remotingConfig.sharedMethods === 'object';\n  if (configHasSharedMethodsSettings)\n    util._extend(settings, remotingConfig.sharedMethods);\n\n  // apply model-config.json settings\n  const options = modelConfig.options;\n  const modelConfigHasSharedMethodsSettings = options &&\n      options.remoting &&\n      options.remoting.sharedMethods &&\n      typeof options.remoting.sharedMethods === 'object';\n  if (modelConfigHasSharedMethodsSettings)\n    util._extend(settings, options.remoting.sharedMethods);\n\n  // validate setting values\n  Object.keys(settings).forEach(function(setting) {\n    const settingValue = settings[setting];\n    const settingValueType = typeof settingValue;\n    if (settingValueType !== 'boolean')\n      throw new TypeError(g.f('Expected boolean, got %s', settingValueType));\n  });\n\n  // set sharedMethod.shared using the merged settings\n  const sharedMethods = modelCtor.sharedClass.methods({includeDisabled: true});\n\n  // re-map glob style values to regular expressions\n  const tests = Object\n    .keys(settings)\n    .filter(function(setting) {\n      return settings.hasOwnProperty(setting) && setting.indexOf('*') >= 0;\n    })\n    .map(function(setting) {\n      // Turn * into an testable regexp string\n      const glob = escapeRegExp(setting).replace(/\\*/g, '(.)*');\n      return {regex: new RegExp(glob), setting: settings[setting]};\n    }) || [];\n  sharedMethods.forEach(function(sharedMethod) {\n    // use the specific setting if it exists\n    const methodName = sharedMethod.isStatic ? sharedMethod.name : 'prototype.' + sharedMethod.name;\n    const hasSpecificSetting = settings.hasOwnProperty(methodName);\n    if (hasSpecificSetting) {\n      if (settings[methodName] === false) {\n        sharedMethod.sharedClass.disableMethodByName(methodName);\n      } else {\n        sharedMethod.shared = true;\n      }\n    } else {\n      tests.forEach(function(glob) {\n        if (glob.regex.test(methodName)) {\n          if (glob.setting === false) {\n            sharedMethod.sharedClass.disableMethodByName(methodName);\n          } else {\n            sharedMethod.shared = true;\n          }\n        }\n      });\n    }\n  });\n};\n\n// Sanitize all RegExp reserved characters except * for pattern gobbing\nfunction escapeRegExp(str) {\n  return str.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\+\\?\\.\\\\\\^\\$\\|]/g, '\\\\$&');\n}\n"
  },
  {
    "path": "lib/connectors/base-connector.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/**\n * Expose `Connector`.\n */\n\n'use strict';\nmodule.exports = Connector;\n\n/**\n * Module dependencies.\n */\n\nconst EventEmitter = require('events').EventEmitter;\nconst debug = require('debug')('connector');\nconst util = require('util');\nconst inherits = util.inherits;\nconst assert = require('assert');\n\n/**\n * Create a new `Connector` with the given `options`.\n *\n * @param {Object} options\n * @return {Connector}\n */\n\nfunction Connector(options) {\n  EventEmitter.apply(this, arguments);\n  this.options = options;\n\n  debug('created with options', options);\n}\n\n/**\n * Inherit from `EventEmitter`.\n */\n\ninherits(Connector, EventEmitter);\n\n/*!\n * Create an connector instance from a JugglingDB adapter.\n */\n\nConnector._createJDBAdapter = function(jdbModule) {\n  const fauxSchema = {};\n  jdbModule.initialize(fauxSchema, function() {\n    // connected\n  });\n};\n\n/*!\n * Add default crud operations from a JugglingDB adapter.\n */\n\nConnector.prototype._addCrudOperationsFromJDBAdapter = function(connector) {\n\n};\n"
  },
  {
    "path": "lib/connectors/mail.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/**\n * Dependencies.\n */\n\n'use strict';\nconst g = require('../globalize');\nconst mailer = require('nodemailer');\nconst assert = require('assert');\nconst debug = require('debug')('loopback:connector:mail');\nconst loopback = require('../loopback');\n\n/**\n * Export the MailConnector class.\n */\n\nmodule.exports = MailConnector;\n\n/**\n * Create an instance of the connector with the given `settings`.\n */\n\nfunction MailConnector(settings) {\n  assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object');\n\n  let transports = settings.transports;\n\n  // if transports is not in settings object AND settings.transport exists\n  if (!transports && settings.transport) {\n    // then wrap single transport in an array and assign to transports\n    transports = [settings.transport];\n  }\n\n  if (!transports) {\n    transports = [];\n  }\n\n  this.transportsIndex = {};\n  this.transports = [];\n\n  if (loopback.isServer) {\n    transports.forEach(this.setupTransport.bind(this));\n  }\n}\n\nMailConnector.initialize = function(dataSource, callback) {\n  dataSource.connector = new MailConnector(dataSource.settings);\n  callback();\n};\n\nMailConnector.prototype.DataAccessObject = Mailer;\n\n/**\n * Add a transport to the available transports. See https://github.com/andris9/Nodemailer#setting-up-a-transport-method.\n *\n * Example:\n *\n *   Email.setupTransport({\n *       type: \"SMTP\",\n *       host: \"smtp.gmail.com\", // hostname\n *       secureConnection: true, // use SSL\n *       port: 465, // port for secure SMTP\n *       alias: \"gmail\", // optional alias for use with 'transport' option when sending\n *       auth: {\n *           user: \"gmail.user@gmail.com\",\n *           pass: \"userpass\"\n *       }\n *   });\n *\n */\n\nMailConnector.prototype.setupTransport = function(setting) {\n  const connector = this;\n  connector.transports = connector.transports || [];\n  connector.transportsIndex = connector.transportsIndex || {};\n\n  let transport;\n  const transportType = (setting.type || 'STUB').toLowerCase();\n  if (transportType === 'smtp') {\n    transport = mailer.createTransport(setting);\n  } else {\n    const transportModuleName = 'nodemailer-' + transportType + '-transport';\n    const transportModule = require(transportModuleName);\n    transport = mailer.createTransport(transportModule(setting));\n  }\n\n  connector.transportsIndex[setting.alias || setting.type] = transport;\n  connector.transports.push(transport);\n};\n\nfunction Mailer() {\n\n}\n\n/**\n * Get a transport by name.\n *\n * @param {String} name\n * @return {Transport} transport\n */\n\nMailConnector.prototype.transportForName = function(name) {\n  return this.transportsIndex[name];\n};\n\n/**\n * Get the default transport.\n *\n * @return {Transport} transport\n */\n\nMailConnector.prototype.defaultTransport = function() {\n  return this.transports[0] || this.stubTransport;\n};\n\n/**\n * Send an email with the given `options`.\n *\n * Example Options:\n *\n * {\n *   from: \"Fred Foo ✔ <foo@blurdybloop.com>\", // sender address\n *   to: \"bar@blurdybloop.com, baz@blurdybloop.com\", // list of receivers\n *   subject: \"Hello ✔\", // Subject line\n *   text: \"Hello world ✔\", // plaintext body\n *   html: \"<b>Hello world ✔</b>\", // html body\n *   transport: \"gmail\", // See 'alias' option above in setupTransport\n * }\n *\n * See https://github.com/andris9/Nodemailer for other supported options.\n *\n * @param {Object} options\n * @param {Function} callback Called after the e-mail is sent or the sending failed\n */\n\nMailer.send = function(options, fn) {\n  const dataSource = this.dataSource;\n  const settings = dataSource && dataSource.settings;\n  const connector = dataSource.connector;\n  assert(connector, 'Cannot send mail without a connector!');\n\n  let transport = connector.transportForName(options.transport);\n\n  if (!transport) {\n    transport = connector.defaultTransport();\n  }\n\n  if (debug.enabled || settings && settings.debug) {\n    g.log('Sending Mail:');\n    if (options.transport) {\n      console.log(g.f('\\t TRANSPORT:%s', options.transport));\n    }\n    g.log('\\t TO:%s', options.to);\n    g.log('\\t FROM:%s', options.from);\n    g.log('\\t SUBJECT:%s', options.subject);\n    g.log('\\t TEXT:%s', options.text);\n    g.log('\\t HTML:%s', options.html);\n  }\n\n  if (transport) {\n    assert(transport.sendMail,\n      'You must supply an Email.settings.transports containing a valid transport');\n    transport.sendMail(options, fn);\n  } else {\n    g.warn('Warning: No email transport specified for sending email.' +\n      ' Setup a transport to send mail messages.');\n    process.nextTick(function() {\n      fn(null, options);\n    });\n  }\n};\n\n/**\n * Send an email instance using `modelInstance.send()`.\n */\n\nMailer.prototype.send = function(fn) {\n  this.constructor.send(this, fn);\n};\n\n/**\n * Access the node mailer object.\n */\n\nMailConnector.mailer =\nMailConnector.prototype.mailer =\nMailer.mailer =\nMailer.prototype.mailer = mailer;\n"
  },
  {
    "path": "lib/connectors/memory.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/**\n * Expose `Memory`.\n */\n\n'use strict';\nmodule.exports = Memory;\n\n/**\n * Module dependencies.\n */\n\nconst Connector = require('./base-connector');\nconst debug = require('debug')('memory');\nconst util = require('util');\nconst inherits = util.inherits;\nconst assert = require('assert');\nconst JdbMemory = require('loopback-datasource-juggler/lib/connectors/memory');\n\n/**\n * Create a new `Memory` connector with the given `options`.\n *\n * @param {Object} options\n * @return {Memory}\n */\n\nfunction Memory() {\n  // TODO implement entire memory connector\n}\n\n/**\n * Inherit from `DBConnector`.\n */\n\ninherits(Memory, Connector);\n\n/**\n * JugglingDB Compatibility\n */\n\nMemory.initialize = JdbMemory.initialize;\n"
  },
  {
    "path": "lib/current-context.js",
    "content": "// Copyright IBM Corp. 2016,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('./globalize');\nconst juggler = require('loopback-datasource-juggler');\nconst remoting = require('strong-remoting');\n\nmodule.exports = function(loopback) {\n  juggler.getCurrentContext =\n  remoting.getCurrentContext =\n  loopback.getCurrentContext = function() {\n    throw new Error(g.f(\n      '%s was removed in version 3.0. See %s for more details.',\n      'loopback.getCurrentContext()',\n      'http://loopback.io/doc/en/lb2/Using-current-context.html',\n    ));\n  };\n\n  loopback.runInContext = function(fn) {\n    throw new Error(g.f(\n      '%s was removed in version 3.0. See %s for more details.',\n      'loopback.runInContext()',\n      'http://loopback.io/doc/en/lb2/Using-current-context.html',\n    ));\n  };\n\n  loopback.createContext = function(scopeName) {\n    throw new Error(g.f(\n      '%s was removed in version 3.0. See %s for more details.',\n      'loopback.createContext()',\n      'http://loopback.io/doc/en/lb2/Using-current-context.html',\n    ));\n  };\n};\n"
  },
  {
    "path": "lib/globalize.js",
    "content": "// Copyright IBM Corp. 2016,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst path = require('path');\nconst SG = require('strong-globalize');\n\nSG.SetRootDir(path.join(__dirname, '..'), {autonomousMsgLoading: 'all'});\nmodule.exports = SG();\n"
  },
  {
    "path": "lib/loopback.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Module dependencies.\n */\n\n'use strict';\nconst express = require('express');\nconst loopbackExpress = require('./server-app');\nconst proto = require('./application');\nconst fs = require('fs');\nconst ejs = require('ejs');\nconst path = require('path');\nconst merge = require('util')._extend;\nconst assert = require('assert');\nconst Registry = require('./registry');\nconst juggler = require('loopback-datasource-juggler');\nconst configureSharedMethods = require('./configure-shared-methods');\n\n/**\n * LoopBack core module. It provides static properties and\n * methods to create models and data sources. The module itself is a function\n * that creates loopback `app`. For example:\n *\n * ```js\n * var loopback = require('loopback');\n * var app = loopback();\n * ```\n *\n * @property {String} version Version of LoopBack framework.  Static read-only property.\n * @property {Boolean} isBrowser True if running in a browser environment; false otherwise.  Static read-only property.\n * @property {Boolean} isServer True if running in a server environment; false otherwise.  Static read-only property.\n * @property {Registry} registry The global `Registry` object.\n * @property {String} faviconFile Path to a default favicon shipped with LoopBack.\n * Use as follows: `app.use(require('serve-favicon')(loopback.faviconFile));`\n * @class loopback\n * @header loopback\n */\n\nconst loopback = module.exports = createApplication;\n\n/*!\n * Framework version.\n */\n\nloopback.version = require('../package.json').version;\n\nloopback.registry = new Registry();\n\nObject.defineProperties(loopback, {\n  Model: {\n    get: function() { return this.registry.getModel('Model'); },\n  },\n  PersistedModel: {\n    get: function() { return this.registry.getModel('PersistedModel'); },\n  },\n  defaultDataSources: {\n    get: function() { return this.registry.defaultDataSources; },\n  },\n  modelBuilder: {\n    get: function() { return this.registry.modelBuilder; },\n  },\n});\n\n/*!\n * Create an loopback application.\n *\n * @return {Function}\n * @api public\n */\n\nfunction createApplication(options) {\n  const app = loopbackExpress();\n\n  merge(app, proto);\n\n  app.loopback = loopback;\n\n  app.on('modelRemoted', function() {\n    app.models().forEach(function(Model) {\n      if (!Model.config) return;\n      configureSharedMethods(Model, app.get('remoting'), Model.config);\n    });\n  });\n\n  // Create a new instance of models registry per each app instance\n  app.models = function() {\n    return proto.models.apply(this, arguments);\n  };\n\n  // Create a new instance of datasources registry per each app instance\n  app.datasources = app.dataSources = {};\n\n  // Create a new instance of connector registry per each app instance\n  app.connectors = {};\n\n  // Register built-in connectors. It's important to keep this code\n  // hand-written, so that all require() calls are static\n  // and thus browserify can process them (include connectors in the bundle)\n  app.connector('memory', loopback.Memory);\n  app.connector('remote', loopback.Remote);\n  app.connector('kv-memory',\n    require('loopback-datasource-juggler/lib/connectors/kv-memory'));\n\n  if (loopback.localRegistry || options && options.localRegistry === true) {\n    // setup the app registry\n    const registry = app.registry = new Registry();\n    if (options && options.loadBuiltinModels === true) {\n      require('./builtin-models')(registry);\n    }\n  } else {\n    app.registry = loopback.registry;\n  }\n\n  return app;\n}\n\nfunction mixin(source) {\n  for (const key in source) {\n    const desc = Object.getOwnPropertyDescriptor(source, key);\n\n    // Fix for legacy (pre-ES5) browsers like PhantomJS\n    if (!desc) continue;\n\n    Object.defineProperty(loopback, key, desc);\n  }\n}\n\nmixin(require('./runtime'));\n\n/*!\n * Expose static express methods like `express.Router`.\n */\n\nmixin(express);\n\n/*!\n * Expose additional loopback middleware\n * for example `loopback.configure` etc.\n *\n * ***only in node***\n */\n\nif (loopback.isServer) {\n  fs\n    .readdirSync(path.join(__dirname, '..', 'server', 'middleware'))\n    .filter(function(file) {\n      return file.match(/\\.js$/);\n    })\n    .forEach(function(m) {\n      loopback[m.replace(/\\.js$/, '')] = require('../server/middleware/' + m);\n    });\n\n  loopback.urlNotFound = loopback['url-not-found'];\n  delete loopback['url-not-found'];\n\n  loopback.errorHandler = loopback['error-handler'];\n  delete loopback['error-handler'];\n}\n\n// Expose path to the default favicon file\n// ***only in node***\n\nif (loopback.isServer) {\n  /*!\n   * Path to a default favicon shipped with LoopBack.\n   *\n   * **Example**\n   *\n   * ```js\n   * app.use(require('serve-favicon')(loopback.faviconFile));\n   * ```\n   */\n  loopback.faviconFile = path.resolve(__dirname, '../favicon.ico');\n}\n\n/**\n * Add a remote method to a model.\n * @param {Function} fn\n * @param {Object} options (optional)\n */\n\nloopback.remoteMethod = function(fn, options) {\n  fn.shared = true;\n  if (typeof options === 'object') {\n    Object.keys(options).forEach(function(key) {\n      fn[key] = options[key];\n    });\n  }\n  fn.http = fn.http || {verb: 'get'};\n};\n\n/**\n * Create a template helper.\n *\n *     var render = loopback.template('foo.ejs');\n *     var html = render({foo: 'bar'});\n *\n * @param {String} file Path to the template file.\n * @returns {Function}\n */\n\nloopback.template = function(file) {\n  const templates = this._templates || (this._templates = {});\n  const str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8'));\n  return ejs.compile(str, {\n    filename: file,\n  });\n};\n\nrequire('../lib/current-context')(loopback);\n\n/**\n * Create a named vanilla JavaScript class constructor with an attached\n * set of properties and options.\n *\n * This function comes with two variants:\n *  * `loopback.createModel(name, properties, options)`\n *  * `loopback.createModel(config)`\n *\n * In the second variant, the parameters `name`, `properties` and `options`\n * are provided in the config object. Any additional config entries are\n * interpreted as `options`, i.e. the following two configs are identical:\n *\n * ```js\n * { name: 'Customer', base: 'User' }\n * { name: 'Customer', options: { base: 'User' } }\n * ```\n *\n * **Example**\n *\n * Create an `Author` model using the three-parameter variant:\n *\n * ```js\n * loopback.createModel(\n *   'Author',\n *   {\n *     firstName: 'string',\n *     lastName: 'string'\n *   },\n *   {\n *     relations: {\n *       books: {\n *         model: 'Book',\n *         type: 'hasAndBelongsToMany'\n *       }\n *     }\n *   }\n * );\n * ```\n *\n * Create the same model using a config object:\n *\n * ```js\n * loopback.createModel({\n *   name: 'Author',\n *   properties: {\n *     firstName: 'string',\n *     lastName: 'string'\n *   },\n *   relations: {\n *     books: {\n *       model: 'Book',\n *       type: 'hasAndBelongsToMany'\n *     }\n *   }\n * });\n * ```\n *\n * @param {String} name Unique name.\n * @param {Object} properties\n * @param {Object} options (optional)\n *\n * @header loopback.createModel\n */\n\nloopback.createModel = function(name, properties, options) {\n  return this.registry.createModel.apply(this.registry, arguments);\n};\n\n/**\n * Alter an existing Model class.\n * @param {Model} ModelCtor The model constructor to alter.\n * @options {Object} config Additional configuration to apply\n * @property {DataSource} dataSource Attach the model to a dataSource.\n * @property {Object} [relations] Model relations to add/update.\n *\n * @header loopback.configureModel(ModelCtor, config)\n */\n\nloopback.configureModel = function(ModelCtor, config) {\n  return this.registry.configureModel.apply(this.registry, arguments);\n};\n\n/**\n * Look up a model class by name from all models created by\n * `loopback.createModel()`\n * @param {String} modelName The model name\n * @returns {Model} The model class\n *\n * @header loopback.findModel(modelName)\n */\nloopback.findModel = function(modelName) {\n  return this.registry.findModel.apply(this.registry, arguments);\n};\n\n/**\n * Look up a model class by name from all models created by\n * `loopback.createModel()`. Throw an error when no such model exists.\n *\n * @param {String} modelName The model name\n * @returns {Model} The model class\n *\n * @header loopback.getModel(modelName)\n */\nloopback.getModel = function(modelName) {\n  return this.registry.getModel.apply(this.registry, arguments);\n};\n\n/**\n * Look up a model class by the base model class.\n * The method can be used by LoopBack\n * to find configured models in models.json over the base model.\n * @param {Model} modelType The base model class\n * @returns {Model} The subclass if found or the base class\n *\n * @header loopback.getModelByType(modelType)\n */\nloopback.getModelByType = function(modelType) {\n  return this.registry.getModelByType.apply(this.registry, arguments);\n};\n\n/**\n * Create a data source with passing the provided options to the connector.\n *\n * @param {String} name Optional name.\n * @options {Object} options Data Source options\n * @property {Object} connector LoopBack connector.\n * @property {*} [*] Other&nbsp;connector properties.\n *   See the relevant connector documentation.\n */\n\nloopback.createDataSource = function(name, options) {\n  return this.registry.createDataSource.apply(this.registry, arguments);\n};\n\n/**\n * Get an in-memory data source. Use one if it already exists.\n *\n * @param {String} [name] The name of the data source.\n * If not provided, the `'default'` is used.\n */\n\nloopback.memory = function(name) {\n  return this.registry.memory.apply(this.registry, arguments);\n};\n/*!\n * Built in models / services\n */\n\nrequire('./builtin-models')(loopback);\n\nloopback.DataSource = juggler.DataSource;\n"
  },
  {
    "path": "lib/model.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Module Dependencies.\n */\n'use strict';\nconst g = require('./globalize');\nconst assert = require('assert');\nconst debug = require('debug')('loopback:model');\nconst RemoteObjects = require('strong-remoting');\nconst SharedClass = require('strong-remoting').SharedClass;\nconst extend = require('util')._extend;\n\nconst deprecated = require('depd')('loopback');\n\nmodule.exports = function(registry) {\n  /**\n   * The base class for **all models**.\n   *\n   * **Inheriting from `Model`**\n   *\n   * ```js\n   * var properties = {...};\n   * var options = {...};\n   * var MyModel = loopback.Model.extend('MyModel', properties, options);\n   * ```\n   *\n   * **Options**\n   *\n   *  - `trackChanges` - If true, changes to the model will be tracked. **Required\n   * for replication.**\n   *\n   * **Events**\n   *\n   * #### Event: `changed`\n   *\n   * Emitted after a model has been successfully created, saved, or updated.\n   * Argument: `inst`, model instance, object\n   *\n   * ```js\n   * MyModel.on('changed', function(inst) {\n   *   console.log('model with id %s has been changed', inst.id);\n   *   // => model with id 1 has been changed\n   * });\n   * ```\n   *\n   * #### Event: `deleted`\n   *\n   * Emitted after an individual model has been deleted.\n   * Argument: `id`, model ID (number).\n   *\n   * ```js\n   * MyModel.on('deleted', function(id) {\n   *   console.log('model with id %s has been deleted', id);\n   *   // => model with id 1 has been deleted\n   * });\n   * ```\n   *\n   * #### Event: `deletedAll`\n   *\n   * Emitted after all models have been deleted.\n   * Argument: `where` (optional), where filter, JSON object.\n   *\n   * ```js\n   * MyModel.on('deletedAll', function(where) {\n   *   if (where) {\n   *     console.log('all models where ', where, ' have been deleted');\n   *     // => all models where\n   *     // => {price: {gt: 100}}\n   *     // => have been deleted\n   *   }\n   * });\n   * ```\n   *\n   * #### Event: `attached`\n   *\n   * Emitted after a `Model` has been attached to an `app`.\n   *\n   * #### Event: `dataSourceAttached`\n   *\n   * Emitted after a `Model` has been attached to a `DataSource`.\n   *\n   * #### Event: set\n   *\n   * Emitted when model property is set.\n   * Argument: `inst`, model instance, object\n   *\n   * ```js\n   * MyModel.on('set', function(inst) {\n   *   console.log('model with id %s has been changed', inst.id);\n   *   // => model with id 1 has been changed\n   * });\n   * ```\n   *\n   * @param {Object} data\n   * @property {String} Model.modelName The name of the model. Static property.\n   * @property {DataSource} Model.dataSource Data source to which the model is connected, if any. Static property.\n   * @property {SharedClass} Model.sharedMethod The `strong-remoting` [SharedClass](http://apidocs.strongloop.com/strong-remoting/#sharedclass) that contains remoting (and http) metadata. Static property.\n   * @property {Object} settings Contains additional model settings.\n   * @property {string} settings.http.path Base URL of the model HTTP route.\n   * @property {Array.<Object>} settings.acls Array of ACLs for the model.\n   * @class\n   */\n  const Model = registry.modelBuilder.define('Model');\n\n  Model.registry = registry;\n\n  /**\n   * The `loopback.Model.extend()` method calls this when you create a model that extends another model.\n   * Add any setup or configuration code you want executed when the model is created.\n   * See  [Setting up a custom model](http://loopback.io/doc/en/lb2/Extending-built-in-models.html#setting-up-a-custom-model).\n   */\n\n  Model.setup = function() {\n    const ModelCtor = this;\n    const Parent = this.super_;\n\n    if (!ModelCtor.registry && Parent && Parent.registry) {\n      ModelCtor.registry = Parent.registry;\n    }\n\n    const options = this.settings;\n    const typeName = this.modelName;\n\n    // support remoting prototype methods\n    // it's important to setup this function *before* calling `new SharedClass`\n    // otherwise remoting metadata from our base model is picked up\n    ModelCtor.sharedCtor = function(data, id, options, fn) {\n      const ModelCtor = this;\n\n      const isRemoteInvocationWithOptions = typeof data !== 'object' &&\n        typeof id === 'object' &&\n        typeof options === 'function';\n      if (isRemoteInvocationWithOptions) {\n        // sharedCtor(id, options, fn)\n        fn = options;\n        options = id;\n        id = data;\n        data = null;\n      } else if (typeof data === 'function') {\n        // sharedCtor(fn)\n        fn = data;\n        data = null;\n        id = null;\n        options = null;\n      } else if (typeof id === 'function') {\n        // sharedCtor(data, fn)\n        // sharedCtor(id, fn)\n        fn = id;\n        options = null;\n\n        if (typeof data !== 'object') {\n          id = data;\n          data = null;\n        } else {\n          id = null;\n        }\n      }\n\n      if (id != null && data) {\n        const model = new ModelCtor(data);\n        model.id = id;\n        fn(null, model);\n      } else if (data) {\n        fn(null, new ModelCtor(data));\n      } else if (id != null) {\n        const filter = {};\n        ModelCtor.findById(id, filter, options, function(err, model) {\n          if (err) {\n            fn(err);\n          } else if (model) {\n            fn(null, model);\n          } else {\n            err = new Error(g.f('could not find a model with {{id}} %s', id));\n            err.statusCode = 404;\n            err.code = 'MODEL_NOT_FOUND';\n            fn(err);\n          }\n        });\n      } else {\n        fn(new Error(g.f('must specify an {{id}} or {{data}}')));\n      }\n    };\n\n    const idDesc = ModelCtor.modelName + ' id';\n    ModelCtor.sharedCtor.accepts = [\n      {arg: 'id', type: 'any', required: true, http: {source: 'path'},\n        description: idDesc},\n      // {arg: 'instance', type: 'object', http: {source: 'body'}}\n      {arg: 'options', type: 'object', http: createOptionsViaModelMethod},\n    ];\n\n    ModelCtor.sharedCtor.http = [\n      {path: '/:id'},\n    ];\n\n    ModelCtor.sharedCtor.returns = {root: true};\n\n    const remotingOptions = {};\n    extend(remotingOptions, options.remoting || {});\n\n    // create a sharedClass\n    const sharedClass = ModelCtor.sharedClass = new SharedClass(\n      ModelCtor.modelName,\n      ModelCtor,\n      remotingOptions,\n    );\n\n    // before remote hook\n    ModelCtor.beforeRemote = function(name, fn) {\n      const className = this.modelName;\n      this._runWhenAttachedToApp(function(app) {\n        const remotes = app.remotes();\n        remotes.before(className + '.' + name, function(ctx, next) {\n          return fn(ctx, ctx.result, next);\n        });\n      });\n    };\n\n    // after remote hook\n    ModelCtor.afterRemote = function(name, fn) {\n      const className = this.modelName;\n      this._runWhenAttachedToApp(function(app) {\n        const remotes = app.remotes();\n        remotes.after(className + '.' + name, function(ctx, next) {\n          return fn(ctx, ctx.result, next);\n        });\n      });\n    };\n\n    ModelCtor.afterRemoteError = function(name, fn) {\n      const className = this.modelName;\n      this._runWhenAttachedToApp(function(app) {\n        const remotes = app.remotes();\n        remotes.afterError(className + '.' + name, fn);\n      });\n    };\n\n    ModelCtor._runWhenAttachedToApp = function(fn) {\n      if (this.app) return fn(this.app);\n      const self = this;\n      self.once('attached', function() {\n        fn(self.app);\n      });\n    };\n\n    if ('injectOptionsFromRemoteContext' in options) {\n      console.warn(g.f(\n        '%s is using model setting %s which is no longer available.',\n        typeName, 'injectOptionsFromRemoteContext',\n      ));\n      console.warn(g.f(\n        'Please rework your app to use the offical solution for injecting ' +\n          '\"options\" argument from request context,\\nsee %s',\n        'http://loopback.io/doc/en/lb3/Using-current-context.html',\n      ));\n    }\n\n    // resolve relation functions\n    sharedClass.resolve(function resolver(define) {\n      const relations = ModelCtor.relations || {};\n      const defineRaw = define;\n      define = function(name, options, fn) {\n        if (options.accepts) {\n          options = extend({}, options);\n          options.accepts = setupOptionsArgs(options.accepts);\n        }\n        defineRaw(name, options, fn);\n      };\n\n      // get the relations\n      for (const relationName in relations) {\n        const relation = relations[relationName];\n        if (relation.type === 'belongsTo') {\n          ModelCtor.belongsToRemoting(relationName, relation, define);\n        } else if (\n          relation.type === 'hasOne' ||\n          relation.type === 'embedsOne'\n        ) {\n          ModelCtor.hasOneRemoting(relationName, relation, define);\n        } else if (\n          relation.type === 'hasMany' ||\n          relation.type === 'embedsMany' ||\n          relation.type === 'referencesMany') {\n          ModelCtor.hasManyRemoting(relationName, relation, define);\n        }\n        // Automatically enable nestRemoting if the flag is set to true in the\n        // relation options\n        if (relation.options && relation.options.nestRemoting) {\n          ModelCtor.nestRemoting(relationName);\n        }\n      }\n\n      // handle scopes\n      const scopes = ModelCtor.scopes || {};\n      for (const scopeName in scopes) {\n        ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);\n      }\n    });\n\n    return ModelCtor;\n  };\n\n  /*!\n   * Get the reference to ACL in a lazy fashion to avoid race condition in require\n   */\n  let _aclModel = null;\n  Model._ACL = function getACL(ACL) {\n    const registry = this.registry;\n    if (ACL !== undefined) {\n      // The function is used as a setter\n      _aclModel = ACL;\n    }\n    if (_aclModel) {\n      return _aclModel;\n    }\n    const aclModel = registry.getModel('ACL');\n    _aclModel = registry.getModelByType(aclModel);\n    return _aclModel;\n  };\n\n  /**\n   * Check if the given access token can invoke the specified method.\n   *\n   * @param {AccessToken} token The access token.\n   * @param {*} modelId The model ID.\n   * @param {SharedMethod} sharedMethod The method in question.\n   * @param {Object} ctx The remote invocation context.\n   * @callback {Function} callback The callback function.\n   * @param {String|Error} err The error object.\n   * @param {Boolean} allowed True if the request is allowed; false otherwise.\n   */\n\n  Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {\n    const ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;\n    token = token || ANONYMOUS;\n    const aclModel = Model._ACL();\n\n    ctx = ctx || {};\n    if (typeof ctx === 'function' && callback === undefined) {\n      callback = ctx;\n      ctx = {};\n    }\n\n    aclModel.checkAccessForContext({\n      accessToken: token,\n      model: this,\n      property: sharedMethod.name,\n      method: sharedMethod.name,\n      sharedMethod: sharedMethod,\n      modelId: modelId,\n      accessType: this._getAccessTypeForMethod(sharedMethod),\n      remotingContext: ctx,\n    }, function(err, accessRequest) {\n      if (err) return callback(err);\n      callback(null, accessRequest.isAllowed());\n    });\n  };\n\n  /*!\n   * Determine the access type for the given `RemoteMethod`.\n   *\n   * @api private\n   * @param {RemoteMethod} method\n   */\n\n  Model._getAccessTypeForMethod = function(method) {\n    if (typeof method === 'string') {\n      method = {name: method};\n    }\n    assert(\n      typeof method === 'object',\n      'method is a required argument and must be a RemoteMethod object',\n    );\n\n    const ACL = Model._ACL();\n\n    // Check the explicit setting of accessType\n    if (method.accessType) {\n      assert(method.accessType === ACL.READ ||\n        method.accessType === ACL.REPLICATE ||\n        method.accessType === ACL.WRITE ||\n        method.accessType === ACL.EXECUTE, 'invalid accessType ' +\n        method.accessType +\n        '. It must be \"READ\", \"REPLICATE\", \"WRITE\", or \"EXECUTE\"');\n      return method.accessType;\n    }\n\n    // Default GET requests to READ\n    let verb = method.http && method.http.verb;\n    if (typeof verb === 'string') {\n      verb = verb.toUpperCase();\n    }\n    if (verb === 'GET' || verb === 'HEAD') {\n      return ACL.READ;\n    }\n\n    switch (method.name) {\n      case 'create':\n        return ACL.WRITE;\n      case 'updateOrCreate':\n        return ACL.WRITE;\n      case 'upsertWithWhere':\n        return ACL.WRITE;\n      case 'upsert':\n        return ACL.WRITE;\n      case 'exists':\n        return ACL.READ;\n      case 'findById':\n        return ACL.READ;\n      case 'find':\n        return ACL.READ;\n      case 'findOne':\n        return ACL.READ;\n      case 'destroyById':\n        return ACL.WRITE;\n      case 'deleteById':\n        return ACL.WRITE;\n      case 'removeById':\n        return ACL.WRITE;\n      case 'count':\n        return ACL.READ;\n      default:\n        return ACL.EXECUTE;\n    }\n  };\n\n  /**\n   * Get the `Application` object to which the Model is attached.\n   *\n   * @callback {Function} callback Callback function called with `(err, app)` arguments.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Application} app Attached application object.\n   * @end\n   */\n\n  Model.getApp = function(callback) {\n    const self = this;\n    self._runWhenAttachedToApp(function(app) {\n      assert(self.app);\n      assert.equal(app, self.app);\n      callback(null, app);\n    });\n  };\n\n  /**\n   * Enable remote invocation for the specified method.\n   * See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.\n   *\n   * Static method example:\n   * ```js\n   * Model.myMethod();\n   * Model.remoteMethod('myMethod');\n   * ```\n   *\n   * @param {String} name The name of the method.\n   * @param {Object} options The remoting options.\n   * See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).\n   */\n\n  Model.remoteMethod = function(name, options) {\n    if (options.isStatic === undefined) {\n      const m = name.match(/^prototype\\.(.*)$/);\n      options.isStatic = !m;\n      name = options.isStatic ? name : m[1];\n    }\n\n    if (options.accepts) {\n      options = extend({}, options);\n      options.accepts = setupOptionsArgs(options.accepts, this);\n    }\n\n    this.sharedClass.defineMethod(name, options);\n    this.emit('remoteMethodAdded', this.sharedClass);\n  };\n\n  function setupOptionsArgs(accepts, modelClass) {\n    if (!Array.isArray(accepts))\n      accepts = [accepts];\n\n    return accepts.map(function(arg) {\n      if (arg.http && arg.http === 'optionsFromRequest') {\n        // clone to preserve the input value\n        arg = extend({}, arg);\n        arg.http = createOptionsViaModelMethod.bind(modelClass);\n      }\n      return arg;\n    });\n  }\n\n  function createOptionsViaModelMethod(ctx) {\n    const ModelCtor = (ctx.method && ctx.method.ctor) || this;\n\n    /**\n     * Configure default values for juggler settings to protect user-supplied\n     * input from attacking juggler\n     */\n    const DEFAULT_OPTIONS = {\n      // Default to `true` so that hidden properties cannot be used in query\n      prohibitHiddenPropertiesInQuery: ModelCtor._getProhibitHiddenPropertiesInQuery({}, true),\n      // Default to `12` for the max depth of a query object\n      maxDepthOfQuery: ModelCtor._getMaxDepthOfQuery({}, 12),\n      // Default to `32` for the max depth of a data object\n      maxDepthOfData: ModelCtor._getMaxDepthOfData({}, 32),\n    };\n\n    if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')\n      return DEFAULT_OPTIONS;\n    debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);\n    return Object.assign(DEFAULT_OPTIONS,\n      ModelCtor.createOptionsFromRemotingContext(ctx));\n  }\n\n  /**\n   * Disable remote invocation for the method with the given name.\n   *\n   * @param {String} name The name of the method.\n   * @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass\n   * `false` if the method defined on the prototype (eg.\n   * `MyModel.prototype.myMethod`).\n   */\n\n  Model.disableRemoteMethod = function(name, isStatic) {\n    deprecated('Model.disableRemoteMethod is deprecated. ' +\n      'Use Model.disableRemoteMethodByName instead.');\n    const key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic);\n    this.sharedClass.disableMethodByName(key);\n    this.emit('remoteMethodDisabled', this.sharedClass, key);\n  };\n\n  /**\n   * Disable remote invocation for the method with the given name.\n   *\n   * @param {String} name The name of the method (include \"prototype.\" if the method is defined on the prototype).\n   *\n   */\n\n  Model.disableRemoteMethodByName = function(name) {\n    this.sharedClass.disableMethodByName(name);\n    this.emit('remoteMethodDisabled', this.sharedClass, name);\n  };\n\n  Model.belongsToRemoting = function(relationName, relation, define) {\n    let modelName = relation.modelTo && relation.modelTo.modelName;\n    modelName = modelName || 'PersistedModel';\n    const fn = this.prototype[relationName];\n    const pathName = (relation.options.http && relation.options.http.path) || relationName;\n    define('__get__' + relationName, {\n      isStatic: false,\n      http: {verb: 'get', path: '/' + pathName},\n      accepts: [\n        {arg: 'refresh', type: 'boolean', http: {source: 'query'}},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      accessType: 'READ',\n      description: g.f('Fetches belongsTo relation %s.', relationName),\n      returns: {arg: relationName, type: modelName, root: true},\n    }, fn);\n  };\n\n  function convertNullToNotFoundError(toModelName, ctx, cb) {\n    if (ctx.result !== null) return cb();\n\n    const fk = ctx.getArgByName('fk');\n    const msg = fk ?\n      g.f('Unknown \"%s\" id \"%s\".', toModelName, fk) :\n      g.f('No \"%s\" instance(s) found', toModelName);\n    const error = new Error(msg);\n    error.statusCode = error.status = 404;\n    error.code = 'MODEL_NOT_FOUND';\n    cb(error);\n  }\n\n  Model.hasOneRemoting = function(relationName, relation, define) {\n    const pathName = (relation.options.http && relation.options.http.path) || relationName;\n    const toModelName = relation.modelTo.modelName;\n\n    define('__get__' + relationName, {\n      isStatic: false,\n      http: {verb: 'get', path: '/' + pathName},\n      accepts: [\n        {arg: 'refresh', type: 'boolean', http: {source: 'query'}},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Fetches hasOne relation %s.', relationName),\n      accessType: 'READ',\n      returns: {arg: relationName, type: relation.modelTo.modelName, root: true},\n      rest: {after: convertNullToNotFoundError.bind(null, toModelName)},\n    });\n\n    define('__create__' + relationName, {\n      isStatic: false,\n      http: {verb: 'post', path: '/' + pathName},\n      accepts: [\n        {\n          arg: 'data', type: 'object', model: toModelName,\n          http: {source: 'body'},\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Creates a new instance in %s of this model.', relationName),\n      accessType: 'WRITE',\n      returns: {arg: 'data', type: toModelName, root: true},\n    });\n\n    define('__update__' + relationName, {\n      isStatic: false,\n      http: {verb: 'put', path: '/' + pathName},\n      accepts: [\n        {\n          arg: 'data', type: 'object', model: toModelName,\n          http: {source: 'body'},\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Update %s of this model.', relationName),\n      accessType: 'WRITE',\n      returns: {arg: 'data', type: toModelName, root: true},\n    });\n\n    define('__destroy__' + relationName, {\n      isStatic: false,\n      http: {verb: 'delete', path: '/' + pathName},\n      accepts: [\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Deletes %s of this model.', relationName),\n      accessType: 'WRITE',\n    });\n  };\n\n  Model.hasManyRemoting = function(relationName, relation, define) {\n    const pathName = (relation.options.http && relation.options.http.path) || relationName;\n    const toModelName = relation.modelTo.modelName;\n\n    const findByIdFunc = this.prototype['__findById__' + relationName];\n    define('__findById__' + relationName, {\n      isStatic: false,\n      http: {verb: 'get', path: '/' + pathName + '/:fk'},\n      accepts: [\n        {\n          arg: 'fk', type: 'any',\n          description: g.f('Foreign key for %s', relationName),\n          required: true,\n          http: {source: 'path'},\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Find a related item by id for %s.', relationName),\n      accessType: 'READ',\n      returns: {arg: 'result', type: toModelName, root: true},\n      rest: {after: convertNullToNotFoundError.bind(null, toModelName)},\n    }, findByIdFunc);\n\n    const destroyByIdFunc = this.prototype['__destroyById__' + relationName];\n    define('__destroyById__' + relationName, {\n      isStatic: false,\n      http: {verb: 'delete', path: '/' + pathName + '/:fk'},\n      accepts: [\n        {\n          arg: 'fk', type: 'any',\n          description: g.f('Foreign key for %s', relationName),\n          required: true,\n          http: {source: 'path'},\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Delete a related item by id for %s.', relationName),\n      accessType: 'WRITE',\n      returns: [],\n    }, destroyByIdFunc);\n\n    const updateByIdFunc = this.prototype['__updateById__' + relationName];\n    define('__updateById__' + relationName, {\n      isStatic: false,\n      http: {verb: 'put', path: '/' + pathName + '/:fk'},\n      accepts: [\n        {arg: 'fk', type: 'any',\n          description: g.f('Foreign key for %s', relationName),\n          required: true,\n          http: {source: 'path'}},\n        {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Update a related item by id for %s.', relationName),\n      accessType: 'WRITE',\n      returns: {arg: 'result', type: toModelName, root: true},\n    }, updateByIdFunc);\n\n    if (relation.modelThrough || relation.type === 'referencesMany') {\n      const modelThrough = relation.modelThrough || relation.modelTo;\n\n      const accepts = [];\n      if (relation.type === 'hasMany' && relation.modelThrough) {\n        // Restrict: only hasManyThrough relation can have additional properties\n        accepts.push({\n          arg: 'data', type: 'object', model: modelThrough.modelName,\n          http: {source: 'body'},\n        });\n      }\n\n      const addFunc = this.prototype['__link__' + relationName];\n      define('__link__' + relationName, {\n        isStatic: false,\n        http: {verb: 'put', path: '/' + pathName + '/rel/:fk'},\n        accepts: [{arg: 'fk', type: 'any',\n          description: g.f('Foreign key for %s', relationName),\n          required: true,\n          http: {source: 'path'}},\n        ].concat(accepts).concat([\n          {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n        ]),\n        description: g.f('Add a related item by id for %s.', relationName),\n        accessType: 'WRITE',\n        returns: {arg: relationName, type: modelThrough.modelName, root: true},\n      }, addFunc);\n\n      const removeFunc = this.prototype['__unlink__' + relationName];\n      define('__unlink__' + relationName, {\n        isStatic: false,\n        http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'},\n        accepts: [\n          {\n            arg: 'fk', type: 'any',\n            description: g.f('Foreign key for %s', relationName),\n            required: true,\n            http: {source: 'path'},\n          },\n          {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n        ],\n        description: g.f('Remove the %s relation to an item by id.', relationName),\n        accessType: 'WRITE',\n        returns: [],\n      }, removeFunc);\n\n      // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD?\n      // true --> 200 and false --> 404?\n      const existsFunc = this.prototype['__exists__' + relationName];\n      define('__exists__' + relationName, {\n        isStatic: false,\n        http: {verb: 'head', path: '/' + pathName + '/rel/:fk'},\n        accepts: [\n          {\n            arg: 'fk', type: 'any',\n            description: g.f('Foreign key for %s', relationName),\n            required: true,\n            http: {source: 'path'},\n          },\n          {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n        ],\n        description: g.f('Check the existence of %s relation to an item by id.', relationName),\n        accessType: 'READ',\n        returns: {arg: 'exists', type: 'boolean', root: true},\n        rest: {\n          // After hook to map exists to 200/404 for HEAD\n          after: function(ctx, cb) {\n            if (ctx.result === false) {\n              const modelName = ctx.method.sharedClass.name;\n              const id = ctx.getArgByName('id');\n              const msg = g.f('Unknown \"%s\" {{id}} \"%s\".', modelName, id);\n              const error = new Error(msg);\n              error.statusCode = error.status = 404;\n              error.code = 'MODEL_NOT_FOUND';\n              cb(error);\n            } else {\n              cb();\n            }\n          },\n        },\n      }, existsFunc);\n    }\n  };\n\n  Model.scopeRemoting = function(scopeName, scope, define) {\n    const pathName =\n      (scope.options && scope.options.http && scope.options.http.path) || scopeName;\n\n    let modelTo = scope.modelTo;\n\n    const isStatic = scope.isStatic;\n    let toModelName = scope.modelTo.modelName;\n\n    // https://github.com/strongloop/loopback/issues/811\n    // Check if the scope is for a hasMany relation\n    const relation = this.relations[scopeName];\n    if (relation && relation.modelTo) {\n      // For a relation with through model, the toModelName should be the one\n      // from the target model\n      toModelName = relation.modelTo.modelName;\n      modelTo = relation.modelTo;\n    }\n\n    // if there is atleast one updateOnly property, then we set\n    // createOnlyInstance flag in __create__ to indicate loopback-swagger\n    // code to create a separate model instance for create operation only\n    const updateOnlyProps = modelTo.getUpdateOnlyProperties ?\n      modelTo.getUpdateOnlyProperties() : false;\n    const hasUpdateOnlyProps = updateOnlyProps && updateOnlyProps.length > 0;\n\n    define('__get__' + scopeName, {\n      isStatic: isStatic,\n      http: {verb: 'get', path: '/' + pathName},\n      accepts: [\n        {arg: 'filter', type: 'object'},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Queries %s of %s.', scopeName, this.modelName),\n      accessType: 'READ',\n      returns: {arg: scopeName, type: [toModelName], root: true},\n    });\n\n    define('__create__' + scopeName, {\n      isStatic: isStatic,\n      http: {verb: 'post', path: '/' + pathName},\n      accepts: [\n        {\n          arg: 'data',\n          type: 'object',\n          allowArray: true,\n          model: toModelName,\n          createOnlyInstance: hasUpdateOnlyProps,\n          http: {source: 'body'},\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Creates a new instance in %s of this model.', scopeName),\n      accessType: 'WRITE',\n      returns: {arg: 'data', type: toModelName, root: true},\n    });\n\n    define('__delete__' + scopeName, {\n      isStatic: isStatic,\n      http: {verb: 'delete', path: '/' + pathName},\n      accepts: [\n        {\n          arg: 'where', type: 'object',\n          // The \"where\" argument is not exposed in the REST API\n          // but we need to provide a value so that we can pass \"options\"\n          // as the third argument.\n          http: function(ctx) { return undefined; },\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Deletes all %s of this model.', scopeName),\n      accessType: 'WRITE',\n    });\n\n    define('__count__' + scopeName, {\n      isStatic: isStatic,\n      http: {verb: 'get', path: '/' + pathName + '/count'},\n      accepts: [\n        {\n          arg: 'where', type: 'object',\n          description: 'Criteria to match model instances',\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      description: g.f('Counts %s of %s.', scopeName, this.modelName),\n      accessType: 'READ',\n      returns: {arg: 'count', type: 'number'},\n    });\n  };\n\n  /**\n  * Enabled deeply-nested queries of related models via REST API.\n  *\n  * @param {String} relationName Name of the nested relation.\n  * @options {Object} [options] It is optional. See below.\n  * @param {String} pathName The HTTP path (relative to the model) at which your remote method is exposed.\n  * @param {String} filterMethod The filter name.\n  * @param {String} paramName The argument name that the remote method accepts.\n  * @param {String} getterName The getter name.\n  * @param {Boolean} hooks Whether to inherit before/after hooks.\n  * @callback {Function} filterCallback The Optional filter function.\n  * @param {Object} SharedMethod object. See [here](https://apidocs.strongloop.com/strong-remoting/#sharedmethod).\n  * @param {Object} RelationDefinition object which includes relation `type`, `ModelConstructor` of `modelFrom`, `modelTo`, `keyFrom`, `keyTo` and more relation definitions.\n   */\n\n  Model.nestRemoting = function(relationName, options, filterCallback) {\n    if (typeof options === 'function' && !filterCallback) {\n      filterCallback = options;\n      options = {};\n    }\n    options = options || {};\n\n    const regExp = /^__([^_]+)__([^_]+)$/;\n    const relation = this.relations[relationName];\n    if (relation && relation._nestRemotingProcessed) {\n      return; // Prevent unwanted circular traversals!\n    } else if (relation && relation.modelTo && relation.modelTo.sharedClass) {\n      relation._nestRemotingProcessed = true;\n      const self = this;\n      const sharedClass = this.sharedClass;\n      const sharedToClass = relation.modelTo.sharedClass;\n      const toModelName = relation.modelTo.modelName;\n\n      const pathName = options.pathName || relation.options.path || relationName;\n      const paramName = options.paramName || 'nk';\n\n      const http = [].concat(sharedToClass.http || [])[0];\n      let httpPath, acceptArgs;\n\n      if (relation.multiple) {\n        httpPath = pathName + '/:' + paramName;\n        acceptArgs = [\n          {\n            arg: paramName, type: 'any', http: {source: 'path'},\n            description: g.f('Foreign key for %s.', relation.name),\n            required: true,\n          },\n        ];\n      } else {\n        httpPath = pathName;\n        acceptArgs = [];\n      }\n\n      if (httpPath[0] !== '/') {\n        httpPath = '/' + httpPath;\n      }\n\n      // A method should return the method name to use, if it is to be\n      // included as a nested method - a falsy return value will skip.\n      const filter = filterCallback || options.filterMethod || function(method, relation) {\n        const matches = method.name.match(regExp);\n        if (matches) {\n          return '__' + matches[1] + '__' + relation.name + '__' + matches[2];\n        }\n      };\n\n      sharedToClass.methods().forEach(function(method) {\n        let methodName;\n        if (!method.isStatic && (methodName = filter(method, relation))) {\n          const prefix = relation.multiple ? '__findById__' : '__get__';\n          const getterName = options.getterName || (prefix + relationName);\n\n          const getterFn = relation.modelFrom.prototype[getterName];\n          if (typeof getterFn !== 'function') {\n            throw new Error(g.f('Invalid remote method: `%s`', getterName));\n          }\n\n          const nestedFn = relation.modelTo.prototype[method.name];\n          if (typeof nestedFn !== 'function') {\n            throw new Error(g.f('Invalid remote method: `%s`', method.name));\n          }\n\n          const opts = {};\n\n          opts.accepts = acceptArgs.concat(method.accepts || []);\n          opts.returns = [].concat(method.returns || []);\n          opts.description = method.description;\n          opts.accessType = method.accessType;\n          opts.rest = extend({}, method.rest || {});\n          opts.rest.delegateTo = method;\n\n          opts.http = [];\n          const routes = [].concat(method.http || []);\n          routes.forEach(function(route) {\n            if (route.path) {\n              const copy = extend({}, route);\n              copy.path = httpPath + route.path;\n              opts.http.push(copy);\n            }\n          });\n\n          const lastArg = opts.accepts[opts.accepts.length - 1] || {};\n          const hasOptionsFromContext =\n            (lastArg.arg || lastArg.name) === 'options' &&\n            lastArg.type === 'object' && lastArg.http;\n\n          if (relation.multiple) {\n            sharedClass.defineMethod(methodName, opts, function(fkId) {\n              const args = Array.prototype.slice.call(arguments, 1);\n              const cb = args[args.length - 1];\n              const contextOptions =\n                hasOptionsFromContext && args[args.length - 2] || {};\n              this[getterName](fkId, contextOptions, function(err, inst) {\n                if (err) return cb(err);\n                if (inst instanceof relation.modelTo) {\n                  try {\n                    nestedFn.apply(inst, args);\n                  } catch (err) {\n                    return cb(err);\n                  }\n                } else {\n                  cb(err, null);\n                }\n              });\n            }, method.isStatic);\n          } else {\n            sharedClass.defineMethod(methodName, opts, function() {\n              const args = Array.prototype.slice.call(arguments);\n              const cb = args[args.length - 1];\n              const contextOptions =\n                hasOptionsFromContext && args[args.length - 2] || {};\n              this[getterName](contextOptions, function(err, inst) {\n                if (err) return cb(err);\n                if (inst instanceof relation.modelTo) {\n                  try {\n                    nestedFn.apply(inst, args);\n                  } catch (err) {\n                    return cb(err);\n                  }\n                } else {\n                  cb(err, null);\n                }\n              });\n            }, method.isStatic);\n          }\n        }\n      });\n\n      this.emit('remoteMethodAdded', this.sharedClass);\n\n      if (options.hooks === false) return; // don't inherit before/after hooks\n\n      self.once('mounted', function(app, sc, remotes) {\n        const listenerTree = extend({}, remotes.listenerTree || {});\n        listenerTree.before = listenerTree.before || {};\n        listenerTree.after = listenerTree.after || {};\n\n        const beforeListeners = listenerTree.before[toModelName] || {};\n        const afterListeners = listenerTree.after[toModelName] || {};\n\n        sharedClass.methods().forEach(function(method) {\n          const delegateTo = method.rest && method.rest.delegateTo;\n          if (delegateTo && delegateTo.ctor == relation.modelTo) {\n            const before = method.isStatic ? beforeListeners : beforeListeners['prototype'];\n            const after = method.isStatic ? afterListeners : afterListeners['prototype'];\n            const m = method.isStatic ? method.name : 'prototype.' + method.name;\n            if (before && before[delegateTo.name]) {\n              self.beforeRemote(m, function(ctx, result, next) {\n                before[delegateTo.name]._listeners.call(null, ctx, next);\n              });\n            }\n            if (after && after[delegateTo.name]) {\n              self.afterRemote(m, function(ctx, result, next) {\n                after[delegateTo.name]._listeners.call(null, ctx, next);\n              });\n            }\n          }\n        });\n      });\n    } else {\n      const msg = g.f('Relation `%s` does not exist for model `%s`', relationName, this.modelName);\n      throw new Error(msg);\n    }\n  };\n\n  Model.ValidationError = require('loopback-datasource-juggler').ValidationError;\n\n  /**\n   * Create \"options\" value to use when invoking model methods\n   * via strong-remoting (e.g. REST).\n   *\n   * Example\n   *\n   * ```js\n   * MyModel.myMethod = function(options, cb) {\n   *   // by default, options contains only one property \"accessToken\"\n   *   var accessToken = options && options.accessToken;\n   *   var userId = accessToken && accessToken.userId;\n   *   var message = 'Hello ' + (userId ? 'user #' + userId : 'anonymous');\n   *   cb(null, message);\n   * });\n   *\n   * MyModel.remoteMethod('myMethod', {\n   *   accepts: {\n   *     arg: 'options',\n   *     type: 'object',\n   *     // \"optionsFromRequest\" is a loopback-specific HTTP mapping that\n   *     // calls Model's createOptionsFromRemotingContext\n   *     // to build the argument value\n   *     http: 'optionsFromRequest'\n   *    },\n   *    returns: {\n   *      arg: 'message',\n   *      type: 'string'\n   *    }\n   * });\n   * ```\n   *\n   * @param {Object} ctx A strong-remoting Context instance\n   * @returns {Object} The value to pass to \"options\" argument.\n   */\n  Model.createOptionsFromRemotingContext = function(ctx) {\n    return {\n      accessToken: ctx.req ? ctx.req.accessToken : null,\n    };\n  };\n\n  // setup the initial model\n  Model.setup();\n\n  return Model;\n};\n"
  },
  {
    "path": "lib/persisted-model.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Module Dependencies.\n */\n'use strict';\nconst g = require('./globalize');\nconst runtime = require('./runtime');\nconst assert = require('assert');\nconst async = require('async');\nconst deprecated = require('depd')('loopback');\nconst debug = require('debug')('loopback:persisted-model');\nconst PassThrough = require('stream').PassThrough;\nconst utils = require('./utils');\nconst filterNodes = require('loopback-filters');\n\nconst REPLICATION_CHUNK_SIZE = -1;\n\nmodule.exports = function(registry) {\n  const Model = registry.getModel('Model');\n\n  /**\n   * Extends Model with basic query and CRUD support.\n   *\n   * **Change Event**\n   *\n   * Listen for model changes using the `change` event.\n   *\n   * ```js\n   * MyPersistedModel.on('changed', function(obj) {\n   *    console.log(obj) // => the changed model\n   * });\n   * ```\n   *\n   * @class PersistedModel\n   */\n\n  const PersistedModel = Model.extend('PersistedModel');\n\n  /*!\n   * Setup the `PersistedModel` constructor.\n   */\n\n  PersistedModel.setup = function setupPersistedModel() {\n    // call Model.setup first\n    Model.setup.call(this);\n\n    const PersistedModel = this;\n\n    // enable change tracking (usually for replication)\n    if (this.settings.trackChanges) {\n      PersistedModel._defineChangeModel();\n      PersistedModel.once('dataSourceAttached', function() {\n        PersistedModel.enableChangeTracking();\n      });\n    } else if (this.settings.enableRemoteReplication) {\n      PersistedModel._defineChangeModel();\n    }\n\n    PersistedModel.setupRemoting();\n  };\n\n  /*!\n   * Throw an error telling the user that the method is not available and why.\n   */\n\n  function throwNotAttached(modelName, methodName) {\n    throw new Error(\n      g.f('Cannot call %s.%s().' +\n      ' The %s method has not been setup.' +\n      ' The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!',\n      modelName, methodName, methodName),\n    );\n  }\n\n  /*!\n   * Convert null callbacks to 404 error objects.\n   * @param  {HttpContext} ctx\n   * @param  {Function} cb\n   */\n\n  function convertNullToNotFoundError(ctx, cb) {\n    if (ctx.result !== null) return cb();\n\n    const modelName = ctx.method.sharedClass.name;\n    const id = ctx.getArgByName('id');\n    const msg = g.f('Unknown \"%s\" {{id}} \"%s\".', modelName, id);\n    const error = new Error(msg);\n    error.statusCode = error.status = 404;\n    error.code = 'MODEL_NOT_FOUND';\n    cb(error);\n  }\n\n  /**\n   * Create new instance of Model, and save to database.\n   *\n   * @param {Object|Object[]} [data] Optional data argument.  Can be either a single model instance or an array of instances.\n   *\n   * @callback {Function} callback Callback function called with `cb(err, obj)` signature.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} models Model instances or null.\n   */\n\n  PersistedModel.create = function(data, callback) {\n    throwNotAttached(this.modelName, 'create');\n  };\n\n  /**\n   * Update or insert a model instance\n   * @param {Object} data The model instance data to insert.\n   * @callback {Function} callback Callback function called with `cb(err, obj)` signature.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} model Updated model instance.\n   */\n\n  PersistedModel.upsert = PersistedModel.updateOrCreate = PersistedModel.patchOrCreate =\n  function upsert(data, callback) {\n    throwNotAttached(this.modelName, 'upsert');\n  };\n\n  /**\n   * Update or insert a model instance based on the search criteria.\n   * If there is a single instance retrieved, update the retrieved model.\n   * Creates a new model if no model instances were found.\n   * Returns an error if multiple instances are found.\n   * @param {Object} [where]  `where` filter, like\n   * ```\n   * { key: val, key2: {gt: 'val2'}, ...}\n   * ```\n   * <br/>see\n   * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods).\n   * @param {Object} data The model instance data to insert.\n   * @callback {Function} callback Callback function called with `cb(err, obj)` signature.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} model Updated model instance.\n   */\n\n  PersistedModel.upsertWithWhere =\n  PersistedModel.patchOrCreateWithWhere = function upsertWithWhere(where, data, callback) {\n    throwNotAttached(this.modelName, 'upsertWithWhere');\n  };\n\n  /**\n   * Replace or insert a model instance; replace existing record if one is found,\n   * such that parameter `data.id` matches `id` of model instance; otherwise,\n   * insert a new record.\n   * @param {Object} data The model instance data.\n   * @options {Object} [options] Options for replaceOrCreate\n   * @property {Boolean} validate Perform validation before saving.  Default is true.\n   * @callback {Function} callback Callback function called with `cb(err, obj)` signature.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} model Replaced model instance.\n   */\n\n  PersistedModel.replaceOrCreate = function replaceOrCreate(data, callback) {\n    throwNotAttached(this.modelName, 'replaceOrCreate');\n  };\n\n  /**\n   * Finds one record matching the optional filter object. If not found, creates\n   * the object using the data provided as second argument. In this sense it is\n   * the same as `find`, but limited to one object. Returns an object, not\n   * collection. If you don't provide the filter object argument, it tries to\n   * locate an existing object that matches the `data` argument.\n   *\n   * @options {Object} [filter] Optional Filter object; see below.\n   * @property {String|Object|Array} fields Identify fields to include in return result.\n   * <br/>See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html).\n   * @property {String|Object|Array} include  See PersistedModel.include documentation.\n   * <br/>See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html).\n   * @property {Number} limit Maximum number of instances to return.\n   * <br/>See [Limit filter](http://loopback.io/doc/en/lb2/Limit-filter.html).\n   * @property {String} order Sort order: either \"ASC\" for ascending or \"DESC\" for descending.\n   * <br/>See [Order filter](http://loopback.io/doc/en/lb2/Order-filter.html).\n   * @property {Number} skip Number of results to skip.\n   * <br/>See [Skip filter](http://loopback.io/doc/en/lb2/Skip-filter.html).\n   * @property {Object} where Where clause, like\n   * ```\n   * {where: {key: val, key2: {gt: val2}, ...}}\n   * ```\n   * <br/>See\n   * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries).\n   * @param {Object} data Data to insert if object matching the `where` filter is not found.\n   * @callback {Function} callback Callback function called with `cb(err, instance, created)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} instance Model instance matching the `where` filter, if found.\n   * @param {Boolean} created True if the instance does not exist and gets created.\n   */\n\n  PersistedModel.findOrCreate = function findOrCreate(query, data, callback) {\n    throwNotAttached(this.modelName, 'findOrCreate');\n  };\n\n  PersistedModel.findOrCreate._delegate = true;\n\n  /**\n   * Check whether a model instance exists in database.\n   *\n   * @param {id} id Identifier of object (primary key value).\n   *\n   * @callback {Function} callback Callback function called with `(err, exists)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Boolean} exists True if the instance with the specified ID exists; false otherwise.\n   */\n\n  PersistedModel.exists = function exists(id, cb) {\n    throwNotAttached(this.modelName, 'exists');\n  };\n\n  /**\n   * Find object by ID with an optional filter for include/fields.\n   *\n   * @param {*} id Primary key value\n   * @options {Object} [filter] Optional Filter JSON object; see below.\n   * @property {String|Object|Array} fields Identify fields to include in return result.\n   * <br/>See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html).\n   * @property {String|Object|Array} include  See PersistedModel.include documentation.\n   * <br/>See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html).\n   * @callback {Function} callback Callback function called with `(err, instance)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} instance Model instance matching the specified ID or null if no instance matches.\n   */\n\n  PersistedModel.findById = function findById(id, filter, cb) {\n    throwNotAttached(this.modelName, 'findById');\n  };\n\n  /**\n   * Find all model instances that match `filter` specification.\n   * See [Querying models](http://loopback.io/doc/en/lb2/Querying-data.html).\n   *\n   * @options {Object} [filter] Optional Filter JSON object; see below.\n   * @property {String|Object|Array} fields Identify fields to include in return result.\n   * <br/>See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html).\n   * @property {String|Object|Array} include  See PersistedModel.include documentation.\n   * <br/>See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html).\n   * @property {Number} limit Maximum number of instances to return.\n   * <br/>See [Limit filter](http://loopback.io/doc/en/lb2/Limit-filter.html).\n   * @property {String} order Sort order: either \"ASC\" for ascending or \"DESC\" for descending.\n   * <br/>See [Order filter](http://loopback.io/doc/en/lb2/Order-filter.html).\n   * @property {Number} skip Number of results to skip.\n   * <br/>See [Skip filter](http://loopback.io/doc/en/lb2/Skip-filter.html).\n   * @property {Object} where Where clause, like\n   * ```\n   * { where: { key: val, key2: {gt: 'val2'}, ...} }\n   * ```\n   * <br/>See\n   * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries).\n   *\n   * @callback {Function} callback Callback function called with `(err, returned-instances)` arguments.    Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Array} models Model instances matching the filter, or null if none found.\n   */\n\n  PersistedModel.find = function find(filter, cb) {\n    throwNotAttached(this.modelName, 'find');\n  };\n\n  /**\n   * Find one model instance that matches `filter` specification.\n   * Same as `find`, but limited to one result;\n   * Returns object, not collection.\n   *\n   * @options {Object} [filter] Optional Filter JSON object; see below.\n   * @property {String|Object|Array} fields Identify fields to include in return result.\n   * <br/>See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html).\n   * @property {String|Object|Array} include  See PersistedModel.include documentation.\n   * <br/>See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html).\n   * @property {String} order Sort order: either \"ASC\" for ascending or \"DESC\" for descending.\n   * <br/>See [Order filter](http://loopback.io/doc/en/lb2/Order-filter.html).\n   * @property {Number} skip Number of results to skip.\n   * <br/>See [Skip filter](http://loopback.io/doc/en/lb2/Skip-filter.html).\n   * @property {Object} where Where clause, like\n   * ```\n   * {where: { key: val, key2: {gt: 'val2'}, ...} }\n   * ```\n   * <br/>See\n   * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries).\n   *\n   * @callback {Function} callback Callback function called with `(err, returned-instance)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Array} model First model instance that matches the filter or null if none found.\n   */\n\n  PersistedModel.findOne = function findOne(filter, cb) {\n    throwNotAttached(this.modelName, 'findOne');\n  };\n\n  /**\n   * Destroy all model instances that match the optional `where` specification.\n   *\n   * @param {Object} [where] Optional where filter, like:\n   * ```\n   * {key: val, key2: {gt: 'val2'}, ...}\n   * ```\n   * <br/>See\n   * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods).\n   *\n   * @callback {Function} callback Optional callback function called with `(err, info)` arguments.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} info Additional information about the command outcome.\n   * @param {Number} info.count Number of instances (rows, documents) destroyed.\n   */\n\n  PersistedModel.destroyAll = function destroyAll(where, cb) {\n    throwNotAttached(this.modelName, 'destroyAll');\n  };\n\n  /**\n   * Alias for `destroyAll`\n   */\n  PersistedModel.remove = PersistedModel.destroyAll;\n\n  /**\n   * Alias for `destroyAll`\n   */\n  PersistedModel.deleteAll = PersistedModel.destroyAll;\n\n  /**\n   * Update multiple instances that match the where clause.\n   *\n   * Example:\n   *\n   *```js\n   * Employee.updateAll({managerId: 'x001'}, {managerId: 'x002'}, function(err, info) {\n   *     ...\n   * });\n   * ```\n   *\n   * @param {Object} [where] Optional `where` filter, like\n   * ```\n   * { key: val, key2: {gt: 'val2'}, ...}\n   * ```\n   * <br/>see\n   * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods).\n   * @param {Object} data Object containing data to replace matching instances, if any.\n   *\n   * @callback {Function} callback Callback function called with `(err, info)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} info Additional information about the command outcome.\n   * @param {Number} info.count Number of instances (rows, documents) updated.\n   *\n   */\n  PersistedModel.updateAll = function updateAll(where, data, cb) {\n    throwNotAttached(this.modelName, 'updateAll');\n  };\n\n  /**\n   * Alias for updateAll.\n   */\n  PersistedModel.update = PersistedModel.updateAll;\n\n  /**\n   * Destroy model instance with the specified ID.\n   * @param {*} id The ID value of model instance to delete.\n   * @callback {Function} callback Callback function called with `(err)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   */\n  PersistedModel.destroyById = function deleteById(id, cb) {\n    throwNotAttached(this.modelName, 'deleteById');\n  };\n\n  /**\n   * Alias for destroyById.\n   */\n  PersistedModel.removeById = PersistedModel.destroyById;\n\n  /**\n   * Alias for destroyById.\n   */\n  PersistedModel.deleteById = PersistedModel.destroyById;\n\n  /**\n   * Return the number of records that match the optional \"where\" filter.\n   * @param {Object} [where] Optional where filter, like\n   * ```\n   * { key: val, key2: {gt: 'val2'}, ...}\n   * ```\n   * <br/>See\n   * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods).\n   * @callback {Function} callback Callback function called with `(err, count)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Number} count Number of instances.\n   */\n\n  PersistedModel.count = function(where, cb) {\n    throwNotAttached(this.modelName, 'count');\n  };\n\n  /**\n   * Save model instance. If the instance doesn't have an ID, then calls [create](#persistedmodelcreatedata-cb) instead.\n   * Triggers: validate, save, update, or create.\n   * @options {Object} [options] See below.\n   * @property {Boolean} validate Perform validation before saving.  Default is true.\n   * @property {Boolean} throws If true, throw a validation error; WARNING: This can crash Node.\n   * If false, report the error via callback.  Default is false.\n   * @callback {Function} callback Optional callback function called with `(err, obj)` arguments.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} instance Model instance saved or created.\n   */\n\n  PersistedModel.prototype.save = function(options, callback) {\n    const Model = this.constructor;\n\n    if (typeof options == 'function') {\n      callback = options;\n      options = {};\n    }\n\n    callback = callback || function() {\n    };\n    options = options || {};\n\n    if (!('validate' in options)) {\n      options.validate = true;\n    }\n    if (!('throws' in options)) {\n      options.throws = false;\n    }\n\n    const inst = this;\n    const data = inst.toObject(true);\n    const id = this.getId();\n\n    if (!id) {\n      return Model.create(this, callback);\n    }\n\n    // validate first\n    if (!options.validate) {\n      return save();\n    }\n\n    inst.isValid(function(valid) {\n      if (valid) {\n        save();\n      } else {\n        const err = new Model.ValidationError(inst);\n        // throws option is dangerous for async usage\n        if (options.throws) {\n          throw err;\n        }\n        callback(err, inst);\n      }\n    });\n\n    // then save\n    function save() {\n      inst.trigger('save', function(saveDone) {\n        inst.trigger('update', function(updateDone) {\n          Model.upsert(inst, function(err) {\n            inst._initProperties(data);\n            updateDone.call(inst, function() {\n              saveDone.call(inst, function() {\n                callback(err, inst);\n              });\n            });\n          });\n        }, data);\n      }, data);\n    }\n  };\n\n  /**\n   * Determine if the data model is new.\n   * @returns {Boolean} Returns true if the data model is new; false otherwise.\n   */\n\n  PersistedModel.prototype.isNewRecord = function() {\n    throwNotAttached(this.constructor.modelName, 'isNewRecord');\n  };\n\n  /**\n   * Deletes the model from persistence.\n   * Triggers `destroy` hook (async) before and after destroying object.\n   * @param {Function} callback Callback function.\n   */\n\n  PersistedModel.prototype.destroy = function(cb) {\n    throwNotAttached(this.constructor.modelName, 'destroy');\n  };\n\n  /**\n   * Alias for destroy.\n   * @header PersistedModel.remove\n   */\n  PersistedModel.prototype.remove = PersistedModel.prototype.destroy;\n\n  /**\n   * Alias for destroy.\n   * @header PersistedModel.delete\n   */\n  PersistedModel.prototype.delete = PersistedModel.prototype.destroy;\n\n  PersistedModel.prototype.destroy._delegate = true;\n\n  /**\n   * Update a single attribute.\n   * Equivalent to `updateAttributes({name: 'value'}, cb)`\n   *\n   * @param {String} name Name of property.\n   * @param {Mixed} value Value of property.\n   * @callback {Function} callback Callback function called with `(err, instance)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} instance Updated instance.\n   */\n\n  PersistedModel.prototype.updateAttribute = function updateAttribute(name, value, callback) {\n    throwNotAttached(this.constructor.modelName, 'updateAttribute');\n  };\n\n  /**\n   * Update set of attributes.  Performs validation before updating.\n   *\n   * Triggers: `validation`, `save` and `update` hooks\n   * @param {Object} data Data to update.\n   * @callback {Function} callback Callback function called with `(err, instance)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} instance Updated instance.\n   */\n\n  PersistedModel.prototype.updateAttributes = PersistedModel.prototype.patchAttributes =\n  function updateAttributes(data, cb) {\n    throwNotAttached(this.modelName, 'updateAttributes');\n  };\n\n  /**\n   * Replace attributes for a model instance and persist it into the datasource.\n   * Performs validation before replacing.\n   *\n   * @param {Object} data Data to replace.\n   * @options {Object} [options] Options for replace\n   * @property {Boolean} validate Perform validation before saving.  Default is true.\n   * @callback {Function} callback Callback function called with `(err, instance)` arguments.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} instance Replaced instance.\n   */\n\n  PersistedModel.prototype.replaceAttributes = function replaceAttributes(data, cb) {\n    throwNotAttached(this.modelName, 'replaceAttributes');\n  };\n\n  /**\n   * Replace attributes for a model instance whose id is the first input\n   * argument and persist it into the datasource.\n   * Performs validation before replacing.\n   *\n   * @param {*} id The ID value of model instance to replace.\n   * @param {Object} data Data to replace.\n   * @options {Object} [options] Options for replace\n   * @property {Boolean} validate Perform validation before saving.  Default is true.\n   * @callback {Function} callback Callback function called with `(err, instance)` arguments.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} instance Replaced instance.\n   */\n\n  PersistedModel.replaceById = function replaceById(id, data, cb) {\n    throwNotAttached(this.modelName, 'replaceById');\n  };\n\n  /**\n   * Reload object from persistence.  Requires `id` member of `object` to be able to call `find`.\n   * @callback {Function} callback Callback function called with `(err, instance)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} instance Model instance.\n   */\n\n  PersistedModel.prototype.reload = function reload(callback) {\n    throwNotAttached(this.constructor.modelName, 'reload');\n  };\n\n  /**\n   * Set the correct `id` property for the `PersistedModel`. Uses the `setId` method if the model is attached to\n   * connector that defines it.  Otherwise, uses the default lookup.\n   * Override this method to handle complex IDs.\n   *\n   * @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.\n   */\n\n  PersistedModel.prototype.setId = function(val) {\n    const ds = this.getDataSource();\n    this[this.getIdName()] = val;\n  };\n\n  /**\n   * Get the `id` value for the `PersistedModel`.\n   *\n   * @returns {*} The `id` value\n   */\n\n  PersistedModel.prototype.getId = function() {\n    const data = this.toObject();\n    if (!data) return;\n    return data[this.getIdName()];\n  };\n\n  /**\n   * Get the `id` property name of the constructor.\n   *\n   * @returns {String} The `id` property name\n   */\n\n  PersistedModel.prototype.getIdName = function() {\n    return this.constructor.getIdName();\n  };\n\n  /**\n   * Get the `id` property name of the constructor.\n   *\n   * @returns {String} The `id` property name\n   */\n\n  PersistedModel.getIdName = function() {\n    const Model = this;\n    const ds = Model.getDataSource();\n\n    if (ds.idName) {\n      return ds.idName(Model.modelName);\n    } else {\n      return 'id';\n    }\n  };\n\n  PersistedModel.setupRemoting = function() {\n    const PersistedModel = this;\n    const typeName = PersistedModel.modelName;\n    const options = PersistedModel.settings;\n\n    // if there is atleast one updateOnly property, then we set\n    // createOnlyInstance flag in __create__ to indicate loopback-swagger\n    // code to create a separate model instance for create operation only\n    const updateOnlyProps = this.getUpdateOnlyProperties ?\n      this.getUpdateOnlyProperties() : false;\n    const hasUpdateOnlyProps = updateOnlyProps && updateOnlyProps.length > 0;\n\n    // This is just for LB 3.x\n    options.replaceOnPUT = options.replaceOnPUT !== false;\n\n    function setRemoting(scope, name, options) {\n      const fn = scope[name];\n      fn._delegate = true;\n      options.isStatic = scope === PersistedModel;\n      PersistedModel.remoteMethod(name, options);\n    }\n\n    setRemoting(PersistedModel, 'create', {\n      description: 'Create a new instance of the model and persist it into the data source.',\n      accessType: 'WRITE',\n      accepts: [\n        {\n          arg: 'data', type: 'object', model: typeName, allowArray: true,\n          createOnlyInstance: hasUpdateOnlyProps,\n          description: 'Model instance data',\n          http: {source: 'body'},\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'data', type: typeName, root: true},\n      http: {verb: 'post', path: '/'},\n    });\n\n    const upsertOptions = {\n      aliases: ['upsert', 'updateOrCreate'],\n      description: 'Patch an existing model instance or insert a new one ' +\n        'into the data source.',\n      accessType: 'WRITE',\n      accepts: [\n        {\n          arg: 'data', type: 'object', model: typeName, http: {source: 'body'},\n          description: 'Model instance data',\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'data', type: typeName, root: true},\n      http: [{verb: 'patch', path: '/'}],\n    };\n\n    if (!options.replaceOnPUT) {\n      upsertOptions.http.unshift({verb: 'put', path: '/'});\n    }\n    setRemoting(PersistedModel, 'patchOrCreate', upsertOptions);\n\n    const replaceOrCreateOptions = {\n      description: 'Replace an existing model instance or insert a new one into the data source.',\n      accessType: 'WRITE',\n      accepts: [\n        {\n          arg: 'data', type: 'object', model: typeName,\n          http: {source: 'body'},\n          description: 'Model instance data',\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'data', type: typeName, root: true},\n      http: [{verb: 'post', path: '/replaceOrCreate'}],\n    };\n\n    if (options.replaceOnPUT) {\n      replaceOrCreateOptions.http.push({verb: 'put', path: '/'});\n    }\n\n    setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions);\n\n    setRemoting(PersistedModel, 'upsertWithWhere', {\n      aliases: ['patchOrCreateWithWhere'],\n      description: 'Update an existing model instance or insert a new one into ' +\n        'the data source based on the where criteria.',\n      accessType: 'WRITE',\n      accepts: [\n        {arg: 'where', type: 'object', http: {source: 'query'},\n          description: 'Criteria to match model instances'},\n        {arg: 'data', type: 'object', model: typeName, http: {source: 'body'},\n          description: 'An object of model property name/value pairs'},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'data', type: typeName, root: true},\n      http: {verb: 'post', path: '/upsertWithWhere'},\n    });\n\n    setRemoting(PersistedModel, 'exists', {\n      description: 'Check whether a model instance exists in the data source.',\n      accessType: 'READ',\n      accepts: [\n        {arg: 'id', type: 'any', description: 'Model id', required: true,\n          http: {source: 'path'}},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'exists', type: 'boolean'},\n      http: [\n        {verb: 'get', path: '/:id/exists'},\n        {verb: 'head', path: '/:id'},\n      ],\n      rest: {\n        // After hook to map exists to 200/404 for HEAD\n        after: function(ctx, cb) {\n          if (ctx.req.method === 'GET') {\n            // For GET, return {exists: true|false} as is\n            return cb();\n          }\n          if (!ctx.result.exists) {\n            const modelName = ctx.method.sharedClass.name;\n            const id = ctx.getArgByName('id');\n            const msg = 'Unknown \"' + modelName + '\" id \"' + id + '\".';\n            const error = new Error(msg);\n            error.statusCode = error.status = 404;\n            error.code = 'MODEL_NOT_FOUND';\n            cb(error);\n          } else {\n            cb();\n          }\n        },\n      },\n    });\n\n    setRemoting(PersistedModel, 'findById', {\n      description: 'Find a model instance by {{id}} from the data source.',\n      accessType: 'READ',\n      accepts: [\n        {arg: 'id', type: 'any', description: 'Model id', required: true,\n          http: {source: 'path'}},\n        {arg: 'filter', type: 'object',\n          description:\n          'Filter defining fields and include - must be a JSON-encoded string (' +\n          '{\"something\":\"value\"})'},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'data', type: typeName, root: true},\n      http: {verb: 'get', path: '/:id'},\n      rest: {after: convertNullToNotFoundError},\n    });\n\n    const replaceByIdOptions = {\n      description: 'Replace attributes for a model instance and persist it into the data source.',\n      accessType: 'WRITE',\n      accepts: [\n        {arg: 'id', type: 'any', description: 'Model id', required: true,\n          http: {source: 'path'}},\n        {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description:\n          'Model instance data'},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'data', type: typeName, root: true},\n      http: [{verb: 'post', path: '/:id/replace'}],\n    };\n\n    if (options.replaceOnPUT) {\n      replaceByIdOptions.http.push({verb: 'put', path: '/:id'});\n    }\n\n    setRemoting(PersistedModel, 'replaceById', replaceByIdOptions);\n\n    setRemoting(PersistedModel, 'find', {\n      description: 'Find all instances of the model matched by filter from the data source.',\n      accessType: 'READ',\n      accepts: [\n        {arg: 'filter', type: 'object', description:\n        'Filter defining fields, where, include, order, offset, and limit - must be a ' +\n        'JSON-encoded string (`{\"where\":{\"something\":\"value\"}}`).  ' +\n        'See https://loopback.io/doc/en/lb3/Querying-data.html#using-stringified-json-in-rest-queries ' +\n        'for more details.'},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'data', type: [typeName], root: true},\n      http: {verb: 'get', path: '/'},\n    });\n\n    setRemoting(PersistedModel, 'findOne', {\n      description: 'Find first instance of the model matched by filter from the data source.',\n      accessType: 'READ',\n      accepts: [\n        {arg: 'filter', type: 'object', description:\n        'Filter defining fields, where, include, order, offset, and limit - must be a ' +\n        'JSON-encoded string (`{\"where\":{\"something\":\"value\"}}`).  ' +\n        'See https://loopback.io/doc/en/lb3/Querying-data.html#using-stringified-json-in-rest-queries ' +\n        'for more details.'},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'data', type: typeName, root: true},\n      http: {verb: 'get', path: '/findOne'},\n      rest: {after: convertNullToNotFoundError},\n    });\n\n    setRemoting(PersistedModel, 'destroyAll', {\n      description: 'Delete all matching records.',\n      accessType: 'WRITE',\n      accepts: [\n        {arg: 'where', type: 'object', description: 'filter.where object'},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {\n        arg: 'count',\n        type: 'object',\n        description: 'The number of instances deleted',\n        root: true,\n      },\n      http: {verb: 'del', path: '/'},\n      shared: false,\n    });\n\n    setRemoting(PersistedModel, 'updateAll', {\n      aliases: ['update'],\n      description: 'Update instances of the model matched by {{where}} from the data source.',\n      accessType: 'WRITE',\n      accepts: [\n        {arg: 'where', type: 'object', http: {source: 'query'},\n          description: 'Criteria to match model instances'},\n        {arg: 'data', type: 'object', model: typeName, http: {source: 'body'},\n          description: 'An object of model property name/value pairs'},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {\n        arg: 'info',\n        description: 'Information related to the outcome of the operation',\n        type: {\n          count: {\n            type: 'number',\n            description: 'The number of instances updated',\n          },\n        },\n        root: true,\n      },\n      http: {verb: 'post', path: '/update'},\n    });\n\n    setRemoting(PersistedModel, 'deleteById', {\n      aliases: ['destroyById', 'removeById'],\n      description: 'Delete a model instance by {{id}} from the data source.',\n      accessType: 'WRITE',\n      accepts: [\n        {arg: 'id', type: 'any', description: 'Model id', required: true,\n          http: {source: 'path'}},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      http: {verb: 'del', path: '/:id'},\n      returns: {arg: 'count', type: 'object', root: true},\n    });\n\n    setRemoting(PersistedModel, 'count', {\n      description: 'Count instances of the model matched by where from the data source.',\n      accessType: 'READ',\n      accepts: [\n        {arg: 'where', type: 'object', description: 'Criteria to match model instances'},\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'count', type: 'number'},\n      http: {verb: 'get', path: '/count'},\n    });\n\n    const updateAttributesOptions = {\n      aliases: ['updateAttributes'],\n      description: 'Patch attributes for a model instance and persist it into ' +\n        'the data source.',\n      accessType: 'WRITE',\n      accepts: [\n        {\n          arg: 'data', type: 'object', model: typeName,\n          http: {source: 'body'},\n          description: 'An object of model property name/value pairs',\n        },\n        {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      ],\n      returns: {arg: 'data', type: typeName, root: true},\n      http: [{verb: 'patch', path: '/'}],\n    };\n\n    setRemoting(PersistedModel.prototype, 'patchAttributes', updateAttributesOptions);\n\n    if (!options.replaceOnPUT) {\n      updateAttributesOptions.http.unshift({verb: 'put', path: '/'});\n    }\n\n    if (options.trackChanges || options.enableRemoteReplication) {\n      setRemoting(PersistedModel, 'diff', {\n        description: 'Get a set of deltas and conflicts since the given checkpoint.',\n        accessType: 'READ',\n        accepts: [\n          {arg: 'since', type: 'number', description: 'Find deltas since this checkpoint'},\n          {arg: 'remoteChanges', type: 'array', description: 'an array of change objects',\n            http: {source: 'body'}},\n        ],\n        returns: {arg: 'result', type: 'object', root: true},\n        http: {verb: 'post', path: '/diff'},\n      });\n\n      setRemoting(PersistedModel, 'changes', {\n        description: 'Get the changes to a model since a given checkpoint.' +\n          'Provide a filter object to reduce the number of results returned.',\n        accessType: 'READ',\n        accepts: [\n          {arg: 'since', type: 'number', description:\n            'Only return changes since this checkpoint'},\n          {arg: 'filter', type: 'object', description:\n            'Only include changes that match this filter'},\n        ],\n        returns: {arg: 'changes', type: 'array', root: true},\n        http: {verb: 'get', path: '/changes'},\n      });\n\n      setRemoting(PersistedModel, 'checkpoint', {\n        description: 'Create a checkpoint.',\n        // The replication algorithm needs to create a source checkpoint,\n        // even though it is otherwise not making any source changes.\n        // We need to allow this method for users that don't have full\n        // WRITE permissions.\n        accessType: 'REPLICATE',\n        returns: {arg: 'checkpoint', type: 'object', root: true},\n        http: {verb: 'post', path: '/checkpoint'},\n      });\n\n      setRemoting(PersistedModel, 'currentCheckpoint', {\n        description: 'Get the current checkpoint.',\n        accessType: 'READ',\n        returns: {arg: 'checkpoint', type: 'object', root: true},\n        http: {verb: 'get', path: '/checkpoint'},\n      });\n\n      setRemoting(PersistedModel, 'createUpdates', {\n        description: 'Create an update list from a delta list.',\n        // This operation is read-only, it does not change any local data.\n        // It is called by the replication algorithm to compile a list\n        // of changes to apply on the target.\n        accessType: 'READ',\n        accepts: {arg: 'deltas', type: 'array', http: {source: 'body'}},\n        returns: {arg: 'updates', type: 'array', root: true},\n        http: {verb: 'post', path: '/create-updates'},\n      });\n\n      setRemoting(PersistedModel, 'bulkUpdate', {\n        description: 'Run multiple updates at once. Note: this is not atomic.',\n        accessType: 'WRITE',\n        accepts: {arg: 'updates', type: 'array'},\n        http: {verb: 'post', path: '/bulk-update'},\n      });\n\n      setRemoting(PersistedModel, 'findLastChange', {\n        description: 'Get the most recent change record for this instance.',\n        accessType: 'READ',\n        accepts: {\n          arg: 'id', type: 'any', required: true, http: {source: 'path'},\n          description: 'Model id',\n        },\n        returns: {arg: 'result', type: this.Change.modelName, root: true},\n        http: {verb: 'get', path: '/:id/changes/last'},\n      });\n\n      setRemoting(PersistedModel, 'updateLastChange', {\n        description:\n          'Update the properties of the most recent change record ' +\n          'kept for this instance.',\n        accessType: 'WRITE',\n        accepts: [\n          {\n            arg: 'id', type: 'any', required: true, http: {source: 'path'},\n            description: 'Model id',\n          },\n          {\n            arg: 'data', type: 'object', model: typeName, http: {source: 'body'},\n            description: 'An object of Change property name/value pairs',\n          },\n        ],\n        returns: {arg: 'result', type: this.Change.modelName, root: true},\n        http: {verb: 'put', path: '/:id/changes/last'},\n      });\n    }\n\n    setRemoting(PersistedModel, 'createChangeStream', {\n      description: 'Create a change stream.',\n      accessType: 'READ',\n      http: [\n        {verb: 'post', path: '/change-stream'},\n        {verb: 'get', path: '/change-stream'},\n      ],\n      accepts: {\n        arg: 'options',\n        type: 'object',\n      },\n      returns: {\n        arg: 'changes',\n        type: 'ReadableStream',\n        json: true,\n      },\n    });\n  };\n\n  /**\n   * Get a set of deltas and conflicts since the given checkpoint.\n   *\n   * See [Change.diff()](#change-diff) for details.\n   *\n   * @param  {Number}  since  Find deltas since this checkpoint.\n   * @param  {Array}  remoteChanges  An array of change objects.\n   * @callback {Function} callback Callback function called with `(err, result)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.\n   */\n\n  PersistedModel.diff = function(since, remoteChanges, callback) {\n    const Change = this.getChangeModel();\n    Change.diff(this.modelName, since, remoteChanges, callback);\n  };\n\n  /**\n   * Get the changes to a model since the specified checkpoint. Provide a filter object\n   * to reduce the number of results returned.\n   * @param  {Number}   since    Return only changes since this checkpoint.\n   * @param  {Object}   filter   Include only changes that match this filter, the same as for [#persistedmodel-find](find()).\n   * @callback {Function} callback Callback function called with `(err, changes)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Array} changes An array of [Change](#change) objects.\n   */\n\n  PersistedModel.changes = function(since, filter, callback) {\n    if (typeof since === 'function') {\n      filter = {};\n      callback = since;\n      since = -1;\n    }\n    if (typeof filter === 'function') {\n      callback = filter;\n      since = -1;\n      filter = {};\n    }\n\n    const idName = this.dataSource.idName(this.modelName);\n    const Change = this.getChangeModel();\n    const model = this;\n    const changeFilter = this.createChangeFilter(since, filter);\n\n    filter = filter || {};\n    filter.fields = {};\n    filter.where = filter.where || {};\n    filter.fields[idName] = true;\n\n    // TODO(ritch) this whole thing could be optimized a bit more\n    Change.find(changeFilter, function(err, changes) {\n      if (err) return callback(err);\n      if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);\n      const ids = changes.map(function(change) {\n        return change.getModelId();\n      });\n      filter.where[idName] = {inq: ids};\n      model.find(filter, function(err, models) {\n        if (err) return callback(err);\n        const modelIds = models.map(function(m) {\n          return m[idName].toString();\n        });\n        callback(null, changes.filter(function(ch) {\n          if (ch.type() === Change.DELETE) return true;\n          return modelIds.indexOf(ch.modelId) > -1;\n        }));\n      });\n    });\n  };\n\n  /**\n   * Create a checkpoint.\n   *\n   * @param  {Function} callback\n   */\n\n  PersistedModel.checkpoint = function(cb) {\n    const Checkpoint = this.getChangeModel().getCheckpointModel();\n    Checkpoint.bumpLastSeq(cb);\n  };\n\n  /**\n   * Get the current checkpoint ID.\n   *\n   * @callback {Function} callback Callback function called with `(err, currentCheckpointId)` arguments.  Required.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Number} currentCheckpointId Current checkpoint ID.\n   */\n\n  PersistedModel.currentCheckpoint = function(cb) {\n    const Checkpoint = this.getChangeModel().getCheckpointModel();\n    Checkpoint.current(cb);\n  };\n\n  /**\n   * Replicate changes since the given checkpoint to the given target model.\n   *\n   * @param  {Number}   [since]  Since this checkpoint\n   * @param  {Model}    targetModel  Target this model class\n   * @param  {Object} [options] An optional options object to pass to underlying data-access calls.\n   * @param {Object} [options.filter] Replicate models that match this filter\n   * @callback {Function} [callback] Callback function called with `(err, conflicts)` arguments.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {Conflict[]} conflicts A list of changes that could not be replicated due to conflicts.\n   * @param {Object} checkpoints The new checkpoints to use as the \"since\"\n   * argument for the next replication.\n   *\n   * @promise\n   */\n\n  PersistedModel.replicate = function(since, targetModel, options, callback) {\n    const lastArg = arguments[arguments.length - 1];\n\n    if (typeof lastArg === 'function' && arguments.length > 1) {\n      callback = lastArg;\n    }\n\n    if (typeof since === 'function' && since.modelName) {\n      targetModel = since;\n      since = -1;\n    }\n\n    if (typeof since !== 'object') {\n      since = {source: since, target: since};\n    }\n\n    if (typeof options === 'function') {\n      options = {};\n    }\n\n    options = options || {};\n\n    const sourceModel = this;\n    callback = callback || utils.createPromiseCallback();\n\n    debug('replicating %s since %s to %s since %s',\n      sourceModel.modelName,\n      since.source,\n      targetModel.modelName,\n      since.target);\n    if (options.filter) {\n      debug('\\twith filter %j', options.filter);\n    }\n\n    // In order to avoid a race condition between the replication and\n    // other clients modifying the data, we must create the new target\n    // checkpoint as the first step of the replication process.\n    // As a side-effect of that, the replicated changes are associated\n    // with the new target checkpoint. This is actually desired behaviour,\n    // because that way clients replicating *from* the target model\n    // since the new checkpoint will pick these changes up.\n    // However, it increases the likelihood of (false) conflicts being detected.\n    // In order to prevent that, we run the replication multiple times,\n    // until no changes were replicated, but at most MAX_ATTEMPTS times\n    // to prevent starvation. In most cases, the second run will find no changes\n    // to replicate and we are done.\n    const MAX_ATTEMPTS = 3;\n\n    run(1, since);\n    return callback.promise;\n\n    function run(attempt, since) {\n      debug('\\titeration #%s', attempt);\n      tryReplicate(sourceModel, targetModel, since, options, next);\n\n      function next(err, conflicts, cps, updates) {\n        const finished = err || conflicts.length ||\n          !updates || updates.length === 0 ||\n          attempt >= MAX_ATTEMPTS;\n\n        if (finished)\n          return callback(err, conflicts, cps);\n        run(attempt + 1, cps);\n      }\n    }\n  };\n\n  function tryReplicate(sourceModel, targetModel, since, options, callback) {\n    const Change = sourceModel.getChangeModel();\n    const TargetChange = targetModel.getChangeModel();\n    const changeTrackingEnabled = Change && TargetChange;\n    let replicationChunkSize = REPLICATION_CHUNK_SIZE;\n\n    if (sourceModel.settings && sourceModel.settings.replicationChunkSize) {\n      replicationChunkSize = sourceModel.settings.replicationChunkSize;\n    }\n\n    assert(\n      changeTrackingEnabled,\n      'You must enable change tracking before replicating',\n    );\n\n    let diff, updates, newSourceCp, newTargetCp;\n\n    const tasks = [\n      checkpoints,\n      getSourceChanges,\n      getDiffFromTarget,\n      createSourceUpdates,\n      bulkUpdate,\n    ];\n\n    async.waterfall(tasks, done);\n\n    function getSourceChanges(cb) {\n      utils.downloadInChunks(\n        options.filter,\n        replicationChunkSize,\n        function(filter, pagingCallback) {\n          sourceModel.changes(since.source, filter, pagingCallback);\n        },\n        debug.enabled ? log : cb,\n      );\n\n      function log(err, result) {\n        if (err) return cb(err);\n        debug('\\tusing source changes');\n        result.forEach(function(it) { debug('\\t\\t%j', it); });\n        cb(err, result);\n      }\n    }\n\n    function getDiffFromTarget(sourceChanges, cb) {\n      utils.uploadInChunks(\n        sourceChanges,\n        replicationChunkSize,\n        function(smallArray, chunkCallback) {\n          return targetModel.diff(since.target, smallArray, chunkCallback);\n        },\n        debug.enabled ? log : cb,\n      );\n\n      function log(err, result) {\n        if (err) return cb(err);\n        if (result.conflicts && result.conflicts.length) {\n          debug('\\tdiff conflicts');\n          result.conflicts.forEach(function(d) { debug('\\t\\t%j', d); });\n        }\n        if (result.deltas && result.deltas.length) {\n          debug('\\tdiff deltas');\n          result.deltas.forEach(function(it) { debug('\\t\\t%j', it); });\n        }\n        cb(err, result);\n      }\n    }\n\n    function createSourceUpdates(_diff, cb) {\n      diff = _diff;\n      diff.conflicts = diff.conflicts || [];\n\n      if (diff && diff.deltas && diff.deltas.length) {\n        debug('\\tbuilding a list of updates');\n        utils.uploadInChunks(\n          diff.deltas,\n          replicationChunkSize,\n          function(smallArray, chunkCallback) {\n            return sourceModel.createUpdates(smallArray, chunkCallback);\n          },\n          cb,\n        );\n      } else {\n        // nothing to replicate\n        done();\n      }\n    }\n\n    function bulkUpdate(_updates, cb) {\n      debug('\\tstarting bulk update');\n      updates = _updates;\n      utils.uploadInChunks(\n        updates,\n        replicationChunkSize,\n        function(smallArray, chunkCallback) {\n          return targetModel.bulkUpdate(smallArray, options, function(err) {\n            // bulk update is a special case where we want to process all chunks and aggregate all errors\n            chunkCallback(null, err);\n          });\n        },\n        function(notUsed, err) {\n          const conflicts = err && err.details && err.details.conflicts;\n          if (conflicts && err.statusCode == 409) {\n            diff.conflicts = conflicts;\n            // filter out updates that were not applied\n            updates = updates.filter(function(u) {\n              return conflicts\n                .filter(function(d) { return d.modelId === u.change.modelId; })\n                .length === 0;\n            });\n            return cb();\n          }\n          cb(err);\n        },\n      );\n    }\n\n    function checkpoints() {\n      const cb = arguments[arguments.length - 1];\n      sourceModel.checkpoint(function(err, source) {\n        if (err) return cb(err);\n        newSourceCp = source.seq;\n        targetModel.checkpoint(function(err, target) {\n          if (err) return cb(err);\n          newTargetCp = target.seq;\n          debug('\\tcreated checkpoints');\n          debug('\\t\\t%s for source model %s', newSourceCp, sourceModel.modelName);\n          debug('\\t\\t%s for target model %s', newTargetCp, targetModel.modelName);\n          cb();\n        });\n      });\n    }\n\n    function done(err) {\n      if (err) return callback(err);\n\n      debug('\\treplication finished');\n      debug('\\t\\t%s conflict(s) detected', diff.conflicts.length);\n      debug('\\t\\t%s change(s) applied', updates ? updates.length : 0);\n      debug('\\t\\tnew checkpoints: { source: %j, target: %j }',\n        newSourceCp, newTargetCp);\n\n      const conflicts = diff.conflicts.map(function(change) {\n        return new Change.Conflict(\n          change.modelId, sourceModel, targetModel,\n        );\n      });\n\n      if (conflicts.length) {\n        sourceModel.emit('conflicts', conflicts);\n      }\n\n      if (callback) {\n        const newCheckpoints = {source: newSourceCp, target: newTargetCp};\n        callback(null, conflicts, newCheckpoints, updates);\n      }\n    }\n  }\n\n  /**\n   * Create an update list (for `Model.bulkUpdate()`) from a delta list\n   * (result of `Change.diff()`).\n   *\n   * @param  {Array}    deltas\n   * @param  {Function} callback\n   */\n\n  PersistedModel.createUpdates = function(deltas, cb) {\n    const Change = this.getChangeModel();\n    const updates = [];\n    const Model = this;\n    const tasks = [];\n\n    deltas.forEach(function(change) {\n      change = new Change(change);\n      const type = change.type();\n      const update = {type: type, change: change};\n      switch (type) {\n        case Change.CREATE:\n        case Change.UPDATE:\n          tasks.push(function(cb) {\n            Model.findById(change.modelId, function(err, inst) {\n              if (err) return cb(err);\n              if (!inst) {\n                return cb &&\n                  cb(new Error(g.f('Missing data for change: %s', change.modelId)));\n              }\n              if (inst.toObject) {\n                update.data = inst.toObject();\n              } else {\n                update.data = inst;\n              }\n              updates.push(update);\n              cb();\n            });\n          });\n          break;\n        case Change.DELETE:\n          updates.push(update);\n          break;\n      }\n    });\n\n    async.parallel(tasks, function(err) {\n      if (err) return cb(err);\n      cb(null, updates);\n    });\n  };\n\n  /**\n   * Apply an update list.\n   *\n   * **Note: this is not atomic**\n   *\n   * @param  {Array} updates An updates list, usually from [createUpdates()](#persistedmodel-createupdates).\n   * @param  {Object} [options] An optional options object to pass to underlying data-access calls.\n   * @param  {Function} callback Callback function.\n   */\n\n  PersistedModel.bulkUpdate = function(updates, options, callback) {\n    const tasks = [];\n    const Model = this;\n    const Change = this.getChangeModel();\n    const conflicts = [];\n\n    const lastArg = arguments[arguments.length - 1];\n\n    if (typeof lastArg === 'function' && arguments.length > 1) {\n      callback = lastArg;\n    }\n\n    if (typeof options === 'function') {\n      options = {};\n    }\n\n    options = options || {};\n\n    buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) {\n      if (err) return callback(err);\n\n      updates.forEach(function(update) {\n        const id = update.change.modelId;\n        const current = currentMap[id];\n        switch (update.type) {\n          case Change.UPDATE:\n            tasks.push(function(cb) {\n              applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb);\n            });\n            break;\n\n          case Change.CREATE:\n            tasks.push(function(cb) {\n              applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb);\n            });\n            break;\n          case Change.DELETE:\n            tasks.push(function(cb) {\n              applyDelete(Model, id, current, update.change, conflicts, options, cb);\n            });\n            break;\n        }\n      });\n\n      async.parallel(tasks, function(err) {\n        if (err) return callback(err);\n        if (conflicts.length) {\n          err = new Error(g.f('Conflict'));\n          err.statusCode = 409;\n          err.details = {conflicts: conflicts};\n          return callback(err);\n        }\n        callback();\n      });\n    });\n  };\n\n  function buildLookupOfAffectedModelData(Model, updates, callback) {\n    const idName = Model.dataSource.idName(Model.modelName);\n    const affectedIds = updates.map(function(u) { return u.change.modelId; });\n    const whereAffected = {};\n    whereAffected[idName] = {inq: affectedIds};\n    Model.find({where: whereAffected}, function(err, affectedList) {\n      if (err) return callback(err);\n      const dataLookup = {};\n      affectedList.forEach(function(it) {\n        dataLookup[it[idName]] = it;\n      });\n      callback(null, dataLookup);\n    });\n  }\n\n  function applyUpdate(Model, id, current, data, change, conflicts, options, cb) {\n    const Change = Model.getChangeModel();\n    const rev = current ? Change.revisionForInst(current) : null;\n\n    if (rev !== change.prev) {\n      debug('Detected non-rectified change of %s %j',\n        Model.modelName, id);\n      debug('\\tExpected revision: %s', change.rev);\n      debug('\\tActual revision:   %s', rev);\n      conflicts.push(change);\n      return Change.rectifyModelChanges(Model.modelName, [id], cb);\n    }\n\n    // TODO(bajtos) modify `data` so that it instructs\n    // the connector to remove any properties included in \"inst\"\n    // but not included in `data`\n    // See https://github.com/strongloop/loopback/issues/1215\n\n    Model.updateAll(current.toObject(), data, options, function(err, result) {\n      if (err) return cb(err);\n\n      const count = result && result.count;\n      switch (count) {\n        case 1:\n          // The happy path, exactly one record was updated\n          return cb();\n\n        case 0:\n          debug('UpdateAll detected non-rectified change of %s %j',\n            Model.modelName, id);\n          conflicts.push(change);\n          // NOTE(bajtos) updateAll triggers change rectification\n          // for all model instances, even when no records were updated,\n          // thus we don't need to rectify explicitly ourselves\n          return cb();\n\n        case undefined:\n        case null:\n          return cb(new Error(\n            g.f('Cannot apply bulk updates, ' +\n            'the connector does not correctly report ' +\n            'the number of updated records.'),\n          ));\n\n        default:\n          debug('%s.updateAll modified unexpected number of instances: %j',\n            Model.modelName, count);\n          return cb(new Error(\n            g.f('Bulk update failed, the connector has modified unexpected ' +\n            'number of records: %s', JSON.stringify(count)),\n          ));\n      }\n    });\n  }\n\n  function applyCreate(Model, id, current, data, change, conflicts, options, cb) {\n    Model.create(data, options, function(createErr) {\n      if (!createErr) return cb();\n\n      // We don't have a reliable way how to detect the situation\n      // where he model was not create because of a duplicate id\n      // The workaround is to query the DB to check if the model already exists\n      Model.findById(id, function(findErr, inst) {\n        if (findErr || !inst) {\n          // There isn't any instance with the same id, thus there isn't\n          // any conflict and we just report back the original error.\n          return cb(createErr);\n        }\n\n        return conflict();\n      });\n    });\n\n    function conflict() {\n      // The instance already exists - report a conflict\n      debug('Detected non-rectified new instance of %s %j',\n        Model.modelName, id);\n      conflicts.push(change);\n\n      const Change = Model.getChangeModel();\n      return Change.rectifyModelChanges(Model.modelName, [id], cb);\n    }\n  }\n\n  function applyDelete(Model, id, current, change, conflicts, options, cb) {\n    if (!current) {\n      // The instance was either already deleted or not created at all,\n      // we are done.\n      return cb();\n    }\n\n    const Change = Model.getChangeModel();\n    const rev = Change.revisionForInst(current);\n    if (rev !== change.prev) {\n      debug('Detected non-rectified change of %s %j',\n        Model.modelName, id);\n      debug('\\tExpected revision: %s', change.rev);\n      debug('\\tActual revision:   %s', rev);\n      conflicts.push(change);\n      return Change.rectifyModelChanges(Model.modelName, [id], cb);\n    }\n\n    Model.deleteAll(current.toObject(), options, function(err, result) {\n      if (err) return cb(err);\n\n      const count = result && result.count;\n      switch (count) {\n        case 1:\n          // The happy path, exactly one record was updated\n          return cb();\n\n        case 0:\n          debug('DeleteAll detected non-rectified change of %s %j',\n            Model.modelName, id);\n          conflicts.push(change);\n          // NOTE(bajtos) deleteAll triggers change rectification\n          // for all model instances, even when no records were updated,\n          // thus we don't need to rectify explicitly ourselves\n          return cb();\n\n        case undefined:\n        case null:\n          return cb(new Error(\n            g.f('Cannot apply bulk updates, ' +\n            'the connector does not correctly report ' +\n            'the number of deleted records.'),\n          ));\n\n        default:\n          debug('%s.deleteAll modified unexpected number of instances: %j',\n            Model.modelName, count);\n          return cb(new Error(\n            g.f('Bulk update failed, the connector has deleted unexpected ' +\n            'number of records: %s', JSON.stringify(count)),\n          ));\n      }\n    });\n  }\n\n  /**\n   * Get the `Change` model.\n   * Throws an error if the change model is not correctly setup.\n   * @return {Change}\n   */\n\n  PersistedModel.getChangeModel = function() {\n    const changeModel = this.Change;\n    const isSetup = changeModel && changeModel.dataSource;\n\n    assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName);\n\n    return changeModel;\n  };\n\n  /**\n   * Get the source identifier for this model or dataSource.\n   *\n   * @callback {Function} callback Callback function called with `(err, id)` arguments.\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   * @param {String} sourceId Source identifier for the model or dataSource.\n   */\n\n  PersistedModel.getSourceId = function(cb) {\n    const dataSource = this.dataSource;\n    if (!dataSource) {\n      this.once('dataSourceAttached', this.getSourceId.bind(this, cb));\n    }\n    assert(\n      dataSource.connector.name,\n      'Model.getSourceId: cannot get id without dataSource.connector.name',\n    );\n    const id = [dataSource.connector.name, this.modelName].join('-');\n    cb(null, id);\n  };\n\n  /**\n   * Enable the tracking of changes made to the model. Usually for replication.\n   */\n\n  PersistedModel.enableChangeTracking = function() {\n    const Model = this;\n    const Change = this.Change || this._defineChangeModel();\n    const cleanupInterval = Model.settings.changeCleanupInterval || 30000;\n\n    assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName +\n      ' is not attached to a dataSource');\n\n    const idName = this.getIdName();\n    const idProp = this.definition.properties[idName];\n    const idType = idProp && idProp.type;\n    const idDefn = idProp && idProp.defaultFn;\n    if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {\n      deprecated('The model ' + this.modelName + ' is tracking changes, ' +\n        'which requires a string id with GUID/UUID default value.');\n    }\n\n    Model.observe('after save', rectifyOnSave);\n\n    Model.observe('after delete', rectifyOnDelete);\n\n    // Only run if the run time is server\n    // Can switch off cleanup by setting the interval to -1\n    if (runtime.isServer && cleanupInterval > 0) {\n      // initial cleanup\n      cleanup();\n\n      // cleanup\n      setInterval(cleanup, cleanupInterval);\n    }\n\n    function cleanup() {\n      Model.rectifyAllChanges(function(err) {\n        if (err) {\n          Model.handleChangeError(err, 'cleanup');\n        }\n      });\n    }\n  };\n\n  function rectifyOnSave(ctx, next) {\n    const instance = ctx.instance || ctx.currentInstance;\n    const id = instance ? instance.getId() :\n      getIdFromWhereByModelId(ctx.Model, ctx.where);\n\n    if (debug.enabled) {\n      debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),\n        ctx.Model.modelName, id ? id : 'ALL');\n      debug('context instance:%j currentInstance:%j where:%j data %j',\n        ctx.instance, ctx.currentInstance, ctx.where, ctx.data);\n    }\n\n    if (id != null) {\n      ctx.Model.rectifyChange(id, reportErrorAndNext);\n    } else {\n      ctx.Model.rectifyAllChanges(reportErrorAndNext);\n    }\n\n    function reportErrorAndNext(err) {\n      if (err) {\n        ctx.Model.handleChangeError(err, 'after save');\n      }\n      next();\n    }\n  }\n\n  function rectifyOnDelete(ctx, next) {\n    const id = ctx.instance ? ctx.instance.getId() :\n      getIdFromWhereByModelId(ctx.Model, ctx.where);\n\n    if (debug.enabled) {\n      debug('rectifyOnDelete %s -> ' + (id ? 'id %j' : '%s'),\n        ctx.Model.modelName, id ? id : 'ALL');\n      debug('context instance:%j where:%j', ctx.instance, ctx.where);\n    }\n\n    if (id != null) {\n      ctx.Model.rectifyChange(id, reportErrorAndNext);\n    } else {\n      ctx.Model.rectifyAllChanges(reportErrorAndNext);\n    }\n\n    function reportErrorAndNext(err) {\n      if (err) {\n        ctx.Model.handleChangeError(err, 'after delete');\n      }\n      next();\n    }\n  }\n\n  function getIdFromWhereByModelId(Model, where) {\n    const idName = Model.getIdName();\n    if (!(idName in where)) return undefined;\n\n    const id = where[idName];\n    // TODO(bajtos) support object values that are not LB conditions\n    if (typeof id === 'string' || typeof id === 'number') {\n      return id;\n    }\n    return undefined;\n  }\n\n  PersistedModel._defineChangeModel = function() {\n    const BaseChangeModel = this.registry.getModel('Change');\n    assert(BaseChangeModel,\n      'Change model must be defined before enabling change replication');\n\n    const additionalChangeModelProperties =\n      this.settings.additionalChangeModelProperties || {};\n\n    this.Change = BaseChangeModel.extend(\n      this.modelName + '-change',\n      additionalChangeModelProperties,\n      {trackModel: this},\n    );\n\n    if (this.dataSource) {\n      attachRelatedModels(this);\n    }\n\n    // Re-attach related models whenever our datasource is changed.\n    const self = this;\n    this.on('dataSourceAttached', function() {\n      attachRelatedModels(self);\n    });\n\n    return this.Change;\n\n    function attachRelatedModels(self) {\n      self.Change.attachTo(self.dataSource);\n      self.Change.getCheckpointModel().attachTo(self.dataSource);\n    }\n  };\n\n  PersistedModel.rectifyAllChanges = function(callback) {\n    this.getChangeModel().rectifyAll(callback);\n  };\n\n  /**\n   * Handle a change error. Override this method in a subclassing model to customize\n   * change error handling.\n   *\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).\n   */\n\n  PersistedModel.handleChangeError = function(err, operationName) {\n    if (!err) return;\n    this.emit('error', err, operationName);\n  };\n\n  /**\n   * Specify that a change to the model with the given ID has occurred.\n   *\n   * @param {*} id The ID of the model that has changed.\n   * @callback {Function} callback\n   * @param {Error} err\n   */\n\n  PersistedModel.rectifyChange = function(id, callback) {\n    const Change = this.getChangeModel();\n    Change.rectifyModelChanges(this.modelName, [id], callback);\n  };\n\n  PersistedModel.findLastChange = function(id, cb) {\n    const Change = this.getChangeModel();\n    Change.findOne({where: {modelId: id}}, cb);\n  };\n\n  PersistedModel.updateLastChange = function(id, data, cb) {\n    const self = this;\n    this.findLastChange(id, function(err, inst) {\n      if (err) return cb(err);\n      if (!inst) {\n        err = new Error(g.f('No change record found for %s with id %s',\n          self.modelName, id));\n        err.statusCode = 404;\n        return cb(err);\n      }\n\n      inst.updateAttributes(data, cb);\n    });\n  };\n\n  /**\n   * Create a change stream. [See here for more info](http://loopback.io/doc/en/lb2/Realtime-server-sent-events.html)\n   *\n   * @param {Object} options\n   * @param {Object} options.where Only changes to models matching this where filter will be included in the `ChangeStream`.\n   * @callback {Function} callback\n   * @param {Error} err\n   * @param {ChangeStream} changes\n   */\n\n  PersistedModel.createChangeStream = function(options, cb) {\n    if (typeof options === 'function') {\n      cb = options;\n      options = undefined;\n    }\n    cb = cb || utils.createPromiseCallback();\n\n    const idName = this.getIdName();\n    const Model = this;\n    const changes = new PassThrough({objectMode: true});\n\n    changes._destroy = function() {\n      changes.end();\n      changes.emit('end');\n      changes.emit('close');\n    };\n\n    changes.destroy = changes.destroy || changes._destroy; // node 8 compability\n\n    changes.on('error', removeHandlers);\n    changes.on('close', removeHandlers);\n    changes.on('finish', removeHandlers);\n    changes.on('end', removeHandlers);\n\n    process.nextTick(function() {\n      cb(null, changes);\n    });\n\n    Model.observe('after save', changeHandler);\n    Model.observe('after delete', deleteHandler);\n\n    return cb.promise;\n\n    function changeHandler(ctx, next) {\n      const change = createChangeObject(ctx, 'save');\n      if (change) {\n        changes.write(change);\n      }\n\n      next();\n    }\n\n    function deleteHandler(ctx, next) {\n      const change = createChangeObject(ctx, 'delete');\n      if (change) {\n        changes.write(change);\n      }\n\n      next();\n    }\n\n    function createChangeObject(ctx, type) {\n      const where = ctx.where;\n      let data = ctx.instance || ctx.data;\n      const whereId = where && where[idName];\n\n      // the data includes the id\n      // or the where includes the id\n      let target;\n\n      if (data && (data[idName] || data[idName] === 0)) {\n        target = data[idName];\n      } else if (where && (where[idName] || where[idName] === 0)) {\n        target = where[idName];\n      }\n\n      const hasTarget = target === 0 || !!target;\n\n      // apply filtering if options is set\n      if (options) {\n        const filtered = filterNodes([data], options);\n        if (filtered.length !== 1) {\n          return null;\n        }\n        data = filtered[0];\n      }\n\n      const change = {\n        target: target,\n        where: where,\n        data: data,\n      };\n\n      switch (type) {\n        case 'save':\n          if (ctx.isNewInstance === undefined) {\n            change.type = hasTarget ? 'update' : 'create';\n          } else {\n            change.type = ctx.isNewInstance ? 'create' : 'update';\n          }\n\n          break;\n        case 'delete':\n          change.type = 'remove';\n          break;\n      }\n\n      return change;\n    }\n\n    function removeHandlers() {\n      Model.removeObserver('after save', changeHandler);\n      Model.removeObserver('after delete', deleteHandler);\n    }\n  };\n\n  /**\n   * Get the filter for searching related changes.\n   *\n   * Models should override this function to copy properties\n   * from the model instance filter into the change search filter.\n   *\n   * ```js\n   * module.exports = (TargetModel, config) => {\n   *   TargetModel.createChangeFilter = function(since, modelFilter) {\n   *     const filter = this.base.createChangeFilter.apply(this, arguments);\n   *     if (modelFilter && modelFilter.where && modelFilter.where.tenantId) {\n   *       filter.where.tenantId = modelFilter.where.tenantId;\n   *     }\n   *     return filter;\n   *   };\n   * };\n   * ```\n   *\n   * @param {Number} since Return only changes since this checkpoint.\n   * @param {Object} modelFilter Filter describing which model instances to\n   * include in the list of changes.\n   * @returns {Object} The filter object to pass to `Change.find()`. Default:\n   * ```\n   * {where: {checkpoint: {gte: since}, modelName: this.modelName}}\n   * ```\n   */\n  PersistedModel.createChangeFilter = function(since, modelFilter) {\n    return {\n      where: {\n        checkpoint: {gte: since},\n        modelName: this.modelName,\n      },\n    };\n  };\n\n  /**\n   * Add custom data to the Change instance.\n   *\n   * Models should override this function to duplicate model instance properties\n   * to the Change instance properties, typically to allow the changes() method\n   * to filter the changes using these duplicated properties directly while\n   * querying the Change model.\n   *\n   * ```js\n   * module.exports = (TargetModel, config) => {\n   *   TargetModel.prototype.fillCustomChangeProperties = function(change, cb) {\n   *     var inst = this;\n   *     const base = this.constructor.base;\n   *     base.prototype.fillCustomChangeProperties.call(this, change, err => {\n   *       if (err) return cb(err);\n   *\n   *       if (inst && inst.tenantId) {\n   *         change.tenantId = inst.tenantId;\n   *       } else {\n   *         change.tenantId = null;\n   *       }\n   *\n   *       cb();\n   *     });\n   *   };\n   * };\n   * ```\n   *\n   * @callback {Function} callback\n   * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb3/Error-object.html).\n   */\n  PersistedModel.prototype.fillCustomChangeProperties = function(change, cb) {\n    // no-op by default\n    cb();\n  };\n\n  PersistedModel.setup();\n\n  return PersistedModel;\n};\n"
  },
  {
    "path": "lib/registry.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('./globalize');\nconst assert = require('assert');\nconst extend = require('util')._extend;\nconst juggler = require('loopback-datasource-juggler');\nconst debug = require('debug')('loopback:registry');\nconst DataSource = juggler.DataSource;\nconst ModelBuilder = juggler.ModelBuilder;\nconst deprecated = require('depd')('strong-remoting');\n\nmodule.exports = Registry;\n\n/**\n * Define and reference `Models` and `DataSources`.\n *\n * @class\n */\n\nfunction Registry() {\n  this.defaultDataSources = {};\n  this.modelBuilder = new ModelBuilder();\n  require('./model')(this);\n  require('./persisted-model')(this);\n\n  // Set the default model base class.\n  this.modelBuilder.defaultModelBaseClass = this.getModel('Model');\n}\n\n/**\n * Create a named vanilla JavaScript class constructor with an attached\n * set of properties and options.\n *\n * This function comes with two variants:\n *  * `loopback.createModel(name, properties, options)`\n *  * `loopback.createModel(config)`\n *\n * In the second variant, the parameters `name`, `properties` and `options`\n * are provided in the config object. Any additional config entries are\n * interpreted as `options`, i.e. the following two configs are identical:\n *\n * ```js\n * { name: 'Customer', base: 'User' }\n * { name: 'Customer', options: { base: 'User' } }\n * ```\n *\n * **Example**\n *\n * Create an `Author` model using the three-parameter variant:\n *\n * ```js\n * loopback.createModel(\n *   'Author',\n *   {\n *     firstName: 'string',\n *     lastName: 'string'\n *   },\n *   {\n *     relations: {\n *       books: {\n *         model: 'Book',\n *         type: 'hasAndBelongsToMany'\n *       }\n *     }\n *   }\n * );\n * ```\n *\n * Create the same model using a config object:\n *\n * ```js\n * loopback.createModel({\n *   name: 'Author',\n *   properties: {\n *     firstName: 'string',\n *     lastName: 'string'\n *   },\n *   relations: {\n *     books: {\n *       model: 'Book',\n *       type: 'hasAndBelongsToMany'\n *     }\n *   }\n * });\n * ```\n *\n * @param {String} name Unique name.\n * @param {Object} properties\n * @param {Object} options (optional)\n *\n * @header loopback.createModel\n */\n\nRegistry.prototype.createModel = function(name, properties, options) {\n  if (arguments.length === 1 && typeof name === 'object') {\n    const config = name;\n    name = config.name;\n    properties = config.properties;\n    options = buildModelOptionsFromConfig(config);\n\n    assert(typeof name === 'string',\n      'The model-config property `name` must be a string');\n  }\n\n  options = options || {};\n  let BaseModel = options.base || options.super;\n\n  if (typeof BaseModel === 'string') {\n    const baseName = BaseModel;\n    BaseModel = this.findModel(BaseModel);\n    if (!BaseModel) {\n      throw new Error(g.f('Model not found: model `%s` is extending an unknown model `%s`.',\n        name, baseName));\n    }\n  }\n\n  BaseModel = BaseModel || this.getModel('PersistedModel');\n  const model = BaseModel.extend(name, properties, options);\n  model.registry = this;\n\n  this._defineRemoteMethods(model, model.settings.methods);\n\n  return model;\n};\n\nfunction buildModelOptionsFromConfig(config) {\n  const options = extend({}, config.options);\n  for (const key in config) {\n    if (['name', 'properties', 'options'].indexOf(key) !== -1) {\n      // Skip items which have special meaning\n      continue;\n    }\n\n    if (options[key] !== undefined) {\n      // When both `config.key` and `config.options.key` are set,\n      // use the latter one\n      continue;\n    }\n\n    options[key] = config[key];\n  }\n  return options;\n}\n\n/*\n * Add the acl entry to the acls\n * @param {Object[]} acls\n * @param {Object} acl\n */\nfunction addACL(acls, acl) {\n  for (let i = 0, n = acls.length; i < n; i++) {\n    // Check if there is a matching acl to be overriden\n    if (acls[i].property === acl.property &&\n      acls[i].accessType === acl.accessType &&\n      acls[i].principalType === acl.principalType &&\n      acls[i].principalId === acl.principalId) {\n      acls[i] = acl;\n      return;\n    }\n  }\n  acls.push(acl);\n}\n\n/**\n * Alter an existing Model class.\n * @param {Model} ModelCtor The model constructor to alter.\n * @options {Object} config Additional configuration to apply\n * @property {DataSource} dataSource Attach the model to a dataSource.\n * @property {Object} [relations] Model relations to add/update.\n *\n * @header loopback.configureModel(ModelCtor, config)\n */\n\nRegistry.prototype.configureModel = function(ModelCtor, config) {\n  const settings = ModelCtor.settings;\n  const modelName = ModelCtor.modelName;\n\n  ModelCtor.config = config;\n\n  // Relations\n  if (typeof config.relations === 'object' && config.relations !== null) {\n    const relations = settings.relations = settings.relations || {};\n    Object.keys(config.relations).forEach(function(key) {\n      // FIXME: [rfeng] We probably should check if the relation exists\n      relations[key] = extend(relations[key] || {}, config.relations[key]);\n    });\n  } else if (config.relations != null) {\n    g.warn('The relations property of `%s` configuration ' +\n      'must be an object', modelName);\n  }\n\n  // ACLs\n  if (Array.isArray(config.acls)) {\n    const acls = settings.acls = settings.acls || [];\n    config.acls.forEach(function(acl) {\n      addACL(acls, acl);\n    });\n  } else if (config.acls != null) {\n    g.warn('The acls property of `%s` configuration ' +\n      'must be an array of objects', modelName);\n  }\n\n  // Settings\n  const excludedProperties = {\n    base: true,\n    'super': true,\n    relations: true,\n    acls: true,\n    dataSource: true,\n  };\n  if (typeof config.options === 'object' && config.options !== null) {\n    for (const p in config.options) {\n      if (!(p in excludedProperties)) {\n        settings[p] = config.options[p];\n      } else {\n        g.warn('Property `%s` cannot be reconfigured for `%s`',\n          p, modelName);\n      }\n    }\n  } else if (config.options != null) {\n    g.warn('The options property of `%s` configuration ' +\n      'must be an object', modelName);\n  }\n\n  // It's important to attach the datasource after we have updated\n  // configuration, so that the datasource picks up updated relations\n  if (config.dataSource) {\n    assert(config.dataSource instanceof DataSource,\n      'Cannot configure ' + ModelCtor.modelName +\n        ': config.dataSource must be an instance of DataSource');\n    ModelCtor.attachTo(config.dataSource);\n    debug('Attached model `%s` to dataSource `%s`',\n      modelName, config.dataSource.name);\n  } else if (config.dataSource === null || config.dataSource === false) {\n    debug('Model `%s` is not attached to any DataSource by configuration.',\n      modelName);\n  } else {\n    debug('Model `%s` is not attached to any DataSource, possibly by a mistake.',\n      modelName);\n    g.warn(\n      'The configuration of `%s` is missing {{`dataSource`}} property.\\n' +\n      'Use `null` or `false` to mark models not attached to any data source.',\n      modelName,\n    );\n  }\n\n  const newMethodNames = config.methods && Object.keys(config.methods);\n  const hasNewMethods = newMethodNames && newMethodNames.length;\n  const hasDescendants = this.getModelByType(ModelCtor) !== ModelCtor;\n  if (hasNewMethods && hasDescendants) {\n    g.warn(\n      'Child models of `%s` will not inherit newly defined remote methods %s.',\n      modelName, newMethodNames,\n    );\n  }\n\n  // Remote methods\n  this._defineRemoteMethods(ModelCtor, config.methods);\n};\n\nRegistry.prototype._defineRemoteMethods = function(ModelCtor, methods) {\n  if (!methods) return;\n  if (typeof methods !== 'object') {\n    g.warn('Ignoring non-object \"methods\" setting of \"%s\".',\n      ModelCtor.modelName);\n    return;\n  }\n\n  Object.keys(methods).forEach(function(key) {\n    let meta = methods[key];\n    const m = key.match(/^prototype\\.(.*)$/);\n    const isStatic = !m;\n\n    if (typeof meta.isStatic !== 'boolean') {\n      key = isStatic ? key : m[1];\n      meta = Object.assign({}, meta, {isStatic});\n    } else if (meta.isStatic && m) {\n      throw new Error(g.f('Remoting metadata for %s.%s {{\"isStatic\"}} does ' +\n      'not match new method name-based style.', ModelCtor.modelName, key));\n    } else {\n      key = isStatic ? key : m[1];\n      deprecated(g.f('Remoting metadata {{\"isStatic\"}} is deprecated. Please ' +\n      'specify {{\"prototype.name\"}} in method name instead for {{isStatic=false}}.'));\n    }\n    ModelCtor.remoteMethod(key, meta);\n  });\n};\n\n/**\n * Look up a model class by name from all models created by\n * `loopback.createModel()`\n * @param {String|Function} modelOrName The model name or a `Model` constructor.\n * @returns {Model} The model class\n *\n * @header loopback.findModel(modelName)\n */\nRegistry.prototype.findModel = function(modelName) {\n  if (typeof modelName === 'function') return modelName;\n  return this.modelBuilder.models[modelName];\n};\n\n/**\n * Look up a model class by name from all models created by\n * `loopback.createModel()`. **Throw an error when no such model exists.**\n *\n * @param {String} modelOrName The model name or a `Model` constructor.\n * @returns {Model} The model class\n *\n * @header loopback.getModel(modelName)\n */\nRegistry.prototype.getModel = function(modelName) {\n  const model = this.findModel(modelName);\n  if (model) return model;\n\n  throw new Error(g.f('Model not found: %s', modelName));\n};\n\n/**\n * Look up a model class by the base model class.\n * The method can be used by LoopBack\n * to find configured models in models.json over the base model.\n * @param {Model} modelType The base model class\n * @returns {Model} The subclass if found or the base class\n *\n * @header loopback.getModelByType(modelType)\n */\nRegistry.prototype.getModelByType = function(modelType) {\n  const type = typeof modelType;\n  const accepted = ['function', 'string'];\n\n  assert(accepted.indexOf(type) > -1,\n    'The model type must be a constructor or model name');\n\n  if (type === 'string') {\n    modelType = this.getModel(modelType);\n  }\n\n  const models = this.modelBuilder.models;\n  for (const m in models) {\n    if (models[m].prototype instanceof modelType) {\n      return models[m];\n    }\n  }\n  return modelType;\n};\n\n/**\n * Create a data source with passing the provided options to the connector.\n *\n * @param {String} name Optional name.\n * @options {Object} options Data Source options\n * @property {Object} connector LoopBack connector.\n * @property {*} [*] Other&nbsp;connector properties.\n *   See the relevant connector documentation.\n */\n\nRegistry.prototype.createDataSource = function(name, options) {\n  const self = this;\n\n  const ds = new DataSource(name, options, self.modelBuilder);\n  ds.createModel = function(name, properties, settings) {\n    settings = settings || {};\n    let BaseModel = settings.base || settings.super;\n    if (!BaseModel) {\n      // Check the connector types\n      const connectorTypes = ds.getTypes();\n      if (Array.isArray(connectorTypes) && connectorTypes.indexOf('db') !== -1) {\n        // Only set up the base model to PersistedModel if the connector is DB\n        BaseModel = self.PersistedModel;\n      } else {\n        BaseModel = self.Model;\n      }\n      settings.base = BaseModel;\n    }\n    const ModelCtor = self.createModel(name, properties, settings);\n    ModelCtor.attachTo(ds);\n    return ModelCtor;\n  };\n\n  if (ds.settings && ds.settings.defaultForType) {\n    const msg = g.f('{{DataSource}} option {{\"defaultForType\"}} is no longer supported');\n    throw new Error(msg);\n  }\n\n  return ds;\n};\n\n/**\n * Get an in-memory data source. Use one if it already exists.\n *\n * @param {String} [name] The name of the data source.\n * If not provided, the `'default'` is used.\n */\n\nRegistry.prototype.memory = function(name) {\n  name = name || 'default';\n  let memory = (\n    this._memoryDataSources || (this._memoryDataSources = {})\n  )[name];\n\n  if (!memory) {\n    memory = this._memoryDataSources[name] = this.createDataSource({\n      connector: 'memory',\n    });\n  }\n\n  return memory;\n};\n"
  },
  {
    "path": "lib/runtime.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*\n * This is an internal file that should not be used outside of loopback.\n * All exported entities can be accessed via the `loopback` object.\n * @private\n */\n\n'use strict';\nconst runtime = exports;\n\n/**\n * True if running in a browser environment; false otherwise.\n * @header loopback.isBrowser\n */\n\nruntime.isBrowser = typeof window !== 'undefined';\n\n/**\n * True if running in a server environment; false otherwise.\n * @header loopback.isServer\n */\n\nruntime.isServer = !runtime.isBrowser;\n"
  },
  {
    "path": "lib/server-app.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('./globalize');\nconst assert = require('assert');\nconst express = require('express');\nconst merge = require('util')._extend;\nconst mergePhaseNameLists = require('loopback-phase').mergePhaseNameLists;\nconst debug = require('debug')('loopback:app');\nconst stableSortInPlace = require('stable').inplace;\n\nconst BUILTIN_MIDDLEWARE = {builtin: true};\n\nconst proto = {};\n\nmodule.exports = function loopbackExpress() {\n  const app = express();\n  app.__expressLazyRouter = app.lazyrouter;\n  merge(app, proto);\n  return app;\n};\n\n/**\n * Register a middleware using a factory function and a JSON config.\n *\n * **Example**\n *\n * ```js\n * app.middlewareFromConfig(compression, {\n *   enabled: true,\n *   phase: 'initial',\n *   params: {\n *     threshold: 128\n *   }\n * });\n * ```\n *\n * @param {function} factory The factory function creating a middleware handler.\n *   Typically a result of `require()` call, e.g. `require('compression')`.\n * @options {Object} config The configuration.\n * @property {String} phase The phase to register the middleware in.\n * @property {Boolean} [enabled] Whether the middleware is enabled.\n *   Default: `true`.\n * @property {Array|*} [params] The arguments to pass to the factory\n *   function. Either an array of arguments,\n *   or the value of the first argument when the factory expects\n *   a single argument only.\n * @property {Array|string|RegExp} [paths] Optional list of paths limiting\n *   the scope of the middleware.\n *\n * @returns {object} this (fluent API)\n *\n * @header app.middlewareFromConfig(factory, config)\n */\nproto.middlewareFromConfig = function(factory, config) {\n  assert(typeof factory === 'function', '\"factory\" must be a function');\n  assert(typeof config === 'object', '\"config\" must be an object');\n  assert(typeof config.phase === 'string' && config.phase,\n    '\"config.phase\" must be a non-empty string');\n\n  if (config.enabled === false)\n    return;\n\n  let params = config.params;\n  if (params === undefined) {\n    params = [];\n  } else if (!Array.isArray(params)) {\n    params = [params];\n  }\n\n  let handler = factory.apply(null, params);\n\n  // Check if methods/verbs filter exists\n  let verbs = config.methods || config.verbs;\n  if (Array.isArray(verbs)) {\n    verbs = verbs.map(function(verb) {\n      return verb && verb.toUpperCase();\n    });\n    if (verbs.indexOf('ALL') === -1) {\n      const originalHandler = handler;\n      if (handler.length <= 3) {\n        // Regular handler\n        handler = function(req, res, next) {\n          if (verbs.indexOf(req.method.toUpperCase()) === -1) {\n            return next();\n          }\n          originalHandler(req, res, next);\n        };\n      } else {\n        // Error handler\n        handler = function(err, req, res, next) {\n          if (verbs.indexOf(req.method.toUpperCase()) === -1) {\n            return next(err);\n          }\n          originalHandler(err, req, res, next);\n        };\n      }\n    }\n  }\n  this.middleware(config.phase, config.paths || [], handler);\n\n  return this;\n};\n\n/**\n * Register (new) middleware phases.\n *\n * If all names are new, then the phases are added just before \"routes\" phase.\n * Otherwise the provided list of names is merged with the existing phases\n * in such way that the order of phases is preserved.\n *\n * **Examples**\n *\n * ```js\n * // built-in phases:\n * // initial, session, auth, parse, routes, files, final\n *\n * app.defineMiddlewarePhases('custom');\n * // new list of phases\n * // initial, session, auth, parse, custom, routes, files, final\n *\n * app.defineMiddlewarePhases([\n *   'initial', 'postinit', 'preauth', 'routes', 'subapps'\n * ]);\n * // new list of phases\n * // initial, postinit, preauth, session, auth, parse, custom,\n * // routes, subapps, files, final\n * ```\n *\n * @param {string|Array.<string>} nameOrArray A phase name or a list of phase\n *   names to add.\n *\n * @returns {object} this (fluent API)\n *\n * @header app.defineMiddlewarePhases(nameOrArray)\n */\nproto.defineMiddlewarePhases = function(nameOrArray) {\n  this.lazyrouter();\n\n  if (Array.isArray(nameOrArray)) {\n    this._requestHandlingPhases =\n      mergePhaseNameLists(this._requestHandlingPhases, nameOrArray);\n  } else {\n    // add the new phase before 'routes'\n    const routesIx = this._requestHandlingPhases.indexOf('routes');\n    this._requestHandlingPhases.splice(routesIx - 1, 0, nameOrArray);\n  }\n\n  return this;\n};\n\n/**\n * Register a middleware handler to be executed in a given phase.\n * @param {string} name The phase name, e.g. \"init\" or \"routes\".\n * @param {Array|string|RegExp} [paths] Optional list of paths limiting\n *   the scope of the middleware.\n *   String paths are interpreted as expressjs path patterns,\n *   regular expressions are used as-is.\n * @param {function} handler The middleware handler, one of\n *   `function(req, res, next)` or\n *   `function(err, req, res, next)`\n * @returns {object} this (fluent API)\n *\n * @header app.middleware(name, handler)\n */\nproto.middleware = function(name, paths, handler) {\n  this.lazyrouter();\n\n  if (handler === undefined && typeof paths === 'function') {\n    handler = paths;\n    paths = undefined;\n  }\n\n  assert(typeof name === 'string' && name, '\"name\" must be a non-empty string');\n  assert(typeof handler === 'function', '\"handler\" must be a function');\n\n  if (paths === undefined) {\n    paths = '/';\n  }\n\n  const fullPhaseName = name;\n  const handlerName = handler.name || '<anonymous>';\n\n  const m = name.match(/^(.+):(before|after)$/);\n  if (m) {\n    name = m[1];\n  }\n\n  if (this._requestHandlingPhases.indexOf(name) === -1)\n    throw new Error(g.f('Unknown {{middleware}} phase %s', name));\n\n  debug('use %s %s %s', fullPhaseName, paths, handlerName);\n\n  this._skipLayerSorting = true;\n  this.use(paths, handler);\n\n  const layer = this._findLayerByHandler(handler);\n  if (layer) {\n    // Set the phase name for sorting\n    layer.phase = fullPhaseName;\n  } else {\n    debug('No matching layer is found for %s %s', fullPhaseName, handlerName);\n  }\n\n  this._skipLayerSorting = false;\n\n  this._sortLayersByPhase();\n\n  return this;\n};\n\n/*!\n * Find the corresponding express layer by handler\n *\n * This is needed because monitoring agents such as NewRelic can add handlers\n * to the stack. For example, NewRelic adds sentinel handler. We need to search\n * the stackto find the correct layer.\n */\nproto._findLayerByHandler = function(handler) {\n  // Other handlers can be added to the stack, for example,\n  // NewRelic adds sentinel handler, and AppDynamics adds\n  // some additional proxy info. We need to search the stack\n  for (let k = this._router.stack.length - 1; k >= 0; k--) {\n    const isOriginal = this._router.stack[k].handle === handler;\n    const isNewRelic = this._router.stack[k].handle['__NR_original'] === handler;\n    const isAppDynamics = this._router.stack[k].handle['__appdynamicsProxyInfo__'] &&\n      this._router.stack[k].handle['__appdynamicsProxyInfo__']['orig'] === handler;\n\n    if (isOriginal || isNewRelic || isAppDynamics) {\n      return this._router.stack[k];\n    } else {\n      // Aggressively check if the original handler has been wrapped\n      // into a new function with a property pointing to the original handler\n      for (const p in this._router.stack[k].handle) {\n        if (this._router.stack[k].handle[p] === handler) {\n          return this._router.stack[k];\n        }\n      }\n    }\n  }\n  return null;\n};\n\n// Install our custom PhaseList-based handler into the app\nproto.lazyrouter = function() {\n  const self = this;\n  if (self._router) return;\n\n  self.__expressLazyRouter();\n\n  const router = self._router;\n\n  // Mark all middleware added by Router ctor as builtin\n  // The sorting algo will keep them at beginning of the list\n  router.stack.forEach(function(layer) {\n    layer.phase = BUILTIN_MIDDLEWARE;\n  });\n\n  router.__expressUse = router.use;\n  router.use = function useAndSort() {\n    const retval = this.__expressUse.apply(this, arguments);\n    self._sortLayersByPhase();\n    return retval;\n  };\n\n  router.__expressRoute = router.route;\n  router.route = function routeAndSort() {\n    const retval = this.__expressRoute.apply(this, arguments);\n    self._sortLayersByPhase();\n    return retval;\n  };\n\n  self._requestHandlingPhases = [\n    'initial', 'session', 'auth', 'parse',\n    'routes', 'files', 'final',\n  ];\n};\n\nproto._sortLayersByPhase = function() {\n  if (this._skipLayerSorting) return;\n\n  const phaseOrder = {};\n  this._requestHandlingPhases.forEach(function(name, ix) {\n    phaseOrder[name + ':before'] = ix * 3;\n    phaseOrder[name] = ix * 3 + 1;\n    phaseOrder[name + ':after'] = ix * 3 + 2;\n  });\n\n  const router = this._router;\n  stableSortInPlace(router.stack, compareLayers);\n\n  function compareLayers(left, right) {\n    const leftPhase = left.phase;\n    const rightPhase = right.phase;\n\n    if (leftPhase === rightPhase) return 0;\n\n    // Builtin middleware is always first\n    if (leftPhase === BUILTIN_MIDDLEWARE) return -1;\n    if (rightPhase === BUILTIN_MIDDLEWARE) return 1;\n\n    // Layers registered via app.use and app.route\n    // are executed as the first items in `routes` phase\n    if (leftPhase === undefined) {\n      if (rightPhase === 'routes')\n        return -1;\n\n      return phaseOrder['routes'] - phaseOrder[rightPhase];\n    }\n\n    if (rightPhase === undefined)\n      return -compareLayers(right, left);\n\n    // Layers registered via `app.middleware` are compared via phase & hook\n    return phaseOrder[leftPhase] - phaseOrder[rightPhase];\n  }\n};\n"
  },
  {
    "path": "lib/utils.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nexports.createPromiseCallback = createPromiseCallback;\nexports.uploadInChunks = uploadInChunks;\nexports.downloadInChunks = downloadInChunks;\nexports.concatResults = concatResults;\n\nconst Promise = require('bluebird');\nconst async = require('async');\n\nfunction createPromiseCallback() {\n  let cb;\n  const promise = new Promise(function(resolve, reject) {\n    cb = function(err, data) {\n      if (err) return reject(err);\n      return resolve(data);\n    };\n  });\n  cb.promise = promise;\n  return cb;\n}\n\nfunction throwPromiseNotDefined() {\n  throw new Error(\n    'Your Node runtime does support ES6 Promises. ' +\n    'Set \"global.Promise\" to your preferred implementation of promises.',\n  );\n}\n\n/**\n * Divide an async call with large array into multiple calls using smaller chunks\n * @param {Array} largeArray - the large array to be chunked\n * @param {Number} chunkSize - size of each chunks\n * @param {Function} processFunction - the function to be called multiple times\n * @param {Function} cb - the callback\n */\nfunction uploadInChunks(largeArray, chunkSize, processFunction, cb) {\n  const chunkArrays = [];\n\n  if (!chunkSize || chunkSize < 1 || largeArray.length <= chunkSize) {\n    // if chunking not required\n    processFunction(largeArray, cb);\n  } else {\n    // copying so that the largeArray object does not get affected during splice\n    const copyOfLargeArray = [].concat(largeArray);\n\n    // chunking to smaller arrays\n    while (copyOfLargeArray.length > 0) {\n      chunkArrays.push(copyOfLargeArray.splice(0, chunkSize));\n    }\n\n    const tasks = chunkArrays.map(function(chunkArray) {\n      return function(previousResults, chunkCallback) {\n        const lastArg = arguments[arguments.length - 1];\n\n        if (typeof lastArg === 'function') {\n          chunkCallback = lastArg;\n        }\n\n        processFunction(chunkArray, function(err, results) {\n          if (err) {\n            return chunkCallback(err);\n          }\n\n          // if this is the first async waterfall call or if previous results was not defined\n          if (typeof previousResults === 'function' || typeof previousResults === 'undefined' ||\n            previousResults === null) {\n            previousResults = results;\n          } else if (results) {\n            previousResults = concatResults(previousResults, results);\n          }\n\n          chunkCallback(err, previousResults);\n        });\n      };\n    });\n\n    async.waterfall(tasks, cb);\n  }\n}\n\n/**\n * Page async download calls\n * @param {Object} filter - filter object used for the async call\n * @param {Number} chunkSize - size of each chunks\n * @param {Function} processFunction - the function to be called multiple times\n * @param {Function} cb - the callback\n */\nfunction downloadInChunks(filter, chunkSize, processFunction, cb) {\n  let results = [];\n  filter = filter ? JSON.parse(JSON.stringify(filter)) : {};\n\n  if (!chunkSize || chunkSize < 1) {\n    // if chunking not required\n    processFunction(filter, cb);\n  } else {\n    filter.skip = 0;\n    filter.limit = chunkSize;\n\n    processFunction(JSON.parse(JSON.stringify(filter)), pageAndConcatResults);\n  }\n\n  function pageAndConcatResults(err, pagedResults) {\n    if (err) {\n      return cb(err);\n    } else {\n      results = concatResults(results, pagedResults);\n      if (pagedResults.length >= chunkSize) {\n        filter.skip += pagedResults.length;\n        processFunction(JSON.parse(JSON.stringify(filter)), pageAndConcatResults);\n      } else {\n        cb(null, results);\n      }\n    }\n  }\n}\n\n/**\n * Concat current results into previous results\n * Assumption made here that the previous results and current results are homogeneous\n * @param {Object|Array} previousResults\n * @param {Object|Array} currentResults\n */\nfunction concatResults(previousResults, currentResults) {\n  if (Array.isArray(currentResults)) {\n    previousResults = previousResults.concat(currentResults);\n  } else if (typeof currentResults === 'object') {\n    Object.keys(currentResults).forEach(function(key) {\n      previousResults[key] = concatResults(previousResults[key], currentResults[key]);\n    });\n  } else {\n    previousResults = currentResults;\n  }\n\n  return previousResults;\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"loopback\",\n  \"version\": \"3.28.0\",\n  \"description\": \"LoopBack: Open Source Framework for Node.js\",\n  \"homepage\": \"http://loopback.io\",\n  \"keywords\": [\n    \"web\",\n    \"restful\",\n    \"rest\",\n    \"api\",\n    \"express\",\n    \"restify\",\n    \"koa\",\n    \"auth\",\n    \"security\",\n    \"oracle\",\n    \"mysql\",\n    \"nosql\",\n    \"mongo\",\n    \"mongodb\",\n    \"sqlserver\",\n    \"mssql\",\n    \"postgres\",\n    \"postgresql\",\n    \"soap\",\n    \"StrongLoop\",\n    \"framework\",\n    \"mobile\",\n    \"mBaaS\"\n  ],\n  \"scripts\": {\n    \"lint\": \"grunt eslint\",\n    \"coverage\": \"nyc report --reporter=text-lcov | coveralls\",\n    \"test\": \"nyc grunt mocha-and-karma\"\n  },\n  \"engines\": {\n    \"node\": \">=8\"\n  },\n  \"dependencies\": {\n    \"async\": \"^2.0.1\",\n    \"bcryptjs\": \"^2.1.0\",\n    \"bluebird\": \"^3.1.1\",\n    \"body-parser\": \"^1.12.0\",\n    \"canonical-json\": \"0.0.4\",\n    \"debug\": \"^2.1.2\",\n    \"depd\": \"^1.0.0\",\n    \"ejs\": \"^2.3.1\",\n    \"express\": \"^4.14.0\",\n    \"inflection\": \"^1.6.0\",\n    \"isemail\": \"^3.2.0\",\n    \"loopback-connector-remote\": \"^3.0.0\",\n    \"loopback-datasource-juggler\": \"^3.28.0\",\n    \"loopback-filters\": \"^1.0.0\",\n    \"loopback-phase\": \"^3.0.0\",\n    \"nodemailer\": \"^6.4.16\",\n    \"nodemailer-direct-transport\": \"^3.3.2\",\n    \"nodemailer-stub-transport\": \"^1.1.0\",\n    \"serve-favicon\": \"^2.2.0\",\n    \"stable\": \"^0.1.5\",\n    \"strong-globalize\": \"^4.1.1\",\n    \"strong-remoting\": \"^3.11.0\",\n    \"uid2\": \"0.0.3\",\n    \"underscore.string\": \"^3.3.5\"\n  },\n  \"devDependencies\": {\n    \"browserify\": \"^16.5.0\",\n    \"chai\": \"^4.2.0\",\n    \"cookie-parser\": \"^1.3.4\",\n    \"coveralls\": \"^3.0.2\",\n    \"dirty-chai\": \"^2.0.1\",\n    \"eslint\": \"^6.5.1\",\n    \"eslint-config-loopback\": \"^13.1.0\",\n    \"express-session\": \"^1.14.0\",\n    \"grunt\": \"^1.0.1\",\n    \"grunt-browserify\": \"^5.0.0\",\n    \"grunt-cli\": \"^1.2.0\",\n    \"grunt-contrib-uglify\": \"^4.0.1\",\n    \"grunt-contrib-watch\": \"^1.0.0\",\n    \"grunt-eslint\": \"^22.0.0\",\n    \"grunt-karma\": \"^3.0.2\",\n    \"grunt-mocha-test\": \"^0.13.3\",\n    \"is-docker\": \"^2.0.0\",\n    \"karma\": \"^4.1.0\",\n    \"karma-browserify\": \"^6.0.0\",\n    \"karma-chrome-launcher\": \"^3.1.0\",\n    \"karma-es6-shim\": \"^1.0.0\",\n    \"karma-firefox-launcher\": \"^1.0.0\",\n    \"karma-html2js-preprocessor\": \"^1.0.0\",\n    \"karma-junit-reporter\": \"^1.2.0\",\n    \"karma-mocha\": \"^1.1.1\",\n    \"karma-script-launcher\": \"^1.0.0\",\n    \"loopback-boot\": \"^2.7.0\",\n    \"loopback-context\": \"^1.0.0\",\n    \"mocha\": \"^6.2.1\",\n    \"nyc\": \"^14.1.1\",\n    \"sinon\": \"^7.5.0\",\n    \"sinon-chai\": \"^3.2.0\",\n    \"strong-error-handler\": \"^3.0.0\",\n    \"strong-task-emitter\": \"^0.0.8\",\n    \"supertest\": \"^4.0.2\",\n    \"which\": \"^2.0.1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/strongloop/loopback\"\n  },\n  \"browser\": {\n    \"express\": \"./lib/browser-express.js\",\n    \"./lib/server-app.js\": \"./lib/browser-express.js\",\n    \"connect\": false,\n    \"nodemailer\": false,\n    \"supertest\": false,\n    \"depd\": \"loopback-datasource-juggler/lib/browser.depd.js\",\n    \"bcrypt\": false\n  },\n  \"config\": {\n    \"ci\": {\n      \"debug\": \"*,-mocha:*,-eslint:*\"\n    }\n  },\n  \"copyright.owner\": \"IBM Corp.\",\n  \"license\": \"MIT\",\n  \"author\": \"IBM Corp.\",\n  \"ci\": {\n    \"downstreamIgnoreList\": [\n      \"bluemix-service-broker\",\n      \"gateway-director-bluemix\",\n      \"plan-manager\"\n    ]\n  }\n}\n"
  },
  {
    "path": "server/middleware/context.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst g = require('../../lib/globalize');\n\nmodule.exports = function() {\n  throw new Error(g.f(\n    '%s middleware was removed in version 3.0. See %s for more details.',\n    'loopback#context',\n    'http://loopback.io/doc/en/lb2/Using-current-context.html',\n  ));\n};\n"
  },
  {
    "path": "server/middleware/error-handler.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(options) {\n  throw new Error('loopback.errorHandler is no longer available.' +\n  ' Please use the module \"strong-error-handler\" instead.');\n};\n"
  },
  {
    "path": "server/middleware/favicon.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst favicon = require('serve-favicon');\nconst path = require('path');\n\n/**\n * Serve the LoopBack favicon.\n * @header loopback.favicon()\n */\nmodule.exports = function(icon, options) {\n  icon = icon || path.join(__dirname, '../../favicon.ico');\n  return favicon(icon, options);\n};\n"
  },
  {
    "path": "server/middleware/rest.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Module dependencies.\n */\n\n'use strict';\nconst g = require('../../lib/globalize');\nconst loopback = require('../../lib/loopback');\nconst async = require('async');\n\n/*!\n * Export the middleware.\n */\n\nmodule.exports = rest;\n\n/**\n * Expose models over REST.\n *\n * For example:\n * ```js\n * app.use(loopback.rest());\n * ```\n * For more information, see [Exposing models over a REST API](http://loopback.io/doc/en/lb2/Exposing-models-over-REST.html).\n * @header loopback.rest()\n */\n\nfunction rest() {\n  let handlers; // Cached handlers\n\n  return function restApiHandler(req, res, next) {\n    const app = req.app;\n    const registry = app.registry;\n\n    if (!handlers) {\n      handlers = [];\n      const remotingOptions = app.get('remoting') || {};\n\n      const contextOptions = remotingOptions.context;\n      if (contextOptions !== undefined && contextOptions !== false) {\n        throw new Error(g.f(\n          '%s was removed in version 3.0. See %s for more details.',\n          'remoting.context option',\n          'http://loopback.io/doc/en/lb2/Using-current-context.html',\n        ));\n      }\n\n      if (app.isAuthEnabled) {\n        const AccessToken = registry.getModelByType('AccessToken');\n        handlers.push(loopback.token({model: AccessToken, app: app}));\n      }\n\n      handlers.push(function(req, res, next) {\n        // Need to get an instance of the REST handler per request\n        return app.handler('rest')(req, res, next);\n      });\n    }\n    if (handlers.length === 1) {\n      return handlers[0](req, res, next);\n    }\n    async.eachSeries(handlers, function(handler, done) {\n      handler(req, res, done);\n    }, next);\n  };\n}\n"
  },
  {
    "path": "server/middleware/static.js",
    "content": "// Copyright IBM Corp. 2014,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/**\n * Serve static assets of a LoopBack application.\n *\n * @param {string} root The root directory from which the static assets are to\n * be served.\n * @param {object} options Refer to\n *   [express documentation](http://expressjs.com/4x/api.html#express.static)\n *   for the full list of available options.\n * @header loopback.static(root, [options])\n */\n'use strict';\nmodule.exports = require('express').static;\n"
  },
  {
    "path": "server/middleware/status.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Export the middleware.\n */\n\n'use strict';\nmodule.exports = status;\n\n/**\n * Return [HTTP response](http://expressjs.com/4x/api.html#res.send) with basic application status information:\n * date the application was started and uptime, in JSON format.\n * For example:\n * ```js\n * {\n *  \"started\": \"2014-06-05T00:26:49.750Z\",\n *  \"uptime\": 9.394\n * }\n * ```\n *\n * @header loopback.status()\n */\nfunction status() {\n  const started = new Date();\n\n  return function(req, res) {\n    res.send({\n      started: started,\n      uptime: (Date.now() - Number(started)) / 1000,\n    });\n  };\n}\n"
  },
  {
    "path": "server/middleware/token.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Module dependencies.\n */\n\n'use strict';\nconst g = require('../../lib/globalize');\nconst loopback = require('../../lib/loopback');\nconst assert = require('assert');\nconst debug = require('debug')('loopback:middleware:token');\n\n/*!\n * Export the middleware.\n */\n\nmodule.exports = token;\n\n/*\n * Rewrite the url to replace current user literal with the logged in user id\n */\nfunction rewriteUserLiteral(req, currentUserLiteral, next) {\n  if (!currentUserLiteral) return next();\n  const literalRegExp = new RegExp('/' + currentUserLiteral + '(/|$|\\\\?)', 'g');\n\n  if (req.accessToken && req.accessToken.userId) {\n    // Replace /me/ with /current-user-id/\n    const urlBeforeRewrite = req.url;\n    req.url = req.url.replace(literalRegExp,\n      '/' + req.accessToken.userId + '$1');\n\n    if (req.url !== urlBeforeRewrite) {\n      debug('req.url has been rewritten from %s to %s', urlBeforeRewrite,\n        req.url);\n    }\n  } else if (!req.accessToken && literalRegExp.test(req.url)) {\n    debug(\n      'URL %s matches current-user literal %s,' +\n        ' but no (valid) access token was provided.',\n      req.url, currentUserLiteral,\n    );\n\n    const e = new Error(g.f('Authorization Required'));\n    e.status = e.statusCode = 401;\n    e.code = 'AUTHORIZATION_REQUIRED';\n    return next(e);\n  }\n\n  next();\n}\n\nfunction escapeRegExp(str) {\n  return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Check for an access token in cookies, headers, and query string parameters.\n * This function always checks for the following:\n *\n * - `access_token` (params only)\n * - `X-Access-Token` (headers only)\n * - `authorization` (headers and cookies)\n *\n * It checks for these values in cookies, headers, and query string parameters _in addition_ to the items\n * specified in the options parameter.\n *\n * **NOTE:** This function only checks for [signed cookies](http://expressjs.com/api.html#req.signedCookies).\n *\n * The following example illustrates how to check for an `accessToken` in a custom cookie, query string parameter\n * and header called `foo-auth`.\n *\n * ```js\n * app.use(loopback.token({\n *   cookies: ['foo-auth'],\n *   headers: ['foo-auth', 'X-Foo-Auth'],\n *   params: ['foo-auth', 'foo_auth']\n * }));\n * ```\n *\n * @options {Object} [options] Each option array is used to add additional keys to find an `accessToken` for a `request`.\n * @property {Array} [cookies] Array of cookie names.\n * @property {Array} [headers] Array of header names.\n * @property {Array} [params] Array of param names.\n * @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request\n * @property {Boolean} [enableDoublecheck] Execute middleware although an instance mounted earlier in the chain didn't find a token\n * @property {Boolean} [overwriteExistingToken] only has effect in combination with `enableDoublecheck`. If truthy, will allow to overwrite an existing accessToken.\n * @property {Function|String} [model] AccessToken model name or class to use.\n * @property {String} [currentUserLiteral] String literal for the current user.\n * @property {Boolean} [bearerTokenBase64Encoded] Defaults to `true`. For `Bearer` token based `Authorization` headers,\n * decode the value from `Base64`. If set to `false`, the decoding will be skipped and the token id will be the raw value\n * parsed from the header.\n * @header loopback.token([options])\n */\n\nfunction token(options) {\n  options = options || {};\n  let TokenModel;\n\n  let currentUserLiteral = options.currentUserLiteral;\n  if (currentUserLiteral && (typeof currentUserLiteral !== 'string')) {\n    debug('Set currentUserLiteral to \\'me\\' as the value is not a string.');\n    currentUserLiteral = 'me';\n  }\n  if (typeof currentUserLiteral === 'string') {\n    currentUserLiteral = escapeRegExp(currentUserLiteral);\n  }\n\n  if (options.bearerTokenBase64Encoded === undefined) {\n    options.bearerTokenBase64Encoded = true;\n  }\n  const enableDoublecheck = !!options.enableDoublecheck;\n  const overwriteExistingToken = !!options.overwriteExistingToken;\n\n  return function(req, res, next) {\n    const app = req.app;\n    const registry = app.registry;\n    if (!TokenModel) {\n      TokenModel = registry.getModel(options.model || 'AccessToken');\n    }\n\n    assert(typeof TokenModel === 'function',\n      'loopback.token() middleware requires a AccessToken model');\n\n    if (req.accessToken !== undefined) {\n      if (!enableDoublecheck) {\n        // req.accessToken is defined already (might also be \"null\" or \"false\") and enableDoublecheck\n        // has not been set --> skip searching for credentials\n        return rewriteUserLiteral(req, currentUserLiteral, next);\n      }\n      if (req.accessToken && req.accessToken.id && !overwriteExistingToken) {\n        // req.accessToken.id is defined, which means that some other middleware has identified a valid user.\n        // when overwriteExistingToken is not set to a truthy value, skip searching for credentials.\n        return rewriteUserLiteral(req, currentUserLiteral, next);\n      }\n      // continue normal operation (as if req.accessToken was undefined)\n    }\n\n    TokenModel.findForRequest(req, options, function(err, token) {\n      req.accessToken = token || null;\n\n      const ctx = req.loopbackContext;\n      if (ctx && ctx.active) ctx.set('accessToken', token);\n\n      if (err) return next(err);\n      rewriteUserLiteral(req, currentUserLiteral, next);\n    });\n  };\n}\n"
  },
  {
    "path": "server/middleware/url-not-found.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n/*!\n * Export the middleware.\n * See discussion in Connect pull request #954 for more details\n * https://github.com/senchalabs/connect/pull/954.\n */\n'use strict';\nmodule.exports = urlNotFound;\n\n/**\n * Convert any request not handled so far to a 404 error\n * to be handled by error-handling middleware.\n * @header loopback.urlNotFound()\n */\nfunction urlNotFound() {\n  return function raiseUrlNotFoundError(req, res, next) {\n    const error = new Error('Cannot ' + req.method + ' ' + req.url);\n    error.status = 404;\n    next(error);\n  };\n}\n"
  },
  {
    "path": "templates/reset-form.ejs",
    "content": "<form>\n  \n</form>\n"
  },
  {
    "path": "templates/verify.ejs",
    "content": "<h1>Thank You</h1>\n\n<p>\n  Thanks for registering. Please follow the link below to complete your registration.\n</p>\n\n<p>\n  <a href=\"<%= verifyHref %>\"><%= verifyHref %></a>\n</p>"
  },
  {
    "path": "test/access-control.integration.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../');\nconst lt = require('./helpers/loopback-testing-helper');\nconst path = require('path');\nconst ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control');\nconst app = require(path.join(ACCESS_CONTROL_APP, 'server/server.js'));\nconst assert = require('assert');\nconst USER = {email: 'test@test.test', password: 'test'};\nconst CURRENT_USER = {email: 'current@test.test', password: 'test'};\nconst debug = require('debug')('loopback:test:access-control.integration');\n\ndescribe('access control - integration', function() {\n  lt.beforeEach.withApp(app);\n\n  /*\n  describe('accessToken', function() {\n    // it('should be a sublcass of AccessToken', function() {\n    //   assert(app.models.accessToken.prototype instanceof loopback.AccessToken);\n    // });\n\n    it('should have a validate method', function() {\n      var token = new app.models.accessToken;\n      assert.equal(typeof token.validate, 'function');\n    });\n  });\n\n  describe('/accessToken', function() {\n\n    lt.beforeEach.givenModel('accessToken', {}, 'randomToken');\n\n    lt.it.shouldBeAllowedWhenCalledAnonymously('POST', '/api/accessTokens');\n    lt.it.shouldBeAllowedWhenCalledUnauthenticated('POST', '/api/accessTokens');\n    lt.it.shouldBeAllowedWhenCalledByUser(USER, 'POST', '/api/accessTokens');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accessTokens');\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accessTokens');\n    lt.it.shouldBeDeniedWhenCalledByUser(USER, 'GET', '/api/accessTokens');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', '/api/accessTokens');\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', '/api/accessTokens');\n    lt.it.shouldBeDeniedWhenCalledByUser(USER, 'PUT', '/api/accessTokens');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForToken);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForToken);\n    lt.it.shouldBeDeniedWhenCalledByUser(USER, 'GET', urlForToken);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForToken);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForToken);\n    lt.it.shouldBeDeniedWhenCalledByUser(USER, 'PUT', urlForToken);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForToken);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForToken);\n    lt.it.shouldBeDeniedWhenCalledByUser(USER, 'DELETE', urlForToken);\n\n    function urlForToken() {\n      return '/api/accessTokens/' + this.randomToken.id;\n    }\n  });\n  */\n\n  describe('/users', function() {\n    lt.beforeEach.givenModel('user', USER, 'randomUser');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/users');\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/users');\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/users');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForUser);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForUser);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForUser);\n\n    lt.it.shouldBeAllowedWhenCalledAnonymously(\n      'POST', '/api/users', newUserData(),\n    );\n\n    lt.it.shouldBeAllowedWhenCalledByUser(\n      CURRENT_USER, 'POST', '/api/users', newUserData(),\n    );\n\n    lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/logout');\n\n    lt.describe.whenCalledRemotely('DELETE', '/api/users', function() {\n      lt.it.shouldNotBeFound();\n    });\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForUser);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForUser);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForUser);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForUser);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForUser);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForUser);\n\n    lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {\n      beforeEach(function() {\n        this.url = '/api/users/' + this.user.id + '?ok';\n      });\n      lt.describe.whenCalledRemotely('DELETE', '/api/users/:id', function() {\n        lt.it.shouldBeAllowed();\n      });\n      lt.describe.whenCalledRemotely('GET', '/api/users/:id', function() {\n        lt.it.shouldBeAllowed();\n        it('should not include a password', function() {\n          debug('GET /api/users/:id response: %s\\nheaders: %j\\nbody string: %s',\n            this.res.statusCode,\n            this.res.headers,\n            this.res.text);\n          const user = this.res.body;\n          assert.equal(user.password, undefined);\n        });\n      });\n\n      // user has replaceOnPUT = false; so then both PUT and PATCH should be allowed for update\n      lt.describe.whenCalledRemotely('PUT', '/api/users/:id', function() {\n        lt.it.shouldBeAllowed();\n      });\n\n      lt.describe.whenCalledRemotely('PATCH', '/api/users/:id', function() {\n        lt.it.shouldBeAllowed();\n      });\n    });\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/users/upsertWithWhere');\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/users/upsertWithWhere');\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/upsertWithWhere');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser);\n\n    function urlForUser() {\n      return '/api/users/' + this.randomUser.id;\n    }\n\n    var userCounter; // eslint-disable-line no-var\n    function newUserData() {\n      userCounter = userCounter ? ++userCounter : 1;\n\n      return {\n        email: 'new-' + userCounter + '@test.test',\n        password: 'test',\n      };\n    }\n  });\n\n  describe('/banks', function() {\n    const SPECIAL_USER = {email: 'special@test.test', password: 'test'};\n\n    // define dynamic role that would only grant access when the authenticated user's email is equal to\n    // SPECIAL_USER's email\n\n    before(function() {\n      const roleModel = app.registry.getModel('Role');\n      const userModel = app.registry.getModel('user');\n\n      roleModel.registerResolver('$dynamic-role', function(role, context, callback) {\n        if (!(context && context.accessToken && context.accessToken.userId)) {\n          return process.nextTick(function() {\n            if (callback) callback(null, false);\n          });\n        }\n        const accessToken = context.accessToken;\n        userModel.findById(accessToken.userId, function(err, user) {\n          if (err) {\n            return callback(err, false);\n          }\n          if (user && user.email === SPECIAL_USER.email) {\n            return callback(null, true);\n          }\n          return callback(null, false);\n        });\n      });\n    });\n\n    lt.beforeEach.givenModel('bank');\n\n    lt.it.shouldBeAllowedWhenCalledAnonymously('GET', '/api/banks');\n    lt.it.shouldBeAllowedWhenCalledUnauthenticated('GET', '/api/banks');\n    lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'GET', '/api/banks');\n\n    lt.it.shouldBeAllowedWhenCalledAnonymously('GET', urlForBank);\n    lt.it.shouldBeAllowedWhenCalledUnauthenticated('GET', urlForBank);\n    lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'GET', urlForBank);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/banks');\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/banks');\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/banks');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForBank);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForBank);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForBank);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForBank);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForBank);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForBank);\n    lt.it.shouldBeAllowedWhenCalledByUser(SPECIAL_USER, 'DELETE', urlForBank);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/banks/upsertWithWhere');\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/banks/upsertWithWhere');\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/banks/upsertWithWhere');\n\n    function urlForBank() {\n      return '/api/banks/' + this.bank.id;\n    }\n  });\n\n  describe('/accounts with replaceOnPUT true', function() {\n    let count = 0;\n    before(function() {\n      const roleModel = loopback.getModelByType(loopback.Role);\n      roleModel.registerResolver('$dummy', function(role, context, callback) {\n        process.nextTick(function() {\n          if (context.remotingContext) {\n            count++;\n          }\n          if (callback) callback(null, false); // Always true\n        });\n      });\n    });\n\n    lt.beforeEach.givenModel('accountWithReplaceOnPUTtrue');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accounts-replacing');\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accounts-replacing');\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/accounts-replacing');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForAccount);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/accounts-replacing');\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/accounts-replacing');\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/accounts-replacing');\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('POST', urlForReplaceAccountPOST);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', urlForReplaceAccountPOST);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', urlForReplaceAccountPOST);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('PATCH', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('PATCH', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);\n\n    lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {\n      let actId;\n      beforeEach(function(done) {\n        const self = this;\n        // Create an account under the given user\n        app.models.accountWithReplaceOnPUTtrue.create({\n          userId: self.user.id,\n          balance: 100,\n        }, function(err, act) {\n          actId = act.id;\n          self.url = '/api/accounts-replacing/' + actId;\n          done();\n        });\n      });\n\n      lt.describe.whenCalledRemotely('PATCH', '/api/accounts-replacing/:id', function() {\n        lt.it.shouldBeAllowed();\n      });\n      lt.describe.whenCalledRemotely('PUT', '/api/accounts-replacing/:id', function() {\n        lt.it.shouldBeAllowed();\n      });\n      lt.describe.whenCalledRemotely('GET', '/api/accounts-replacing/:id', function() {\n        lt.it.shouldBeAllowed();\n      });\n      lt.describe.whenCalledRemotely('DELETE', '/api/accounts-replacing/:id', function() {\n        lt.it.shouldBeDenied();\n      });\n      describe('replace on POST verb', function() {\n        beforeEach(function(done) {\n          this.url = '/api/accounts-replacing/' + actId + '/replace';\n          done();\n        });\n        lt.describe.whenCalledRemotely('POST', '/api/accounts-replacing/:id/replace', function() {\n          lt.it.shouldBeAllowed();\n        });\n      });\n    });\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);\n\n    function urlForAccount() {\n      return '/api/accounts-replacing/' + this.accountWithReplaceOnPUTtrue.id;\n    }\n    function urlForReplaceAccountPOST() {\n      return '/api/accounts-replacing/' + this.accountWithReplaceOnPUTtrue.id + '/replace';\n    }\n  });\n\n  describe('/accounts with replaceOnPUT false', function() {\n    lt.beforeEach.givenModel('accountWithReplaceOnPUTfalse');\n    lt.it.shouldBeDeniedWhenCalledAnonymously('POST', urlForReplaceAccountPOST);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', urlForReplaceAccountPOST);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', urlForReplaceAccountPOST);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('PATCH', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('PATCH', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);\n\n    lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {\n      let actId;\n      beforeEach(function(done) {\n        const self = this;\n        // Create an account under the given user\n        app.models.accountWithReplaceOnPUTfalse.create({\n          userId: self.user.id,\n          balance: 100,\n        }, function(err, act) {\n          actId = act.id;\n          self.url = '/api/accounts-updating/' + actId;\n          done();\n        });\n      });\n\n      lt.describe.whenCalledRemotely('PATCH', '/api/accounts-updating/:id', function() {\n        lt.it.shouldBeAllowed();\n      });\n\n      lt.describe.whenCalledRemotely('PUT', '/api/accounts-updating/:id', function() {\n        lt.it.shouldBeAllowed();\n      });\n      lt.describe.whenCalledRemotely('GET', '/api/accounts-updating/:id', function() {\n        lt.it.shouldBeAllowed();\n      });\n      lt.describe.whenCalledRemotely('DELETE', '/api/accounts-updating/:id', function() {\n        lt.it.shouldBeDenied();\n      });\n\n      describe('replace on POST verb', function() {\n        beforeEach(function(done) {\n          this.url = '/api/accounts-updating/' + actId + '/replace';\n          done();\n        });\n        lt.describe.whenCalledRemotely('POST', '/api/accounts-updating/:id/replace', function() {\n          lt.it.shouldBeAllowed();\n        });\n      });\n    });\n\n    lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForAccount);\n    lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);\n\n    function urlForAccount() {\n      return '/api/accounts-updating/' + this.accountWithReplaceOnPUTfalse.id;\n    }\n    function urlForReplaceAccountPOST() {\n      return '/api/accounts-updating/' + this.accountWithReplaceOnPUTfalse.id + '/replace';\n    }\n  });\n});\n"
  },
  {
    "path": "test/access-token.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst expect = require('./helpers/expect');\nconst cookieParser = require('cookie-parser');\nconst LoopBackContext = require('loopback-context');\nconst contextMiddleware = require('loopback-context').perRequest;\nconst loopback = require('../');\nconst extend = require('util')._extend;\nconst session = require('express-session');\nconst request = require('supertest');\n\nlet Token, ACL, User, TestModel;\n\ndescribe('loopback.token(options)', function() {\n  let app;\n  beforeEach(function(done) {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.dataSource('db', {connector: 'memory'});\n\n    ACL = app.registry.getModel('ACL');\n    app.model(ACL, {dataSource: 'db'});\n\n    User = app.registry.getModel('User');\n    app.model(User, {dataSource: 'db'});\n\n    Token = app.registry.createModel({\n      name: 'MyToken',\n      base: 'AccessToken',\n    });\n    app.model(Token, {dataSource: 'db'});\n\n    TestModel = app.registry.createModel({\n      name: 'TestModel',\n      base: 'Model',\n    });\n    TestModel.getToken = function(options, cb) {\n      cb(null, options && options.accessToken || null);\n    };\n    TestModel.remoteMethod('getToken', {\n      accepts: {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      returns: {arg: 'token', type: 'object'},\n      http: {verb: 'GET', path: '/token'},\n    });\n    app.model(TestModel, {dataSource: 'db'});\n\n    createTestingToken.call(this, done);\n  });\n\n  it('defaults to built-in AccessToken model', function() {\n    const BuiltInToken = app.registry.getModel('AccessToken');\n    app.model(BuiltInToken, {dataSource: 'db'});\n\n    app.enableAuth({dataSource: 'db'});\n    app.use(loopback.token());\n    app.use(loopback.rest());\n\n    return BuiltInToken.create({userId: 123}).then(function(token) {\n      return request(app)\n        .get('/TestModels/token?_format=json')\n        .set('authorization', token.id)\n        .expect(200)\n        .expect('Content-Type', /json/)\n        .then(res => {\n          expect(res.body.token.id).to.eql(token.id);\n        });\n    });\n  });\n\n  it('uses correct custom AccessToken model from model class param', function() {\n    User.hasMany(Token, {\n      as: 'accessTokens',\n      options: {disableInclude: true},\n    });\n\n    app.enableAuth();\n    app.use(loopback.token({model: Token}));\n    app.use(loopback.rest());\n\n    return Token.create({userId: 123}).then(function(token) {\n      return request(app)\n        .get('/TestModels/token?_format=json')\n        .set('authorization', token.id)\n        .expect(200)\n        .expect('Content-Type', /json/)\n        .then(res => {\n          expect(res.body.token.id).to.eql(token.id);\n        });\n    });\n  });\n\n  it('uses correct custom AccessToken model from string param', function() {\n    User.hasMany(Token, {\n      as: 'accessTokens',\n      options: {disableInclude: true},\n    });\n\n    app.enableAuth();\n    app.use(loopback.token({model: Token.modelName}));\n    app.use(loopback.rest());\n\n    return Token.create({userId: 123}).then(function(token) {\n      return request(app)\n        .get('/TestModels/token?_format=json')\n        .set('authorization', token.id)\n        .expect(200)\n        .expect('Content-Type', /json/)\n        .then(res => {\n          expect(res.body.token.id).to.eql(token.id);\n        });\n    });\n  });\n\n  it('populates req.token from the query string', function(done) {\n    createTestAppAndRequest(this.token, done)\n      .get('/?access_token=' + this.token.id)\n      .expect(200)\n      .end(done);\n  });\n\n  it('populates req.token from an authorization header', function(done) {\n    createTestAppAndRequest(this.token, done)\n      .get('/')\n      .set('authorization', this.token.id)\n      .expect(200)\n      .end(done);\n  });\n\n  it('populates req.token from an X-Access-Token header', function(done) {\n    createTestAppAndRequest(this.token, done)\n      .get('/')\n      .set('X-Access-Token', this.token.id)\n      .expect(200)\n      .end(done);\n  });\n\n  it('does not search default keys when searchDefaultTokenKeys is false',\n    function(done) {\n      const tokenId = this.token.id;\n      const app = createTestApp(\n        this.token,\n        {token: {searchDefaultTokenKeys: false}},\n        done,\n      );\n      const agent = request.agent(app);\n\n      // Set the token cookie\n      agent.get('/token').expect(200).end(function(err, res) {\n        if (err) return done(err);\n\n        // Make a request that sets the token in all places searched by default\n        agent.get('/check-access?access_token=' + tokenId)\n          .set('X-Access-Token', tokenId)\n          .set('authorization', tokenId)\n        // Expect 401 because there is no (non-default) place configured where\n        // the middleware should load the token from\n          .expect(401)\n          .end(done);\n      });\n    });\n\n  it('populates req.token from an authorization header with bearer token with base64',\n    function(done) {\n      let token = this.token.id;\n      token = 'Bearer ' + new Buffer(token).toString('base64');\n      createTestAppAndRequest(this.token, done)\n        .get('/')\n        .set('authorization', token)\n        .expect(200)\n        .end(done);\n    });\n\n  it('populates req.token from an authorization header with bearer token', function(done) {\n    let token = this.token.id;\n    token = 'Bearer ' + token;\n    createTestAppAndRequest(this.token, {token: {bearerTokenBase64Encoded: false}}, done)\n      .get('/')\n      .set('authorization', token)\n      .expect(200)\n      .end(done);\n  });\n\n  describe('populating req.token from HTTP Basic Auth formatted authorization header', function() {\n    it('parses \"standalone-token\"', function(done) {\n      let token = this.token.id;\n      token = 'Basic ' + new Buffer(token).toString('base64');\n      createTestAppAndRequest(this.token, done)\n        .get('/')\n        .set('authorization', this.token.id)\n        .expect(200)\n        .end(done);\n    });\n\n    it('parses \"token-and-empty-password:\"', function(done) {\n      let token = this.token.id + ':';\n      token = 'Basic ' + new Buffer(token).toString('base64');\n      createTestAppAndRequest(this.token, done)\n        .get('/')\n        .set('authorization', this.token.id)\n        .expect(200)\n        .end(done);\n    });\n\n    it('parses \"ignored-user:token-is-password\"', function(done) {\n      let token = 'username:' + this.token.id;\n      token = 'Basic ' + new Buffer(token).toString('base64');\n      createTestAppAndRequest(this.token, done)\n        .get('/')\n        .set('authorization', this.token.id)\n        .expect(200)\n        .end(done);\n    });\n\n    it('parses \"token-is-username:ignored-password\"', function(done) {\n      let token = this.token.id + ':password';\n      token = 'Basic ' + new Buffer(token).toString('base64');\n      createTestAppAndRequest(this.token, done)\n        .get('/')\n        .set('authorization', this.token.id)\n        .expect(200)\n        .end(done);\n    });\n  });\n\n  it('populates req.token from a secure cookie', function(done) {\n    const app = createTestApp(this.token, done);\n\n    request(app)\n      .get('/token')\n      .end(function(err, res) {\n        request(app)\n          .get('/')\n          .set('Cookie', res.header['set-cookie'])\n          .end(done);\n      });\n  });\n\n  it('populates req.token from a header or a secure cookie', function(done) {\n    const app = createTestApp(this.token, done);\n    const id = this.token.id;\n    request(app)\n      .get('/token')\n      .end(function(err, res) {\n        request(app)\n          .get('/')\n          .set('authorization', id)\n          .set('Cookie', res.header['set-cookie'])\n          .end(done);\n      });\n  });\n\n  it('rewrites url for the current user literal at the end without query',\n    function(done) {\n      const app = createTestApp(this.token, done);\n      const id = this.token.id;\n      const userId = this.token.userId;\n      request(app)\n        .get('/users/me')\n        .set('authorization', id)\n        .end(function(err, res) {\n          assert(!err);\n          assert.deepEqual(res.body, {userId: userId});\n\n          done();\n        });\n    });\n\n  it('rewrites url for the current user literal at the end with query',\n    function(done) {\n      const app = createTestApp(this.token, done);\n      const id = this.token.id;\n      const userId = this.token.userId;\n      request(app)\n        .get('/users/me?state=1')\n        .set('authorization', id)\n        .end(function(err, res) {\n          assert(!err);\n          assert.deepEqual(res.body, {userId: userId, state: 1});\n\n          done();\n        });\n    });\n\n  it('rewrites url for the current user literal in the middle',\n    function(done) {\n      const app = createTestApp(this.token, done);\n      const id = this.token.id;\n      const userId = this.token.userId;\n      request(app)\n        .get('/users/me/1')\n        .set('authorization', id)\n        .end(function(err, res) {\n          assert(!err);\n          assert.deepEqual(res.body, {userId: userId, state: 1});\n\n          done();\n        });\n    });\n\n  it('generates a 401 on a current user literal route without an authToken',\n    function(done) {\n      const app = createTestApp(null, done);\n      request(app)\n        .get('/users/me')\n        .set('authorization', null)\n        .expect(401)\n        .end(done);\n    });\n\n  it('generates a 401 on a current user literal route with empty authToken',\n    function(done) {\n      const app = createTestApp(null, done);\n      request(app)\n        .get('/users/me')\n        .set('authorization', '')\n        .expect(401)\n        .end(done);\n    });\n\n  it('generates a 401 on a current user literal route with invalid authToken',\n    function(done) {\n      const app = createTestApp(this.token, done);\n      request(app)\n        .get('/users/me')\n        .set('Authorization', 'invald-token-id')\n        .expect(401)\n        .end(done);\n    });\n\n  it('skips when req.token is already present', function(done) {\n    const tokenStub = {id: 'stub id'};\n    app.use(function(req, res, next) {\n      req.accessToken = tokenStub;\n\n      next();\n    });\n    app.use(loopback.token({model: Token}));\n    app.get('/', function(req, res, next) {\n      res.send(req.accessToken);\n    });\n\n    request(app).get('/')\n      .set('Authorization', this.token.id)\n      .expect(200)\n      .end(function(err, res) {\n        if (err) return done(err);\n\n        expect(res.body).to.eql(tokenStub);\n\n        done();\n      });\n  });\n\n  describe('loading multiple instances of token middleware', function() {\n    it('skips when req.token is already present and no further options are set',\n      function(done) {\n        const tokenStub = {id: 'stub id'};\n        app.use(function(req, res, next) {\n          req.accessToken = tokenStub;\n\n          next();\n        });\n        app.use(loopback.token({model: Token}));\n        app.get('/', function(req, res, next) {\n          res.send(req.accessToken);\n        });\n\n        request(app).get('/')\n          .set('Authorization', this.token.id)\n          .expect(200)\n          .end(function(err, res) {\n            if (err) return done(err);\n\n            expect(res.body).to.eql(tokenStub);\n\n            done();\n          });\n      });\n\n    it('does not overwrite valid existing token (has \"id\" property) ' +\n      ' when overwriteExistingToken is falsy',\n    function(done) {\n      const tokenStub = {id: 'stub id'};\n      app.use(function(req, res, next) {\n        req.accessToken = tokenStub;\n\n        next();\n      });\n      app.use(loopback.token({\n        model: Token,\n        enableDoublecheck: true,\n      }));\n      app.get('/', function(req, res, next) {\n        res.send(req.accessToken);\n      });\n\n      request(app).get('/')\n        .set('Authorization', this.token.id)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.eql(tokenStub);\n\n          done();\n        });\n    });\n\n    it('overwrites invalid existing token (is !== undefined and has no \"id\" property) ' +\n      ' when enableDoublecheck is true',\n    function(done) {\n      const token = this.token;\n      app.use(function(req, res, next) {\n        req.accessToken = null;\n        next();\n      });\n\n      app.use(loopback.token({\n        model: Token,\n        enableDoublecheck: true,\n      }));\n\n      app.get('/', function(req, res, next) {\n        res.send(req.accessToken);\n      });\n\n      request(app).get('/')\n        .set('Authorization', token.id)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n          expect(res.body).to.eql({\n            id: token.id,\n            ttl: token.ttl,\n            userId: token.userId,\n            created: token.created.toJSON(),\n          });\n          done();\n        });\n    });\n\n    it('overwrites existing token when enableDoublecheck ' +\n      'and overwriteExistingToken options are truthy',\n    function(done) {\n      const token = this.token;\n      const tokenStub = {id: 'stub id'};\n      app.use(function(req, res, next) {\n        req.accessToken = tokenStub;\n\n        next();\n      });\n      app.use(loopback.token({\n        model: Token,\n        enableDoublecheck: true,\n        overwriteExistingToken: true,\n      }));\n      app.get('/', function(req, res, next) {\n        res.send(req.accessToken);\n      });\n\n      request(app).get('/')\n        .set('Authorization', token.id)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.eql({\n            id: token.id,\n            ttl: token.ttl,\n            userId: token.userId,\n            created: token.created.toJSON(),\n          });\n\n          done();\n        });\n    });\n  });\n});\n\ndescribe('AccessToken', function() {\n  beforeEach(createTestingToken);\n\n  it('has getIdForRequest method', function() {\n    expect(typeof Token.getIdForRequest).to.eql('function');\n  });\n\n  it('has resolve method', function() {\n    expect(typeof Token.resolve).to.eql('function');\n  });\n\n  it('generates id automatically', function() {\n    assert(this.token.id);\n    assert.equal(this.token.id.length, 64);\n  });\n\n  it('generates created date automatically', function() {\n    assert(this.token.created);\n    assert(Object.prototype.toString.call(this.token.created), '[object Date]');\n  });\n\n  describe('.validate()', function() {\n    it('accepts valid tokens', function(done) {\n      this.token.validate(function(err, isValid) {\n        assert(isValid);\n        done();\n      });\n    });\n\n    it('rejects eternal TTL by default', function(done) {\n      this.token.ttl = -1;\n      this.token.validate(function(err, isValid) {\n        if (err) return done(err);\n        expect(isValid, 'isValid').to.equal(false);\n        done();\n      });\n    });\n\n    it('allows eternal tokens when enabled by User.allowEternalTokens',\n      function(done) {\n        const Token = givenLocalTokenModel();\n\n        // Overwrite User settings - enable eternal tokens\n        Token.app.models.User.settings.allowEternalTokens = true;\n\n        Token.create({userId: '123', ttl: -1}, function(err, token) {\n          if (err) return done(err);\n          token.validate(function(err, isValid) {\n            if (err) return done(err);\n            expect(isValid, 'isValid').to.equal(true);\n            done();\n          });\n        });\n      });\n  });\n\n  describe('.findForRequest()', function() {\n    beforeEach(createTestingToken);\n\n    it('supports two-arg variant with no options', function(done) {\n      const expectedTokenId = this.token.id;\n      const req = mockRequest({\n        headers: {'authorization': expectedTokenId},\n      });\n\n      Token.findForRequest(req, function(err, token) {\n        if (err) return done(err);\n\n        expect(token.id).to.eql(expectedTokenId);\n\n        done();\n      });\n    });\n\n    it('allows getIdForRequest() to be overridden', function(done) {\n      const expectedTokenId = this.token.id;\n      const current = Token.getIdForRequest;\n      let called = false;\n      Token.getIdForRequest = function(req, options) {\n        called = true;\n        return expectedTokenId;\n      };\n      const req = mockRequest({\n        headers: {'authorization': 'dummy'},\n      });\n\n      Token.findForRequest(req, function(err, token) {\n        Token.getIdForRequest = current;\n        if (err) return done(err);\n\n        expect(token.id).to.eql(expectedTokenId);\n        expect(called).to.be.true();\n\n        done();\n      });\n    });\n\n    it('allows resolve() to be overridden', function(done) {\n      const expectedTokenId = this.token.id;\n      const current = Token.resolve;\n      let called = false;\n      Token.resolve = function(id, cb) {\n        called = true;\n        process.nextTick(function() {\n          cb(null, {id: expectedTokenId});\n        });\n      };\n      const req = mockRequest({\n        headers: {'authorization': expectedTokenId},\n      });\n\n      Token.findForRequest(req, function(err, token) {\n        Token.validate = current;\n        if (err) return done(err);\n\n        expect(token.id).to.eql(expectedTokenId);\n        expect(called).to.be.true();\n\n        done();\n      });\n    });\n\n    function mockRequest(opts) {\n      return extend(\n        {\n          method: 'GET',\n          url: '/a-test-path',\n          headers: {},\n          _params: {},\n\n          // express helpers\n          param: function(name) { return this._params[name]; },\n          header: function(name) { return this.headers[name]; },\n        },\n        opts,\n      );\n    }\n  });\n});\n\ndescribe('app.enableAuth()', function() {\n  let app;\n  beforeEach(function setupAuthWithModels() {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.dataSource('db', {connector: 'memory'});\n\n    Token = app.registry.createModel({\n      name: 'MyToken',\n      base: 'AccessToken',\n    });\n    app.model(Token, {dataSource: 'db'});\n\n    ACL = app.registry.getModel('ACL');\n\n    // Fix User's \"hasMany accessTokens\" relation to use our new MyToken model\n    const User = app.registry.getModel('User');\n    User.settings.relations.accessTokens.model = 'MyToken';\n\n    app.enableAuth({dataSource: 'db'});\n  });\n  beforeEach(createTestingToken);\n\n  it('prevents remote call with 401 status on denied ACL', function(done) {\n    createTestAppAndRequest(this.token, done)\n      .del('/tests/123')\n      .expect(401)\n      .set('authorization', this.token.id)\n      .end(function(err, res) {\n        if (err) {\n          return done(err);\n        }\n\n        const errorResponse = res.body.error;\n        assert(errorResponse);\n        assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');\n\n        done();\n      });\n  });\n\n  it('denies remote call with app setting status 403', function(done) {\n    createTestAppAndRequest(this.token, {app: {aclErrorStatus: 403}}, done)\n      .del('/tests/123')\n      .expect(403)\n      .set('authorization', this.token.id)\n      .end(function(err, res) {\n        if (err) {\n          return done(err);\n        }\n\n        const errorResponse = res.body.error;\n        assert(errorResponse);\n        assert.equal(errorResponse.code, 'ACCESS_DENIED');\n\n        done();\n      });\n  });\n\n  it('denies remote call with app setting status 404', function(done) {\n    createTestAppAndRequest(this.token, {model: {aclErrorStatus: 404}}, done)\n      .del('/tests/123')\n      .expect(404)\n      .set('authorization', this.token.id)\n      .end(function(err, res) {\n        if (err) {\n          return done(err);\n        }\n\n        const errorResponse = res.body.error;\n        assert(errorResponse);\n        assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');\n\n        done();\n      });\n  });\n\n  it('prevents remote call if the accessToken is missing and required', function(done) {\n    createTestAppAndRequest(null, done)\n      .del('/tests/123')\n      .expect(401)\n      .set('authorization', null)\n      .end(function(err, res) {\n        if (err) {\n          return done(err);\n        }\n\n        const errorResponse = res.body.error;\n        assert(errorResponse);\n        assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');\n\n        done();\n      });\n  });\n\n  it('stores token in the context', function(done) {\n    const TestModel = app.registry.createModel('TestModel', {base: 'Model'});\n    TestModel.getToken = function(cb) {\n      const ctx = LoopBackContext.getCurrentContext();\n      cb(null, ctx && ctx.get('accessToken') || null);\n    };\n    TestModel.remoteMethod('getToken', {\n      returns: {arg: 'token', type: 'object'},\n      http: {verb: 'GET', path: '/token'},\n    });\n\n    app.model(TestModel, {dataSource: null});\n\n    app.enableAuth();\n    app.use(contextMiddleware());\n    app.use(loopback.token({model: Token}));\n    app.use(loopback.rest());\n\n    const token = this.token;\n    request(app)\n      .get('/TestModels/token?_format=json')\n      .set('authorization', token.id)\n      .expect(200)\n      .expect('Content-Type', /json/)\n      .end(function(err, res) {\n        if (err) return done(err);\n\n        expect(res.body.token.id).to.eql(token.id);\n\n        done();\n      });\n  });\n\n  // See https://github.com/strongloop/loopback-context/issues/6\n  it('checks whether context is active', function(done) {\n    app.enableAuth();\n    app.use(contextMiddleware());\n    app.use(session({\n      secret: 'kitty',\n      saveUninitialized: true,\n      resave: true,\n    }));\n    app.use(loopback.token({model: Token}));\n    app.get('/', function(req, res) { res.send('OK'); });\n    app.use(loopback.rest());\n\n    request(app)\n      .get('/')\n      .set('authorization', this.token.id)\n      .set('cookie', 'connect.sid=s%3AFTyno9_MbGTJuOwdh9bxsYCVxlhlulTZ.' +\n        'PZvp85jzLXZBCBkhCsSfuUjhij%2Fb0B1K2RYZdxSQU0c')\n      .expect(200, 'OK')\n      .end(done);\n  });\n});\n\nfunction createTestingToken(done) {\n  const test = this;\n  Token.create({userId: '123'}, function(err, token) {\n    if (err) return done(err);\n\n    test.token = token;\n\n    done();\n  });\n}\n\nfunction createTestAppAndRequest(testToken, settings, done) {\n  const app = createTestApp(testToken, settings, done);\n  return request(app);\n}\n\nfunction createTestApp(testToken, settings, done) {\n  if (!done && typeof settings === 'function') {\n    done = settings;\n    settings = {};\n  }\n\n  const appSettings = settings.app || {};\n  const modelSettings = settings.model || {};\n  const tokenSettings = extend({\n    model: Token,\n    currentUserLiteral: 'me',\n  }, settings.token);\n\n  const app = loopback({localRegistry: true, loadBuiltinModels: true});\n  app.dataSource('db', {connector: 'memory'});\n\n  app.use(cookieParser('secret'));\n  app.use(loopback.token(tokenSettings));\n  app.set('remoting', {errorHandler: {debug: true, log: false}});\n  app.get('/token', function(req, res) {\n    res.cookie('authorization', testToken.id, {signed: true});\n    res.cookie('access_token', testToken.id, {signed: true});\n    res.end();\n  });\n  app.get('/', function(req, res) {\n    try {\n      assert(req.accessToken, 'req should have accessToken');\n      assert(req.accessToken.id === testToken.id);\n    } catch (e) {\n      return done(e);\n    }\n    res.send('ok');\n  });\n  app.get('/check-access', function(req, res) {\n    res.status(req.accessToken ? 200 : 401).end();\n  });\n  app.use('/users/:uid', function(req, res) {\n    const result = {userId: req.params.uid};\n    if (req.query.state) {\n      result.state = req.query.state;\n    } else if (req.url !== '/') {\n      result.state = req.url.substring(1);\n    }\n    res.status(200).send(result);\n  });\n  app.use(loopback.rest());\n  app.enableAuth({dataSource: 'db'});\n\n  Object.keys(appSettings).forEach(function(key) {\n    app.set(key, appSettings[key]);\n  });\n\n  const modelOptions = {\n    acls: [\n      {\n        principalType: 'ROLE',\n        principalId: '$everyone',\n        accessType: ACL.ALL,\n        permission: ACL.DENY,\n        property: 'deleteById',\n      },\n    ],\n  };\n\n  Object.keys(modelSettings).forEach(function(key) {\n    modelOptions[key] = modelSettings[key];\n  });\n\n  const TestModel = app.registry.createModel('test', {}, modelOptions);\n  app.model(TestModel, {dataSource: 'db'});\n\n  return app;\n}\n\nfunction givenLocalTokenModel() {\n  const app = loopback({localRegistry: true, loadBuiltinModels: true});\n  app.dataSource('db', {connector: 'memory'});\n\n  const User = app.registry.getModel('User');\n  app.model(User, {dataSource: 'db'});\n\n  const Token = app.registry.getModel('AccessToken');\n  app.model(Token, {dataSource: 'db'});\n\n  return Token;\n}\n"
  },
  {
    "path": "test/acl.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst expect = require('./helpers/expect');\nconst loopback = require('../index');\nconst Scope = loopback.Scope;\nconst ACL = loopback.ACL;\nconst request = require('supertest');\nconst Promise = require('bluebird');\nconst supertest = require('supertest');\nconst Role = loopback.Role;\nconst RoleMapping = loopback.RoleMapping;\nconst User = loopback.User;\nconst async = require('async');\n\n// Speed up the password hashing algorithm for tests\nUser.settings.saltWorkFactor = 4;\n\nlet ds = null;\nlet testModel;\n\ndescribe('ACL model', function() {\n  it('provides DEFAULT_SCOPE constant', () => {\n    expect(ACL).to.have.property('DEFAULT_SCOPE', 'DEFAULT');\n  });\n});\n\ndescribe('security scopes', function() {\n  beforeEach(setupTestModels);\n\n  it('should allow access to models for the given scope by wildcard', function(done) {\n    Scope.create({name: 'userScope', description: 'access user information'},\n      function(err, scope) {\n        ACL.create({\n          principalType: ACL.SCOPE, principalId: scope.id,\n          model: 'User', property: ACL.ALL,\n          accessType: ACL.ALL, permission: ACL.ALLOW,\n        }, function(err, resource) {\n          async.parallel([\n            cb => Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, cb),\n            cb => Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, cb),\n            cb => Scope.checkPermission('userScope', 'User', 'name', ACL.READ, cb),\n          ], (err) => {\n            assert.ifError(err);\n            done();\n          });\n        });\n      });\n  });\n\n  it('should allow access to models for the given scope', function(done) {\n    Scope.create({name: 'testModelScope', description: 'access testModel information'},\n      function(err, scope) {\n        ACL.create({\n          principalType: ACL.SCOPE, principalId: scope.id,\n          model: 'testModel', property: 'name',\n          accessType: ACL.READ, permission: ACL.ALLOW,\n        }, function(err, resource) {\n          ACL.create({principalType: ACL.SCOPE, principalId: scope.id,\n            model: 'testModel', property: 'name',\n            accessType: ACL.WRITE, permission: ACL.DENY,\n          }, function(err, resource) {\n            async.parallel([\n              cb => Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, cb),\n              cb => Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, cb),\n              cb => Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, cb),\n              cb => Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, cb),\n            ], (err, perms) => {\n              if (err) return done(err);\n              assert.deepEqual(perms.map(p => p.permission), [\n                ACL.DENY,\n                ACL.DENY,\n                ACL.ALLOW,\n                ACL.DENY,\n              ]);\n              done();\n            });\n          });\n        });\n      });\n  });\n});\n\ndescribe('security ACLs', function() {\n  beforeEach(setupTestModels);\n\n  it('supports checkPermission() returning a promise', function() {\n    return ACL.create({\n      principalType: ACL.USER,\n      principalId: 'u001',\n      model: 'testModel',\n      property: ACL.ALL,\n      accessType: ACL.ALL,\n      permission: ACL.ALLOW,\n    })\n      .then(function() {\n        return ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL);\n      })\n      .then(function(access) {\n        assert(access.permission === ACL.ALLOW);\n      });\n  });\n\n  it('supports ACL rules with a wildcard for models', function() {\n    const A_USER_ID = 'a-test-user';\n\n    // By default, access is allowed to all users\n    return assertPermission(ACL.ALLOW, 'initial state')\n      // An ACL rule applying to all models denies access to everybody\n      .then(() => ACL.create({\n        model: '*',\n        property: '*',\n        accessType: '*',\n        principalType: 'ROLE',\n        principalId: '$everyone',\n        permission: 'DENY',\n      }))\n      .then(() => assertPermission(ACL.DENY, 'all denied'))\n      // A rule for a specific model overrides the rule matching all models\n      .then(() => ACL.create({\n        model: testModel.modelName,\n        property: '*',\n        accessType: '*',\n        principalType: ACL.USER,\n        principalId: A_USER_ID,\n        permission: ACL.ALLOW,\n      }))\n      .then(() => assertPermission(ACL.ALLOW, 'only a single model allowed'));\n\n    function assertPermission(expectedPermission, msg) {\n      return ACL.checkAccessForContext({\n        principals: [{type: ACL.USER, id: A_USER_ID}],\n        model: testModel.modelName,\n        accessType: ACL.ALL,\n      }).then(accessContext => {\n        const actual = accessContext.isAllowed() ? ACL.ALLOW : ACL.DENY;\n        expect(actual, msg).to.equal(expectedPermission);\n      });\n    }\n  });\n\n  it('supports checkAccessForContext() returning a promise', function() {\n    const testModel = ds.createModel('testModel', {\n      acls: [\n        {principalType: ACL.USER, principalId: 'u001',\n          accessType: ACL.ALL, permission: ACL.ALLOW},\n      ],\n    });\n\n    return ACL.checkAccessForContext({\n      principals: [{type: ACL.USER, id: 'u001'}],\n      model: 'testModel',\n      accessType: ACL.ALL,\n    })\n      .then(function(access) {\n        assert(access.permission === ACL.ALLOW);\n      });\n  });\n\n  it('should order ACL entries based on the matching score', function() {\n    let acls = [\n      {\n        'model': 'account',\n        'accessType': '*',\n        'permission': 'DENY',\n        'principalType': 'ROLE',\n        'principalId': '$everyone',\n      },\n      {\n        'model': 'account',\n        'accessType': '*',\n        'permission': 'ALLOW',\n        'principalType': 'ROLE',\n        'principalId': '$owner',\n      },\n      {\n        'model': 'account',\n        'accessType': 'READ',\n        'permission': 'ALLOW',\n        'principalType': 'ROLE',\n        'principalId': '$everyone',\n      }];\n    const req = {\n      model: 'account',\n      property: 'find',\n      accessType: 'WRITE',\n    };\n\n    acls = acls.map(function(a) { return new ACL(a); });\n\n    const perm = ACL.resolvePermission(acls, req);\n    // remove the registry from AccessRequest instance to ease asserting\n    delete perm.registry;\n    assert.deepEqual(perm, {model: 'account',\n      property: 'find',\n      accessType: 'WRITE',\n      permission: 'ALLOW',\n      methodNames: []});\n\n    // NOTE: when fixed in chaijs, use this implement rather than modifying\n    // the resolved access request\n    //\n    // expect(perm).to.deep.include({\n    //   model: 'account',\n    //   property: 'find',\n    //   accessType: 'WRITE',\n    //   permission: 'ALLOW',\n    //   methodNames: [],\n    // });\n  });\n\n  it('should order ACL entries based on the matching score even with wildcard req', function() {\n    let acls = [\n      {\n        'model': 'account',\n        'accessType': '*',\n        'permission': 'DENY',\n        'principalType': 'ROLE',\n        'principalId': '$everyone',\n      },\n      {\n        'model': 'account',\n        'accessType': '*',\n        'permission': 'ALLOW',\n        'principalType': 'ROLE',\n        'principalId': '$owner',\n      }];\n    const req = {\n      model: 'account',\n      property: '*',\n      accessType: 'WRITE',\n    };\n\n    acls = acls.map(function(a) { return new ACL(a); });\n\n    const perm = ACL.resolvePermission(acls, req);\n    // remove the registry from AccessRequest instance to ease asserting.\n    // Check the above test case for more info.\n    delete perm.registry;\n    assert.deepEqual(perm, {model: 'account',\n      property: '*',\n      accessType: 'WRITE',\n      permission: 'ALLOW',\n      methodNames: []});\n  });\n\n  it('should allow access to models for the given principal by wildcard', function(done) {\n    // jscs:disable validateIndentation\n    ACL.create({\n      principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,\n      accessType: ACL.ALL, permission: ACL.ALLOW,\n    }, function(err, acl) {\n      ACL.create({\n        principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,\n        accessType: ACL.READ, permission: ACL.DENY,\n      }, function(err, acl) {\n        async.parallel([\n          cb => ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, cb),\n          cb => ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, cb),\n        ], (err, perms) => {\n          if (err) return done(err);\n          assert.deepEqual(perms.map(p => p.permission), [\n            ACL.DENY,\n            ACL.DENY,\n          ]);\n          done();\n        });\n      });\n    });\n  });\n\n  it('should allow access to models by exception', function(done) {\n    ACL.create({\n      principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,\n      accessType: ACL.ALL, permission: ACL.DENY,\n    }, function(err, acl) {\n      ACL.create({\n        principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,\n        accessType: ACL.READ, permission: ACL.ALLOW,\n      }, function(err, acl) {\n        ACL.create({\n          principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL,\n          accessType: ACL.EXECUTE, permission: ACL.ALLOW,\n        }, function(err, acl) {\n          async.parallel([\n            cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, cb),\n            cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, cb),\n            cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, cb),\n            cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, cb),\n            cb => ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.WRITE, cb),\n            cb => ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.READ, cb),\n          ], (err, perms) => {\n            if (err) return done(err);\n            assert.deepEqual(perms.map(p => p.permission), [\n              ACL.ALLOW,\n              ACL.ALLOW,\n              ACL.DENY,\n              ACL.DENY,\n              ACL.ALLOW,\n              ACL.ALLOW,\n            ]);\n            done();\n          });\n        });\n      });\n    });\n  });\n\n  it('should honor defaultPermission from the model', function(done) {\n    const Customer = ds.createModel('Customer', {\n      name: {\n        type: String,\n        acls: [\n          {principalType: ACL.USER, principalId: 'u001',\n            accessType: ACL.WRITE, permission: ACL.DENY},\n          {principalType: ACL.USER, principalId: 'u001',\n            accessType: ACL.ALL, permission: ACL.ALLOW},\n        ],\n      },\n    }, {\n      acls: [\n        {principalType: ACL.USER, principalId: 'u001',\n          accessType: ACL.ALL, permission: ACL.ALLOW},\n      ],\n    });\n\n    // ACL default permission is to DENY for model Customer\n    Customer.settings.defaultPermission = ACL.DENY;\n\n    async.parallel([\n      cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, cb),\n      cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, cb),\n      cb => ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, cb),\n    ], (err, perms) => {\n      if (err) return done(err);\n      assert.deepEqual(perms.map(p => p.permission), [\n        ACL.DENY,\n        ACL.ALLOW,\n        ACL.DENY,\n      ]);\n      done();\n    });\n  });\n\n  it('should honor static ACLs from the model', function(done) {\n    const Customer = ds.createModel('Customer', {\n      name: {\n        type: String,\n        acls: [\n          {principalType: ACL.USER, principalId: 'u001',\n            accessType: ACL.WRITE, permission: ACL.DENY},\n          {principalType: ACL.USER, principalId: 'u001',\n            accessType: ACL.ALL, permission: ACL.ALLOW},\n        ],\n      },\n    }, {\n      acls: [\n        {principalType: ACL.USER, principalId: 'u001',\n          accessType: ACL.ALL, permission: ACL.ALLOW},\n        {principalType: ACL.USER, principalId: 'u002',\n          accessType: ACL.EXECUTE, permission: ACL.ALLOW},\n        {principalType: ACL.USER, principalId: 'u003',\n          accessType: ACL.EXECUTE, permission: ACL.DENY},\n      ],\n    });\n\n    /*\n     Customer.settings.acls = [\n     {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}\n     ];\n     */\n\n    async.parallel([\n      cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, cb),\n      cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, cb),\n      cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL, cb),\n      cb => ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.READ, cb),\n      cb => ACL.checkPermission(ACL.USER, 'u003', 'Customer', 'name', ACL.WRITE, cb),\n    ], (err, perms) => {\n      if (err) return done(err);\n      assert.deepEqual(perms.map(p => p.permission), [\n        ACL.DENY,\n        ACL.ALLOW,\n        ACL.ALLOW,\n        ACL.ALLOW,\n        ACL.DENY,\n      ]);\n      done();\n    });\n  });\n\n  it('should filter static ACLs by model/property', function() {\n    const Model1 = ds.createModel('Model1', {\n      name: {\n        type: String,\n        acls: [\n          {principalType: ACL.USER, principalId: 'u001',\n            accessType: ACL.WRITE, permission: ACL.DENY},\n          {principalType: ACL.USER, principalId: 'u001',\n            accessType: ACL.ALL, permission: ACL.ALLOW},\n        ],\n      },\n    }, {\n      acls: [\n        {principalType: ACL.USER, principalId: 'u001', property: 'name',\n          accessType: ACL.ALL, permission: ACL.ALLOW},\n        {principalType: ACL.USER, principalId: 'u002', property: 'findOne',\n          accessType: ACL.ALL, permission: ACL.ALLOW},\n        {principalType: ACL.USER, principalId: 'u003', property: ['findOne', 'findById'],\n          accessType: ACL.ALL, permission: ACL.ALLOW},\n      ],\n    });\n\n    let staticACLs = ACL.getStaticACLs('Model1', 'name');\n    assert(staticACLs.length === 3);\n\n    staticACLs = ACL.getStaticACLs('Model1', 'findOne');\n    assert(staticACLs.length === 2);\n\n    staticACLs = ACL.getStaticACLs('Model1', 'findById');\n    assert(staticACLs.length === 1);\n    assert(staticACLs[0].property === 'findById');\n  });\n\n  it('should check access against LDL, ACL, and Role', function(done) {\n    const log = function() {};\n\n    // Create\n    User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {\n      log('User: ', user.toObject());\n\n      const userId = user.id;\n\n      // Define a model with static ACLs\n      const Customer = ds.createModel('Customer', {\n        name: {\n          type: String,\n          acls: [\n            {principalType: ACL.USER, principalId: userId,\n              accessType: ACL.WRITE, permission: ACL.DENY},\n            {principalType: ACL.USER, principalId: userId,\n              accessType: ACL.ALL, permission: ACL.ALLOW},\n          ],\n        },\n      }, {\n        acls: [\n          {principalType: ACL.USER, principalId: userId,\n            accessType: ACL.ALL, permission: ACL.ALLOW},\n        ],\n        defaultPermission: 'DENY',\n      });\n\n      ACL.create({\n        principalType: ACL.USER, principalId: userId,\n        model: 'Customer', property: ACL.ALL,\n        accessType: ACL.ALL, permission: ACL.ALLOW,\n      }, function(err, acl) {\n        log('ACL 1: ', acl.toObject());\n\n        Role.create({name: 'MyRole'}, function(err, myRole) {\n          log('Role: ', myRole.toObject());\n\n          myRole.principals.create({principalType: RoleMapping.USER, principalId: userId},\n            function(err, p) {\n              log('Principal added to role: ', p.toObject());\n\n              ACL.create({\n                principalType: ACL.ROLE, principalId: 'MyRole',\n                model: 'Customer', property: ACL.ALL,\n                accessType: ACL.READ, permission: ACL.DENY,\n              }, function(err, acl) {\n                log('ACL 2: ', acl.toObject());\n\n                async.parallel([\n                  cb => {\n                    ACL.checkAccessForContext({\n                      principals: [\n                        {type: ACL.USER, id: userId},\n                      ],\n                      model: 'Customer',\n                      property: 'name',\n                      accessType: ACL.READ,\n                    }, function(err, access) {\n                      assert.ifError(err);\n                      assert.equal(access.permission, ACL.ALLOW);\n                      cb();\n                    });\n                  },\n                  cb => {\n                    ACL.checkAccessForContext({\n                      principals: [\n                        {type: ACL.ROLE, id: Role.EVERYONE},\n                      ],\n                      model: 'Customer',\n                      property: 'name',\n                      accessType: ACL.READ,\n                    }, function(err, access) {\n                      assert.ifError(err);\n                      assert.equal(access.permission, ACL.DENY);\n                      cb();\n                    });\n                  }], done);\n              });\n            });\n        });\n      });\n    });\n  });\n});\n\ndescribe('access check', function() {\n  it('should occur before other remote hooks', function(done) {\n    const app = loopback();\n    const MyTestModel = app.registry.createModel('MyTestModel');\n    let checkAccessCalled = false;\n    let beforeHookCalled = false;\n\n    app.use(loopback.rest());\n    app.set('remoting', {errorHandler: {debug: true, log: false}});\n    app.enableAuth();\n    app.dataSource('test', {connector: 'memory'});\n    app.model(MyTestModel, {dataSource: 'test'});\n\n    // fake / spy on the checkAccess method\n    MyTestModel.checkAccess = function() {\n      const cb = arguments[arguments.length - 1];\n      checkAccessCalled = true;\n      const allowed = true;\n      cb(null, allowed);\n    };\n\n    MyTestModel.beforeRemote('find', function(ctx, next) {\n      // ensure this is called after checkAccess\n      if (!checkAccessCalled) return done(new Error('incorrect order'));\n\n      beforeHookCalled = true;\n\n      next();\n    });\n\n    request(app)\n      .get('/MyTestModels')\n      .end(function(err, result) {\n        assert(beforeHookCalled, 'the before hook should be called');\n        assert(checkAccessCalled, 'checkAccess should have been called');\n\n        done();\n      });\n  });\n});\n\ndescribe('authorized roles propagation in RemotingContext', function() {\n  let app, request, accessToken;\n  let models = {};\n\n  beforeEach(setupAppAndRequest);\n\n  it('contains all authorized roles for a principal if query is allowed', function() {\n    return createACLs('MyTestModel', [\n      {permission: ACL.ALLOW, principalId: '$everyone'},\n      {permission: ACL.ALLOW, principalId: '$authenticated'},\n      {permission: ACL.ALLOW, principalId: 'myRole'},\n    ])\n      .then(makeAuthorizedHttpRequestOnMyTestModel)\n      .then(function() {\n        const ctx = models.MyTestModel.lastRemotingContext;\n        expect(ctx.args.options.authorizedRoles).to.eql(\n          {\n            $everyone: true,\n            $authenticated: true,\n            myRole: true,\n          },\n        );\n      });\n  });\n\n  it('does not contain any denied role even if query is allowed', function() {\n    return createACLs('MyTestModel', [\n      {permission: ACL.ALLOW, principalId: '$everyone'},\n      {permission: ACL.DENY, principalId: '$authenticated'},\n      {permission: ACL.ALLOW, principalId: 'myRole'},\n    ])\n      .then(makeAuthorizedHttpRequestOnMyTestModel)\n      .then(function() {\n        const ctx = models.MyTestModel.lastRemotingContext;\n        expect(ctx.args.options.authorizedRoles).to.eql(\n          {\n            $everyone: true,\n            myRole: true,\n          },\n        );\n      });\n  });\n\n  it('honors default permission setting', function() {\n    // default permission is set to DENY for MyTestModel\n    models.MyTestModel.settings.defaultPermission = ACL.DENY;\n\n    return createACLs('MyTestModel', [\n      {permission: ACL.DEFAULT, principalId: '$everyone'},\n      {permission: ACL.DENY, principalId: '$authenticated'},\n      {permission: ACL.ALLOW, principalId: 'myRole'},\n    ])\n      .then(makeAuthorizedHttpRequestOnMyTestModel)\n      .then(function() {\n        const ctx = models.MyTestModel.lastRemotingContext;\n        expect(ctx.args.options.authorizedRoles).to.eql(\n        // '$everyone' is not expected as default permission is DENY\n          {myRole: true},\n        );\n      });\n  });\n\n  // helpers\n  function setupAppAndRequest() {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.use(loopback.rest());\n    app.set('remoting', {errorHandler: {debug: true, log: true}});\n    app.dataSource('db', {connector: 'memory'});\n    request = supertest(app);\n\n    app.enableAuth({dataSource: 'db'});\n    models = app.models;\n\n    // Speed up the password hashing algorithm for tests\n    models.User.settings.saltWorkFactor = 4;\n\n    // creating a custom model\n    const MyTestModel = app.registry.createModel('MyTestModel');\n    app.model(MyTestModel, {dataSource: 'db'});\n\n    // capturing the value of the last remoting context\n    models.MyTestModel.beforeRemote('find', function(ctx, unused, next) {\n      models.MyTestModel.lastRemotingContext = ctx;\n      next();\n    });\n\n    // creating a user, a role and a rolemapping binding that user with that role\n    return Promise.all([\n      models.User.create({username: 'myUser', email: 'myuser@example.com', password: 'pass'}),\n      models.Role.create({name: 'myRole'}),\n    ])\n      .spread(function(myUser, myRole) {\n        return Promise.all([\n          myRole.principals.create({principalType: 'USER', principalId: myUser.id}),\n          models.User.login({username: 'myUser', password: 'pass'}),\n        ]);\n      })\n      .spread(function(role, token) {\n        accessToken = token;\n      });\n  }\n\n  function createACLs(model, acls) {\n    acls = acls.map(function(acl) {\n      return models.ACL.create({\n        principalType: acl.principalType || ACL.ROLE,\n        principalId: acl.principalId,\n        model: acl.model || model,\n        property: acl.property || ACL.ALL,\n        accessType: acl.accessType || ACL.ALL,\n        permission: acl.permission,\n      });\n    });\n    return Promise.all(acls);\n  }\n\n  function makeAuthorizedHttpRequestOnMyTestModel() {\n    return request.get('/MyTestModels')\n      .set('X-Access-Token', accessToken.id)\n      .expect(200);\n  }\n});\n\nfunction setupTestModels() {\n  ds = this.ds = loopback.createDataSource({connector: loopback.Memory});\n  testModel = loopback.PersistedModel.extend('testModel');\n  ACL.attachTo(ds);\n  Role.attachTo(ds);\n  RoleMapping.attachTo(ds);\n  User.attachTo(ds);\n  Scope.attachTo(ds);\n  testModel.attachTo(ds);\n}\n"
  },
  {
    "path": "test/app.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst async = require('async');\nconst path = require('path');\n\nconst http = require('http');\nconst express = require('express');\nconst loopback = require('../');\nconst PersistedModel = loopback.PersistedModel;\n\nconst describe = require('./util/describe');\nconst expect = require('./helpers/expect');\nconst it = require('./util/it');\nconst request = require('supertest');\nconst sinon = require('sinon');\n\ndescribe('app', function() {\n  let app;\n  beforeEach(function() {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n  });\n\n  describe.onServer('.middleware(phase, handler)', function() {\n    let steps;\n\n    beforeEach(function setup() {\n      steps = [];\n    });\n\n    it('runs middleware in phases', function(done) {\n      const PHASES = [\n        'initial', 'session', 'auth', 'parse',\n        'routes', 'files', 'final',\n      ];\n\n      PHASES.forEach(function(name) {\n        app.middleware(name, namedHandler(name));\n      });\n      app.use(namedHandler('main'));\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(steps).to.eql([\n          'initial', 'session', 'auth', 'parse',\n          'main', 'routes', 'files', 'final',\n        ]);\n\n        done();\n      });\n    });\n\n    it('preserves order of handlers in the same phase', function(done) {\n      app.middleware('initial', namedHandler('first'));\n      app.middleware('initial', namedHandler('second'));\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(steps).to.eql(['first', 'second']);\n\n        done();\n      });\n    });\n\n    it('supports `before:` and `after:` prefixes', function(done) {\n      app.middleware('routes:before', namedHandler('routes:before'));\n      app.middleware('routes:after', namedHandler('routes:after'));\n      app.use(namedHandler('main'));\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(steps).to.eql(['routes:before', 'main', 'routes:after']);\n\n        done();\n      });\n    });\n\n    it('allows extra handlers on express stack during app.use', function(done) {\n      function handlerThatAddsHandler(name) {\n        app.use(namedHandler('extra-handler'));\n        return namedHandler(name);\n      }\n\n      let myHandler;\n      app.middleware('routes:before',\n        myHandler = handlerThatAddsHandler('my-handler'));\n      const found = app._findLayerByHandler(myHandler);\n      expect(found).to.be.an('object');\n      expect(myHandler).to.equal(found.handle);\n      expect(found).have.property('phase', 'routes:before');\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(steps).to.eql(['my-handler', 'extra-handler']);\n\n        done();\n      });\n    });\n\n    it('allows handlers to be wrapped as __NR_handler on express stack',\n      function(done) {\n        const myHandler = namedHandler('my-handler');\n        const wrappedHandler = function(req, res, next) {\n          myHandler(req, res, next);\n        };\n        wrappedHandler['__NR_handler'] = myHandler;\n        app.middleware('routes:before', wrappedHandler);\n        const found = app._findLayerByHandler(myHandler);\n        expect(found).to.be.an('object');\n        expect(found).have.property('phase', 'routes:before');\n        executeMiddlewareHandlers(app, function(err) {\n          if (err) return done(err);\n\n          expect(steps).to.eql(['my-handler']);\n\n          done();\n        });\n      });\n\n    it('allows handlers to be wrapped as __appdynamicsProxyInfo__ on express stack',\n      function(done) {\n        const myHandler = namedHandler('my-handler');\n        const wrappedHandler = function(req, res, next) {\n          myHandler(req, res, next);\n        };\n        wrappedHandler['__appdynamicsProxyInfo__'] = {\n          orig: myHandler,\n        };\n        app.middleware('routes:before', wrappedHandler);\n        const found = app._findLayerByHandler(myHandler);\n        expect(found).to.be.an('object');\n        expect(found).have.property('phase', 'routes:before');\n        executeMiddlewareHandlers(app, function(err) {\n          if (err) return done(err);\n\n          expect(steps).to.eql(['my-handler']);\n\n          done();\n        });\n      });\n\n    it('allows handlers to be wrapped as a property on express stack',\n      function(done) {\n        const myHandler = namedHandler('my-handler');\n        const wrappedHandler = function(req, res, next) {\n          myHandler(req, res, next);\n        };\n        wrappedHandler['__handler'] = myHandler;\n        app.middleware('routes:before', wrappedHandler);\n        const found = app._findLayerByHandler(myHandler);\n        expect(found).to.be.an('object');\n        expect(found).have.property('phase', 'routes:before');\n        executeMiddlewareHandlers(app, function(err) {\n          if (err) return done(err);\n\n          expect(steps).to.eql(['my-handler']);\n\n          done();\n        });\n      });\n\n    it('injects error from previous phases into the router', function(done) {\n      const expectedError = new Error('expected error');\n\n      app.middleware('initial', function(req, res, next) {\n        steps.push('initial');\n\n        next(expectedError);\n      });\n\n      // legacy solution for error handling\n      app.use(function errorHandler(err, req, res, next) {\n        expect(err).to.equal(expectedError);\n        steps.push('error');\n\n        next();\n      });\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(steps).to.eql(['initial', 'error']);\n\n        done();\n      });\n    });\n\n    it('passes unhandled error to callback', function(done) {\n      const expectedError = new Error('expected error');\n\n      app.middleware('initial', function(req, res, next) {\n        next(expectedError);\n      });\n\n      executeMiddlewareHandlers(app, function(err) {\n        expect(err).to.equal(expectedError);\n\n        done();\n      });\n    });\n\n    it('passes errors to error handlers in the same phase', function(done) {\n      const expectedError = new Error('this should be handled by middleware');\n      let handledError;\n\n      app.middleware('initial', function(req, res, next) {\n        // continue in the next tick, this verifies that the next\n        // handler waits until the previous one is done\n        process.nextTick(function() {\n          next(expectedError);\n        });\n      });\n\n      app.middleware('initial', function(err, req, res, next) {\n        handledError = err;\n\n        next();\n      });\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(handledError).to.equal(expectedError);\n\n        done();\n      });\n    });\n\n    it('scopes middleware to a string path', function(done) {\n      app.middleware('initial', '/scope', pathSavingHandler());\n\n      async.eachSeries(\n        ['/', '/scope', '/scope/item', '/other'],\n        function(url, next) { executeMiddlewareHandlers(app, url, next); },\n        function(err) {\n          if (err) return done(err);\n\n          expect(steps).to.eql(['/scope', '/scope/item']);\n\n          done();\n        },\n      );\n    });\n\n    it('scopes middleware to a regex path', function(done) {\n      app.middleware('initial', /^\\/(a|b)/, pathSavingHandler());\n\n      async.eachSeries(\n        ['/', '/a', '/b', '/c'],\n        function(url, next) { executeMiddlewareHandlers(app, url, next); },\n        function(err) {\n          if (err) return done(err);\n\n          expect(steps).to.eql(['/a', '/b']);\n\n          done();\n        },\n      );\n    });\n\n    it('scopes middleware to a list of scopes', function(done) {\n      app.middleware('initial', ['/scope', /^\\/(a|b)/], pathSavingHandler());\n\n      async.eachSeries(\n        ['/', '/a', '/b', '/c', '/scope', '/other'],\n        function(url, next) { executeMiddlewareHandlers(app, url, next); },\n        function(err) {\n          if (err) return done(err);\n\n          expect(steps).to.eql(['/a', '/b', '/scope']);\n\n          done();\n        },\n      );\n    });\n\n    it('sets req.url to a sub-path', function(done) {\n      app.middleware('initial', ['/scope'], function(req, res, next) {\n        steps.push(req.url);\n\n        next();\n      });\n\n      executeMiddlewareHandlers(app, '/scope/id', function(err) {\n        if (err) return done(err);\n\n        expect(steps).to.eql(['/id']);\n\n        done();\n      });\n    });\n\n    it('exposes express helpers on req and res objects', function(done) {\n      let req, res;\n\n      app.middleware('initial', function(rq, rs, next) {\n        req = rq;\n        res = rs;\n\n        next();\n      });\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(getObjectAndPrototypeKeys(req), 'request').to.include.members([\n          'accepts',\n          'get',\n          'param',\n          'params',\n          'query',\n          'res',\n        ]);\n\n        expect(getObjectAndPrototypeKeys(res), 'response').to.include.members([\n          'cookie',\n          'download',\n          'json',\n          'jsonp',\n          'redirect',\n          'req',\n          'send',\n          'sendFile',\n          'set',\n        ]);\n\n        done();\n      });\n    });\n\n    it('sets req.baseUrl and req.originalUrl', function(done) {\n      let reqProps;\n      app.middleware('initial', function(req, res, next) {\n        reqProps = {baseUrl: req.baseUrl, originalUrl: req.originalUrl};\n\n        next();\n      });\n\n      executeMiddlewareHandlers(app, '/test/url', function(err) {\n        if (err) return done(err);\n\n        expect(reqProps).to.eql({baseUrl: '', originalUrl: '/test/url'});\n\n        done();\n      });\n    });\n\n    it('preserves correct order of routes vs. middleware', function(done) {\n      // This test verifies that `app.route` triggers sort of layers\n      app.middleware('files', namedHandler('files'));\n      app.get('/test', namedHandler('route'));\n\n      executeMiddlewareHandlers(app, '/test', function(err) {\n        if (err) return done(err);\n\n        expect(steps).to.eql(['route', 'files']);\n\n        done();\n      });\n    });\n\n    it('preserves order of middleware in the same phase', function(done) {\n      // while we are discouraging developers from depending on\n      // the registration order of middleware in the same phase,\n      // we must preserve the order for compatibility with `app.use`\n      // and `app.route`.\n\n      // we need at least 9 elements to expose non-stability\n      // of the built-in sort function\n      const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];\n      numbers.forEach(function(n) {\n        app.middleware('routes', namedHandler(n));\n      });\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done;\n\n        expect(steps).to.eql(numbers);\n\n        done();\n      });\n    });\n\n    it('correctly mounts express apps', function(done) {\n      let data, mountWasEmitted;\n      const subapp = express();\n      subapp.use(function(req, res, next) {\n        data = {\n          mountpath: req.app.mountpath,\n          parent: req.app.parent,\n        };\n\n        next();\n      });\n      subapp.on('mount', function() { mountWasEmitted = true; });\n\n      app.middleware('routes', '/mountpath', subapp);\n\n      executeMiddlewareHandlers(app, '/mountpath/test', function(err) {\n        if (err) return done(err);\n\n        expect(mountWasEmitted, 'mountWasEmitted').to.be.true();\n        expect(data).to.eql({\n          mountpath: '/mountpath',\n          parent: app,\n        });\n\n        done();\n      });\n    });\n\n    it('restores req & res on return from mounted express app', function(done) {\n      const expected = {};\n      const actual = {};\n\n      const subapp = express();\n      subapp.use(function verifyTestAssumptions(req, res, next) {\n        expect(req.__proto__).to.not.equal(expected.req);\n        expect(res.__proto__).to.not.equal(expected.res);\n\n        next();\n      });\n\n      app.middleware('initial', function saveOriginalValues(req, res, next) {\n        expected.req = req.__proto__;\n        expected.res = res.__proto__;\n\n        next();\n      });\n      app.middleware('routes', subapp);\n      app.middleware('final', function saveActualValues(req, res, next) {\n        actual.req = req.__proto__;\n        actual.res = res.__proto__;\n\n        next();\n      });\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(actual.req, 'req').to.equal(expected.req);\n        expect(actual.res, 'res').to.equal(expected.res);\n\n        done();\n      });\n    });\n\n    function namedHandler(name) {\n      return function(req, res, next) {\n        steps.push(name);\n        next();\n      };\n    }\n\n    function pathSavingHandler() {\n      return function(req, res, next) {\n        steps.push(req.originalUrl);\n\n        next();\n      };\n    }\n\n    function getObjectAndPrototypeKeys(obj) {\n      const result = [];\n      for (const k in obj) {\n        result.push(k);\n      }\n      result.sort();\n      return result;\n    }\n  });\n\n  describe.onServer('.middlewareFromConfig', function() {\n    it('provides API for loading middleware from JSON config', function(done) {\n      const steps = [];\n      const expectedConfig = {key: 'value'};\n\n      const handlerFactory = function() {\n        const args = Array.prototype.slice.apply(arguments);\n        return function(req, res, next) {\n          steps.push(args);\n\n          next();\n        };\n      };\n\n      // Config as an object (single arg)\n      app.middlewareFromConfig(handlerFactory, {\n        enabled: true,\n        phase: 'session',\n        params: expectedConfig,\n      });\n\n      // Config as a value (single arg)\n      app.middlewareFromConfig(handlerFactory, {\n        enabled: true,\n        phase: 'session:before',\n        params: 'before',\n      });\n\n      // Config as a list of args\n      app.middlewareFromConfig(handlerFactory, {\n        enabled: true,\n        phase: 'session:after',\n        params: ['after', 2],\n      });\n\n      // Disabled by configuration\n      app.middlewareFromConfig(handlerFactory, {\n        enabled: false,\n        phase: 'initial',\n        params: null,\n      });\n\n      // This should be triggered with matching verbs\n      app.middlewareFromConfig(handlerFactory, {\n        enabled: true,\n        phase: 'routes:before',\n        methods: ['get', 'head'],\n        params: {x: 1},\n      });\n\n      // This should be skipped as the verb doesn't match\n      app.middlewareFromConfig(handlerFactory, {\n        enabled: true,\n        phase: 'routes:before',\n        methods: ['post'],\n        params: {x: 2},\n      });\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(steps).to.eql([\n          ['before'],\n          [expectedConfig],\n          ['after', 2],\n          [{x: 1}],\n        ]);\n\n        done();\n      });\n    });\n\n    it('scopes middleware from config to a list of scopes', function(done) {\n      const steps = [];\n      app.middlewareFromConfig(\n        function factory() {\n          return function(req, res, next) {\n            steps.push(req.originalUrl);\n\n            next();\n          };\n        },\n        {\n          phase: 'initial',\n          paths: ['/scope', /^\\/(a|b)/],\n        },\n      );\n\n      async.eachSeries(\n        ['/', '/a', '/b', '/c', '/scope', '/other'],\n        function(url, next) { executeMiddlewareHandlers(app, url, next); },\n        function(err) {\n          if (err) return done(err);\n\n          expect(steps).to.eql(['/a', '/b', '/scope']);\n\n          done();\n        },\n      );\n    });\n  });\n\n  describe.onServer('.defineMiddlewarePhases(nameOrArray)', function() {\n    let app;\n    beforeEach(function() {\n      app = loopback();\n    });\n\n    it('adds the phase just before `routes` by default', function(done) {\n      app.defineMiddlewarePhases('custom');\n      verifyMiddlewarePhases(['custom', 'routes'], done);\n    });\n\n    it('merges phases adding to the start of the list', function(done) {\n      app.defineMiddlewarePhases(['first', 'routes', 'subapps']);\n      verifyMiddlewarePhases([\n        'first',\n        'initial', // this was the original first phase\n        'routes',\n        'subapps',\n      ], done);\n    });\n\n    it('merges phases preserving the order', function(done) {\n      app.defineMiddlewarePhases([\n        'initial',\n        'postinit', 'preauth', // add\n        'auth', 'routes',\n        'subapps', // add\n        'final',\n        'last', // add\n      ]);\n      verifyMiddlewarePhases([\n        'initial',\n        'postinit', 'preauth', // new\n        'auth', 'routes',\n        'subapps', // new\n        'files', 'final',\n        'last', // new\n      ], done);\n    });\n\n    it('throws helpful error on ordering conflict', function() {\n      app.defineMiddlewarePhases(['first', 'second']);\n      expect(function() { app.defineMiddlewarePhases(['second', 'first']); })\n        .to.throw(/Ordering conflict.*first.*second/);\n    });\n\n    function verifyMiddlewarePhases(names, done) {\n      const steps = [];\n      names.forEach(function(it) {\n        app.middleware(it, function(req, res, next) {\n          steps.push(it);\n\n          next();\n        });\n      });\n\n      executeMiddlewareHandlers(app, function(err) {\n        if (err) return done(err);\n\n        expect(steps).to.eql(names);\n\n        done();\n      });\n    }\n  });\n\n  describe('app.model(Model)', function() {\n    let app, db, MyTestModel;\n    beforeEach(function() {\n      app = loopback();\n      app.set('remoting', {errorHandler: {debug: true, log: false}});\n      db = loopback.createDataSource({connector: loopback.Memory});\n      MyTestModel = app.registry.createModel('MyTestModel');\n    });\n\n    it('Expose a `Model` to remote clients', function() {\n      const Color = PersistedModel.extend('color', {name: String});\n      app.model(Color);\n      Color.attachTo(db);\n\n      expect(app.models()).to.eql([Color]);\n    });\n\n    it('uses singular name as app.remoteObjects() key', function() {\n      const Color = PersistedModel.extend('color', {name: String});\n      app.model(Color);\n      Color.attachTo(db);\n      expect(app.remoteObjects()).to.eql({color: Color});\n    });\n\n    it('uses singular name as shared class name', function() {\n      const Color = PersistedModel.extend('color', {name: String});\n      app.model(Color);\n      Color.attachTo(db);\n      const classes = app.remotes().classes().map(function(c) { return c.name; });\n      expect(classes).to.contain('color');\n    });\n\n    it('registers existing models to app.models', function() {\n      const Color = db.createModel('color', {name: String});\n      app.model(Color);\n      expect(Color.app).to.be.equal(app);\n      expect(Color.shared).to.equal(true);\n      expect(app.models.color).to.equal(Color);\n      expect(app.models.Color).to.equal(Color);\n    });\n\n    it('emits a `modelRemoted` event', function() {\n      const Color = PersistedModel.extend('color', {name: String});\n      Color.shared = true;\n      let remotedClass;\n      app.on('modelRemoted', function(sharedClass) {\n        remotedClass = sharedClass;\n      });\n      app.model(Color);\n      expect(remotedClass).to.exist();\n      expect(remotedClass).to.eql(Color.sharedClass);\n    });\n\n    it('emits a `remoteMethodDisabled` event', function() {\n      const Color = PersistedModel.extend('color', {name: String});\n      Color.shared = true;\n      let remoteMethodDisabledClass, disabledRemoteMethod;\n      app.on('remoteMethodDisabled', function(sharedClass, methodName) {\n        remoteMethodDisabledClass = sharedClass;\n        disabledRemoteMethod = methodName;\n      });\n      app.model(Color);\n      app.models.Color.disableRemoteMethodByName('findOne');\n      expect(remoteMethodDisabledClass).to.exist();\n      expect(remoteMethodDisabledClass).to.eql(Color.sharedClass);\n      expect(disabledRemoteMethod).to.exist();\n      expect(disabledRemoteMethod).to.eql('findOne');\n    });\n\n    it('emits a `remoteMethodAdded` event', function() {\n      app.dataSource('db', {connector: 'memory'});\n      const Book = app.registry.createModel(\n        'Book',\n        {name: 'string'},\n        {plural: 'books'},\n      );\n      app.model(Book, {dataSource: 'db'});\n\n      const Page = app.registry.createModel(\n        'Page',\n        {name: 'string'},\n        {plural: 'pages'},\n      );\n      app.model(Page, {dataSource: 'db'});\n\n      Book.hasMany(Page);\n\n      let remoteMethodAddedClass;\n      app.on('remoteMethodAdded', function(sharedClass) {\n        remoteMethodAddedClass = sharedClass;\n      });\n      Book.nestRemoting('pages');\n      expect(remoteMethodAddedClass).to.exist();\n      expect(remoteMethodAddedClass).to.eql(Book.sharedClass);\n    });\n\n    it('accepts null dataSource', function(done) {\n      app.model(MyTestModel, {dataSource: null});\n      expect(MyTestModel.dataSource).to.eql(null);\n      done();\n    });\n\n    it('accepts false dataSource', function(done) {\n      app.model(MyTestModel, {dataSource: false});\n      expect(MyTestModel.getDataSource()).to.eql(null);\n      done();\n    });\n\n    it('does not require dataSource', function(done) {\n      app.model(MyTestModel);\n      done();\n    });\n\n    it('throws error if model typeof string is passed', function() {\n      const fn = function() { app.model('MyTestModel'); };\n      expect(fn).to.throw(/app(\\.model|\\.registry)/);\n    });\n  });\n\n  describe('app.model(ModelCtor, config)', function() {\n    it('attaches the model to a datasource', function() {\n      const previousModel = loopback.registry.findModel('TestModel');\n      app.dataSource('db', {connector: 'memory'});\n\n      if (previousModel) {\n        delete previousModel.dataSource;\n      }\n\n      assert(!previousModel || !previousModel.dataSource);\n      const TestModel = app.registry.createModel('TestModel');\n      app.model(TestModel, {dataSource: 'db'});\n      expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db);\n    });\n  });\n\n  describe('app.deleteModelByName()', () => {\n    let TestModel;\n    beforeEach(setupTestModel);\n\n    it('removes the model from app registries', () => {\n      expect(Object.keys(app.models))\n        .to.contain('test-model')\n        .and.contain('TestModel')\n        .and.contain('testModel');\n      expect(app.models().map(m => m.modelName))\n        .to.contain('test-model');\n\n      app.deleteModelByName('test-model');\n\n      expect(Object.keys(app.models))\n        .to.not.contain('test-model')\n        .and.not.contain('TestModel')\n        .and.not.contain('testModel');\n      expect(app.models().map(m => m.modelName))\n        .to.not.contain('test-model');\n    });\n\n    it('removes the model from juggler registries', () => {\n      expect(Object.keys(app.registry.modelBuilder.models))\n        .to.contain('test-model');\n\n      app.deleteModelByName('test-model');\n\n      expect(Object.keys(app.registry.modelBuilder.models))\n        .to.not.contain('test-model');\n    });\n\n    it('removes the model from remoting registries', () => {\n      expect(Object.keys(app.remotes()._classes))\n        .to.contain('test-model');\n\n      app.deleteModelByName('test-model');\n\n      expect(Object.keys(app.remotes()._classes))\n        .to.not.contain('test-model');\n    });\n\n    it('emits \"modelDeleted\" event', () => {\n      const spy = sinon.spy();\n      app.on('modelDeleted', spy);\n\n      app.deleteModelByName('test-model');\n\n      sinon.assert.calledWith(spy, TestModel);\n    });\n\n    function setupTestModel() {\n      TestModel = app.registry.createModel({\n        name: 'test-model',\n        base: 'Model',\n      });\n      app.model(TestModel, {dataSource: null});\n    }\n  });\n\n  describe('app.models', function() {\n    it('is unique per app instance', function() {\n      app.dataSource('db', {connector: 'memory'});\n      const Color = app.registry.createModel('Color');\n      app.model(Color, {dataSource: 'db'});\n      expect(app.models.Color).to.equal(Color);\n      const anotherApp = loopback();\n      expect(anotherApp.models.Color).to.equal(undefined);\n    });\n  });\n\n  describe('app.dataSources', function() {\n    it('is unique per app instance', function() {\n      app.dataSource('ds', {connector: 'memory'});\n      expect(app.datasources.ds).to.not.equal(undefined);\n      const anotherApp = loopback();\n      expect(anotherApp.datasources.ds).to.equal(undefined);\n    });\n  });\n\n  describe('app.dataSource', function() {\n    it('looks up the connector in `app.connectors`', function() {\n      app.connector('custom', loopback.Memory);\n      app.dataSource('custom', {connector: 'custom'});\n      expect(app.dataSources.custom.name).to.equal('custom');\n    });\n\n    it('adds data source name to error messages', function() {\n      app.connector('throwing', {\n        initialize: function() { throw new Error('expected test error'); },\n      });\n\n      expect(function() {\n        app.dataSource('bad-ds', {connector: 'throwing'});\n      }).to.throw(/bad-ds.*throwing/);\n    });\n\n    it('adds app reference to the data source object', function() {\n      app.dataSource('ds', {connector: 'memory'});\n      expect(app.datasources.ds.app).to.not.equal(undefined);\n      expect(app.datasources.ds.app).to.equal(app);\n    });\n  });\n\n  describe.onServer('listen()', function() {\n    it('starts http server', function(done) {\n      const app = loopback();\n      app.set('port', 0);\n      app.get('/', function(req, res) { res.status(200).send('OK'); });\n\n      const server = app.listen();\n\n      expect(server).to.be.an.instanceOf(require('http').Server);\n\n      request(server)\n        .get('/')\n        .expect(200, done);\n    });\n\n    it('updates port on `listening` event', function(done) {\n      const app = loopback();\n      app.set('port', 0);\n\n      app.listen(function() {\n        expect(app.get('port'), 'port').to.not.equal(0);\n\n        done();\n      });\n    });\n\n    it('updates `url` on `listening` event', function(done) {\n      const app = loopback();\n      app.set('port', 0);\n      app.set('host', undefined);\n\n      app.listen(function() {\n        const expectedUrl = 'http://localhost:' + app.get('port') + '/';\n        expect(app.get('url'), 'url').to.equal(expectedUrl);\n\n        done();\n      });\n    });\n\n    it('forwards to http.Server.listen on more than one arg', function(done) {\n      const app = loopback();\n      app.set('port', 1);\n      app.listen(0, '127.0.0.1', function() {\n        expect(app.get('port'), 'port').to.not.equal(0).and.not.equal(1);\n        expect(this.address().address).to.equal('127.0.0.1');\n\n        done();\n      });\n    });\n\n    it('forwards to http.Server.listen when the single arg is not a function', function(done) {\n      const app = loopback();\n      app.set('port', 1);\n      app.listen(0).on('listening', function() {\n        expect(app.get('port'), 'port') .to.not.equal(0).and.not.equal(1);\n\n        done();\n      });\n    });\n\n    it('uses app config when no parameter is supplied', function(done) {\n      const app = loopback();\n      // Http listens on all interfaces by default\n      // Custom host serves as an indicator whether\n      // the value was used by app.listen\n      app.set('host', '127.0.0.1');\n      app.listen()\n        .on('listening', function() {\n          expect(this.address().address).to.equal('127.0.0.1');\n\n          done();\n        });\n    });\n  });\n\n  describe.onServer('enableAuth', function() {\n    it('should set app.isAuthEnabled to true', function() {\n      expect(app.isAuthEnabled).to.not.equal(true);\n      app.enableAuth();\n      expect(app.isAuthEnabled).to.equal(true);\n    });\n\n    it('auto-configures required models to provided dataSource', function() {\n      const AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];\n      const app = loopback({localRegistry: true, loadBuiltinModels: true});\n      require('../lib/builtin-models')(app.registry);\n      const db = app.dataSource('db', {connector: 'memory'});\n\n      app.enableAuth({dataSource: 'db'});\n\n      expect(Object.keys(app.models)).to.include.members(AUTH_MODELS);\n\n      AUTH_MODELS.forEach(function(m) {\n        const Model = app.models[m];\n        expect(Model.dataSource, m + '.dataSource').to.equal(db);\n        expect(Model.shared, m + '.shared').to.equal(m === 'User');\n      });\n    });\n\n    it('detects already configured subclass of a required model', function() {\n      const app = loopback({localRegistry: true, loadBuiltinModels: true});\n      const db = app.dataSource('db', {connector: 'memory'});\n      const Customer = app.registry.createModel('Customer', {}, {base: 'User'});\n      app.model(Customer, {dataSource: 'db'});\n\n      // Fix AccessToken's \"belongsTo user\" relation to use our new Customer model\n      const AccessToken = app.registry.getModel('AccessToken');\n      AccessToken.settings.relations.user.model = 'Customer';\n\n      app.enableAuth({dataSource: 'db'});\n\n      expect(Object.keys(app.models)).to.not.include('User');\n    });\n  });\n\n  describe.onServer('app.get(\\'/\\', loopback.status())', function() {\n    it('should return the status of the application', function(done) {\n      const app = loopback();\n      app.get('/', loopback.status());\n      request(app)\n        .get('/')\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.an('object');\n          expect(res.body).to.have.property('started');\n          expect(res.body.uptime, 'uptime').to.be.gte(0);\n\n          const elapsed = Date.now() - Number(new Date(res.body.started));\n\n          // elapsed should be a small positive number...\n          expect(elapsed, 'elapsed').to.be.within(0, 300);\n\n          done();\n        });\n    });\n  });\n\n  describe('app.connectors', function() {\n    it('is unique per app instance', function() {\n      app.connectors.foo = 'bar';\n      const anotherApp = loopback();\n      expect(anotherApp.connectors.foo).to.equal(undefined);\n    });\n\n    it('includes Remote connector', function() {\n      expect(app.connectors.remote).to.equal(loopback.Remote);\n    });\n\n    it('includes Memory connector', function() {\n      expect(app.connectors.memory).to.equal(loopback.Memory);\n    });\n  });\n\n  describe('app.connector', function() {\n    // any connector will do\n    it('adds the connector to the registry', function() {\n      app.connector('foo-bar', loopback.Memory);\n      expect(app.connectors['foo-bar']).to.equal(loopback.Memory);\n    });\n\n    it('adds a classified alias', function() {\n      app.connector('foo-bar', loopback.Memory);\n      expect(app.connectors.FooBar).to.equal(loopback.Memory);\n    });\n\n    it('adds a camelized alias', function() {\n      app.connector('FOO-BAR', loopback.Memory);\n      expect(app.connectors.FOOBAR).to.equal(loopback.Memory);\n    });\n  });\n\n  describe('app.settings', function() {\n    it('can be altered via `app.set(key, value)`', function() {\n      app.set('write-key', 'write-value');\n      expect(app.settings).to.have.property('write-key', 'write-value');\n    });\n\n    it('can be read via `app.get(key)`', function() {\n      app.settings['read-key'] = 'read-value';\n      expect(app.get('read-key')).to.equal('read-value');\n    });\n\n    it('is unique per app instance', function() {\n      const app1 = loopback();\n      const app2 = loopback();\n\n      expect(app1.settings).to.not.equal(app2.settings);\n\n      app1.set('key', 'value');\n      expect(app2.get('key'), 'app2 value').to.equal(undefined);\n    });\n  });\n\n  it('exposes loopback as a property', function() {\n    const app = loopback();\n    expect(app.loopback).to.equal(loopback);\n  });\n\n  function setupUserModels(app, options, done) {\n    app.dataSource('db', {connector: 'memory'});\n    const UserAccount = app.registry.createModel(\n      'UserAccount',\n      {name: 'string'},\n      options,\n    );\n    const UserRole = app.registry.createModel(\n      'UserRole',\n      {name: 'string'},\n    );\n    app.model(UserAccount, {dataSource: 'db'});\n    app.model(UserRole, {dataSource: 'db'});\n    UserAccount.hasMany(UserRole);\n    UserAccount.create({\n      name: 'user',\n    }, function(err, user) {\n      if (err) return done(err);\n      app.use(loopback.rest());\n      done();\n    });\n  }\n\n  describe('Model-level normalizeHttpPath option', function() {\n    let app;\n    beforeEach(function() {\n      app = loopback();\n    });\n\n    it.onServer('honours Model-level setting of `false`', function(done) {\n      setupUserModels(app, {\n        remoting: {normalizeHttpPath: false},\n      }, function(err) {\n        if (err) return done(err);\n        request(app).get('/UserAccounts').expect(200, function(err) {\n          if (err) return done(err);\n          request(app).get('/UserAccounts/1/userRoles').expect(200, function(err) {\n            if (err) return done(err);\n            done();\n          });\n        });\n      });\n    });\n\n    it.onServer('honours Model-level setting of `true`', function(done) {\n      setupUserModels(app, {\n        remoting: {normalizeHttpPath: true},\n      }, function(err) {\n        if (err) return done(err);\n        request(app).get('/user-accounts').expect(200, function(err) {\n          if (err) return done(err);\n          request(app).get('/user-accounts/1/user-roles').expect(200, function(err) {\n            if (err) return done(err);\n            done();\n          });\n        });\n      });\n    });\n  });\n  describe('app-level normalizeHttpPath option', function() {\n    let app;\n    beforeEach(function() {\n      app = loopback();\n    });\n\n    it.onServer('honours app-level setting of `false`', function(done) {\n      app.set('remoting', {rest: {normalizeHttpPath: false}});\n      setupUserModels(app, null, function(err) {\n        if (err) return done(err);\n        request(app).get('/UserAccounts').expect(200, function(err) {\n          if (err) return done(err);\n          request(app).get('/UserAccounts/1/userRoles').expect(200, function(err) {\n            if (err) return done(err);\n            done();\n          });\n        });\n      });\n    });\n\n    it.onServer('honours app-level setting of `true`', function(done) {\n      app.set('remoting', {rest: {normalizeHttpPath: true}});\n      setupUserModels(app, null, function(err) {\n        if (err) return done(err);\n        request(app).get('/user-accounts').expect(200, function(err) {\n          if (err) return done(err);\n          request(app).get('/user-accounts/1/user-roles').expect(200, function(err) {\n            if (err) return done(err);\n            done();\n          });\n        });\n      });\n    });\n  });\n\n  describe('Model-level and app-level normalizeHttpPath options', function() {\n    let app;\n    beforeEach(function() {\n      app = loopback();\n    });\n\n    it.onServer('prioritizes Model-level setting over the app-level one', function(done) {\n      app.set('remoting', {rest: {normalizeHttpPath: true}});\n      setupUserModels(app, {\n        remoting: {normalizeHttpPath: false},\n      }, function(err) {\n        if (err) return done(err);\n        request(app).get('/UserAccounts').expect(200, function(err) {\n          if (err) return done(err);\n          request(app).get('/UserAccounts/1/userRoles').expect(200, function(err) {\n            if (err) return done(err);\n            done();\n          });\n        });\n      });\n    });\n  });\n});\n\nfunction executeMiddlewareHandlers(app, urlPath, callback) {\n  let handlerError = undefined;\n  const server = http.createServer(function(req, res) {\n    app.handle(req, res, function(err) {\n      if (err) {\n        handlerError = err;\n        res.statusCode = err.status || err.statusCode || 500;\n        res.end(err.stack || err);\n      } else {\n        res.statusCode = 204;\n        res.end();\n      }\n    });\n  });\n\n  if (callback === undefined && typeof urlPath === 'function') {\n    callback = urlPath;\n    urlPath = '/test/url';\n  }\n\n  request(server)\n    .get(urlPath)\n    .end(function(err) {\n      callback(handlerError || err);\n    });\n}\n"
  },
  {
    "path": "test/authorization-scopes.test.js",
    "content": "// Copyright IBM Corp. 2017,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst loopback = require('../');\nconst supertest = require('supertest');\nconst strongErrorHandler = require('strong-error-handler');\nconst loggers = require('./helpers/error-loggers');\n\nconst logAllServerErrors = loggers.logAllServerErrors;\nconst logServerErrorsOtherThan = loggers.logServerErrorsOtherThan;\n\ndescribe('Authorization scopes', () => {\n  const CUSTOM_SCOPE = 'read:custom';\n\n  let app, request, User, testUser, regularToken, scopedToken;\n  beforeEach(givenAppAndRequest);\n  beforeEach(givenRemoteMethodWithCustomScope);\n  beforeEach(givenUser);\n  beforeEach(givenDefaultToken);\n  beforeEach(givenScopedToken);\n\n  it('denies regular token to invoke custom-scoped method', () => {\n    logServerErrorsOtherThan(401, app);\n    return request.get('/users/scoped')\n      .set('Authorization', regularToken.id)\n      .expect(401);\n  });\n\n  it('allows regular tokens to invoke default-scoped method', () => {\n    logAllServerErrors(app);\n    return request.get('/users/' + testUser.id)\n      .set('Authorization', regularToken.id)\n      .expect(200);\n  });\n\n  it('allows scoped token to invoke custom-scoped method', () => {\n    logAllServerErrors(app);\n    return request.get('/users/scoped')\n      .set('Authorization', scopedToken.id)\n      .expect(204);\n  });\n\n  it('denies scoped token to invoke default-scoped method', () => {\n    logServerErrorsOtherThan(401, app);\n    return request.get('/users/' + testUser.id)\n      .set('Authorization', scopedToken.id)\n      .expect(401);\n  });\n\n  describe('token granted both default and custom scope', () => {\n    beforeEach('given token with default and custom scope',\n      () => givenScopedToken(['DEFAULT', CUSTOM_SCOPE]));\n    beforeEach(() => logAllServerErrors(app));\n\n    it('allows invocation of default-scoped method', () => {\n      return request.get('/users/' + testUser.id)\n        .set('Authorization', scopedToken.id)\n        .expect(200);\n    });\n\n    it('allows invocation of custom-scoped method', () => {\n      return request.get('/users/scoped')\n        .set('Authorization', scopedToken.id)\n        .expect(204);\n    });\n  });\n\n  it('allows invocation when at least one method scope is matched', () => {\n    givenRemoteMethodWithCustomScope(['read', 'write']);\n    return givenScopedToken(['read', 'execute']).then(() => {\n      return request.get('/users/scoped')\n        .set('Authorization', scopedToken.id)\n        .expect(204);\n    });\n  });\n\n  function givenAppAndRequest() {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.set('remoting', {rest: {handleErrors: false}});\n    app.dataSource('db', {connector: 'memory'});\n    app.enableAuth({dataSource: 'db'});\n    request = supertest(app);\n\n    app.use(loopback.rest());\n\n    User = app.models.User;\n  }\n\n  function givenRemoteMethodWithCustomScope() {\n    // Delete any previously registered instance of the method \"scoped\"\n    User.sharedClass._methods = User.sharedClass._methods\n      .filter(m => m.name !== 'scoped');\n\n    const accessScopes = arguments[0] || [CUSTOM_SCOPE];\n    User.scoped = function(cb) { cb(); };\n    User.remoteMethod('scoped', {\n      accessScopes,\n      http: {verb: 'GET', path: '/scoped'},\n    });\n    User.settings.acls.push({\n      principalType: 'ROLE',\n      principalId: '$authenticated',\n      permission: 'ALLOW',\n      property: 'scoped',\n      accessType: 'EXECUTE',\n    });\n  }\n\n  function givenUser() {\n    return User.create({email: 'test@example.com', password: 'pass'})\n      .then(u => testUser = u);\n  }\n\n  function givenDefaultToken() {\n    return testUser.createAccessToken(60)\n      .then(t => regularToken = t);\n  }\n\n  function givenScopedToken() {\n    const scopes = arguments[0] || [CUSTOM_SCOPE];\n    return testUser.accessTokens.create({ttl: 60, scopes})\n      .then(t => scopedToken = t);\n  }\n});\n"
  },
  {
    "path": "test/change-stream.test.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst expect = require('./helpers/expect');\nconst sinon = require('sinon');\nconst loopback = require('../');\n\ndescribe('PersistedModel.createChangeStream()', function() {\n  describe('configured to source changes locally', function() {\n    before(function() {\n      const test = this;\n      const app = loopback({localRegistry: true});\n      const ds = app.dataSource('ds', {connector: 'memory'});\n      const Score = app.registry.createModel('Score');\n      this.Score = app.model(Score, {\n        dataSource: 'ds',\n        changeDataSource: false, // use only local observers\n      });\n    });\n\n    afterEach(verifyObserversRemoval);\n\n    it('should detect create', function(done) {\n      const Score = this.Score;\n\n      Score.createChangeStream(function(err, changes) {\n        changes.on('data', function(change) {\n          expect(change.type).to.equal('create');\n          changes.destroy();\n          done();\n        });\n\n        Score.create({team: 'foo'});\n      });\n    });\n\n    it('should detect update', function(done) {\n      const Score = this.Score;\n      Score.create({team: 'foo'}, function(err, newScore) {\n        Score.createChangeStream(function(err, changes) {\n          changes.on('data', function(change) {\n            expect(change.type).to.equal('update');\n            changes.destroy();\n\n            done();\n          });\n          newScore.updateAttributes({\n            bat: 'baz',\n          });\n        });\n      });\n    });\n\n    it('should detect delete', function(done) {\n      const Score = this.Score;\n      Score.create({team: 'foo'}, function(err, newScore) {\n        Score.createChangeStream(function(err, changes) {\n          changes.on('data', function(change) {\n            expect(change.type).to.equal('remove');\n            changes.destroy();\n\n            done();\n          });\n\n          newScore.remove();\n        });\n      });\n    });\n\n    it('should apply \"where\" and \"fields\" to create events', function() {\n      const Score = this.Score;\n      const data = [\n        {team: 'baz', player: 'baz', value: 1},\n        {team: 'bar', player: 'baz', value: 2},\n        {team: 'foo', player: 'bar', value: 3},\n      ];\n      const options = {where: {player: 'bar'}, fields: ['team', 'value']};\n      const changes = [];\n      let changeStream;\n\n      return Score.createChangeStream(options)\n        .then(stream => {\n          changeStream = stream;\n          changeStream.on('data', function(change) {\n            changes.push(change);\n          });\n\n          return Score.create(data);\n        })\n        .then(scores => {\n          changeStream.destroy();\n\n          expect(changes).to.have.length(1);\n          expect(changes[0]).to.have.property('type', 'create');\n          expect(changes[0].data).to.eql({\n            'team': 'foo',\n            value: 3,\n          });\n        });\n    });\n\n    it('should not emit changes after destroy', function(done) {\n      const Score = this.Score;\n\n      const spy = sinon.spy();\n\n      Score.createChangeStream(function(err, changes) {\n        changes.on('data', function() {\n          spy();\n          changes.destroy();\n        });\n\n        Score.create({team: 'foo'})\n          .then(() => Score.deleteAll())\n          .then(() => {\n            expect(spy.calledOnce);\n            done();\n          });\n      });\n    });\n\n    function verifyObserversRemoval() {\n      const Score = this.Score;\n      expect(Score._observers['after save']).to.be.empty();\n      expect(Score._observers['after delete']).to.be.empty();\n    }\n  });\n\n  // TODO(ritch) implement multi-server support\n  describe.skip('configured to source changes using pubsub', function() {\n    before(function() {\n      const test = this;\n      const app = loopback({localRegistry: true});\n      const db = app.dataSource('ds', {connector: 'memory'});\n      const ps = app.dataSource('ps', {\n        host: 'localhost',\n        port: '12345',\n        connector: 'pubsub',\n        pubsubAdapter: 'mqtt',\n      });\n      this.Score = app.model('Score', {\n        dataSource: 'db',\n        changeDataSource: 'ps',\n      });\n    });\n\n    it('should detect a change', function(done) {\n      const Score = this.Score;\n\n      Score.createChangeStream(function(err, changes) {\n        changes.on('data', function(change) {\n          done();\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/change.test.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst async = require('async');\nconst expect = require('./helpers/expect');\nconst loopback = require('../');\n\ndescribe('Change', function() {\n  let Change, TestModel;\n\n  beforeEach(function() {\n    const memory = loopback.createDataSource({\n      connector: loopback.Memory,\n    });\n    TestModel = loopback.PersistedModel.extend('ChangeTestModel',\n      {\n        id: {id: true, type: 'string', defaultFn: 'guid'},\n      },\n      {\n        trackChanges: true,\n      });\n    this.modelName = TestModel.modelName;\n    TestModel.attachTo(memory);\n    Change = TestModel.getChangeModel();\n  });\n\n  beforeEach(function(done) {\n    const test = this;\n    test.data = {\n      foo: 'bar',\n    };\n    TestModel.create(test.data, function(err, model) {\n      if (err) return done(err);\n\n      test.model = model;\n      test.modelId = model.id;\n      test.revisionForModel = Change.revisionForInst(model);\n\n      done();\n    });\n  });\n\n  describe('Change.getCheckpointModel()', function() {\n    it('Shouldnt create two models if called twice', function() {\n      assert.equal(Change.getCheckpointModel(), Change.getCheckpointModel());\n    });\n  });\n\n  describe('change.id', function() {\n    it('should be a hash of the modelName and modelId', function() {\n      const change = new Change({\n        rev: 'abc',\n        modelName: 'foo',\n        modelId: 'bar',\n      });\n\n      const hash = Change.hash([change.modelName, change.modelId].join('-'));\n\n      assert.equal(change.id, hash);\n    });\n  });\n\n  describe('Change.rectifyModelChanges(modelName, modelIds, callback)', function() {\n    describe('using an existing untracked model', function() {\n      beforeEach(function(done) {\n        const test = this;\n        Change.rectifyModelChanges(this.modelName, [this.modelId], function(err, trackedChanges) {\n          if (err) return done(err);\n\n          done();\n        });\n      });\n\n      it('should create an entry', function(done) {\n        const test = this;\n        Change.find(function(err, trackedChanges) {\n          assert.equal(trackedChanges[0].modelId, test.modelId.toString());\n\n          done();\n        });\n      });\n\n      it('should only create one change', function(done) {\n        Change.count(function(err, count) {\n          assert.equal(count, 1);\n\n          done();\n        });\n      });\n    });\n  });\n\n  describe('Change.rectifyModelChanges - promise variant', function() {\n    describe('using an existing untracked model', function() {\n      beforeEach(function(done) {\n        const test = this;\n        Change.rectifyModelChanges(this.modelName, [this.modelId])\n          .then(function(trackedChanges) {\n            done();\n          })\n          .catch(done);\n      });\n\n      it('should create an entry', function(done) {\n        const test = this;\n        Change.find()\n          .then(function(trackedChanges) {\n            assert.equal(trackedChanges[0].modelId, test.modelId.toString());\n\n            done();\n          })\n          .catch(done);\n      });\n\n      it('should only create one change', function(done) {\n        Change.count()\n          .then(function(count) {\n            assert.equal(count, 1);\n\n            done();\n          })\n          .catch(done);\n      });\n    });\n  });\n\n  describe('Change.findOrCreateChange(modelName, modelId, callback)', function() {\n    describe('when a change doesnt exist', function() {\n      beforeEach(function(done) {\n        const test = this;\n        Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {\n          if (err) return done(err);\n\n          test.result = result;\n\n          done();\n        });\n      });\n\n      it('should create an entry', function(done) {\n        const test = this;\n        Change.findById(this.result.id, function(err, change) {\n          if (err) return done(err);\n\n          assert.equal(change.id, test.result.id);\n\n          done();\n        });\n      });\n    });\n\n    describe('when a change doesnt exist - promise variant', function() {\n      beforeEach(function(done) {\n        const test = this;\n        Change.findOrCreateChange(this.modelName, this.modelId)\n          .then(function(result) {\n            test.result = result;\n\n            done();\n          })\n          .catch(done);\n      });\n\n      it('should create an entry', function(done) {\n        const test = this;\n        Change.findById(this.result.id, function(err, change) {\n          if (err) return done(err);\n\n          assert.equal(change.id, test.result.id);\n\n          done();\n        });\n      });\n    });\n\n    describe('when a change does exist', function() {\n      beforeEach(function(done) {\n        const test = this;\n        Change.create({\n          modelName: test.modelName,\n          modelId: test.modelId,\n        }, function(err, change) {\n          test.existingChange = change;\n\n          done();\n        });\n      });\n\n      beforeEach(function(done) {\n        const test = this;\n        Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {\n          if (err) return done(err);\n\n          test.result = result;\n\n          done();\n        });\n      });\n\n      it('should find the entry', function(done) {\n        const test = this;\n        assert.equal(test.existingChange.id, test.result.id);\n\n        done();\n      });\n    });\n  });\n\n  describe('change.rectify(callback)', function() {\n    let change;\n    beforeEach(function(done) {\n      Change.findOrCreate(\n        {\n          modelName: this.modelName,\n          modelId: this.modelId,\n        },\n        function(err, ch) {\n          change = ch;\n\n          done(err);\n        },\n      );\n    });\n\n    it('should create a new change with the correct revision', function(done) {\n      const test = this;\n      change.rectify(function(err, ch) {\n        assert.equal(ch.rev, test.revisionForModel);\n\n        done();\n      });\n    });\n\n    // This test is a low-level equivalent of the test in replication.test.js\n    // called \"replicates multiple updates within the same CP\"\n    it('should merge updates within the same checkpoint', function(done) {\n      const test = this;\n      const originalRev = this.revisionForModel;\n      let cp;\n\n      async.series([\n        rectify,\n        checkpoint,\n        update,\n        rectify,\n        update,\n        rectify,\n        function(next) {\n          expect(change.checkpoint, 'checkpoint').to.equal(cp);\n          expect(change.type(), 'type').to.equal('update');\n          expect(change.prev, 'prev').to.equal(originalRev);\n          expect(change.rev, 'rev').to.equal(test.revisionForModel);\n\n          next();\n        },\n      ], done);\n\n      function rectify(next) {\n        change.rectify(next);\n      }\n\n      function checkpoint(next) {\n        TestModel.checkpoint(function(err, inst) {\n          if (err) return next(err);\n\n          cp = inst.seq;\n\n          next();\n        });\n      }\n\n      function update(next) {\n        const model = test.model;\n\n        model.name += 'updated';\n        model.save(function(err) {\n          test.revisionForModel = Change.revisionForInst(model);\n\n          next(err);\n        });\n      }\n    });\n\n    it('should not change checkpoint when rev is the same', function(done) {\n      const test = this;\n      const originalCheckpoint = change.checkpoint;\n      const originalRev = change.rev;\n\n      TestModel.checkpoint(function(err, inst) {\n        if (err) return done(err);\n\n        change.rectify(function(err, c) {\n          if (err) return done(err);\n\n          expect(c.rev, 'rev').to.equal(originalRev); // sanity check\n          expect(c.checkpoint, 'checkpoint').to.equal(originalCheckpoint);\n\n          done();\n        });\n      });\n    });\n  });\n\n  describe('change.rectify - promise variant', function() {\n    let change;\n    beforeEach(function(done) {\n      Change.findOrCreateChange(this.modelName, this.modelId)\n        .then(function(ch) {\n          change = ch;\n\n          done();\n        })\n        .catch(done);\n    });\n\n    it('should create a new change with the correct revision', function(done) {\n      const test = this;\n      change.rectify()\n        .then(function(ch) {\n          assert.equal(ch.rev, test.revisionForModel);\n          done();\n        })\n        .catch(done);\n    });\n  });\n\n  describe('change.currentRevision(callback)', function() {\n    it('should get the correct revision', function(done) {\n      const test = this;\n      const change = new Change({\n        modelName: this.modelName,\n        modelId: this.modelId,\n      });\n\n      change.currentRevision(function(err, rev) {\n        assert.equal(rev, test.revisionForModel);\n\n        done();\n      });\n    });\n  });\n\n  describe('change.currentRevision - promise variant', function() {\n    it('should get the correct revision', function(done) {\n      const test = this;\n      const change = new Change({\n        modelName: this.modelName,\n        modelId: this.modelId,\n      });\n\n      change.currentRevision()\n        .then(function(rev) {\n          assert.equal(rev, test.revisionForModel);\n\n          done();\n        })\n        .catch(done);\n    });\n  });\n\n  describe('Change.hash(str)', function() {\n    // todo(ritch) test other hashing algorithms\n    it('should hash the given string', function() {\n      const str = 'foo';\n      const hash = Change.hash(str);\n      assert(hash !== str);\n      assert(typeof hash === 'string');\n    });\n  });\n\n  describe('Change.revisionForInst(inst)', function() {\n    it('should return the same revision for the same data', function() {\n      const a = {\n        b: {\n          b: ['c', 'd'],\n          c: ['d', 'e'],\n        },\n      };\n      const b = {\n        b: {\n          c: ['d', 'e'],\n          b: ['c', 'd'],\n        },\n      };\n\n      const aRev = Change.revisionForInst(a);\n      const bRev = Change.revisionForInst(b);\n      assert.equal(aRev, bRev);\n    });\n  });\n\n  describe('change.type()', function() {\n    it('CREATE', function() {\n      const change = new Change({\n        rev: this.revisionForModel,\n      });\n      assert.equal(Change.CREATE, change.type());\n    });\n    it('UPDATE', function() {\n      const change = new Change({\n        rev: this.revisionForModel,\n        prev: this.revisionForModel,\n      });\n      assert.equal(Change.UPDATE, change.type());\n    });\n    it('DELETE', function() {\n      const change = new Change({\n        prev: this.revisionForModel,\n      });\n      assert.equal(Change.DELETE, change.type());\n    });\n    it('UNKNOWN', function() {\n      const change = new Change();\n      assert.equal(Change.UNKNOWN, change.type());\n    });\n  });\n\n  describe('change.getModelCtor()', function() {\n    it('should get the correct model class', function() {\n      const change = new Change({\n        modelName: this.modelName,\n      });\n\n      assert.equal(change.getModelCtor(), TestModel);\n    });\n  });\n\n  describe('change.equals(otherChange)', function() {\n    it('should return true when the change is equal', function() {\n      const change = new Change({\n        rev: this.revisionForModel,\n      });\n\n      const otherChange = new Change({\n        rev: this.revisionForModel,\n      });\n\n      assert.equal(change.equals(otherChange), true);\n    });\n\n    it('should return true when both changes are deletes', function() {\n      const REV = 'foo';\n      const change = new Change({\n        rev: null,\n        prev: REV,\n      });\n\n      const otherChange = new Change({\n        rev: undefined,\n        prev: REV,\n      });\n\n      assert.equal(change.type(), Change.DELETE);\n      assert.equal(otherChange.type(), Change.DELETE);\n\n      assert.equal(change.equals(otherChange), true);\n    });\n  });\n\n  describe('change.isBasedOn(otherChange)', function() {\n    it('should return true when the change is based on the other', function() {\n      const change = new Change({\n        prev: this.revisionForModel,\n      });\n\n      const otherChange = new Change({\n        rev: this.revisionForModel,\n      });\n\n      assert.equal(change.isBasedOn(otherChange), true);\n    });\n  });\n\n  describe('Change.diff(modelName, since, remoteChanges, callback)', function() {\n    beforeEach(function(done) {\n      Change.create([\n        {rev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1},\n        {rev: 'bar', modelName: this.modelName, modelId: 10, checkpoint: 1},\n        {rev: 'bat', modelName: this.modelName, modelId: 11, checkpoint: 1},\n      ], done);\n    });\n\n    it('should return delta and conflict lists', function(done) {\n      const remoteChanges = [\n        // an update => should result in a delta\n        {rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1},\n        // no change => should not result in a delta / conflict\n        {rev: 'bar', prev: 'bar', modelName: this.modelName, modelId: 10, checkpoint: 1},\n        // a conflict => should result in a conflict\n        {rev: 'bat2', prev: 'bat0', modelName: this.modelName, modelId: 11, checkpoint: 1},\n      ];\n\n      Change.diff(this.modelName, 0, remoteChanges, function(err, diff) {\n        if (err) return done(err);\n\n        assert.equal(diff.deltas.length, 1);\n        assert.equal(diff.conflicts.length, 1);\n\n        done();\n      });\n    });\n\n    it('should return delta and conflict lists - promise variant', function(done) {\n      const remoteChanges = [\n        // an update => should result in a delta\n        {rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1},\n        // no change => should not result in a delta / conflict\n        {rev: 'bar', prev: 'bar', modelName: this.modelName, modelId: 10, checkpoint: 1},\n        // a conflict => should result in a conflict\n        {rev: 'bat2', prev: 'bat0', modelName: this.modelName, modelId: 11, checkpoint: 1},\n      ];\n\n      Change.diff(this.modelName, 0, remoteChanges)\n        .then(function(diff) {\n          assert.equal(diff.deltas.length, 1);\n          assert.equal(diff.conflicts.length, 1);\n\n          done();\n        })\n        .catch(done);\n    });\n\n    it('should set \"prev\" to local revision in non-conflicting delta', function(done) {\n      const updateRecord = {\n        rev: 'foo-new',\n        prev: 'foo',\n        modelName: this.modelName,\n        modelId: '9',\n        checkpoint: 2,\n      };\n      Change.diff(this.modelName, 0, [updateRecord], function(err, diff) {\n        if (err) return done(err);\n\n        expect(diff.conflicts, 'conflicts').to.have.length(0);\n        expect(diff.deltas, 'deltas').to.have.length(1);\n        const actual = diff.deltas[0].toObject();\n        delete actual.id;\n        expect(actual).to.eql({\n          checkpoint: 2,\n          modelId: '9',\n          modelName: updateRecord.modelName,\n          prev: 'foo', // this is the current local revision\n          rev: 'foo-new',\n        });\n\n        done();\n      });\n    });\n\n    it('should set \"prev\" to local revision in remote-only delta', function(done) {\n      const updateRecord = {\n        rev: 'foo-new',\n        prev: 'foo-prev',\n        modelName: this.modelName,\n        modelId: '9',\n        checkpoint: 2,\n      };\n      // IMPORTANT: the diff call excludes the local change\n      // with rev=foo CP=1\n      Change.diff(this.modelName, 2, [updateRecord], function(err, diff) {\n        if (err) return done(err);\n\n        expect(diff.conflicts, 'conflicts').to.have.length(0);\n        expect(diff.deltas, 'deltas').to.have.length(1);\n        const actual = diff.deltas[0].toObject();\n        delete actual.id;\n        expect(actual).to.eql({\n          checkpoint: 2,\n          modelId: '9',\n          modelName: updateRecord.modelName,\n          prev: 'foo', // this is the current local revision\n          rev: 'foo-new',\n        });\n\n        done();\n      });\n    });\n\n    it('should set \"prev\" to null for a new instance', function(done) {\n      const updateRecord = {\n        rev: 'new-rev',\n        prev: 'new-prev',\n        modelName: this.modelName,\n        modelId: 'new-id',\n        checkpoint: 2,\n      };\n\n      Change.diff(this.modelName, 0, [updateRecord], function(err, diff) {\n        if (err) return done(err);\n\n        expect(diff.conflicts).to.have.length(0);\n        expect(diff.deltas).to.have.length(1);\n        const actual = diff.deltas[0].toObject();\n        delete actual.id;\n        expect(actual).to.eql({\n          checkpoint: 2,\n          modelId: 'new-id',\n          modelName: updateRecord.modelName,\n          prev: null, // this is the current local revision\n          rev: 'new-rev',\n        });\n\n        done();\n      });\n    });\n  });\n});\n\ndescribe('Change with with custom properties', function() {\n  let Change, TestModel;\n\n  beforeEach(function() {\n    const memory = loopback.createDataSource({\n      connector: loopback.Memory,\n    });\n\n    TestModel = loopback.PersistedModel.extend('ChangeTestModelWithTenant',\n      {\n        id: {id: true, type: 'string', defaultFn: 'guid'},\n        tenantId: 'string',\n      },\n      {\n        trackChanges: true,\n        additionalChangeModelProperties: {tenantId: 'string'},\n      });\n    this.modelName = TestModel.modelName;\n\n    TestModel.prototype.fillCustomChangeProperties = function(change, cb) {\n      const inst = this;\n\n      if (inst && inst.tenantId) {\n        change.tenantId = inst.tenantId;\n      } else {\n        change.tenantId = null;\n      }\n\n      cb();\n    };\n\n    TestModel.attachTo(memory);\n    TestModel._defineChangeModel();\n    Change = TestModel.getChangeModel();\n  });\n\n  describe('change.rectify', function() {\n    const TENANT_ID = '123';\n    let change;\n\n    beforeEach(givenChangeInstance);\n\n    it('stores the custom property in the Change instance', function() {\n      return change.rectify().then(function(ch) {\n        expect(ch.toObject()).to.have.property('tenantId', TENANT_ID);\n      });\n    });\n\n    function givenChangeInstance() {\n      const data = {\n        foo: 'bar',\n        tenantId: TENANT_ID,\n      };\n\n      return TestModel.create(data)\n        .then(function(model) {\n          const modelName = TestModel.modelName;\n          return Change.findOrCreateChange(modelName, model.id);\n        }).then(function(ch) {\n          change = ch;\n        });\n    }\n  });\n});\n"
  },
  {
    "path": "test/checkpoint.test.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst async = require('async');\nconst loopback = require('../');\nconst expect = require('./helpers/expect');\n\nconst Checkpoint = loopback.Checkpoint.extend('TestCheckpoint');\n\ndescribe('Checkpoint', function() {\n  describe('bumpLastSeq() and current()', function() {\n    beforeEach(function() {\n      const memory = loopback.createDataSource({\n        connector: loopback.Memory,\n      });\n      Checkpoint.attachTo(memory);\n    });\n\n    it('returns the highest `seq` value', function(done) {\n      async.series([\n        Checkpoint.bumpLastSeq.bind(Checkpoint),\n        Checkpoint.bumpLastSeq.bind(Checkpoint),\n        function(next) {\n          Checkpoint.current(function(err, seq) {\n            if (err) next(err);\n\n            expect(seq).to.equal(3);\n\n            next();\n          });\n        },\n      ], done);\n    });\n\n    it('Should be no race condition for current() when calling in parallel', function(done) {\n      async.parallel([\n        function(next) { Checkpoint.current(next); },\n        function(next) { Checkpoint.current(next); },\n      ], function(err, list) {\n        if (err) return done(err);\n\n        Checkpoint.find(function(err, data) {\n          if (err) return done(err);\n\n          expect(data).to.have.length(1);\n\n          done();\n        });\n      });\n    });\n\n    it('Should be no race condition for bumpLastSeq() when calling in parallel', function(done) {\n      async.parallel([\n        function(next) { Checkpoint.bumpLastSeq(next); },\n        function(next) { Checkpoint.bumpLastSeq(next); },\n      ], function(err, list) {\n        if (err) return done(err);\n\n        Checkpoint.find(function(err, data) {\n          if (err) return done(err);\n          // The invariant \"we have at most 1 checkpoint instance\" is preserved\n          // even when multiple calls are made in parallel\n          expect(data).to.have.length(1);\n          // There is a race condition here, we could end up with both 2 or 3 as the \"seq\".\n          // The current implementation of the memory connector always yields 2 though.\n          expect(data[0].seq).to.equal(2);\n          // In this particular case, since the new last seq is always 2, both results\n          // should be 2.\n          expect(list.map(function(it) { return it.seq; }))\n            .to.eql([2, 2]);\n\n          done();\n        });\n      });\n    });\n\n    it('Checkpoint.current() for non existing checkpoint should initialize checkpoint',\n      function(done) {\n        Checkpoint.current(function(err, seq) {\n          expect(seq).to.equal(1);\n\n          done(err);\n        });\n      });\n\n    it('bumpLastSeq() works when singleton instance does not exists yet', function(done) {\n      Checkpoint.bumpLastSeq(function(err, cp) {\n        // We expect `seq` to be 2 since `checkpoint` does not exist and\n        // `bumpLastSeq` for the first time not only initializes it to one,\n        // but also increments the initialized value by one.\n        expect(cp.seq).to.equal(2);\n\n        done(err);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/context-options.test.js",
    "content": "// Copyright IBM Corp. 2016,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst expect = require('chai').expect;\nconst loopback = require('..');\nconst supertest = require('supertest');\n\ndescribe('OptionsFromRemotingContext', function() {\n  let app, request, accessToken, userId, Product, actualOptions;\n\n  beforeEach(setupAppAndRequest);\n  beforeEach(resetActualOptions);\n\n  context('when making updates via REST', function() {\n    beforeEach(observeOptionsBeforeSave);\n\n    it('injects options to create()', function() {\n      return request.post('/products')\n        .send({name: 'Pen'})\n        .expect(200)\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to patchOrCreate()', function() {\n      return request.patch('/products')\n        .send({id: 1, name: 'Pen'})\n        .expect(200)\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to replaceOrCreate()', function() {\n      return request.put('/products')\n        .send({id: 1, name: 'Pen'})\n        .expect(200)\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to patchOrCreateWithWhere()', function() {\n      return request.post('/products/upsertWithWhere?where[name]=Pen')\n        .send({name: 'Pencil'})\n        .expect(200)\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to replaceById()', function() {\n      return Product.create({id: 1, name: 'Pen'})\n        .then(function(p) {\n          return request.put('/products/1')\n            .send({name: 'Pencil'})\n            .expect(200);\n        })\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to prototype.patchAttributes()', function() {\n      return Product.create({id: 1, name: 'Pen'})\n        .then(function(p) {\n          return request.patch('/products/1')\n            .send({name: 'Pencil'})\n            .expect(200);\n        })\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to updateAll()', function() {\n      return request.post('/products/update?where[name]=Pen')\n        .send({name: 'Pencil'})\n        .expect(200)\n        .then(expectInjectedOptions);\n    });\n  });\n\n  context('when deleting via REST', function() {\n    beforeEach(observeOptionsBeforeDelete);\n\n    it('injects options to deleteById()', function() {\n      return Product.create({id: 1, name: 'Pen'})\n        .then(function(p) {\n          return request.delete('/products/1').expect(200);\n        })\n        .then(expectInjectedOptions);\n    });\n  });\n\n  context('when querying via REST', function() {\n    beforeEach(observeOptionsOnAccess);\n    beforeEach(givenProductId1);\n\n    it('injects options to find()', function() {\n      return request.get('/products').expect(200)\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to findById()', function() {\n      return request.get('/products/1').expect(200)\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to findOne()', function() {\n      return request.get('/products/findOne?where[id]=1').expect(200)\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to exists()', function() {\n      return request.head('/products/1').expect(200)\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to count()', function() {\n      return request.get('/products/count').expect(200)\n        .then(expectInjectedOptions);\n    });\n  });\n\n  context('when invoking prototype methods', function() {\n    beforeEach(observeOptionsOnAccess);\n    beforeEach(givenProductId1);\n\n    it('injects options to sharedCtor', function() {\n      Product.prototype.dummy = function(cb) { cb(); };\n      Product.remoteMethod('prototype.dummy', {});\n      return request.post('/products/1/dummy').expect(204)\n        .then(expectInjectedOptions);\n    });\n  });\n\n  // Catch: because relations methods are defined on \"modelFrom\",\n  // they will invoke createOptionsFromRemotingContext on \"modelFrom\" too,\n  // despite the fact that under the hood a method on \"modelTo\" is called.\n\n  context('hasManyThrough', function() {\n    let Category, ThroughModel;\n\n    beforeEach(givenCategoryHasManyProductsThroughAnotherModel);\n    beforeEach(givenCategoryAndProduct);\n\n    it('injects options to findById', function() {\n      observeOptionsOnAccess(Product);\n      return request.get('/categories/1/products/1').expect(200)\n        .then(expectOptionsInjectedFromCategory);\n    });\n\n    it('injects options to destroyById', function() {\n      observeOptionsBeforeDelete(Product);\n      return request.del('/categories/1/products/1').expect(204)\n        .then(expectOptionsInjectedFromCategory);\n    });\n\n    it('injects options to updateById', function() {\n      observeOptionsBeforeSave(Product);\n      return request.put('/categories/1/products/1')\n        .send({description: 'a description'})\n        .expect(200)\n        .then(expectInjectedOptions);\n    });\n\n    context('through-model operations', function() {\n      it('injects options to link', function() {\n        observeOptionsBeforeSave(ThroughModel);\n        return Product.create({id: 2, name: 'Car2'})\n          .then(function() {\n            return request.put('/categories/1/products/rel/2')\n              .send({description: 'a description'})\n              .expect(200);\n          })\n          .then(expectOptionsInjectedFromCategory);\n      });\n\n      it('injects options to unlink', function() {\n        observeOptionsBeforeDelete(ThroughModel);\n        return request.del('/categories/1/products/rel/1').expect(204)\n          .then(expectOptionsInjectedFromCategory);\n      });\n\n      it('injects options to exists', function() {\n        observeOptionsOnAccess(ThroughModel);\n        return request.head('/categories/1/products/rel/1').expect(200)\n          .then(expectOptionsInjectedFromCategory);\n      });\n    });\n\n    context('scope operations', function() {\n      it('injects options to get', function() {\n        observeOptionsOnAccess(Product);\n        return request.get('/categories/1/products').expect(200)\n          .then(expectOptionsInjectedFromCategory);\n      });\n\n      it('injects options to create', function() {\n        observeOptionsBeforeSave(Product);\n        return request.post('/categories/1/products')\n          .send({name: 'Pen'})\n          .expect(200)\n          .then(expectOptionsInjectedFromCategory);\n      });\n\n      it('injects options to delete', function() {\n        observeOptionsBeforeDelete(ThroughModel);\n        return request.del('/categories/1/products').expect(204)\n          .then(expectOptionsInjectedFromCategory);\n      });\n\n      it('injects options to count', function() {\n        observeOptionsOnAccess(ThroughModel);\n        return request.get('/categories/1/products/count').expect(200)\n          .then(expectOptionsInjectedFromCategory);\n      });\n    });\n\n    function givenCategoryHasManyProductsThroughAnotherModel() {\n      Category = app.registry.createModel(\n        'Category',\n        {name: String},\n        {forceId: false, replaceOnPUT: true},\n      );\n\n      app.model(Category, {dataSource: 'db'});\n      // This is a shortcut for creating CategoryProduct \"through\" model\n      Category.hasAndBelongsToMany(Product);\n\n      Category.createOptionsFromRemotingContext = function(ctx) {\n        return {injectedFrom: 'Category'};\n      };\n\n      ThroughModel = app.registry.getModel('CategoryProduct');\n    }\n\n    function givenCategoryAndProduct() {\n      return Category.create({id: 1, name: 'First Category'})\n        .then(function(cat) {\n          return cat.products.create({id: 1, name: 'Pen'});\n        });\n    }\n\n    function expectOptionsInjectedFromCategory() {\n      expect(actualOptions).to.have.property('injectedFrom', 'Category');\n    }\n  });\n\n  context('hasOne', function() {\n    let Category;\n\n    beforeEach(givenCategoryHasOneProduct);\n    beforeEach(givenCategoryId1);\n\n    it('injects options to get', function() {\n      observeOptionsOnAccess(Product);\n      return givenProductInCategory1()\n        .then(function() {\n          return request.get('/categories/1/product').expect(200);\n        })\n        .then(expectOptionsInjectedFromCategory);\n    });\n\n    it('injects options to create', function() {\n      observeOptionsBeforeSave(Product);\n      return request.post('/categories/1/product')\n        .send({name: 'Pen'})\n        .expect(200)\n        .then(expectOptionsInjectedFromCategory);\n    });\n\n    it('injects options to update', function() {\n      return givenProductInCategory1()\n        .then(function() {\n          observeOptionsBeforeSave(Product);\n          return request.put('/categories/1/product')\n            .send({description: 'a description'})\n            .expect(200);\n        })\n        .then(expectInjectedOptions);\n    });\n\n    it('injects options to destroy', function() {\n      observeOptionsBeforeDelete(Product);\n      return givenProductInCategory1()\n        .then(function() {\n          return request.del('/categories/1/product').expect(204);\n        })\n        .then(expectOptionsInjectedFromCategory);\n    });\n\n    function givenCategoryHasOneProduct() {\n      Category = app.registry.createModel(\n        'Category',\n        {name: String},\n        {forceId: false, replaceOnPUT: true},\n      );\n\n      app.model(Category, {dataSource: 'db'});\n      Category.hasOne(Product);\n\n      Category.createOptionsFromRemotingContext = function(ctx) {\n        return {injectedFrom: 'Category'};\n      };\n    }\n\n    function givenCategoryId1() {\n      return Category.create({id: 1, name: 'First Category'});\n    }\n\n    function givenProductInCategory1() {\n      return Product.create({id: 1, name: 'Pen', categoryId: 1});\n    }\n\n    function expectOptionsInjectedFromCategory() {\n      expect(actualOptions).to.have.property('injectedFrom', 'Category');\n    }\n  });\n\n  context('belongsTo', function() {\n    let Category;\n\n    beforeEach(givenCategoryBelongsToProduct);\n\n    it('injects options to get', function() {\n      observeOptionsOnAccess(Product);\n      return Product.create({id: 1, name: 'Pen'})\n        .then(function() {\n          return Category.create({id: 1, name: 'a name', productId: 1});\n        })\n        .then(function() {\n          return request.get('/categories/1/product').expect(200);\n        })\n        .then(expectOptionsInjectedFromCategory);\n    });\n\n    function givenCategoryBelongsToProduct() {\n      Category = app.registry.createModel(\n        'Category',\n        {name: String},\n        {forceId: false, replaceOnPUT: true},\n      );\n\n      app.model(Category, {dataSource: 'db'});\n      Category.belongsTo(Product);\n\n      Category.createOptionsFromRemotingContext = function(ctx) {\n        return {injectedFrom: 'Category'};\n      };\n    }\n\n    function givenCategoryId1() {\n      return Category.create({id: 1, name: 'First Category'});\n    }\n\n    function givenProductInCategory1() {\n      return Product.create({id: 1, name: 'Pen', categoryId: 1});\n    }\n\n    function expectOptionsInjectedFromCategory() {\n      expect(actualOptions).to.have.property('injectedFrom', 'Category');\n    }\n  });\n\n  function setupAppAndRequest() {\n    app = loopback({localRegistry: true});\n    app.dataSource('db', {connector: 'memory'});\n\n    Product = app.registry.createModel(\n      'Product',\n      {name: String},\n      {forceId: false, replaceOnPUT: true},\n    );\n\n    Product.createOptionsFromRemotingContext = function(ctx) {\n      return {injectedFrom: 'Product'};\n    };\n\n    app.model(Product, {dataSource: 'db'});\n\n    app.use(loopback.rest());\n    request = supertest(app);\n  }\n\n  function resetActualOptions() {\n    actualOptions = undefined;\n  }\n\n  function observeOptionsBeforeSave() {\n    const Model = arguments[0] || Product;\n    Model.observe('before save', function(ctx, next) {\n      actualOptions = ctx.options;\n      next();\n    });\n  }\n\n  function observeOptionsBeforeDelete() {\n    const Model = arguments[0] || Product;\n    Model.observe('before delete', function(ctx, next) {\n      actualOptions = ctx.options;\n      next();\n    });\n  }\n\n  function observeOptionsOnAccess() {\n    const Model = arguments[0] || Product;\n    Model.observe('access', function(ctx, next) {\n      actualOptions = ctx.options;\n      next();\n    });\n  }\n\n  function givenProductId1() {\n    return Product.create({id: 1, name: 'Pen'});\n  }\n\n  function expectInjectedOptions(name) {\n    expect(actualOptions).to.have.property('injectedFrom');\n  }\n});\n"
  },
  {
    "path": "test/data-source.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst loopback = require('../');\n\ndescribe('DataSource', function() {\n  let memory;\n\n  beforeEach(function() {\n    memory = loopback.createDataSource({\n      connector: loopback.Memory,\n    });\n\n    assertValidDataSource(memory);\n  });\n\n  describe('dataSource.createModel(name, properties, settings)', function() {\n    it('Define a model and attach it to a `DataSource`', function() {\n      const Color = memory.createModel('color', {name: String});\n      assert.isFunc(Color, 'find');\n      assert.isFunc(Color, 'findById');\n      assert.isFunc(Color, 'findOne');\n      assert.isFunc(Color, 'create');\n      assert.isFunc(Color, 'updateOrCreate');\n      assert.isFunc(Color, 'upsertWithWhere');\n      assert.isFunc(Color, 'upsert');\n      assert.isFunc(Color, 'findOrCreate');\n      assert.isFunc(Color, 'exists');\n      assert.isFunc(Color, 'destroyAll');\n      assert.isFunc(Color, 'count');\n      assert.isFunc(Color, 'include');\n      assert.isFunc(Color, 'hasMany');\n      assert.isFunc(Color, 'belongsTo');\n      assert.isFunc(Color, 'hasAndBelongsToMany');\n      assert.isFunc(Color.prototype, 'save');\n      assert.isFunc(Color.prototype, 'isNewRecord');\n      assert.isFunc(Color.prototype, 'destroy');\n      assert.isFunc(Color.prototype, 'updateAttribute');\n      assert.isFunc(Color.prototype, 'updateAttributes');\n      assert.isFunc(Color.prototype, 'reload');\n    });\n\n    it('should honor settings.base', function() {\n      const Base = memory.createModel('base');\n      const Color = memory.createModel('color', {name: String}, {base: Base});\n      assert(Color.prototype instanceof Base);\n      assert.equal(Color.base, Base);\n    });\n\n    it('should use loopback.PersistedModel as the base for DBs', function() {\n      const Color = memory.createModel('color', {name: String});\n      assert(Color.prototype instanceof loopback.PersistedModel);\n      assert.equal(Color.base, loopback.PersistedModel);\n    });\n\n    it('should use loopback.Model as the base for non DBs', function() {\n      // Mock up a non-DB connector\n      const Connector = function() {\n      };\n      Connector.prototype.getTypes = function() {\n        return ['rest'];\n      };\n\n      const ds = loopback.createDataSource({\n        connector: new Connector(),\n      });\n\n      const Color = ds.createModel('color', {name: String});\n      assert(Color.prototype instanceof Color.registry.getModel('Model'));\n      assert.equal(Color.base.modelName, 'PersistedModel');\n    });\n  });\n\n  describe.skip('PersistedModel Methods', function() {\n    it('List the enabled and disabled methods', function() {\n      const TestModel = loopback.PersistedModel.extend('TestPersistedModel');\n      TestModel.attachTo(loopback.memory());\n\n      // assert the defaults\n      // - true: the method should be remote enabled\n      // - false: the method should not be remote enabled\n      // -\n      existsAndShared('_forDB', false);\n      existsAndShared('create', true);\n      existsAndShared('updateOrCreate', true);\n      existsAndShared('upsertWithWhere', true);\n      existsAndShared('upsert', true);\n      existsAndShared('findOrCreate', false);\n      existsAndShared('exists', true);\n      existsAndShared('find', true);\n      existsAndShared('findOne', true);\n      existsAndShared('destroyAll', false);\n      existsAndShared('count', true);\n      existsAndShared('include', false);\n      existsAndShared('hasMany', false);\n      existsAndShared('belongsTo', false);\n      existsAndShared('hasAndBelongsToMany', false);\n      existsAndShared('save', false);\n      existsAndShared('isNewRecord', false);\n      existsAndShared('_adapter', false);\n      existsAndShared('destroyById', true);\n      existsAndShared('destroy', false);\n      existsAndShared('updateAttributes', true);\n      existsAndShared('updateAll', true);\n      existsAndShared('reload', false);\n\n      function existsAndShared(Model, name, isRemoteEnabled, isProto) {\n        const scope = isProto ? Model.prototype : Model;\n        const fn = scope[name];\n        const actuallyEnabled = Model.getRemoteMethod(name);\n        assert(fn, name + ' should be defined!');\n        assert(actuallyEnabled === isRemoteEnabled,\n          name + ' ' + (isRemoteEnabled ? 'should' : 'should not') +\n            ' be remote enabled');\n      }\n    });\n  });\n});\n\nfunction assertValidDataSource(dataSource) {\n  // has methods\n  assert.isFunc(dataSource, 'createModel');\n  assert.isFunc(dataSource, 'discoverModelDefinitions');\n  assert.isFunc(dataSource, 'discoverSchema');\n  assert.isFunc(dataSource, 'enableRemote');\n  assert.isFunc(dataSource, 'disableRemote');\n  assert.isFunc(dataSource, 'defineOperation');\n  assert.isFunc(dataSource, 'operations');\n}\n\nassert.isFunc = function(obj, name) {\n  assert(obj, 'cannot assert function ' + name + ' on object that doesnt exist');\n  assert(typeof obj[name] === 'function', name + ' is not a function');\n};\n"
  },
  {
    "path": "test/e2e/remote-connector.e2e.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst path = require('path');\nconst loopback = require('../../');\nconst models = require('../fixtures/e2e/models');\nconst TestModel = models.TestModel;\nconst assert = require('assert');\n\ndescribe('RemoteConnector', function() {\n  before(function() {\n    // setup the remote connector\n    const ds = loopback.createDataSource({\n      url: 'http://127.0.0.1:3000/api',\n      connector: loopback.Remote,\n    });\n    TestModel.attachTo(ds);\n  });\n\n  it('should be able to call create', function(done) {\n    TestModel.create({\n      foo: 'bar',\n    }, function(err, inst) {\n      if (err) return done(err);\n\n      assert(inst.id);\n\n      done();\n    });\n  });\n\n  it('should be able to call save', function(done) {\n    const m = new TestModel({\n      foo: 'bar',\n    });\n    m.save(function(err, data) {\n      if (err) return done(err);\n\n      assert(data.foo === 'bar');\n\n      done();\n    });\n  });\n});\n"
  },
  {
    "path": "test/e2e/replication.e2e.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst path = require('path');\nconst loopback = require('../../');\nconst models = require('../fixtures/e2e/models');\nconst TestModel = models.TestModel;\nconst LocalTestModel = TestModel.extend('LocalTestModel', {}, {\n  trackChanges: true,\n});\nconst assert = require('assert');\n\ndescribe('Replication', function() {\n  before(function() {\n    // setup the remote connector\n    const ds = loopback.createDataSource({\n      url: 'http://127.0.0.1:3000/api',\n      connector: loopback.Remote,\n    });\n    TestModel.attachTo(ds);\n    const memory = loopback.memory();\n    LocalTestModel.attachTo(memory);\n  });\n\n  it('should replicate local data to the remote', function(done) {\n    const RANDOM = Math.random();\n\n    LocalTestModel.create({\n      n: RANDOM,\n    }, function(err, created) {\n      LocalTestModel.replicate(0, TestModel, function() {\n        if (err) return done(err);\n\n        TestModel.findOne({n: RANDOM}, function(err, found) {\n          assert.equal(created.id, found.id);\n\n          done();\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/email.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../');\nlet MyEmail;\nconst assert = require('assert');\nconst MailConnector = require('../lib/connectors/mail');\n\ndescribe('Email connector', function() {\n  it('should set up SMTP', function() {\n    const connector = new MailConnector({transports: [\n      {type: 'smtp', service: 'gmail'},\n    ]});\n    assert(connector.transportForName('smtp'));\n  });\n\n  it('should set up DIRECT', function() {\n    const connector = new MailConnector({transports: [\n      {type: 'direct', name: 'localhost'},\n    ]});\n    assert(connector.transportForName('direct'));\n  });\n\n  it('should set up STUB', function() {\n    const connector = new MailConnector({transports: [\n      {type: 'stub', service: 'gmail'},\n    ]});\n    assert(connector.transportForName('stub'));\n  });\n\n  it('should set up a single transport for SMTP', function() {\n    const connector = new MailConnector({transport:\n      {type: 'smtp', service: 'gmail'},\n    });\n\n    assert(connector.transportForName('smtp'));\n  });\n\n  it('should set up a aliased transport for SMTP', function() {\n    const connector = new MailConnector({transport:\n      {type: 'smtp', service: 'ses-us-east-1', alias: 'ses-smtp'},\n    });\n\n    assert(connector.transportForName('ses-smtp'));\n  });\n});\n\ndescribe('Email and SMTP', function() {\n  beforeEach(function() {\n    MyEmail = loopback.Email.extend('my-email');\n    const ds = loopback.createDataSource('email', {\n      connector: loopback.Mail,\n      transports: [{type: 'STUB'}],\n    });\n    MyEmail.attachTo(ds);\n  });\n\n  it('should have a send method', function() {\n    assert(typeof MyEmail.send === 'function');\n    assert(typeof MyEmail.prototype.send === 'function');\n  });\n\n  describe('MyEmail', function() {\n    it('MyEmail.send(options, callback)', function(done) {\n      const options = {\n        to: 'to@to.com',\n        from: 'from@from.com',\n        subject: 'subject',\n        text: 'text',\n        html: '<h1>html</h1>',\n      };\n\n      MyEmail.send(options, function(err, mail) {\n        assert(!err);\n        assert(mail.response);\n        assert(mail.envelope);\n        assert(mail.messageId);\n\n        done(err);\n      });\n    });\n\n    it('myEmail.send(callback)', function(done) {\n      const message = new MyEmail({\n        to: 'to@to.com',\n        from: 'from@from.com',\n        subject: 'subject',\n        text: 'text',\n        html: '<h1>html</h1>',\n      });\n\n      message.send(function(err, mail) {\n        assert(mail.response);\n        assert(mail.envelope);\n        assert(mail.messageId);\n\n        done(err);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/error-handler.test.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../');\nlet app;\nconst assert = require('assert');\nconst request = require('supertest');\nconst expect = require('./helpers/expect');\n\ndescribe('loopback.errorHandler(options)', function() {\n  it('should throw a descriptive error', function() {\n    expect(function() { loopback.errorHandler(); })\n      .to.throw(/no longer available.*strong-error-handler/);\n  });\n});\n"
  },
  {
    "path": "test/fixtures/access-control/common/models/access-token.json",
    "content": "{\n  \"name\": \"accessToken\",\n  \"base\": \"AccessToken\",\n  \"baseUrl\": \"access-tokens\",\n  \"acls\": [\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    },\n    {\n      \"permission\": \"ALLOW\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\",\n      \"property\": \"create\"\n    }\n  ],\n  \"relations\": {\n    \"user\": {\n      \"type\": \"belongsTo\",\n      \"model\": \"user\",\n      \"foreignKey\": \"userId\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/access-control/common/models/account.json",
    "content": "{\n  \"name\": \"accountWithReplaceOnPUTtrue\",\n  \"plural\": \"accounts-replacing\",\n  \"relations\": {\n    \"transactions\": {\n      \"model\": \"transaction\",\n      \"type\": \"hasMany\"\n    },\n    \"user\": {\n      \"model\": \"user\",\n      \"type\": \"belongsTo\",\n      \"foreignKey\": \"userId\"\n    }\n  },\n  \"acls\": [\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    },\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"ALLOW\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$owner\"\n    },\n    {\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$owner\",\n      \"property\": \"deleteById\"\n    },\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"property\": \"find\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$dummy\"\n    }\n  ],\n  \"properties\": {},\n  \"replaceOnPUT\": true\n}"
  },
  {
    "path": "test/fixtures/access-control/common/models/accountWithReplaceOnPUTfalse.json",
    "content": "{\n  \"name\": \"accountWithReplaceOnPUTfalse\",\n  \"plural\": \"accounts-updating\",\n  \"relations\": {\n    \"transactions\": {\n      \"model\": \"transaction\",\n      \"type\": \"hasMany\"\n    },\n    \"user\": {\n      \"model\": \"user\",\n      \"type\": \"belongsTo\",\n      \"foreignKey\": \"userId\"\n    }\n  },\n  \"acls\": [\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    },\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"ALLOW\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$owner\"\n    },\n    {\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$owner\",\n      \"property\": \"deleteById\"\n    },\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"property\": \"find\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$dummy\"\n    }\n  ],\n  \"properties\": {},\n  \"replaceOnPUT\": false\n}"
  },
  {
    "path": "test/fixtures/access-control/common/models/alert.json",
    "content": "{\n  \"name\": \"alert\",\n  \"acls\": [\n    {\n      \"accessType\": \"WRITE\",\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    }\n  ],\n  \"properties\": {}\n}"
  },
  {
    "path": "test/fixtures/access-control/common/models/bank.json",
    "content": "{\n  \"name\": \"bank\",\n  \"relations\": {\n    \"users\": {\n      \"model\": \"user\",\n      \"type\": \"hasMany\"\n    },\n    \"accounts\": {\n      \"model\": \"account\",\n      \"type\": \"hasMany\"\n    }\n  },\n  \"acls\": [\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    },\n    {\n      \"accessType\": \"READ\",\n      \"permission\": \"ALLOW\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    },\n    {\n      \"accessType\": \"WRITE\",\n      \"permission\": \"ALLOW\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$dynamic-role\"\n    }\n  ],\n  \"properties\": {}\n}"
  },
  {
    "path": "test/fixtures/access-control/common/models/email.json",
    "content": "{\n  \"name\": \"email\",\n  \"base\": \"Email\",\n  \"acls\": [\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    }\n  ]\n}"
  },
  {
    "path": "test/fixtures/access-control/common/models/transaction.json",
    "content": "{\n  \"name\": \"transaction\",\n  \"acls\": [\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    }\n  ],\n  \"properties\": {}\n}"
  },
  {
    "path": "test/fixtures/access-control/common/models/user.json",
    "content": "{\n  \"name\": \"user\",\n  \"base\": \"User\",\n  \"relations\": {\n    \"accessTokens\": {\n      \"model\": \"accessToken\",\n      \"type\": \"hasMany\",\n      \"foreignKey\": \"userId\"\n    },\n    \"transactions\": {\n      \"model\": \"transaction\",\n      \"type\": \"hasMany\"\n    }\n  },\n  \"acls\": [\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    }\n  ],\n  \"replaceOnPUT\": false\n}\n"
  },
  {
    "path": "test/fixtures/access-control/server/config.json",
    "content": "{\n  \"port\": 3000,\n  \"host\": \"0.0.0.0\",\n  \"remoting\": {\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/access-control/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"connector\": \"memory\"\n  },\n  \"mail\": {\n    \"connector\": \"mail\"\n  }\n}"
  },
  {
    "path": "test/fixtures/access-control/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"../common/models\",\n      \"./models\",\n      \"../../../../common/models\"\n    ]\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"email\": {\n    \"dataSource\": \"mail\",\n    \"public\": false\n  },\n  \"user\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"accessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"bank\": {\n    \"public\": true,\n    \"dataSource\": \"db\"\n  },\n  \"accountWithReplaceOnPUTtrue\": {\n    \"public\": true,\n    \"dataSource\": \"db\"\n  },\n  \"accountWithReplaceOnPUTfalse\": {\n    \"public\": true,\n    \"dataSource\": \"db\"\n  },\n  \"transaction\": {\n    \"public\": true,\n    \"dataSource\": \"db\"\n  },\n  \"alert\": {\n    \"public\": true,\n    \"dataSource\": \"db\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/access-control/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../../..');\nconst boot = require('loopback-boot');\nconst app = module.exports = loopback({\n  localRegistry: true,\n  loadBuiltinModels: true,\n});\nconst errorHandler = require('strong-error-handler');\n\nboot(app, __dirname);\n\nconst apiPath = '/api';\napp.use(loopback.token({model: app.models.accessToken}));\napp.use(apiPath, loopback.rest());\n\napp.use(loopback.urlNotFound());\napp.use(errorHandler());\napp.enableAuth();\n"
  },
  {
    "path": "test/fixtures/e2e/server/models.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../../../index');\nconst PersistedModel = loopback.PersistedModel;\n\nexports.TestModel = PersistedModel.extend('TestModel', {}, {\n  trackChanges: true,\n});\n"
  },
  {
    "path": "test/fixtures/e2e/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../../../index');\nconst app = module.exports = loopback({localRegistry: true});\nconst models = require('./models');\nconst TestModel = models.TestModel;\n\nconst apiPath = '/api';\napp.use(apiPath, loopback.rest());\n\nTestModel.attachTo(loopback.memory());\napp.model(TestModel);\napp.model(TestModel.getChangeModel());\n\n// app.use(loopback.static(path.join(__dirname, 'public')));\napp.use(loopback.urlNotFound());\napp.use(loopback.errorHandler());\n"
  },
  {
    "path": "test/fixtures/shared-methods/both-configs-set/common/models/todo.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Todo) {\n\n};\n"
  },
  {
    "path": "test/fixtures/shared-methods/both-configs-set/common/models/todo.json",
    "content": "{\n  \"name\": \"Todo\",\n  \"base\": \"PersistedModel\",\n  \"idInjection\": true,\n  \"options\": {\n    \"validateUpsert\": true\n  },\n  \"properties\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"validations\": [],\n  \"relations\": {},\n  \"acls\": [],\n  \"methods\": {}\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/both-configs-set/server/config.json",
    "content": "{\n  \"restApiRoot\": \"/api\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 3000,\n  \"remoting\": {\n    \"rest\": {\n      \"normalizeHttpPath\": false,\n      \"xml\": false\n    },\n    \"json\": {\n      \"strict\": false,\n      \"limit\": \"100kb\"\n    },\n    \"urlencoded\": {\n      \"extended\": true,\n      \"limit\": \"100kb\"\n    },\n    \"cors\": false,\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    },\n    \"sharedMethods\": {\n      \"*\": false,\n      \"destroyAll\": true\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/both-configs-set/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"name\": \"db\",\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/both-configs-set/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"loopback/common/models\",\n      \"loopback/server/models\",\n      \"../common/models\",\n      \"./models\"\n    ],\n    \"mixins\": [\n      \"loopback/common/mixins\",\n      \"loopback/server/mixins\",\n      \"../common/mixins\",\n      \"./mixins\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\"\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Todo\": {\n    \"dataSource\": \"db\",\n    \"public\": true,\n    \"options\": {\n      \"remoting\": {\n        \"sharedMethods\": {\n          \"destroyAll\": false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/both-configs-set/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst boot = require('loopback-boot');\nconst loopback = require('../../../../../index');\n\nconst app = module.exports = loopback();\nboot(app, __dirname);\napp.use(loopback.rest());\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-false/common/models/todo.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Todo) {\n\n};\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-false/common/models/todo.json",
    "content": "{\n  \"name\": \"Todo\",\n  \"base\": \"PersistedModel\",\n  \"idInjection\": true,\n  \"options\": {\n    \"validateUpsert\": true\n  },\n  \"properties\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"validations\": [],\n  \"relations\": {},\n  \"acls\": [],\n  \"methods\": {}\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-false/server/config.json",
    "content": "{\n  \"restApiRoot\": \"/api\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 3000,\n  \"remoting\": {\n    \"rest\": {\n      \"normalizeHttpPath\": false,\n      \"xml\": false\n    },\n    \"json\": {\n      \"strict\": false,\n      \"limit\": \"100kb\"\n    },\n    \"urlencoded\": {\n      \"extended\": true,\n      \"limit\": \"100kb\"\n    },\n    \"cors\": false,\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    },\n    \"sharedMethods\": {\n      \"*\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-false/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"name\": \"db\",\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-false/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"loopback/common/models\",\n      \"loopback/server/models\",\n      \"../common/models\",\n      \"./models\"\n    ],\n    \"mixins\": [\n      \"loopback/common/mixins\",\n      \"loopback/server/mixins\",\n      \"../common/mixins\",\n      \"./mixins\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\"\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Todo\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-false/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst boot = require('loopback-boot');\nconst loopback = require('../../../../../index');\n\nconst app = module.exports = loopback();\nboot(app, __dirname);\napp.use(loopback.rest());\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-true/common/models/todo.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Todo) {\n\n};\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-true/common/models/todo.json",
    "content": "{\n  \"name\": \"Todo\",\n  \"base\": \"PersistedModel\",\n  \"idInjection\": true,\n  \"options\": {\n    \"validateUpsert\": true\n  },\n  \"properties\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"validations\": [],\n  \"relations\": {},\n  \"acls\": [],\n  \"methods\": {}\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-true/server/config.json",
    "content": "{\n  \"restApiRoot\": \"/api\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 3000,\n  \"remoting\": {\n    \"rest\": {\n      \"normalizeHttpPath\": false,\n      \"xml\": false\n    },\n    \"json\": {\n      \"strict\": false,\n      \"limit\": \"100kb\"\n    },\n    \"urlencoded\": {\n      \"extended\": true,\n      \"limit\": \"100kb\"\n    },\n    \"cors\": false,\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    },\n    \"sharedMethods\": {\n      \"*\": true\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-true/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"name\": \"db\",\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-true/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"loopback/common/models\",\n      \"loopback/server/models\",\n      \"../common/models\",\n      \"./models\"\n    ],\n    \"mixins\": [\n      \"loopback/common/mixins\",\n      \"loopback/server/mixins\",\n      \"../common/mixins\",\n      \"./mixins\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\"\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Todo\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-default-true/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst boot = require('loopback-boot');\nconst loopback = require('../../../../../index');\n\nconst app = module.exports = loopback();\nboot(app, __dirname);\napp.use(loopback.rest());\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-false/common/models/todo.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Todo) {\n\n};\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-false/common/models/todo.json",
    "content": "{\n  \"name\": \"Todo\",\n  \"base\": \"PersistedModel\",\n  \"idInjection\": true,\n  \"options\": {\n    \"validateUpsert\": true\n  },\n  \"properties\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"validations\": [],\n  \"relations\": {},\n  \"acls\": [],\n  \"methods\": {}\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-false/server/config.json",
    "content": "{\n  \"restApiRoot\": \"/api\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 3000,\n  \"remoting\": {\n    \"rest\": {\n      \"normalizeHttpPath\": false,\n      \"xml\": false\n    },\n    \"json\": {\n      \"strict\": false,\n      \"limit\": \"100kb\"\n    },\n    \"urlencoded\": {\n      \"extended\": true,\n      \"limit\": \"100kb\"\n    },\n    \"cors\": false,\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    },\n    \"sharedMethods\": {\n      \"find\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-false/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"name\": \"db\",\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-false/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"loopback/common/models\",\n      \"loopback/server/models\",\n      \"../common/models\",\n      \"./models\"\n    ],\n    \"mixins\": [\n      \"loopback/common/mixins\",\n      \"loopback/server/mixins\",\n      \"../common/mixins\",\n      \"./mixins\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\"\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Todo\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-false/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst boot = require('loopback-boot');\nconst loopback = require('../../../../../index');\n\nconst app = module.exports = loopback();\nboot(app, __dirname);\napp.use(loopback.rest());\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-true/common/models/todo.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Todo) {\n\n};\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-true/common/models/todo.json",
    "content": "{\n  \"name\": \"Todo\",\n  \"base\": \"PersistedModel\",\n  \"idInjection\": true,\n  \"options\": {\n    \"validateUpsert\": true\n  },\n  \"properties\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"validations\": [],\n  \"relations\": {},\n  \"acls\": [],\n  \"methods\": {}\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-true/server/config.json",
    "content": "{\n  \"restApiRoot\": \"/api\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 3000,\n  \"remoting\": {\n    \"rest\": {\n      \"normalizeHttpPath\": false,\n      \"xml\": false\n    },\n    \"json\": {\n      \"strict\": false,\n      \"limit\": \"100kb\"\n    },\n    \"urlencoded\": {\n      \"extended\": true,\n      \"limit\": \"100kb\"\n    },\n    \"cors\": false,\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    },\n    \"sharedMethods\": {\n      \"find\": true\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-true/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"name\": \"db\",\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-true/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"loopback/common/models\",\n      \"loopback/server/models\",\n      \"../common/models\",\n      \"./models\"\n    ],\n    \"mixins\": [\n      \"loopback/common/mixins\",\n      \"loopback/server/mixins\",\n      \"../common/mixins\",\n      \"./mixins\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\"\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Todo\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/config-defined-true/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst boot = require('loopback-boot');\nconst loopback = require('../../../../../index');\n\nconst app = module.exports = loopback();\nboot(app, __dirname);\napp.use(loopback.rest());\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-false/common/models/todo.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Todo) {\n\n};\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-false/common/models/todo.json",
    "content": "{\n  \"name\": \"Todo\",\n  \"base\": \"PersistedModel\",\n  \"idInjection\": true,\n  \"options\": {\n    \"validateUpsert\": true\n  },\n  \"properties\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"validations\": [],\n  \"relations\": {},\n  \"acls\": [],\n  \"methods\": {}\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-false/server/config.json",
    "content": "{\n  \"restApiRoot\": \"/api\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 3000,\n  \"remoting\": {\n    \"rest\": {\n      \"normalizeHttpPath\": false,\n      \"xml\": false\n    },\n    \"json\": {\n      \"strict\": false,\n      \"limit\": \"100kb\"\n    },\n    \"urlencoded\": {\n      \"extended\": true,\n      \"limit\": \"100kb\"\n    },\n    \"cors\": false,\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-false/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"name\": \"db\",\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-false/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"loopback/common/models\",\n      \"loopback/server/models\",\n      \"../common/models\",\n      \"./models\"\n    ],\n    \"mixins\": [\n      \"loopback/common/mixins\",\n      \"loopback/server/mixins\",\n      \"../common/mixins\",\n      \"./mixins\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\"\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Todo\": {\n    \"dataSource\": \"db\",\n    \"public\": true,\n    \"options\": {\n      \"remoting\": {\n        \"sharedMethods\": {\n          \"*\": false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-false/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst boot = require('loopback-boot');\nconst loopback = require('../../../../../index');\n\nconst app = module.exports = loopback();\nboot(app, __dirname);\napp.use(loopback.rest());\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-true/common/models/todo.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Todo) {\n\n};\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-true/common/models/todo.json",
    "content": "{\n  \"name\": \"Todo\",\n  \"base\": \"PersistedModel\",\n  \"idInjection\": true,\n  \"options\": {\n    \"validateUpsert\": true\n  },\n  \"properties\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"validations\": [],\n  \"relations\": {},\n  \"acls\": [],\n  \"methods\": {}\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-true/server/config.json",
    "content": "{\n  \"restApiRoot\": \"/api\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 3000,\n  \"remoting\": {\n    \"rest\": {\n      \"normalizeHttpPath\": false,\n      \"xml\": false\n    },\n    \"json\": {\n      \"strict\": false,\n      \"limit\": \"100kb\"\n    },\n    \"urlencoded\": {\n      \"extended\": true,\n      \"limit\": \"100kb\"\n    },\n    \"cors\": false,\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-true/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"name\": \"db\",\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-true/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"loopback/common/models\",\n      \"loopback/server/models\",\n      \"../common/models\",\n      \"./models\"\n    ],\n    \"mixins\": [\n      \"loopback/common/mixins\",\n      \"loopback/server/mixins\",\n      \"../common/mixins\",\n      \"./mixins\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\"\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Todo\": {\n    \"dataSource\": \"db\",\n    \"public\": true,\n    \"options\": {\n      \"remoting\": {\n        \"sharedMethods\": {\n          \"*\": true\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-default-true/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst boot = require('loopback-boot');\nconst loopback = require('../../../../../index');\n\nconst app = module.exports = loopback();\nboot(app, __dirname);\napp.use(loopback.rest());\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-false/common/models/todo.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Todo) {\n\n};\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-false/common/models/todo.json",
    "content": "{\n  \"name\": \"Todo\",\n  \"base\": \"PersistedModel\",\n  \"idInjection\": true,\n  \"options\": {\n    \"validateUpsert\": true\n  },\n  \"properties\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"validations\": [],\n  \"relations\": {},\n  \"acls\": [],\n  \"methods\": {}\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-false/server/config.json",
    "content": "{\n  \"restApiRoot\": \"/api\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 3000,\n  \"remoting\": {\n    \"rest\": {\n      \"normalizeHttpPath\": false,\n      \"xml\": false\n    },\n    \"json\": {\n      \"strict\": false,\n      \"limit\": \"100kb\"\n    },\n    \"urlencoded\": {\n      \"extended\": true,\n      \"limit\": \"100kb\"\n    },\n    \"cors\": false,\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-false/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"name\": \"db\",\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-false/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"loopback/common/models\",\n      \"loopback/server/models\",\n      \"../common/models\",\n      \"./models\"\n    ],\n    \"mixins\": [\n      \"loopback/common/mixins\",\n      \"loopback/server/mixins\",\n      \"../common/mixins\",\n      \"./mixins\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\"\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Todo\": {\n    \"dataSource\": \"db\",\n    \"public\": true,\n    \"options\": {\n      \"remoting\": {\n        \"sharedMethods\": {\n          \"find\": false\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-false/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst boot = require('loopback-boot');\nconst loopback = require('../../../../../index');\n\nconst app = module.exports = loopback();\nboot(app, __dirname);\napp.use(loopback.rest());\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-true/common/models/todo.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Todo) {\n\n};\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-true/common/models/todo.json",
    "content": "{\n  \"name\": \"Todo\",\n  \"base\": \"PersistedModel\",\n  \"idInjection\": true,\n  \"options\": {\n    \"validateUpsert\": true\n  },\n  \"properties\": {\n    \"content\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"validations\": [],\n  \"relations\": {},\n  \"acls\": [],\n  \"methods\": {}\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-true/server/config.json",
    "content": "{\n  \"restApiRoot\": \"/api\",\n  \"host\": \"0.0.0.0\",\n  \"port\": 3000,\n  \"remoting\": {\n    \"rest\": {\n      \"normalizeHttpPath\": false,\n      \"xml\": false\n    },\n    \"json\": {\n      \"strict\": false,\n      \"limit\": \"100kb\"\n    },\n    \"urlencoded\": {\n      \"extended\": true,\n      \"limit\": \"100kb\"\n    },\n    \"cors\": false,\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-true/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"name\": \"db\",\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-true/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"loopback/common/models\",\n      \"loopback/server/models\",\n      \"../common/models\",\n      \"./models\"\n    ],\n    \"mixins\": [\n      \"loopback/common/mixins\",\n      \"loopback/server/mixins\",\n      \"../common/mixins\",\n      \"./mixins\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\"\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Todo\": {\n    \"dataSource\": \"db\",\n    \"public\": true,\n    \"options\": {\n      \"remoting\": {\n        \"sharedMethods\": {\n          \"find\": true\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/shared-methods/model-config-defined-true/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst boot = require('loopback-boot');\nconst loopback = require('../../../../../index');\n\nconst app = module.exports = loopback();\nboot(app, __dirname);\napp.use(loopback.rest());\n"
  },
  {
    "path": "test/fixtures/simple-app/boot/bad.txt",
    "content": "this is not a js file!\n"
  },
  {
    "path": "test/fixtures/simple-app/boot/foo.js",
    "content": "// Copyright IBM Corp. 2013,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nprocess.loadedFooJS = true;\n"
  },
  {
    "path": "test/fixtures/simple-app/common/models/bar.js",
    "content": "// Copyright IBM Corp. 2015,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nmodule.exports = function(Bar) {\n  process.loadedBarJS = true;\n};\n"
  },
  {
    "path": "test/fixtures/simple-app/common/models/bar.json",
    "content": "{\n  \"name\": \"bar\",\n  \"properties\": {}\n}"
  },
  {
    "path": "test/fixtures/simple-app/common/models/foo.json",
    "content": "{\n  \"name\": \"foo\",\n  \"properties\": {}\n}"
  },
  {
    "path": "test/fixtures/simple-app/server/config.json",
    "content": "{\n  \"port\": 3000,\n  \"host\": \"127.0.0.1\",\n  \"remoting\": {\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/simple-app/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"connector\": \"memory\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/simple-app/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"../common/models\",\n      \"./models\",\n      \"../../../../common/models\"\n    ]\n  },\n  \"User\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"foo\": {\n    \"dataSource\": \"db\"\n  }\n}\n"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/access-token.json",
    "content": "{\n  \"name\": \"accessToken\",\n  \"base\": \"AccessToken\"\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/appointment.json",
    "content": "{\n  \"name\": \"appointment\",\n  \"properties\": {\n    \"date\": \"date\"\n  },\n  \"options\": {\n    \"relations\": {\n      \"physician\": {\n        \"model\": \"physician\",\n        \"type\": \"belongsTo\",\n        \"foreignKey\": \"physicianId\"\n      },\n      \"patient\": {\n        \"model\": \"patient\",\n        \"type\": \"belongsTo\",\n        \"foreignKey\": \"patientId\"\n      }\n    }\n  }\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/customer-forceid.json",
    "content": "{\n  \"name\": \"customerforceidfalse\",\n  \"base\": \"PersistedModel\",\n  \"forceId\": false,\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  }\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/customer.json",
    "content": "{\n  \"name\": \"customer\",\n  \"base\": \"PersistedModel\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  },\n  \"relations\": {\n    \"profile\": {\n      \"type\": \"hasOne\",\n      \"model\": \"profile\"\n    }\n  }\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/email.json",
    "content": "{\n  \"name\": \"email\",\n  \"base\": \"Email\",\n  \"acls\": [\n    {\n      \"accessType\": \"*\",\n      \"permission\": \"DENY\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$everyone\"\n    }\n  ]\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/patient.json",
    "content": "{\n  \"name\": \"patient\",\n  \"properties\": {\n    \"name\": \"string\"\n  },\n  \"options\": {\n    \"relations\": {\n      \"physicians\": {\n        \"model\": \"physician\",\n        \"type\": \"hasMany\",\n        \"through\": \"appointment\",\n        \"foreignKey\": \"physicianId\"\n      }\n    }\n  }\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/physician.json",
    "content": "{\n  \"name\": \"physician\",\n  \"properties\": {\n    \"name\": \"string\"\n  },\n  \"relations\": {\n    \"patients\": {\n      \"model\": \"patient\",\n      \"type\": \"hasMany\",\n      \"through\": \"appointment\",\n      \"foreignKey\": \"patientId\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/profile.json",
    "content": "{\n  \"name\": \"profile\",\n  \"base\": \"PersistedModel\",\n  \"properties\": {\n    \"points\": {\n      \"type\": \"number\"\n    }\n  }\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/store-replacing.json",
    "content": "{\n  \"name\": \"storeWithReplaceOnPUTtrue\",\n  \"plural\": \"stores-replacing\",\n  \"properties\": {},\n  \"scopes\": {\n    \"superStores\": {\n      \"where\": {\n        \"size\": \"super\"\n      }\n    }\n  },\n  \"relations\": {\n    \"widgets\": {\n      \"model\": \"widget\",\n      \"type\": \"hasMany\"\n    }\n  },\n  \"replaceOnPUT\": true\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/store-updating.json",
    "content": "{\n  \"name\": \"storeWithReplaceOnPUTfalse\",\n  \"plural\": \"stores-updating\",\n  \"properties\": {},\n  \"scopes\": {\n    \"superStores\": {\n      \"where\": {\n        \"size\": \"super\"\n      }\n    }\n  },\n  \"relations\": {\n    \"widgets\": {\n      \"model\": \"widget\",\n      \"type\": \"hasMany\"\n    }\n  },\n  \"replaceOnPUT\": false\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/store.json",
    "content": "{\n  \"name\": \"store\",\n  \"properties\": {},\n  \"scopes\": {\n    \"superStores\": {\n      \"where\": {\n        \"size\": \"super\"\n      }\n    }\n  },\n  \"relations\": {\n    \"widgets\": {\n      \"model\": \"widget\",\n      \"type\": \"hasMany\"\n    }\n  }\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/user.json",
    "content": "{\n  \"name\": \"user\",\n  \"base\": \"User\",\n  \"relations\": {\n    \"accessTokens\": {\n      \"model\": \"accessToken\",\n      \"type\": \"hasMany\",\n      \"foreignKey\": \"userId\"\n    }\n  }\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/common/models/widget.json",
    "content": "{\n  \"name\": \"widget\",\n  \"properties\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"default\": \"DefaultWidgetName\"\n    }\n  },\n  \"relations\": {\n    \"store\": {\n      \"model\": \"store\",\n      \"type\": \"belongsTo\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/simple-integration-app/server/config.json",
    "content": "{\n  \"port\": 3000,\n  \"host\": \"0.0.0.0\",\n  \"cookieSecret\": \"2d13a01d-44fb-455c-80cb-db9cb3cd3cd0\",\n  \"remoting\": {\n    \"json\": {\n      \"limit\": \"1kb\",\n      \"strict\": false\n    },\n    \"urlencoded\": {\n      \"limit\": \"8kb\"\n    },\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/simple-integration-app/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"connector\": \"memory\"\n  },\n  \"mail\": {\n    \"connector\": \"mail\"\n  }\n}"
  },
  {
    "path": "test/fixtures/simple-integration-app/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"../common/models\",\n      \"./models\",\n      \"../../../../common/models\"\n    ]\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"email\": {\n    \"dataSource\": \"mail\",\n    \"public\": false\n  },\n  \"user\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"accessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"widget\": {\n    \"public\": true,\n    \"dataSource\": \"db\"\n  },\n  \"store\": {\n    \"public\": true,\n    \"dataSource\": \"db\"\n  },\n  \"storeWithReplaceOnPUTfalse\": {\n    \"public\": true,\n    \"dataSource\": \"db\"\n  },\n  \"storeWithReplaceOnPUTtrue\": {\n    \"public\": true,\n    \"dataSource\": \"db\"\n  },\n  \"physician\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"patient\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"appointment\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"customer\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"profile\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"customerforceidfalse\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  }\n}\n"
  },
  {
    "path": "test/fixtures/simple-integration-app/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../../../index');\nconst boot = require('loopback-boot');\nconst app = module.exports = loopback({localRegistry: true});\nconst errorHandler = require('strong-error-handler');\n\nboot(app, __dirname);\nconst apiPath = '/api';\napp.use(apiPath, loopback.rest());\napp.use(loopback.urlNotFound());\napp.use(errorHandler());\n"
  },
  {
    "path": "test/fixtures/user-integration-app/common/models/blog.json",
    "content": "{\n  \"name\": \"blog\",\n  \"base\": \"PersistedModel\",\n  \"properties\": {\n    \"title\": {\n      \"type\": \"string\"\n    },\n    \"content\": {\n      \"type\": \"string\"\n    }\n  },\n  \"relations\": {\n    \"user\": {\n      \"type\": \"belongsTo\",\n      \"model\": \"myUser\"\n    }\n  }\n}"
  },
  {
    "path": "test/fixtures/user-integration-app/common/models/my-user.json",
    "content": "{\n  \"name\": \"myUser\",\n  \"base\": \"User\",\n  \"relations\": {\n    \"blogs\": {\n      \"model\": \"blog\",\n      \"type\": \"hasMany\",\n      \"foreignKey\": \"userId\"\n    }\n  },\n  \"acls\": [\n    {\n      \"permission\": \"ALLOW\",\n      \"principalType\": \"ROLE\",\n      \"principalId\": \"$owner\"\n    }\n  ],\n  \"saltWorkFactor\": 4\n}\n"
  },
  {
    "path": "test/fixtures/user-integration-app/common/models/post.json",
    "content": "{\n  \"name\": \"Post\",\n  \"base\": \"PersistedModel\",\n  \"properties\": {\n    \"title\": {\n      \"type\": \"string\"\n    },\n    \"content\": {\n      \"type\": \"string\"\n    },\n    \"notes\": {\n      \"type\": \"string\"\n    }\n  },\n  \"relations\": {\n    \"user\": {\n      \"type\": \"belongsTo\",\n      \"model\": \"User\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/user-integration-app/server/config.json",
    "content": "{\n  \"port\": 3000,\n  \"host\": \"0.0.0.0\",\n  \"cookieSecret\": \"2d13a01d-44fb-455c-80cb-db9cb3cd3cd0\",\n  \"remoting\": {\n    \"json\": {\n      \"limit\": \"1kb\",\n      \"strict\": false\n    },\n    \"urlencoded\": {\n      \"limit\": \"8kb\"\n    },\n    \"errorHandler\": {\n      \"debug\": true,\n      \"log\": false\n    }\n  }\n}\n"
  },
  {
    "path": "test/fixtures/user-integration-app/server/datasources.json",
    "content": "{\n  \"db\": {\n    \"connector\": \"memory\"\n  },\n  \"mail\": {\n    \"connector\": \"mail\",\n    \"transports\": [\n      {\"type\": \"STUB\"}\n    ]\n  }\n}\n"
  },
  {
    "path": "test/fixtures/user-integration-app/server/model-config.json",
    "content": "{\n  \"_meta\": {\n    \"sources\": [\n      \"../common/models\",\n      \"./models\",\n      \"../../../../common/models\"\n    ]\n  },\n  \"Email\": {\n    \"dataSource\": \"mail\",\n    \"public\": false\n  },\n  \"User\": {\n    \"dataSource\": \"db\",\n    \"public\": true,\n    \"relations\": {\n      \"posts\": {\n        \"model\": \"Post\",\n        \"type\": \"hasMany\",\n        \"foreignKey\": \"userId\"\n      }\n    },\n    \"acls\": [\n      {\n        \"permission\": \"ALLOW\",\n        \"principalType\": \"ROLE\",\n        \"principalId\": \"$owner\"\n      }\n    ]\n  },\n  \"AccessToken\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"ACL\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"RoleMapping\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"Role\": {\n    \"dataSource\": \"db\",\n    \"public\": false\n  },\n  \"myUser\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"blog\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  },\n  \"Post\": {\n    \"dataSource\": \"db\",\n    \"public\": true\n  }\n}\n"
  },
  {
    "path": "test/fixtures/user-integration-app/server/server.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../../../index');\nconst boot = require('loopback-boot');\nconst app = module.exports = loopback({\n  localRegistry: true,\n  loadBuiltinModels: true,\n});\nconst errorHandler = require('strong-error-handler');\n\napp.enableAuth();\nboot(app, __dirname);\napp.use(loopback.token({model: app.models.AccessToken}));\nconst apiPath = '/api';\napp.use(apiPath, loopback.rest());\napp.use(loopback.urlNotFound());\napp.use(errorHandler());\n"
  },
  {
    "path": "test/geo-point.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst loopback = require('../');\nconst GeoPoint = loopback.GeoPoint;\n\ndescribe('GeoPoint', function() {\n  describe('geoPoint.distanceTo(geoPoint, options)', function() {\n    it('Get the distance to another `GeoPoint`', function() {\n      const here = new GeoPoint({lat: 10, lng: 10});\n      const there = new GeoPoint({lat: 5, lng: 5});\n      const distance = here.distanceTo(there, {type: 'meters'});\n\n      assert.equal(Math.floor(distance), 782777);\n    });\n  });\n\n  describe('GeoPoint.distanceBetween(a, b, options)', function() {\n    it('Get the distance between two points', function() {\n      const here = new GeoPoint({lat: 10, lng: 10});\n      const there = new GeoPoint({lat: 5, lng: 5});\n      const distance = GeoPoint.distanceBetween(here, there, {type: 'feet'});\n\n      assert.equal(Math.floor(distance), 2568169);\n    });\n  });\n\n  describe('GeoPoint()', function() {\n    it('Create from string', function() {\n      const point = new GeoPoint('1.234,5.678');\n      assert.equal(point.lat, 1.234);\n      assert.equal(point.lng, 5.678);\n      const point2 = new GeoPoint('1.222,         5.333');\n      assert.equal(point2.lat, 1.222);\n      assert.equal(point2.lng, 5.333);\n      const point3 = new GeoPoint('1.333, 5.111');\n      assert.equal(point3.lat, 1.333);\n      assert.equal(point3.lng, 5.111);\n    });\n    it('Serialize as string', function() {\n      const str = '1.234,5.678';\n      const point = new GeoPoint(str);\n      assert.equal(point.toString(), str);\n    });\n    it('Create from array', function() {\n      const point = new GeoPoint([5.555, 6.777]);\n      assert.equal(point.lat, 5.555);\n      assert.equal(point.lng, 6.777);\n    });\n    it('Create as Model property', function() {\n      const Model = loopback.createModel('geo-model', {\n        geo: {type: 'GeoPoint'},\n      });\n\n      const m = new Model({\n        geo: '1.222,3.444',\n      });\n\n      assert(m.geo instanceof GeoPoint);\n      assert.equal(m.geo.lat, 1.222);\n      assert.equal(m.geo.lng, 3.444);\n    });\n  });\n});\n"
  },
  {
    "path": "test/helpers/error-loggers.js",
    "content": "// Copyright IBM Corp. 2017,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nexports.logAllServerErrors = function(app) {\n  exports.logServerErrorsOtherThan(-1, app);\n};\n\nexports.logServerErrorsOtherThan = function(statusCode, app) {\n  app.use((err, req, res, next) => {\n    if ((err.statusCode || 500) !== statusCode) {\n      console.log('Unhandled error for request %s %s: %s',\n        req.method, req.url, err.stack || err);\n    }\n    res.statusCode = err.statusCode || 500;\n    res.json(err);\n  });\n};\n"
  },
  {
    "path": "test/helpers/expect.js",
    "content": "// Copyright IBM Corp. 2016,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst chai = require('chai');\nchai.use(require('dirty-chai'));\nchai.use(require('sinon-chai'));\n\nmodule.exports = chai.expect;\n"
  },
  {
    "path": "test/helpers/loopback-testing-helper.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst _describe = {};\nconst _it = {};\nconst _beforeEach = {};\nconst helpers = {\n  describe: _describe,\n  it: _it,\n  beforeEach: _beforeEach,\n};\nmodule.exports = helpers;\n\nconst assert = require('assert');\nconst request = require('supertest');\nconst chai = require('chai');\nconst expect = chai.expect;\nconst sinon = require('sinon');\nchai.use(require('sinon-chai'));\n\n_beforeEach.withApp = function(app) {\n  if (app.models.User) {\n    // Speed up the password hashing algorithm\n    app.models.User.settings.saltWorkFactor = 4;\n  }\n\n  beforeEach(function(done) {\n    this.app = app;\n    const _request = this.request = request(app);\n    this.post = _request.post;\n    this.get = _request.get;\n    this.put = _request.put;\n    this.del = _request.del;\n    this.patch = _request.patch;\n\n    if (app.booting) {\n      return app.once('booted', done);\n    }\n\n    done();\n  });\n};\n\n_beforeEach.withArgs = function() {\n  const args = Array.prototype.slice.call(arguments, 0);\n  beforeEach(function() {\n    this.args = args;\n  });\n};\n\n_beforeEach.givenModel = function(modelName, attrs, optionalHandler) {\n  let modelKey = modelName;\n\n  if (typeof attrs === 'function') {\n    optionalHandler = attrs;\n    attrs = undefined;\n  }\n\n  if (typeof optionalHandler === 'string') {\n    modelKey = optionalHandler;\n  }\n\n  attrs = attrs || {};\n\n  beforeEach(function(done) {\n    const test = this;\n    const app = this.app;\n    const model = app.models[modelName];\n\n    app.set('remoting', {errorHandler: {debug: true, log: false}});\n    assert(model, 'cannot get model of name ' + modelName + ' from app.models');\n    assert(model.dataSource, 'cannot test model ' + modelName +\n        ' without attached dataSource');\n    assert(\n      typeof model.create === 'function',\n      modelName + ' does not have a create method',\n    );\n\n    model.create(attrs, function(err, result) {\n      if (err) {\n        console.error(err.message);\n        if (err.details) console.error(err.details);\n\n        done(err);\n      } else {\n        test[modelKey] = result;\n\n        done();\n      }\n    });\n  });\n\n  if (typeof optionalHandler === 'function') {\n    beforeEach(optionalHandler);\n  }\n\n  afterEach(function(done) {\n    this[modelKey].destroy(done);\n  });\n};\n\n_beforeEach.givenUser = function(attrs, optionalHandler) {\n  _beforeEach.givenModel('user', attrs, optionalHandler);\n};\n\n_beforeEach.givenLoggedInUser = function(credentials, optionalHandler) {\n  _beforeEach.givenUser(credentials, function(done) {\n    const test = this;\n    this.user.constructor.login(credentials, function(err, token) {\n      if (err) {\n        done(err);\n      } else {\n        test.loggedInAccessToken = token;\n\n        done();\n      }\n    });\n  });\n\n  afterEach(function(done) {\n    const test = this;\n    this.loggedInAccessToken.destroy(function(err) {\n      if (err) return done(err);\n\n      test.loggedInAccessToken = undefined;\n\n      done();\n    });\n  });\n};\n\n_beforeEach.givenAnUnauthenticatedToken = function(attrs, optionalHandler) {\n  _beforeEach.givenModel('accessToken', attrs, optionalHandler);\n};\n\n_beforeEach.givenAnAnonymousToken = function(attrs, optionalHandler) {\n  _beforeEach.givenModel('accessToken', {id: '$anonymous'}, optionalHandler);\n};\n\n_describe.whenCalledRemotely = function(verb, url, data, cb) {\n  if (cb === undefined) {\n    cb = data;\n    data = null;\n  }\n\n  let urlStr = url;\n  if (typeof url === 'function') {\n    urlStr = '/<dynamic>';\n  }\n\n  describe(verb.toUpperCase() + ' ' + urlStr, function() {\n    beforeEach(function(cb) {\n      if (typeof url === 'function') {\n        this.url = url.call(this);\n      }\n      this.remotely = true;\n      this.verb = verb.toUpperCase();\n      this.url = this.url || url;\n      let methodForVerb = verb.toLowerCase();\n      if (methodForVerb === 'delete') methodForVerb = 'del';\n\n      if (this.request === undefined) {\n        const msg = 'App is not specified. ' +\n          'Please use lt.beforeEach.withApp to specify the app.';\n        throw new Error(msg);\n      }\n\n      this.http = this.request[methodForVerb](this.url);\n      delete this.url;\n      this.http.set('Accept', 'application/json');\n      if (this.loggedInAccessToken) {\n        this.http.set('authorization', this.loggedInAccessToken.id);\n      }\n      if (data) {\n        let payload = data;\n        if (typeof data === 'function')\n          payload = data.call(this);\n        this.http.send(payload);\n      }\n      this.req = this.http.req;\n      const test = this;\n      this.http.end(function(err) {\n        test.req = test.http.req;\n        test.res = test.http.response;\n        delete test.url;\n\n        cb();\n      });\n    });\n\n    cb();\n  });\n};\n\n_describe.whenLoggedInAsUser = function(credentials, cb) {\n  describe('when logged in as user', function() {\n    _beforeEach.givenLoggedInUser(credentials);\n\n    cb();\n  });\n};\n\n_describe.whenCalledByUser = function(credentials, verb, url, data, cb) {\n  describe('when called by logged in user', function() {\n    _beforeEach.givenLoggedInUser(credentials);\n    _describe.whenCalledRemotely(verb, url, data, cb);\n  });\n};\n\n_describe.whenCalledAnonymously = function(verb, url, data, cb) {\n  describe('when called anonymously', function() {\n    _beforeEach.givenAnAnonymousToken();\n    _describe.whenCalledRemotely(verb, url, data, cb);\n  });\n};\n\n_describe.whenCalledUnauthenticated = function(verb, url, data, cb) {\n  describe('when called with unauthenticated token', function() {\n    _beforeEach.givenAnAnonymousToken();\n    _describe.whenCalledRemotely(verb, url, data, cb);\n  });\n};\n\n_it.shouldBeAllowed = function() {\n  it('should be allowed', function() {\n    assert(this.req);\n    assert(this.res);\n    // expect success - status 2xx or 3xx\n    expect(this.res.statusCode).to.be.within(100, 399);\n  });\n};\n\n_it.shouldBeDenied = function() {\n  it('should not be allowed', function() {\n    assert(this.res);\n    const expectedStatus = this.aclErrorStatus ||\n      this.app && this.app.get('aclErrorStatus') ||\n      401;\n    expect(this.res.statusCode).to.equal(expectedStatus);\n  });\n};\n\n_it.shouldNotBeFound = function() {\n  it('should not be found', function() {\n    assert(this.res);\n    assert.equal(this.res.statusCode, 404);\n  });\n};\n\n_it.shouldBeAllowedWhenCalledAnonymously =\nfunction(verb, url, data) {\n  _describe.whenCalledAnonymously(verb, url, data, function() {\n    _it.shouldBeAllowed();\n  });\n};\n\n_it.shouldBeDeniedWhenCalledAnonymously =\nfunction(verb, url) {\n  _describe.whenCalledAnonymously(verb, url, function() {\n    _it.shouldBeDenied();\n  });\n};\n\n_it.shouldBeAllowedWhenCalledUnauthenticated =\nfunction(verb, url, data) {\n  _describe.whenCalledUnauthenticated(verb, url, data, function() {\n    _it.shouldBeAllowed();\n  });\n};\n\n_it.shouldBeDeniedWhenCalledUnauthenticated =\nfunction(verb, url) {\n  _describe.whenCalledUnauthenticated(verb, url, function() {\n    _it.shouldBeDenied();\n  });\n};\n\n_it.shouldBeAllowedWhenCalledByUser =\nfunction(credentials, verb, url, data) {\n  _describe.whenCalledByUser(credentials, verb, url, data, function() {\n    _it.shouldBeAllowed();\n  });\n};\n\n_it.shouldBeDeniedWhenCalledByUser =\nfunction(credentials, verb, url) {\n  _describe.whenCalledByUser(credentials, verb, url, function() {\n    _it.shouldBeDenied();\n  });\n};\n"
  },
  {
    "path": "test/helpers/use-english.js",
    "content": "// Copyright IBM Corp. 2017,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst env = process.env;\n\n// delete any user-provided language settings\ndelete env.LC_ALL;\ndelete env.LC_MESSAGES;\ndelete env.LANG;\ndelete env.LANGUAGE;\ndelete env.STRONGLOOP_GLOBALIZE_APP_LANGUAGE;\n"
  },
  {
    "path": "test/helpers/wait-for-event.js",
    "content": "// Copyright IBM Corp. 2017,2018. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst Promise = require('bluebird');\n\nfunction waitForEvent(emitter, event) {\n  return new Promise((resolve, reject) => {\n    emitter.on(event, resolve);\n  });\n}\n\nmodule.exports = waitForEvent;\n\n"
  },
  {
    "path": "test/hidden-properties.test.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst loopback = require('../');\nconst request = require('supertest');\n\ndescribe('hidden properties', function() {\n  beforeEach(function(done) {\n    const app = this.app = loopback();\n    const Product = this.Product = loopback.PersistedModel.extend(\n      'product',\n      {},\n      {hidden: ['secret']},\n    );\n    Product.attachTo(loopback.memory());\n\n    const Category = this.Category = loopback.PersistedModel.extend('category');\n    Category.attachTo(loopback.memory());\n    Category.hasMany(Product);\n\n    app.model(Product);\n    app.model(Category);\n    app.use(loopback.rest());\n\n    Category.create({\n      name: 'my category',\n    }, function(err, category) {\n      category.products.create({\n        name: 'pencil',\n        secret: 'a secret',\n      }, done);\n    });\n  });\n\n  afterEach(function(done) {\n    const Product = this.Product;\n    this.Category.destroyAll(function() {\n      Product.destroyAll(done);\n    });\n  });\n\n  it('should hide a property remotely', function(done) {\n    request(this.app)\n      .get('/products')\n      .expect('Content-Type', /json/)\n      .expect(200)\n      .end(function(err, res) {\n        if (err) return done(err);\n\n        const product = res.body[0];\n        assert.equal(product.secret, undefined);\n\n        done();\n      });\n  });\n\n  it('should hide a property of nested models', function(done) {\n    const app = this.app;\n    request(app)\n      .get('/categories?filter[include]=products')\n      .expect('Content-Type', /json/)\n      .expect(200)\n      .end(function(err, res) {\n        if (err) return done(err);\n\n        const category = res.body[0];\n        const product = category.products[0];\n        assert.equal(product.secret, undefined);\n\n        done();\n      });\n  });\n});\n"
  },
  {
    "path": "test/integration.test.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst expect = require('./helpers/expect');\nconst loopback = require('../');\nconst net = require('net');\n\ndescribe('loopback application', function() {\n  it('pauses request stream during authentication', function(done) {\n    // This test reproduces the issue reported in\n    //   https://github.com/strongloop/loopback-storage-service/issues/7\n    const app = loopback();\n    setupAppWithStreamingMethod();\n\n    app.listen(0, function() {\n      sendHttpRequestInOnePacket(\n        this.address().port,\n        'POST /streamers/read HTTP/1.0\\n' +\n          'Content-Length: 1\\n' +\n          'Content-Type: application/x-custom-octet-stream\\n' +\n          '\\n' +\n          'X',\n        function(err, res) {\n          if (err) return done(err);\n\n          expect(res).to.match(/\\nX$/);\n\n          done();\n        },\n      );\n    });\n\n    function setupAppWithStreamingMethod() {\n      app.dataSource('db', {\n        connector: loopback.Memory,\n      });\n      const db = app.datasources.db;\n\n      loopback.User.attachTo(db);\n      loopback.AccessToken.attachTo(db);\n      loopback.Role.attachTo(db);\n      loopback.ACL.attachTo(db);\n      loopback.User.hasMany(loopback.AccessToken, {as: 'accessTokens'});\n\n      const Streamer = app.registry.createModel('Streamer');\n      app.model(Streamer, {dataSource: 'db'});\n      Streamer.read = function(req, res, cb) {\n        let body = new Buffer(0);\n        req.on('data', function(chunk) {\n          body += chunk;\n        });\n        req.on('end', function() {\n          res.end(body.toString());\n          // we must not call the callback here\n          // because it will attempt to add response headers\n        });\n        req.once('error', function(err) {\n          cb(err);\n        });\n      };\n      loopback.remoteMethod(Streamer.read, {\n        http: {method: 'post'},\n        accepts: [\n          {arg: 'req', type: 'Object', http: {source: 'req'}},\n          {arg: 'res', type: 'Object', http: {source: 'res'}},\n        ],\n      });\n\n      app.enableAuth();\n      app.use(loopback.token({model: app.models.accessToken}));\n      app.use(loopback.rest());\n    }\n\n    function sendHttpRequestInOnePacket(port, reqString, cb) {\n      const socket = net.createConnection(port);\n      let response = new Buffer(0);\n\n      socket.on('data', function(chunk) {\n        response += chunk;\n      });\n      socket.on('end', function() {\n        callCb(null, response.toString());\n      });\n      socket.once('error', function(err) {\n        callCb(err);\n      });\n\n      socket.write(reqString.replace(/\\n/g, '\\r\\n'));\n\n      function callCb(err, res) {\n        if (!cb) return;\n        cb(err, res);\n        cb = null;\n      }\n    }\n  });\n});\n"
  },
  {
    "path": "test/karma.conf.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst isDocker = require('is-docker');\nconst which = require('which');\n\n// Karma configuration\n// http://karma-runner.github.io/0.12/config/configuration-file.html\n\nmodule.exports = function(config) {\n  // see https://github.com/docker/for-linux/issues/496\n  const disableChromeSandbox = isDocker() && !process.env.TRAVIS;\n  if (disableChromeSandbox) {\n    console.log('!! Disabling Chrome sandbox to support un-privileged Docker !!');\n  }\n\n  const hasChromium =\n    which.sync('chromium-browser', {nothrow: true}) ||\n    which.sync('chromium', {nothrow: true});\n\n  config.set({\n    customLaunchers: {\n      ChromeDocker: {\n        // cis-jenkins build server does not provide Chrome, only Chromium\n        base: hasChromium ? 'ChromiumHeadless' : 'ChromeHeadless',\n        // We must disable the Chrome sandbox when running Chrome inside Docker\n        // (Chrome's sandbox needs more permissions than Docker allows by default)\n        // See https://github.com/docker/for-linux/issues/496\n        flags: disableChromeSandbox ? ['--no-sandbox'] : [],\n      },\n    },\n\n    // enable / disable watching file and executing tests whenever any file changes\n    autoWatch: true,\n\n    // base path, that will be used to resolve files and exclude\n    basePath: '../',\n\n    // testing framework to use (jasmine/mocha/qunit/...)\n    frameworks: ['es6-shim', 'browserify', 'mocha'],\n\n    // list of files / patterns to load in the browser\n    files: [\n      'test/loopback.test.js',\n      'test/model.test.js',\n      // [rfeng] Browserified common/models/application.js\n      // (crypto.randomBytes()) is not compatible with phantomjs. Skip\n      // the karma test for now.\n      // 'test/model.application.test.js',\n      'test/geo-point.test.js',\n      'test/replication.test.js',\n      'test/change.test.js',\n      'test/checkpoint.test.js',\n      'test/app.test.js',\n    ],\n\n    // list of files / patterns to exclude\n    exclude: [\n    ],\n\n    // test results reporter to use\n    // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'\n    reporters: ['dots'],\n\n    // web server port\n    port: 9876,\n\n    // cli runner port\n    runnerPort: 9100,\n\n    // Start these browsers, currently available:\n    // - Chrome\n    // - ChromeCanary\n    // - Firefox\n    // - Opera\n    // - Safari (only Mac)\n    // - PhantomJS\n    // - IE (only Windows)\n    browsers: [\n      'Chrome',\n    ],\n\n    // Which plugins to enable\n    plugins: [\n      'karma-browserify',\n      'karma-es6-shim',\n      'karma-mocha',\n      'karma-chrome-launcher',\n      'karma-junit-reporter',\n    ],\n\n    // If browser does not capture in given timeout [ms], kill it\n    captureTimeout: 60000,\n\n    // to avoid DISCONNECTED messages\n    browserDisconnectTimeout: 10000, // default 2000\n    browserDisconnectTolerance: 1, // default 0\n    browserNoActivityTimeout: 60000, // default 10000\n\n    // Continuous Integration mode\n    // if true, it capture browsers, run tests and exit\n    singleRun: false,\n\n    colors: true,\n\n    // level of logging\n    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG\n    logLevel: config.LOG_INFO,\n\n    // Uncomment the following lines if you are using grunt's server to run the tests\n    // proxies: {\n    //   '/': 'http://localhost:9000/'\n    // },\n    // URL root prevent conflicts with the site root\n    // urlRoot: '_karma_'\n\n    // Browserify config (all optional)\n    browserify: {\n      // extensions: ['.coffee'],\n      ignore: [\n        'nodemailer',\n        'passport',\n        'passport-local',\n        'superagent',\n        'supertest',\n      ],\n      packageFilter: function(pkg, dir) {\n        // async@3 (used e.g. by loopback-connector) is specifying custom\n        // browserify config, in particular it wants to apply transformation\n        // `babelify`. We don't have `babelify` installed because we are\n        // testing using latest Chrome and thus don't need any transpilation.\n        // Let's remove the browserify config from the package and force\n        // browserify to use our config instead.\n        if (pkg.name === 'async') {\n          delete pkg.browserify;\n        }\n        return pkg;\n      },\n      debug: true,\n      // noParse: ['jquery'],\n      watch: true,\n    },\n\n    // Add browserify to preprocessors\n    preprocessors: {'test/**/*.js': ['browserify']},\n  });\n};\n"
  },
  {
    "path": "test/key-value-model.test.js",
    "content": "// Copyright IBM Corp. 2016,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst expect = require('./helpers/expect');\nconst http = require('http');\nconst loopback = require('..');\nconst supertest = require('supertest');\n\nconst AN_OBJECT_VALUE = {name: 'an-object'};\n\ndescribe('KeyValueModel', function() {\n  let request, app, CacheItem;\n  beforeEach(setupAppAndCacheItem);\n\n  describe('REST API', function() {\n    before(setupSharedHttpServer);\n\n    it('provides \"get(key)\" at \"GET /key\"', function(done) {\n      CacheItem.set('get-key', AN_OBJECT_VALUE);\n      request.get('/CacheItems/get-key')\n        .end(function(err, res) {\n          if (err) return done(err);\n          expect(res.body).to.eql(AN_OBJECT_VALUE);\n          done();\n        });\n    });\n\n    it('returns 404 when getting a key that does not exist', function(done) {\n      request.get('/CacheItems/key-does-not-exist')\n        .expect(404, done);\n    });\n\n    it('provides \"set(key)\" at \"PUT /key\"', function(done) {\n      request.put('/CacheItems/set-key')\n        .send(AN_OBJECT_VALUE)\n        .expect(204)\n        .end(function(err, res) {\n          if (err) return done(err);\n          CacheItem.get('set-key', function(err, value) {\n            if (err) return done(err);\n            expect(value).to.eql(AN_OBJECT_VALUE);\n            done();\n          });\n        });\n    });\n\n    it('provides \"set(key, ttl)\" at \"PUT /key?ttl={num}\"', function(done) {\n      request.put('/CacheItems/set-key-ttl?ttl=10')\n        .send(AN_OBJECT_VALUE)\n        .end(function(err, res) {\n          if (err) return done(err);\n          setTimeout(function() {\n            CacheItem.get('set-key-ttl', function(err, value) {\n              if (err) return done(err);\n              expect(value).to.equal(null);\n              done();\n            });\n          }, 20);\n        });\n    });\n\n    it('provides \"expire(key, ttl)\" at \"PUT /key/expire\"',\n      function(done) {\n        CacheItem.set('expire-key', AN_OBJECT_VALUE, function(err) {\n          if (err) return done(err);\n          request.put('/CacheItems/expire-key/expire')\n            .send({ttl: 10})\n            .end(function(err, res) {\n              if (err) return done(err);\n              setTimeout(function() {\n                CacheItem.get('set-key-ttl', function(err, value) {\n                  if (err) return done(err);\n                  expect(value).to.equal(null);\n                  done();\n                });\n              }, 20);\n            });\n        });\n      });\n\n    it('returns 404 when expiring a key that does not exist', function(done) {\n      request.put('/CacheItems/key-does-not-exist/expire')\n        .send({ttl: 10})\n        .expect(404, done);\n    });\n\n    it('provides \"ttl(key)\" at \"GET /key/ttl\"', function(done) {\n      request.put('/CacheItems/ttl-key?ttl=2000')\n        .end(function(err, res) {\n          if (err) return done(err);\n          request.get('/CacheItems/ttl-key/ttl')\n            .end(function(err, res) {\n              if (err) return done(err);\n              expect(res.body).to.be.a('number');\n              done();\n            });\n        });\n    });\n\n    it('returns 204 when getting TTL for a key that does not have TTL set',\n      function(done) {\n        request.put('/CacheItems/ttl-key')\n          .end(function(err, res) {\n            if (err) return done(err);\n            request.get('/CacheItems/ttl-key/ttl')\n              .expect(204, done);\n          });\n      });\n\n    it('returns 404 when getting TTL for a key when TTL has expired',\n      function(done) {\n        request.put('/CacheItems/ttl-key?ttl=10')\n          .end(function(err, res) {\n            setTimeout(function() {\n              if (err) return done(err);\n              request.get('/CacheItems/ttl-key/ttl')\n                .expect(404, done);\n            }, 20);\n          });\n      });\n\n    it('returns 404 when getting TTL for a key that does not exist',\n      function(done) {\n        request.get('/CacheItems/key-does-not-exist/ttl')\n          .expect(404, done);\n      });\n\n    it('provides \"keys(filter)\" at \"GET /keys\"', function(done) {\n      CacheItem.set('list-key', AN_OBJECT_VALUE, function(err) {\n        if (err) return done(err);\n        request.get('/CacheItems/keys')\n          .send(AN_OBJECT_VALUE)\n          .end(function(err, res) {\n            if (err) return done(err);\n            if (res.body.error) return done(res.body.error);\n            expect(res.body).to.eql(['list-key']);\n            done();\n          });\n      });\n    });\n  });\n\n  function setupAppAndCacheItem() {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.use(loopback.rest());\n\n    CacheItem = app.registry.createModel({\n      name: 'CacheItem',\n      base: 'KeyValueModel',\n    });\n\n    app.dataSource('kv', {connector: 'kv-memory'});\n    app.model(CacheItem, {dataSource: 'kv'});\n  }\n\n  let _server, _requestHandler; // eslint-disable-line one-var\n  function setupSharedHttpServer(done) {\n    _server = http.createServer(function(req, res) {\n      app(req, res);\n    });\n    _server.listen(0, '127.0.0.1')\n      .once('listening', function() {\n        request = supertest('http://127.0.0.1:' + this.address().port);\n        done();\n      })\n      .once('error', function(err) { done(err); });\n  }\n});\n"
  },
  {
    "path": "test/loopback.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst it = require('./util/it');\nconst describe = require('./util/describe');\nconst Domain = require('domain');\nconst EventEmitter = require('events').EventEmitter;\nconst loopback = require('../');\nconst expect = require('./helpers/expect');\nconst assert = require('assert');\n\ndescribe('loopback', function() {\n  let nameCounter = 0;\n  let uniqueModelName;\n\n  beforeEach(function() {\n    uniqueModelName = 'TestModel-' + (++nameCounter);\n  });\n\n  describe('exports', function() {\n    it('ValidationError', function() {\n      expect(loopback.ValidationError).to.be.a('function')\n        .and.have.property('name', 'ValidationError');\n    });\n\n    it.onServer('includes `faviconFile`', function() {\n      const file = loopback.faviconFile;\n      expect(file, 'faviconFile').to.not.equal(undefined);\n      expect(require('fs').existsSync(loopback.faviconFile), 'file exists')\n        .to.equal(true);\n    });\n\n    it.onServer('has `getCurrentContext` method', function() {\n      expect(loopback.getCurrentContext).to.be.a('function');\n    });\n\n    it.onServer('exports all expected properties', function() {\n      const EXPECTED = [\n        'ACL',\n        'AccessToken',\n        'Application',\n        'Change',\n        'Checkpoint',\n        'Connector',\n        'DataSource',\n        'DateString',\n        'Email',\n        'GeoPoint',\n        'KeyValueModel',\n        'Mail',\n        'Memory',\n        'Model',\n        'PersistedModel',\n        'Remote',\n        'Role',\n        'RoleMapping',\n        'Route',\n        'Router',\n        'Scope',\n        'User',\n        'ValidationError',\n        'application',\n        'configureModel',\n        'context',\n        'createContext',\n        'createDataSource',\n        'createModel',\n        'defaultDataSources',\n        'errorHandler',\n        'favicon',\n        'faviconFile',\n        'findModel',\n        'getCurrentContext',\n        'getModel',\n        'getModelByType',\n        'isBrowser',\n        'isServer',\n        'length',\n        'memory',\n        'modelBuilder',\n        'name',\n        'prototype',\n        'query',\n        'registry',\n        'remoteMethod',\n        'request',\n        'response',\n        'rest',\n        'runInContext',\n        'static',\n        'status',\n        'template',\n        'token',\n        'urlNotFound',\n        'version',\n      ];\n\n      const actual = Object.getOwnPropertyNames(loopback);\n      actual.sort();\n      expect(actual).to.include.members(EXPECTED);\n    });\n  });\n\n  describe('loopback(options)', function() {\n    it('supports localRegistry:true', function() {\n      const app = loopback({localRegistry: true});\n      expect(app.registry).to.not.equal(loopback.registry);\n    });\n\n    it('does not load builtin models into the local registry', function() {\n      const app = loopback({localRegistry: true});\n      expect(app.registry.findModel('User')).to.equal(undefined);\n    });\n\n    it('supports loadBuiltinModels:true', function() {\n      const app = loopback({localRegistry: true, loadBuiltinModels: true});\n      expect(app.registry.findModel('User'))\n        .to.have.property('modelName', 'User');\n    });\n  });\n\n  describe('loopback.createDataSource(options)', function() {\n    it('Create a data source with a connector.', function() {\n      const dataSource = loopback.createDataSource({\n        connector: loopback.Memory,\n      });\n      assert(dataSource.connector);\n    });\n  });\n\n  describe('data source created by loopback', function() {\n    it('should create model extending Model by default', function() {\n      const dataSource = loopback.createDataSource({\n        connector: loopback.Memory,\n      });\n      const m1 = dataSource.createModel('m1', {});\n      assert(m1.prototype instanceof loopback.Model);\n    });\n  });\n\n  describe('model created by loopback', function() {\n    it('should extend from Model by default', function() {\n      const m1 = loopback.createModel('m1', {});\n      assert(m1.prototype instanceof loopback.Model);\n    });\n  });\n\n  describe('loopback.remoteMethod(Model, fn, [options]);', function() {\n    it('Setup a remote method.', function() {\n      const Product = loopback.createModel('product', {price: Number});\n\n      Product.stats = function(fn) {\n        // ...\n      };\n\n      loopback.remoteMethod(\n        Product.stats,\n        {\n          returns: {arg: 'stats', type: 'array'},\n          http: {path: '/info', verb: 'get'},\n        },\n      );\n\n      assert.equal(Product.stats.returns.arg, 'stats');\n      assert.equal(Product.stats.returns.type, 'array');\n      assert.equal(Product.stats.http.path, '/info');\n      assert.equal(Product.stats.http.verb, 'get');\n      assert.equal(Product.stats.shared, true);\n    });\n  });\n\n  describe('loopback.createModel(name, properties, options)', function() {\n    describe('options.base', function() {\n      it('should extend from options.base', function() {\n        const MyModel = loopback.createModel('MyModel', {}, {\n          foo: {\n            bar: 'bat',\n          },\n        });\n        const MyCustomModel = loopback.createModel('MyCustomModel', {}, {\n          base: 'MyModel',\n          foo: {\n            bat: 'baz',\n          },\n        });\n        assert(MyCustomModel.super_ === MyModel);\n        assert.deepEqual(MyCustomModel.settings.foo, {bar: 'bat', bat: 'baz'});\n        assert(MyCustomModel.super_.modelName === MyModel.modelName);\n      });\n    });\n\n    describe('loopback.getModel and getModelByType', function() {\n      it('should be able to get model by name', function() {\n        const MyModel = loopback.createModel('MyModel', {}, {\n          foo: {\n            bar: 'bat',\n          },\n        });\n        const MyCustomModel = loopback.createModel('MyCustomModel', {}, {\n          base: 'MyModel',\n          foo: {\n            bat: 'baz',\n          },\n        });\n        assert(loopback.getModel('MyModel') === MyModel);\n        assert(loopback.getModel('MyCustomModel') === MyCustomModel);\n        assert(loopback.findModel('Invalid') === undefined);\n        assert(loopback.getModel(MyModel) === MyModel);\n      });\n      it('should be able to get model by type', function() {\n        const MyModel = loopback.createModel('MyModel', {}, {\n          foo: {\n            bar: 'bat',\n          },\n        });\n        const MyCustomModel = loopback.createModel('MyCustomModel', {}, {\n          base: 'MyModel',\n          foo: {\n            bat: 'baz',\n          },\n        });\n        assert(loopback.getModelByType(MyModel) === MyCustomModel);\n        assert(loopback.getModelByType(MyCustomModel) === MyCustomModel);\n      });\n\n      it('should throw when the model does not exist', function() {\n        expect(function() { loopback.getModel(uniqueModelName); })\n          .to.throw(Error, new RegExp('Model not found: ' + uniqueModelName));\n      });\n    });\n\n    it('configures remote methods', function() {\n      const TestModel = loopback.createModel(uniqueModelName, {}, {\n        methods: {\n          staticMethod: {\n            isStatic: true,\n            http: {path: '/static'},\n          },\n          instanceMethod: {\n            isStatic: false,\n            http: {path: '/instance'},\n          },\n        },\n      });\n\n      const methodNames = TestModel.sharedClass.methods().map(function(m) {\n        return m.stringName.replace(/^[^.]+\\./, ''); // drop the class name\n      });\n\n      expect(methodNames).to.include.members([\n        'staticMethod',\n        'prototype.instanceMethod',\n      ]);\n    });\n  });\n\n  describe('loopback.createModel(config)', function() {\n    it('creates the model', function() {\n      const model = loopback.createModel({\n        name: uniqueModelName,\n      });\n\n      expect(model.prototype).to.be.instanceof(loopback.Model);\n    });\n\n    it('interprets extra first-level keys as options', function() {\n      const model = loopback.createModel({\n        name: uniqueModelName,\n        base: 'User',\n      });\n\n      expect(model.prototype).to.be.instanceof(loopback.User);\n    });\n\n    it('prefers config.options.key over config.key', function() {\n      const model = loopback.createModel({\n        name: uniqueModelName,\n        base: 'User',\n        options: {\n          base: 'Application',\n        },\n      });\n\n      expect(model.prototype).to.be.instanceof(loopback.Application);\n    });\n  });\n\n  describe('loopback.configureModel(ModelCtor, config)', function() {\n    it('adds new relations', function() {\n      const model = loopback.Model.extend(uniqueModelName);\n\n      loopback.configureModel(model, {\n        dataSource: null,\n        relations: {\n          owner: {\n            type: 'belongsTo',\n            model: 'User',\n          },\n        },\n      });\n\n      expect(model.settings.relations).to.have.property('owner');\n    });\n\n    it('updates existing relations', function() {\n      const model = loopback.Model.extend(uniqueModelName, {}, {\n        relations: {\n          owner: {\n            type: 'belongsTo',\n            model: 'User',\n          },\n        },\n      });\n\n      loopback.configureModel(model, {\n        dataSource: false,\n        relations: {\n          owner: {\n            model: 'Application',\n          },\n        },\n      });\n\n      expect(model.settings.relations.owner).to.eql({\n        type: 'belongsTo',\n        model: 'Application',\n      });\n    });\n\n    it('updates relations before attaching to a dataSource', function() {\n      const db = loopback.createDataSource({connector: loopback.Memory});\n      const model = loopback.Model.extend(uniqueModelName);\n\n      // This test used to work because User model was already attached\n      // by other tests via `loopback.autoAttach()`\n      // Now that autoAttach is gone, it turns out the tested functionality\n      // does not work exactly as intended. To keep this change narrowly\n      // focused on removing autoAttach, we are attaching the User model\n      // to simulate the old test setup.\n      loopback.User.attachTo(db);\n\n      loopback.configureModel(model, {\n        dataSource: db,\n        relations: {\n          owner: {\n            type: 'belongsTo',\n            model: 'User',\n          },\n        },\n      });\n\n      const owner = model.prototype.owner;\n      expect(owner, 'model.prototype.owner').to.be.a('function');\n      expect(owner._targetClass).to.equal('User');\n    });\n\n    it('adds new acls', function() {\n      const model = loopback.Model.extend(uniqueModelName, {}, {\n        acls: [\n          {\n            property: 'find',\n            accessType: 'EXECUTE',\n            principalType: 'ROLE',\n            principalId: '$everyone',\n            permission: 'DENY',\n          },\n        ],\n      });\n\n      loopback.configureModel(model, {\n        dataSource: null,\n        acls: [\n          {\n            property: 'find',\n            accessType: 'EXECUTE',\n            principalType: 'ROLE',\n            principalId: 'admin',\n            permission: 'ALLOW',\n          },\n        ],\n      });\n\n      expect(model.settings.acls).eql([\n        {\n          property: 'find',\n          accessType: 'EXECUTE',\n          principalType: 'ROLE',\n          principalId: '$everyone',\n          permission: 'DENY',\n        },\n        {\n          property: 'find',\n          accessType: 'EXECUTE',\n          principalType: 'ROLE',\n          principalId: 'admin',\n          permission: 'ALLOW',\n        },\n      ]);\n    });\n\n    it('updates existing acls', function() {\n      const model = loopback.Model.extend(uniqueModelName, {}, {\n        acls: [\n          {\n            property: 'find',\n            accessType: 'EXECUTE',\n            principalType: 'ROLE',\n            principalId: '$everyone',\n            permission: 'DENY',\n          },\n        ],\n      });\n\n      loopback.configureModel(model, {\n        dataSource: null,\n        acls: [\n          {\n            property: 'find',\n            accessType: 'EXECUTE',\n            principalType: 'ROLE',\n            principalId: '$everyone',\n            permission: 'ALLOW',\n          },\n        ],\n      });\n\n      expect(model.settings.acls).eql([\n        {\n          property: 'find',\n          accessType: 'EXECUTE',\n          principalType: 'ROLE',\n          principalId: '$everyone',\n          permission: 'ALLOW',\n        },\n      ]);\n    });\n\n    it('updates existing settings', function() {\n      const model = loopback.Model.extend(uniqueModelName, {}, {\n        ttl: 10,\n        emailVerificationRequired: false,\n      });\n\n      const baseName = model.settings.base.name;\n\n      loopback.configureModel(model, {\n        dataSource: null,\n        options: {\n          ttl: 20,\n          realmRequired: true,\n          base: 'X',\n        },\n      });\n\n      expect(model.settings).to.have.property('ttl', 20);\n      expect(model.settings).to.have.property('emailVerificationRequired',\n        false);\n      expect(model.settings).to.have.property('realmRequired', true);\n\n      // configureModel MUST NOT change Model's base class\n      expect(model.settings.base.name).to.equal(baseName);\n    });\n\n    it('configures remote methods', function() {\n      const TestModel = loopback.createModel(uniqueModelName);\n      loopback.configureModel(TestModel, {\n        dataSource: null,\n        methods: {\n          staticMethod: {\n            isStatic: true,\n            http: {path: '/static'},\n          },\n          instanceMethod: {\n            isStatic: false,\n            http: {path: '/instance'},\n          },\n        },\n      });\n\n      const methodNames = TestModel.sharedClass.methods().map(function(m) {\n        return m.stringName.replace(/^[^.]+\\./, ''); // drop the class name\n      });\n\n      expect(methodNames).to.include.members([\n        'staticMethod',\n        'prototype.instanceMethod',\n      ]);\n    });\n  });\n\n  describe('loopback object', function() {\n    it('inherits properties from express', function() {\n      const express = require('express');\n      for (const i in express) {\n        expect(loopback).to.have.property(i, express[i]);\n      }\n    });\n\n    it('exports all built-in models', function() {\n      const expectedModelNames = [\n        'Email',\n        'User',\n        'Application',\n        'AccessToken',\n        'Role',\n        'RoleMapping',\n        'ACL',\n        'Scope',\n        'Change',\n        'Checkpoint',\n      ];\n\n      expect(Object.keys(loopback)).to.include.members(expectedModelNames);\n\n      expectedModelNames.forEach(function(name) {\n        expect(loopback[name], name).to.be.a('function');\n        expect(loopback[name].modelName, name + '.modelName').to.eql(name);\n      });\n    });\n  });\n\n  describe('new remote method configuration', function() {\n    function getAllMethodNamesWithoutClassName(TestModel) {\n      return TestModel.sharedClass.methods().map(function(m) {\n        return m.stringName.replace(/^[^.]+\\./, ''); // drop the class name\n      });\n    }\n\n    it('treats method names that don\\'t start with \"prototype.\" as \"isStatic:true\"', function() {\n      const TestModel = loopback.createModel(uniqueModelName);\n      loopback.configureModel(TestModel, {\n        dataSource: null,\n        methods: {\n          staticMethod: {\n            http: {path: '/static'},\n          },\n        },\n      });\n\n      const methodNames = getAllMethodNamesWithoutClassName(TestModel);\n\n      expect(methodNames).to.include('staticMethod');\n    });\n\n    it('treats method names starting with \"prototype.\" as \"isStatic:false\"', function() {\n      const TestModel = loopback.createModel(uniqueModelName);\n      loopback.configureModel(TestModel, {\n        dataSource: null,\n        methods: {\n          'prototype.instanceMethod': {\n            http: {path: '/instance'},\n          },\n        },\n      });\n\n      const methodNames = getAllMethodNamesWithoutClassName(TestModel);\n\n      expect(methodNames).to.include('prototype.instanceMethod');\n    });\n\n    // Skip this test in browsers because strong-globalize is not removing\n    // `{{` and `}}` control characters from the string.\n    it.onServer('throws when \"isStatic:true\" and method name starts with \"prototype.\"', function() {\n      const TestModel = loopback.createModel(uniqueModelName);\n      expect(function() {\n        loopback.configureModel(TestModel, {\n          dataSource: null,\n          methods: {\n            'prototype.instanceMethod': {\n              isStatic: true,\n              http: {path: '/instance'},\n            },\n          },\n        });\n      }).to.throw(Error, 'Remoting metadata for ' + TestModel.modelName +\n      '.prototype.instanceMethod \"isStatic\" does not match new method name-based style.');\n    });\n\n    it('use \"isStatic:true\" if method name does not start with \"prototype.\"', function() {\n      const TestModel = loopback.createModel(uniqueModelName);\n      loopback.configureModel(TestModel, {\n        dataSource: null,\n        methods: {\n          staticMethod: {\n            isStatic: true,\n            http: {path: '/static'},\n          },\n        },\n      });\n\n      const methodNames = getAllMethodNamesWithoutClassName(TestModel);\n\n      expect(methodNames).to.include('staticMethod');\n    });\n\n    it('use \"isStatic:false\" if method name starts with \"prototype.\"', function() {\n      const TestModel = loopback.createModel(uniqueModelName);\n      loopback.configureModel(TestModel, {\n        dataSource: null,\n        methods: {\n          'prototype.instanceMethod': {\n            isStatic: false,\n            http: {path: '/instance'},\n          },\n        },\n      });\n\n      const methodNames = getAllMethodNamesWithoutClassName(TestModel);\n\n      expect(methodNames).to.include('prototype.instanceMethod');\n    });\n  });\n\n  describe('Remote method inheritance', function() {\n    let app;\n\n    beforeEach(setupLoopback);\n\n    it('inherits remote methods defined via createModel', function() {\n      const Base = app.registry.createModel('Base', {}, {\n        methods: {\n          greet: {\n            http: {path: '/greet'},\n          },\n        },\n      });\n\n      const MyCustomModel = app.registry.createModel('MyCustomModel', {}, {\n        base: 'Base',\n        methods: {\n          hello: {\n            http: {path: '/hello'},\n          },\n        },\n      });\n      const methodNames = getAllMethodNamesWithoutClassName(MyCustomModel);\n\n      expect(methodNames).to.include('greet');\n      expect(methodNames).to.include('hello');\n    });\n\n    it('same remote method with different metadata should override parent', function() {\n      const Base = app.registry.createModel('Base', {}, {\n        methods: {\n          greet: {\n            http: {path: '/greet'},\n          },\n        },\n      });\n\n      const MyCustomModel = app.registry.createModel('MyCustomModel', {}, {\n        base: 'Base',\n        methods: {\n          greet: {\n            http: {path: '/hello'},\n          },\n        },\n      });\n      const methodNames = getAllMethodNamesWithoutClassName(MyCustomModel);\n      const baseMethod = Base.sharedClass.findMethodByName('greet');\n      const customMethod = MyCustomModel.sharedClass.findMethodByName('greet');\n\n      // Base Method\n      expect(baseMethod.http).to.eql({path: '/greet'});\n      expect(baseMethod.http.path).to.equal('/greet');\n      expect(baseMethod.http.path).to.not.equal('/hello');\n\n      // Custom Method\n      expect(methodNames).to.include('greet');\n      expect(customMethod.http).to.eql({path: '/hello'});\n      expect(customMethod.http.path).to.equal('/hello');\n      expect(customMethod.http.path).to.not.equal('/greet');\n    });\n\n    it('does not inherit remote methods defined via configureModel', function() {\n      const Base = app.registry.createModel('Base');\n      app.registry.configureModel(Base, {\n        dataSource: null,\n        methods: {\n          greet: {\n            http: {path: '/greet'},\n          },\n        },\n      });\n\n      const MyCustomModel = app.registry.createModel('MyCustomModel', {}, {\n        base: 'Base',\n        methods: {\n          hello: {\n            http: {path: '/hello'},\n          },\n        },\n      });\n      const methodNames = getAllMethodNamesWithoutClassName(MyCustomModel);\n\n      expect(methodNames).to.not.include('greet');\n      expect(methodNames).to.include('hello');\n    });\n\n    it('does not inherit remote methods defined via configureModel after child model ' +\n        'was created', function() {\n      const Base = app.registry.createModel('Base');\n      const MyCustomModel = app.registry.createModel('MyCustomModel', {}, {\n        base: 'Base',\n      });\n\n      app.registry.configureModel(Base, {\n        dataSource: null,\n        methods: {\n          greet: {\n            http: {path: '/greet'},\n          },\n        },\n      });\n\n      app.registry.configureModel(MyCustomModel, {\n        dataSource: null,\n        methods: {\n          hello: {\n            http: {path: '/hello'},\n          },\n        },\n      });\n      const baseMethodNames = getAllMethodNamesWithoutClassName(Base);\n      const methodNames = getAllMethodNamesWithoutClassName(MyCustomModel);\n\n      expect(baseMethodNames).to.include('greet');\n      expect(methodNames).to.not.include('greet');\n      expect(methodNames).to.include('hello');\n    });\n\n    function setupLoopback() {\n      app = loopback({localRegistry: true});\n    }\n\n    function getAllMethodNamesWithoutClassName(Model) {\n      return Model.sharedClass.methods().map(function(m) {\n        return m.stringName.replace(/^[^.]+\\./, ''); // drop the class name\n      });\n    }\n  });\n\n  describe('Hiding shared methods', function() {\n    let app;\n\n    beforeEach(setupLoopback);\n\n    it('hides remote methods using fixed method names', function() {\n      const TestModel = app.registry.createModel(uniqueModelName);\n      app.model(TestModel, {\n        dataSource: null,\n        methods: {\n          staticMethod: {\n            isStatic: true,\n            http: {path: '/static'},\n          },\n        },\n        options: {\n          remoting: {\n            sharedMethods: {\n              staticMethod: false,\n            },\n          },\n        },\n      });\n\n      const publicMethods = getSharedMethods(TestModel);\n\n      expect(publicMethods).not.to.include.members([\n        'staticMethod',\n      ]);\n    });\n\n    it('hides remote methods using a glob pattern', function() {\n      const TestModel = app.registry.createModel(uniqueModelName);\n      app.model(TestModel, {\n        dataSource: null,\n        methods: {\n          staticMethod: {\n            isStatic: true,\n            http: {path: '/static'},\n          },\n          instanceMethod: {\n            isStatic: false,\n            http: {path: '/instance'},\n          },\n        },\n        options: {\n          remoting: {\n            sharedMethods: {\n              'prototype.*': false,\n            },\n          },\n        },\n      });\n\n      const publicMethods = getSharedMethods(TestModel);\n\n      expect(publicMethods).to.include.members([\n        'staticMethod',\n      ]);\n      expect(publicMethods).not.to.include.members([\n        'instanceMethod',\n      ]);\n    });\n\n    it('hides all remote methods using *', function() {\n      const TestModel = app.registry.createModel(uniqueModelName);\n      app.model(TestModel, {\n        dataSource: null,\n        methods: {\n          staticMethod: {\n            isStatic: true,\n            http: {path: '/static'},\n          },\n          instanceMethod: {\n            isStatic: false,\n            http: {path: '/instance'},\n          },\n        },\n        options: {\n          remoting: {\n            sharedMethods: {\n              '*': false,\n            },\n          },\n        },\n      });\n\n      const publicMethods = getSharedMethods(TestModel);\n\n      expect(publicMethods).to.be.empty();\n    });\n\n    it('hides methods for related models using globs (model configured first)', function() {\n      const TestModel = app.registry.createModel('TestModel');\n      const RelatedModel = app.registry.createModel('RelatedModel');\n      app.dataSource('test', {connector: 'memory'});\n      app.model(TestModel, {\n        dataSource: 'test',\n        relations: {\n          related: {\n            type: 'hasOne',\n            model: RelatedModel,\n          },\n        },\n        options: {\n          remoting: {\n            sharedMethods: {\n              '*__related': false,\n            },\n          },\n        },\n      });\n      app.model(RelatedModel, {dataSource: 'test'});\n\n      const publicMethods = getSharedMethods(TestModel);\n\n      expect(publicMethods).to.not.include.members([\n        'prototype.__create__related',\n      ]);\n    });\n\n    it('hides methods for related models using globs (related model configured first)', function() {\n      const TestModel = app.registry.createModel('TestModel');\n      const RelatedModel = app.registry.createModel('RelatedModel');\n      app.dataSource('test', {connector: 'memory'});\n      app.model(RelatedModel, {dataSource: 'test'});\n      app.model(TestModel, {\n        dataSource: 'test',\n        relations: {\n          related: {\n            type: 'hasOne',\n            model: RelatedModel,\n          },\n        },\n        options: {\n          remoting: {\n            sharedMethods: {\n              '*__related': false,\n            },\n          },\n        },\n      });\n\n      const publicMethods = getSharedMethods(TestModel);\n\n      expect(publicMethods).to.not.include.members([\n        'prototype.__create__related',\n      ]);\n    });\n\n    function setupLoopback() {\n      app = loopback({localRegistry: true});\n    }\n\n    function getSharedMethods(Model) {\n      return Model.sharedClass\n        .methods()\n        .filter(function(m) {\n          return m.shared === true;\n        })\n        .map(function(m) {\n          return m.stringName.replace(/^[^.]+\\./, ''); // drop the class name\n        });\n    }\n  });\n});\n"
  },
  {
    "path": "test/memory.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst loopback = require('../');\n\ndescribe('Memory Connector', function() {\n  it('Create a model using the memory connector', function(done) {\n    // use the built in memory function\n    // to create a memory data source\n    let memory = loopback.memory();\n\n    // or create it using the standard\n    // data source creation api\n    memory = loopback.createDataSource({\n      connector: loopback.Memory,\n    });\n\n    // create a model using the\n    // memory data source\n    const properties = {\n      name: String,\n      price: Number,\n    };\n\n    const Product = memory.createModel('product', properties);\n\n    Product.create([\n      {name: 'apple', price: 0.79},\n      {name: 'pear', price: 1.29},\n      {name: 'orange', price: 0.59},\n    ], count);\n\n    function count() {\n      Product.count(function(err, count) {\n        assert.equal(count, 3);\n\n        done();\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "test/mocha.opts",
    "content": "--require ./test/helpers/use-english\n"
  },
  {
    "path": "test/model.application.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require(('../'));\nconst assert = require('assert');\nconst Application = loopback.Application;\n\ndescribe('Application', function() {\n  let registeredApp = null;\n\n  before(function attachToMemory() {\n    Application.attachTo(loopback.memory());\n  });\n\n  it('honors `application.register` - callback variant', function(done) {\n    Application.register('rfeng', 'MyTestApp',\n      {description: 'My test application'}, function(err, result) {\n        const app = result;\n        assert.equal(app.owner, 'rfeng');\n        assert.equal(app.name, 'MyTestApp');\n        assert.equal(app.description, 'My test application');\n\n        done(err, result);\n      });\n  });\n\n  it('honors `application.register` - promise variant', function(done) {\n    Application.register('rfeng', 'MyTestApp',\n      {description: 'My test application'})\n      .then(function(result) {\n        const app = result;\n        assert.equal(app.owner, 'rfeng');\n        assert.equal(app.name, 'MyTestApp');\n        assert.equal(app.description, 'My test application');\n\n        done();\n      })\n      .catch(function(err) {\n        done(err);\n      });\n  });\n\n  it('Create a new application', function(done) {\n    Application.create({owner: 'rfeng',\n      name: 'MyApp1',\n      description: 'My first mobile application'}, function(err, result) {\n      const app = result;\n      assert.equal(app.owner, 'rfeng');\n      assert.equal(app.name, 'MyApp1');\n      assert.equal(app.description, 'My first mobile application');\n      assert(app.clientKey);\n      assert(app.javaScriptKey);\n      assert(app.restApiKey);\n      assert(app.windowsKey);\n      assert(app.masterKey);\n      assert(app.created);\n      assert(app.modified);\n      assert.equal(typeof app.id, 'string');\n\n      done(err, result);\n    });\n  });\n\n  it('Create a new application with push settings', function(done) {\n    Application.create({\n      owner: 'rfeng',\n      name: 'MyAppWithPush',\n      description: 'My push mobile application',\n      pushSettings: {\n        apns: {\n          production: false,\n          certData: 'cert',\n          keyData: 'key',\n          pushOptions: {\n            gateway: 'gateway.sandbox.push.apple.com',\n            port: 2195,\n          },\n          feedbackOptions: {\n            gateway: 'feedback.sandbox.push.apple.com',\n            port: 2196,\n            interval: 300,\n            batchFeedback: true,\n          },\n        },\n        gcm: {\n          serverApiKey: 'serverKey',\n        },\n      }},\n    function(err, result) {\n      const app = result;\n      assert.deepEqual(app.pushSettings.toObject(), {\n        apns: {\n          production: false,\n          certData: 'cert',\n          keyData: 'key',\n          pushOptions: {\n            gateway: 'gateway.sandbox.push.apple.com',\n            port: 2195,\n          },\n          feedbackOptions: {\n            gateway: 'feedback.sandbox.push.apple.com',\n            port: 2196,\n            interval: 300,\n            batchFeedback: true,\n          },\n        },\n        gcm: {\n          serverApiKey: 'serverKey',\n        },\n      });\n\n      done(err, result);\n    });\n  });\n\n  beforeEach(function(done) {\n    Application.register('rfeng', 'MyApp2',\n      {description: 'My second mobile application'}, function(err, result) {\n        const app = result;\n        assert.equal(app.owner, 'rfeng');\n        assert.equal(app.name, 'MyApp2');\n        assert.equal(app.description, 'My second mobile application');\n        assert(app.clientKey);\n        assert(app.javaScriptKey);\n        assert(app.restApiKey);\n        assert(app.windowsKey);\n        assert(app.masterKey);\n        assert(app.created);\n        assert(app.modified);\n        registeredApp = app;\n\n        done(err, result);\n      });\n  });\n\n  it('Reset keys', function(done) {\n    Application.resetKeys(registeredApp.id, function(err, result) {\n      const app = result;\n      assert.equal(app.owner, 'rfeng');\n      assert.equal(app.name, 'MyApp2');\n      assert.equal(app.description, 'My second mobile application');\n      assert(app.clientKey);\n      assert(app.javaScriptKey);\n      assert(app.restApiKey);\n      assert(app.windowsKey);\n      assert(app.masterKey);\n\n      assert(app.clientKey !== registeredApp.clientKey);\n      assert(app.javaScriptKey !== registeredApp.javaScriptKey);\n      assert(app.restApiKey !== registeredApp.restApiKey);\n      assert(app.windowsKey !== registeredApp.windowsKey);\n      assert(app.masterKey !== registeredApp.masterKey);\n\n      assert(app.created);\n      assert(app.modified);\n      registeredApp = app;\n\n      done(err, result);\n    });\n  });\n\n  it('Reset keys - promise variant', function(done) {\n    Application.resetKeys(registeredApp.id)\n      .then(function(result) {\n        const app = result;\n        assert.equal(app.owner, 'rfeng');\n        assert.equal(app.name, 'MyApp2');\n        assert.equal(app.description, 'My second mobile application');\n        assert(app.clientKey);\n        assert(app.javaScriptKey);\n        assert(app.restApiKey);\n        assert(app.windowsKey);\n        assert(app.masterKey);\n\n        assert(app.clientKey !== registeredApp.clientKey);\n        assert(app.javaScriptKey !== registeredApp.javaScriptKey);\n        assert(app.restApiKey !== registeredApp.restApiKey);\n        assert(app.windowsKey !== registeredApp.windowsKey);\n        assert(app.masterKey !== registeredApp.masterKey);\n\n        assert(app.created);\n        assert(app.modified);\n        registeredApp = app;\n\n        done();\n      })\n      .catch(function(err) {\n        done(err);\n      });\n  });\n\n  it('Reset keys without create a new instance', function(done) {\n    Application.resetKeys(registeredApp.id, function(err, result) {\n      const app = result;\n      assert(app.id);\n      assert(app.id === registeredApp.id);\n      registeredApp = app;\n\n      done(err, result);\n    });\n  });\n\n  it('Reset keys without create a new instance - promise variant', function(done) {\n    Application.resetKeys(registeredApp.id)\n      .then(function(result) {\n        const app = result;\n        assert(app.id);\n        assert(app.id === registeredApp.id);\n        registeredApp = app;\n\n        done();\n      })\n      .catch(function(err) {\n        done(err);\n      });\n  });\n\n  it('Authenticate with application id & clientKey', function(done) {\n    Application.authenticate(registeredApp.id, registeredApp.clientKey,\n      function(err, result) {\n        assert.equal(result.application.id, registeredApp.id);\n        assert.equal(result.keyType, 'clientKey');\n\n        done(err, result);\n      });\n  });\n\n  it('Authenticate with application id & clientKey - promise variant',\n    function(done) {\n      Application.authenticate(registeredApp.id, registeredApp.clientKey)\n        .then(function(result) {\n          assert.equal(result.application.id, registeredApp.id);\n          assert.equal(result.keyType, 'clientKey');\n\n          done();\n        })\n        .catch(function(err) {\n          done(err);\n        });\n    });\n\n  it('Authenticate with application id & javaScriptKey', function(done) {\n    Application.authenticate(registeredApp.id, registeredApp.javaScriptKey,\n      function(err, result) {\n        assert.equal(result.application.id, registeredApp.id);\n        assert.equal(result.keyType, 'javaScriptKey');\n\n        done(err, result);\n      });\n  });\n\n  it('Authenticate with application id & restApiKey', function(done) {\n    Application.authenticate(registeredApp.id, registeredApp.restApiKey,\n      function(err, result) {\n        assert.equal(result.application.id, registeredApp.id);\n        assert.equal(result.keyType, 'restApiKey');\n\n        done(err, result);\n      });\n  });\n\n  it('Authenticate with application id & masterKey', function(done) {\n    Application.authenticate(registeredApp.id, registeredApp.masterKey,\n      function(err, result) {\n        assert.equal(result.application.id, registeredApp.id);\n        assert.equal(result.keyType, 'masterKey');\n\n        done(err, result);\n      });\n  });\n\n  it('Authenticate with application id & windowsKey', function(done) {\n    Application.authenticate(registeredApp.id, registeredApp.windowsKey,\n      function(err, result) {\n        assert.equal(result.application.id, registeredApp.id);\n        assert.equal(result.keyType, 'windowsKey');\n\n        done(err, result);\n      });\n  });\n\n  it('Fail to authenticate with application id & invalid key', function(done) {\n    Application.authenticate(registeredApp.id, 'invalid-key',\n      function(err, result) {\n        assert(!result);\n\n        done(err, result);\n      });\n  });\n\n  it('Fail to authenticate with application id - promise variant', function(done) {\n    Application.authenticate(registeredApp.id, 'invalid-key')\n      .then(function(result) {\n        assert(!result);\n\n        done();\n      })\n      .catch(function(err) {\n        done(err);\n        throw new Error('Error should NOT be thrown');\n      });\n  });\n});\n\ndescribe('Application subclass', function() {\n  it('should use subclass model name', function(done) {\n    const MyApp = Application.extend('MyApp');\n    const ds = loopback.createDataSource({connector: loopback.Memory});\n    MyApp.attachTo(ds);\n    MyApp.register('rfeng', 'MyApp123',\n      {description: 'My 123 mobile application'}, function(err, result) {\n        const app = result;\n        assert.equal(app.owner, 'rfeng');\n        assert.equal(app.name, 'MyApp123');\n        assert.equal(app.description, 'My 123 mobile application');\n        assert(app.clientKey);\n        assert(app.javaScriptKey);\n        assert(app.restApiKey);\n        assert(app.windowsKey);\n        assert(app.masterKey);\n        assert(app.created);\n        assert(app.modified);\n        // Remove all instances from Application model to avoid left-over data\n        Application.destroyAll(function() {\n          MyApp.findById(app.id, function(err, myApp) {\n            assert(!err);\n            assert(myApp);\n\n            Application.findById(app.id, function(err, myApp) {\n              assert(!err);\n              assert(myApp === null);\n\n              done(err, myApp);\n            });\n          });\n        });\n      });\n  });\n});\n"
  },
  {
    "path": "test/model.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst async = require('async');\nconst describe = require('./util/describe');\nconst loopback = require('../');\nconst ACL = loopback.ACL;\nconst defineModelTestsWithDataSource = require('./util/model-tests');\nconst PersistedModel = loopback.PersistedModel;\nconst Promise = require('bluebird');\nconst TaskEmitter = require('strong-task-emitter');\nconst request = require('supertest');\n\nconst expect = require('./helpers/expect');\n\ndescribe('Model / PersistedModel', function() {\n  defineModelTestsWithDataSource({\n    dataSource: {\n      connector: loopback.Memory,\n    },\n  });\n\n  describe('Model.validatesUniquenessOf(property, options)', function() {\n    it('Ensure the value for `property` is unique', function(done) {\n      const User = PersistedModel.extend('ValidatedUser', {\n        'first': String,\n        'last': String,\n        'age': Number,\n        'password': String,\n        'gender': String,\n        'domain': String,\n        'email': String,\n      });\n\n      const dataSource = loopback.createDataSource({\n        connector: loopback.Memory,\n      });\n\n      User.attachTo(dataSource);\n\n      User.validatesUniquenessOf('email', {message: 'email is not unique'});\n\n      const joe = new User({email: 'joe@joe.com'});\n      const joe2 = new User({email: 'joe@joe.com'});\n\n      joe.save(function() {\n        joe2.save(function(err) {\n          assert(err, 'should get a validation error');\n          assert(joe2.errors.email, 'model should have email error');\n\n          done();\n        });\n      });\n    });\n  });\n\n  describe('Model.attachTo(dataSource)', function() {\n    it('Attach a model to a [DataSource](#data-source)', function() {\n      const MyModel = loopback.createModel('my-model', {name: String});\n      const dataSource = loopback.createDataSource({\n        connector: loopback.Memory,\n      });\n\n      MyModel.attachTo(dataSource);\n\n      MyModel.find(function(err, results) {\n        assert(results.length === 0,\n          'should have data access methods after attaching to a data source');\n      });\n    });\n  });\n});\n\ndescribe.onServer('Remote Methods', function() {\n  let User, Post, dataSource, app;\n\n  beforeEach(function() {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.set('remoting', {errorHandler: {debug: true, log: false}});\n\n    User = app.registry.createModel('user', {\n      id: {id: true, type: String, defaultFn: 'guid'},\n      'first': String,\n      'last': String,\n      'age': Number,\n      'password': String,\n      'gender': String,\n      'domain': String,\n      'email': String,\n    }, {\n      trackChanges: true,\n    });\n\n    Post = app.registry.createModel('post', {\n      id: {id: true, type: String, defaultFn: 'guid'},\n      title: String,\n      content: String,\n    }, {\n      trackChanges: true,\n    });\n\n    dataSource = app.dataSource('db', {connector: 'memory'});\n\n    app.model(User, {dataSource: 'db'});\n    app.model(Post, {dataSource: 'db'});\n\n    User.hasMany(Post);\n\n    User.login = function(username, password, fn) {\n      if (username === 'foo' && password === 'bar') {\n        fn(null, 123);\n      } else {\n        throw new Error('bad username and password!');\n      }\n    };\n\n    User.remoteMethod('login', {\n      accepts: [\n        {arg: 'username', type: 'string', required: true},\n        {arg: 'password', type: 'string', required: true},\n      ],\n      returns: {arg: 'sessionId', type: 'any', root: true},\n      http: {path: '/sign-in', verb: 'get'},\n    });\n\n    app.use(loopback.rest());\n  });\n\n  describe('Model.create(data, callback)', function() {\n    it('creates model', function(done) {\n      const anObject = {first: 'June'};\n      request(app)\n        .post('/users')\n        // sends an object\n        .send(anObject)\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n          expect(res.body).to.have.property('id');\n          expect(res.body).to.have.property('first', 'June');\n          done();\n        });\n    });\n    // batch create must be tested with a remote request because there are\n    // coercion being done on strong-remoting side\n    it('creates array of models', function(done) {\n      const arrayOfObjects = [\n        {first: 'John'}, {first: 'Jane'},\n      ];\n      request(app)\n        .post('/users')\n        // sends an array of objects\n        .send(arrayOfObjects)\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n          expect(res.body.length).to.eql(2);\n          expect(res.body).to.have.nested.property('[0].first', 'John');\n          expect(res.body).to.have.nested.property('[1].first', 'Jane');\n          done();\n        });\n    });\n\n    it('creates related models', function(done) {\n      User.create({first: 'Bob'}, function(err, res) {\n        expect(res).to.have.property('id');\n        const aPost = {title: 'A story', content: 'Once upon a time'};\n        request(app)\n          .post('/users/' + res.id + '/posts')\n          .send(aPost)\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .end(function(err, result) {\n            if (err) return done(err);\n            expect(result.body).to.have.property('id');\n            expect(result.body).to.have.property('title', aPost.title);\n            expect(result.body).to.have.property('content', aPost.content);\n            done();\n          });\n      });\n    });\n\n    it('creates array of hasMany models', function(done) {\n      User.create({first: 'Bob'}, function(err, res) {\n        expect(res).to.have.property('id');\n        const twoPosts = [\n          {title: 'One story', content: 'Content #1'},\n          {title: 'Two story', content: 'Content #2'},\n        ];\n        request(app)\n          .post('/users/' + res.id + '/posts')\n          .send(twoPosts)\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .end(function(err, result) {\n            if (err) return done(err);\n            expect(result.body.length).to.eql(2);\n            expect(result.body).to.have.nested.property('[0].title', 'One story');\n            expect(result.body).to.have.nested.property('[1].title', 'Two story');\n            done();\n          });\n      });\n    });\n\n    it('rejects array of obj input for hasOne relation', function(done) {\n      const Friend = app.registry.createModel('friend', {name: String});\n      app.model(Friend, {dataSource: 'db'});\n      User.hasOne(Friend);\n\n      User.create({first: 'Bob'}, function(err, res) {\n        expect(res).to.have.property('id');\n        const twoFriends = [\n          {name: 'bob'},\n          {name: 'rob'},\n        ];\n        request(app)\n          .post('/users/' + res.id + '/friend')\n          .send(twoFriends)\n          .expect('Content-Type', /json/)\n          .expect(400)\n          .end(function(err, result) {\n            if (err) return done(err);\n            const resError = result.body.error;\n            expect(resError.message).to.match(/value(.*?)not(.*?)object(\\.?)/i);\n            done();\n          });\n      });\n    });\n  });\n  // destoryAll is not exposed as a remoteMethod by default\n  describe('Model.destroyAll(callback)', function() {\n    it('Delete all Model instances from data source', function(done) {\n      (new TaskEmitter())\n        .task(User, 'create', {first: 'jill'})\n        .task(User, 'create', {first: 'bob'})\n        .task(User, 'create', {first: 'jan'})\n        .task(User, 'create', {first: 'sam'})\n        .task(User, 'create', {first: 'suzy'})\n        .on('done', function() {\n          User.count(function(err, count) {\n            User.destroyAll(function() {\n              User.count(function(err, count) {\n                assert.equal(count, 0);\n\n                done();\n              });\n            });\n          });\n        });\n    });\n  });\n\n  describe('Model.upsertWithWhere(where, data, callback)', function() {\n    it('Updates when a Model instance is retreived from data source', function(done) {\n      const taskEmitter = new TaskEmitter();\n      taskEmitter\n        .task(User, 'create', {first: 'jill', second: 'pill'})\n        .task(User, 'create', {first: 'bob', second: 'sob'})\n        .on('done', function() {\n          User.upsertWithWhere({second: 'pill'}, {second: 'jones'}, function(err, user) {\n            if (err) return done(err);\n            const id = user.id;\n            User.findById(id, function(err, user) {\n              if (err) return done(err);\n              assert.equal(user.second, 'jones');\n              done();\n            });\n          });\n        });\n    });\n\n    it('Creates when no Model instance is retreived from data source', function(done) {\n      const taskEmitter = new TaskEmitter();\n      taskEmitter\n        .task(User, 'create', {first: 'simon', second: 'somers'})\n        .on('done', function() {\n          User.upsertWithWhere({first: 'somers'}, {first: 'Simon'}, function(err, user) {\n            if (err) return done(err);\n            const id = user.id;\n            User.findById(id, function(err, user) {\n              if (err) return done(err);\n              assert.equal(user.first, 'Simon');\n              done();\n            });\n          });\n        });\n    });\n  });\n\n  describe('Example Remote Method', function() {\n    it('Call the method using HTTP / REST', function(done) {\n      request(app)\n        .get('/users/sign-in?username=foo&password=bar')\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          assert.equal(res.body, 123);\n\n          done();\n        });\n    });\n\n    it('Converts null result of findById to 404 Not Found', function(done) {\n      request(app)\n        .get('/users/not-found')\n        .expect(404)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const errorResponse = res.body.error;\n          assert(errorResponse);\n          assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');\n\n          done();\n        });\n    });\n\n    it('Call the findById with filter.fields using HTTP / REST', function(done) {\n      request(app)\n        .post('/users')\n        .send({first: 'x', last: 'y'})\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const userId = res.body.id;\n          assert(userId);\n          request(app)\n            .get('/users/' + userId + '?filter[fields]=first')\n            .expect('Content-Type', /json/)\n            .expect(200)\n            .end(function(err, res) {\n              if (err) return done(err);\n\n              assert.equal(res.body.first, 'x', 'first should be x');\n              assert(res.body.last === undefined, 'last should not be present');\n\n              done();\n            });\n        });\n    });\n\n    it('Call the findById with filter.include using HTTP / REST', function(done) {\n      request(app)\n        .post('/users')\n        .send({first: 'x', last: 'y'})\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const userId = res.body.id;\n          assert(userId);\n          request(app)\n            .post('/users/' + userId + '/posts')\n            .send({title: 'T1', content: 'C1'})\n            .expect('Content-Type', /json/)\n            .expect(200)\n            .end(function(err, res) {\n              if (err) return done(err);\n\n              const post = res.body;\n              request(app)\n                .get('/users/' + userId + '?filter[include]=posts')\n                .expect('Content-Type', /json/)\n                .expect(200)\n                .end(function(err, res) {\n                  if (err) return done(err);\n\n                  assert.equal(res.body.first, 'x', 'first should be x');\n                  assert.equal(res.body.last, 'y', 'last should be y');\n                  assert.deepEqual(post, res.body.posts[0]);\n\n                  done();\n                });\n            });\n        });\n    });\n  });\n\n  describe('Model.beforeRemote(name, fn)', function() {\n    it('Run a function before a remote method is called by a client', function(done) {\n      let hookCalled = false;\n\n      User.beforeRemote('create', function(ctx, user, next) {\n        hookCalled = true;\n\n        next();\n      });\n\n      // invoke save\n      request(app)\n        .post('/users')\n        .send({data: {first: 'foo', last: 'bar'}})\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          assert(hookCalled, 'hook wasnt called');\n\n          done();\n        });\n    });\n\n    it('Does not stop the hook chain after returning a promise', function(done) {\n      const hooksCalled = [];\n\n      User.beforeRemote('create', function() {\n        hooksCalled.push('first');\n        return Promise.resolve();\n      });\n\n      User.beforeRemote('create', function(ctx, user, next) {\n        hooksCalled.push('second');\n        next();\n      });\n\n      // invoke save\n      request(app)\n        .post('/users')\n        .send({data: {first: 'foo', last: 'bar'}})\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n          expect(hooksCalled).to.eql(['first', 'second']);\n          done();\n        });\n    });\n  });\n\n  describe('Model.afterRemote(name, fn)', function() {\n    it('Run a function after a remote method is called by a client', function(done) {\n      let beforeCalled = false;\n      let afterCalled = false;\n\n      User.beforeRemote('create', function(ctx, user, next) {\n        assert(!afterCalled);\n        beforeCalled = true;\n\n        next();\n      });\n      User.afterRemote('create', function(ctx, user, next) {\n        assert(beforeCalled);\n        afterCalled = true;\n\n        next();\n      });\n\n      // invoke save\n      request(app)\n        .post('/users')\n        .send({data: {first: 'foo', last: 'bar'}})\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          assert(beforeCalled, 'before hook was not called');\n          assert(afterCalled, 'after hook was not called');\n\n          done();\n        });\n    });\n  });\n\n  describe('Model.afterRemoteError(name, fn)', function() {\n    it('runs the function when method fails', function(done) {\n      let actualError = 'hook not called';\n      User.afterRemoteError('login', function(ctx, next) {\n        actualError = ctx.error;\n\n        next();\n      });\n\n      request(app).get('/users/sign-in?username=bob&password=123')\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          expect(actualError)\n            .to.have.property('message', 'bad username and password!');\n\n          done();\n        });\n    });\n  });\n\n  describe('Remote Method invoking context', function() {\n    describe('ctx.req', function() {\n      it('The express ServerRequest object', function(done) {\n        let hookCalled = false;\n\n        User.beforeRemote('create', function(ctx, user, next) {\n          hookCalled = true;\n          assert(ctx.req);\n          assert(ctx.req.url);\n          assert(ctx.req.method);\n          assert(ctx.res);\n          assert(ctx.res.write);\n          assert(ctx.res.end);\n\n          next();\n        });\n\n        // invoke save\n        request(app)\n          .post('/users')\n          .send({data: {first: 'foo', last: 'bar'}})\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .end(function(err, res) {\n            if (err) return done(err);\n\n            assert(hookCalled);\n\n            done();\n          });\n      });\n    });\n\n    describe('ctx.res', function() {\n      it('The express ServerResponse object', function(done) {\n        let hookCalled = false;\n\n        User.beforeRemote('create', function(ctx, user, next) {\n          hookCalled = true;\n          assert(ctx.req);\n          assert(ctx.req.url);\n          assert(ctx.req.method);\n          assert(ctx.res);\n          assert(ctx.res.write);\n          assert(ctx.res.end);\n\n          next();\n        });\n\n        // invoke save\n        request(app)\n          .post('/users')\n          .send({data: {first: 'foo', last: 'bar'}})\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .end(function(err, res) {\n            if (err) return done(err);\n\n            assert(hookCalled);\n\n            done();\n          });\n      });\n    });\n  });\n\n  describe('Model.hasMany(Model)', function() {\n    it('Define a one to many relationship', function(done) {\n      const Book = dataSource.createModel('book', {title: String, author: String});\n      const Chapter = dataSource.createModel('chapter', {title: String});\n\n      // by referencing model\n      Book.hasMany(Chapter);\n\n      Book.create({title: 'Into the Wild', author: 'Jon Krakauer'}, function(err, book) {\n        // using 'chapters' scope for build:\n        const c = book.chapters.build({title: 'Chapter 1'});\n        book.chapters.create({title: 'Chapter 2'}, function() {\n          c.save(function() {\n            Chapter.count({bookId: book.id}, function(err, count) {\n              assert.equal(count, 2);\n              book.chapters({where: {title: 'Chapter 1'}}, function(err, chapters) {\n                assert.equal(chapters.length, 1);\n                assert.equal(chapters[0].title, 'Chapter 1');\n\n                done();\n              });\n            });\n          });\n        });\n      });\n    });\n  });\n\n  describe('Model.properties', function() {\n    it('Normalized properties passed in originally by loopback.createModel()', function() {\n      const props = {\n        s: String,\n        n: {type: 'Number'},\n        o: {type: 'String', min: 10, max: 100},\n        d: Date,\n        g: loopback.GeoPoint,\n      };\n\n      const MyModel = loopback.createModel('foo', props);\n\n      Object.keys(MyModel.definition.properties).forEach(function(key) {\n        const p = MyModel.definition.properties[key];\n        const o = MyModel.definition.properties[key];\n        assert(p);\n        assert(o);\n        assert(typeof p.type === 'function');\n\n        if (typeof o === 'function') {\n          // the normalized property\n          // should match the given property\n          assert(\n            p.type.name === o.name ||\n            p.type.name === o,\n          );\n        }\n      });\n    });\n  });\n\n  describe('Model.extend()', function() {\n    it('Create a new model by extending an existing model', function() {\n      const User = loopback.PersistedModel.extend('test-user', {\n        email: String,\n      });\n\n      User.foo = function() {\n        return 'bar';\n      };\n\n      User.prototype.bar = function() {\n        return 'foo';\n      };\n\n      const MyUser = User.extend('my-user', {\n        a: String,\n        b: String,\n      });\n\n      assert.equal(MyUser.prototype.bar, User.prototype.bar);\n      assert.equal(MyUser.foo, User.foo);\n\n      const user = new MyUser({\n        email: 'foo@bar.com',\n        a: 'foo',\n        b: 'bar',\n      });\n\n      assert.equal(user.email, 'foo@bar.com');\n      assert.equal(user.a, 'foo');\n      assert.equal(user.b, 'bar');\n    });\n  });\n\n  describe('Model.extend() events', function() {\n    it('create isolated emitters for subclasses', function() {\n      const User1 = loopback.createModel('User1', {\n        'first': String,\n        'last': String,\n      });\n\n      const User2 = loopback.createModel('User2', {\n        'name': String,\n      });\n\n      let user1Triggered = false;\n      User1.once('x', function(event) {\n        user1Triggered = true;\n      });\n\n      let user2Triggered = false;\n      User2.once('x', function(event) {\n        user2Triggered = true;\n      });\n\n      assert(User1.once !== User2.once);\n      assert(User1.once !== loopback.Model.once);\n\n      User1.emit('x', User1);\n\n      assert(user1Triggered);\n      assert(!user2Triggered);\n    });\n  });\n\n  describe('Model.checkAccessTypeForMethod(remoteMethod)', function() {\n    shouldReturn('create', ACL.WRITE);\n    shouldReturn('updateOrCreate', ACL.WRITE);\n    shouldReturn('upsertWithWhere', ACL.WRITE);\n    shouldReturn('upsert', ACL.WRITE);\n    shouldReturn('exists', ACL.READ);\n    shouldReturn('findById', ACL.READ);\n    shouldReturn('find', ACL.READ);\n    shouldReturn('findOne', ACL.READ);\n    shouldReturn('destroyById', ACL.WRITE);\n    shouldReturn('deleteById', ACL.WRITE);\n    shouldReturn('removeById', ACL.WRITE);\n    shouldReturn('count', ACL.READ);\n    shouldReturn('unkown-model-method', ACL.EXECUTE);\n\n    function shouldReturn(methodName, expectedAccessType) {\n      describe(methodName, function() {\n        it('should return ' + expectedAccessType, function() {\n          const remoteMethod = {name: methodName};\n          assert.equal(\n            User._getAccessTypeForMethod(remoteMethod),\n            expectedAccessType,\n          );\n        });\n      });\n    }\n  });\n\n  describe('Model.getChangeModel()', function() {\n    it('Get the Change Model', function() {\n      const UserChange = User.getChangeModel();\n      const change = new UserChange();\n      assert(change instanceof app.registry.getModel('Change'));\n    });\n  });\n\n  describe('Model.getSourceId(callback)', function() {\n    it('Get the Source Id', function(done) {\n      User.getSourceId(function(err, id) {\n        assert.equal('memory-user', id);\n\n        done();\n      });\n    });\n  });\n\n  describe('Model.checkpoint(callback)', function() {\n    it('Create a checkpoint', function(done) {\n      const Checkpoint = User.getChangeModel().getCheckpointModel();\n      const tasks = [\n        getCurrentCheckpoint,\n        checkpoint,\n      ];\n      let result, current;\n\n      async.series(tasks, function(err) {\n        if (err) return done(err);\n\n        assert.equal(result, current + 1);\n\n        done();\n      });\n\n      function getCurrentCheckpoint(cb) {\n        Checkpoint.current(function(err, cp) {\n          current = cp;\n          cb(err);\n        });\n      }\n\n      function checkpoint(cb) {\n        User.checkpoint(function(err, cp) {\n          result = cp.seq;\n          cb(err);\n        });\n      }\n    });\n  });\n\n  describe('Model._getACLModel()', function() {\n    it('should return the subclass of ACL', function() {\n      const Model = require('../').Model;\n      const originalValue = Model._ACL();\n      const acl = ACL.extend('acl');\n      Model._ACL(null); // Reset the ACL class for the base model\n      const model = Model._ACL();\n      Model._ACL(originalValue); // Reset the value back\n      assert.equal(model, acl);\n    });\n  });\n\n  describe('PersistedModel remote methods', function() {\n    it('includes all aliases', function() {\n      const app = loopback();\n      const model = PersistedModel.extend('PersistedModelForAliases');\n      app.dataSource('db', {connector: 'memory'});\n      app.model(model, {dataSource: 'db'});\n\n      // this code is used by loopback-sdk-angular codegen\n      const metadata = app.handler('rest')\n        .adapter\n        .getClasses()\n        .filter(function(c) { return c.name === model.modelName; })[0];\n\n      let methodNames = [];\n      metadata.methods.forEach(function(method) {\n        methodNames.push(method.name);\n        let aliases = method.sharedMethod.aliases;\n        if (method.name.indexOf('prototype.') === 0) {\n          aliases = aliases.map(function(alias) {\n            return 'prototype.' + alias;\n          });\n        }\n        methodNames = methodNames.concat(aliases || []);\n      });\n      expect(methodNames).to.have.members([\n        // NOTE(bajtos) These three methods are disabled by default\n        // Because all tests share the same global registry model\n        // and one of the tests was enabling remoting of \"destroyAll\",\n        // this test was seeing this method (with all aliases) as public\n        // 'destroyAll', 'deleteAll', 'remove',\n        'create',\n        'upsert', 'updateOrCreate', 'patchOrCreate',\n        'upsertWithWhere', 'patchOrCreateWithWhere',\n        'exists',\n        'findById',\n        'replaceById',\n        'replaceOrCreate',\n        'find',\n        'findOne',\n        'updateAll', 'update',\n        'deleteById',\n        'destroyById',\n        'removeById',\n        'count',\n        'prototype.patchAttributes', 'prototype.updateAttributes',\n        'createChangeStream',\n      ]);\n    });\n\n    it('emits a `remoteMethodDisabled` event', function() {\n      const app = loopback();\n      const model = PersistedModel.extend('TestModelForDisablingRemoteMethod');\n      app.dataSource('db', {connector: 'memory'});\n      app.model(model, {dataSource: 'db'});\n\n      const callbackSpy = require('sinon').spy();\n      const TestModel = app.models.TestModelForDisablingRemoteMethod;\n      TestModel.on('remoteMethodDisabled', callbackSpy);\n      TestModel.disableRemoteMethod('findOne', true);\n\n      expect(callbackSpy).to.have.been.calledWith(TestModel.sharedClass, 'findOne');\n    });\n\n    it('emits a `remoteMethodDisabled` event from disableRemoteMethodByName', function() {\n      const app = loopback();\n      const model = PersistedModel.extend('TestModelForDisablingRemoteMethod');\n      app.dataSource('db', {connector: 'memory'});\n      app.model(model, {dataSource: 'db'});\n\n      const callbackSpy = require('sinon').spy();\n      const TestModel = app.models.TestModelForDisablingRemoteMethod;\n      TestModel.on('remoteMethodDisabled', callbackSpy);\n      TestModel.disableRemoteMethodByName('findOne');\n\n      expect(callbackSpy).to.have.been.calledWith(TestModel.sharedClass, 'findOne');\n    });\n\n    it('emits a `remoteMethodAdded` event', function() {\n      const app = loopback();\n      app.dataSource('db', {connector: 'memory'});\n\n      const User = app.registry.getModel('User');\n      app.model(User, {dataSource: 'db'});\n\n      const Token = app.registry.getModel('AccessToken');\n      app.model(Token, {dataSource: 'db'});\n\n      const callbackSpy = require('sinon').spy();\n      const TestModel = app.models.User;\n      TestModel.on('remoteMethodAdded', callbackSpy);\n      TestModel.nestRemoting('accessTokens');\n\n      expect(callbackSpy).to.have.been.calledWith(TestModel.sharedClass);\n    });\n  });\n\n  it('emits a `remoteMethodAdded` event from remoteMethod', function() {\n    const app = loopback();\n    const model = PersistedModel.extend('TestModelForAddingRemoteMethod');\n    app.dataSource('db', {connector: 'memory'});\n    app.model(model, {dataSource: 'db'});\n\n    const callbackSpy = require('sinon').spy();\n    const TestModel = app.models.TestModelForAddingRemoteMethod;\n    TestModel.on('remoteMethodAdded', callbackSpy);\n    TestModel.remoteMethod('getTest', {\n      accepts: {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n      returns: {arg: 'test', type: 'object'},\n      http: {verb: 'GET', path: '/test'},\n    });\n\n    expect(callbackSpy).to.have.been.calledWith(TestModel.sharedClass);\n  });\n\n  describe('Model.getApp(cb)', function() {\n    let app, TestModel;\n    beforeEach(function setup() {\n      app = loopback();\n      TestModel = loopback.createModel('TestModelForGetApp'); // unique name\n      app.dataSource('db', {connector: 'memory'});\n    });\n\n    it('calls the callback when already attached', function(done) {\n      app.model(TestModel, {dataSource: 'db'});\n      TestModel.getApp(function(err, a) {\n        if (err) return done(err);\n\n        expect(a).to.equal(app);\n\n        done();\n      });\n      // fails on time-out when not implemented correctly\n    });\n\n    it('calls the callback after attached', function(done) {\n      TestModel.getApp(function(err, a) {\n        if (err) return done(err);\n\n        expect(a).to.equal(app);\n\n        done();\n      });\n      app.model(TestModel, {dataSource: 'db'});\n      // fails on time-out when not implemented correctly\n    });\n  });\n\n  describe('Model.createOptionsFromRemotingContext', function() {\n    let app, TestModel, accessToken, actualOptions;\n\n    before(setupAppAndRequest);\n    before(createUserAndAccessToken);\n\n    beforeEach(function() {\n      TestModel.definition.settings = {};\n    });\n\n    it('sets empty options.accessToken for anonymous requests', function(done) {\n      request(app).get('/TestModels/saveOptions')\n        .expect(204, function(err) {\n          if (err) return done(err);\n          expect(actualOptions).to.include({accessToken: null});\n          done();\n        });\n    });\n\n    it('sets options for juggler', function(done) {\n      request(app).get('/TestModels/saveOptions')\n        .expect(204, function(err) {\n          if (err) return done(err);\n          expect(actualOptions).to.include({\n            prohibitHiddenPropertiesInQuery: true,\n            maxDepthOfQuery: 12,\n            maxDepthOfData: 32,\n          });\n          done();\n        });\n    });\n\n    it('honors model settings to create options for juggler', function(done) {\n      TestModel.definition.settings = {\n        prohibitHiddenPropertiesInQuery: false,\n        maxDepthOfData: 64,\n      };\n      request(app).get('/TestModels/saveOptions')\n        .expect(204, function(err) {\n          if (err) return done(err);\n          expect(actualOptions).to.include({\n            prohibitHiddenPropertiesInQuery: false,\n            maxDepthOfQuery: 12,\n            maxDepthOfData: 64,\n          });\n          done();\n        });\n    });\n\n    it('sets options.accessToken for authorized requests', function(done) {\n      request(app).get('/TestModels/saveOptions')\n        .set('Authorization', accessToken.id)\n        .expect(204, function(err) {\n          if (err) return done(err);\n          expect(actualOptions).to.have.property('accessToken');\n          expect(actualOptions.accessToken.toObject())\n            .to.eql(accessToken.toObject());\n          done();\n        });\n    });\n\n    it('allows \"beforeRemote\" hooks to contribute options', function(done) {\n      TestModel.beforeRemote('saveOptions', function(ctx, unused, next) {\n        ctx.args.options.hooked = true;\n        next();\n      });\n\n      request(app).get('/TestModels/saveOptions')\n        .expect(204, function(err) {\n          if (err) return done(err);\n          expect(actualOptions).to.have.property('hooked', true);\n          done();\n        });\n    });\n\n    it('sets empty options.accessToken for requests coming from websocket/primus adapters', function() {\n      const primusContext = {};\n      const opts = TestModel.createOptionsFromRemotingContext(primusContext);\n      expect(opts).to.have.property('accessToken', null);\n    });\n\n    it('allows apps to add options before remoting hooks', function(done) {\n      TestModel.createOptionsFromRemotingContext = function(ctx) {\n        return {hooks: []};\n      };\n\n      TestModel.beforeRemote('saveOptions', function(ctx, unused, next) {\n        ctx.args.options.hooks.push('beforeRemote');\n        next();\n      });\n\n      // In real apps, this code can live in a component or in a boot script\n      app.remotes().phases\n        .addBefore('invoke', 'options-from-request')\n        .use(function(ctx, next) {\n          ctx.args.options.hooks.push('custom');\n          next();\n        });\n\n      request(app).get('/TestModels/saveOptions')\n        .expect(204, function(err) {\n          if (err) return done(err);\n          expect(actualOptions.hooks).to.eql(['custom', 'beforeRemote']);\n          done();\n        });\n    });\n\n    function setupAppAndRequest() {\n      app = loopback({localRegistry: true, loadBuiltinModels: true});\n\n      app.dataSource('db', {connector: 'memory'});\n\n      TestModel = app.registry.createModel('TestModel', {base: 'Model'});\n      TestModel.saveOptions = function(options, cb) {\n        actualOptions = options;\n        cb();\n      };\n\n      TestModel.remoteMethod('saveOptions', {\n        accepts: {arg: 'options', type: 'object', http: 'optionsFromRequest'},\n        http: {verb: 'GET', path: '/saveOptions'},\n      });\n\n      app.model(TestModel, {dataSource: null});\n\n      app.enableAuth({dataSource: 'db'});\n\n      app.use(loopback.token());\n      app.use(loopback.rest());\n    }\n\n    function createUserAndAccessToken() {\n      const CREDENTIALS = {email: 'context@example.com', password: 'pass'};\n      const User = app.registry.getModel('User');\n      return User.create(CREDENTIALS)\n        .then(function(u) {\n          return User.login(CREDENTIALS);\n        }).then(function(token) {\n          accessToken = token;\n        });\n    }\n  });\n\n  describe('Create Model with remote methods from JSON description', function() {\n    it('does not add isStatic properties to the method settings', function() {\n      const app = loopback();\n      const Foo = app.registry.createModel({\n        name: 'Foo',\n        methods: {\n          staticMethod: {},\n        },\n      });\n      app.model(Foo);\n      expect(app.models.Foo.settings.methods.staticMethod).to.eql({});\n    });\n  });\n});\n"
  },
  {
    "path": "test/multiple-user-principal-accessing-another-user-model.js",
    "content": "// Copyright IBM Corp. 2018,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst debug = require('debug')('test');\nconst loopback = require('../');\nconst waitForEvent = require('./helpers/wait-for-event');\nconst supertest = require('supertest');\nconst loggers = require('./helpers/error-loggers');\nconst logServerErrorsOtherThan = loggers.logServerErrorsOtherThan;\nconst logAllServerErrors = loggers.logAllServerErrors;\n\ndescribe('One user model accessing another user model', () => {\n  let app,\n    Seller, Customer, Building, CustomAccessToken,\n    seniorSeller, juniorSeller, customer,\n    seniorToken, juniorToken, customerToken,\n    building;\n\n  /* Setup the following models and relations:\n   *  - Seller extends User\n   *  - Customer extends User\n   *  - Building\n   *  - CustomAccessToken belongs to Seller or Customer\n   *  - Building belongs to Seller\n   *  - Customer belongs to Seller\n   */\n  beforeEach(setupAppAndModels);\n\n  /* Setup the following model instances:\n   *  - seniorSeller + access token\n   *  - juniorSeller + access token\n   *  - customer owned by senior seller + access token\n   *  - building owned by senior seller\n   */\n  beforeEach(setupModelInstances);\n\n  context('seniorSeller', () => {\n    it('can patch seniorSeller', () => {\n      logAllServerErrors(app);\n\n      return supertest(app).patch(`/Sellers/${seniorSeller.id}`)\n        .set('Authorization', seniorToken.id)\n        .send({name: 'updated name'})\n        .expect(200);\n    });\n\n    it('cannot patch juniorSeller', () => {\n      logServerErrorsOtherThan(401, app);\n\n      return supertest(app).patch(`/Sellers/${juniorSeller.id}`)\n        .set('Authorization', seniorToken.id)\n        .send({name: 'updated name'})\n        .expect(401);\n    });\n\n    it('can patch customer', () => {\n      logAllServerErrors(app);\n\n      return supertest(app).patch(`/Customers/${customer.id}`)\n        .set('Authorization', seniorToken.id)\n        .send({name: 'updated name'})\n        .expect(200);\n    });\n\n    it('can retrieve buildings owned by seniorSeller', () => {\n      logAllServerErrors(app);\n\n      return supertest(app).get(`/buildings/${building.id}`)\n        .set('Authorization', seniorToken.id)\n        .expect(200);\n    });\n  });\n\n  context('juniorSeller', () => {\n    it('cannot patch seniorSeller', () => {\n      logServerErrorsOtherThan(401, app);\n\n      return supertest(app).patch(`/Sellers/${seniorToken.id}`)\n        .set('Authorization', juniorToken.id)\n        .send({name: 'updated name'})\n        .expect(401);\n    });\n\n    it('can patch juniorSeller', () => {\n      logAllServerErrors(app);\n\n      return supertest(app).patch(`/Sellers/${juniorSeller.id}`)\n        .set('Authorization', juniorToken.id)\n        .send({name: 'updated name'})\n        .expect(200);\n    });\n\n    it('cannot patch customer', () => {\n      logServerErrorsOtherThan(401, app);\n\n      return supertest(app).patch(`/Customers/${customer.id}`)\n        .set('Authorization', juniorToken.id)\n        .send({name: 'updated name'})\n        .expect(401);\n    });\n\n    it('cannot retrieve buildings owned by seniorSeller', () => {\n      logServerErrorsOtherThan(401, app);\n\n      return supertest(app).get(`/buildings/${building.id}`)\n        .set('Authorization', juniorToken.id)\n        .expect(401);\n    });\n  });\n\n  context('customer', () => {\n    it('cannot patch seniorSeller', () => {\n      logServerErrorsOtherThan(401, app);\n\n      return supertest(app).patch(`/Sellers/${seniorSeller.id}`)\n        .set('Authorization', customerToken.id)\n        .send({name: 'updated name'})\n        .expect(401);\n    });\n\n    it('cannnot patch juniorSeller', () => {\n      logServerErrorsOtherThan(401, app);\n\n      return supertest(app).patch(`/Sellers/${juniorSeller.id}`)\n        .set('Authorization', customerToken.id)\n        .send({name: 'updated name'})\n        .expect(401);\n    });\n\n    it('can patch customer (own data)', () => {\n      logAllServerErrors(app);\n\n      return supertest(app).patch(`/Customers/${customer.id}`)\n        .set('Authorization', customerToken.id)\n        .send({name: 'updated name'})\n        .expect(200);\n    });\n\n    it('cannot retrieve buildings owned by seniorSeller', () => {\n      logServerErrorsOtherThan(401, app);\n\n      return supertest(app).get(`/buildings/${building.id}`)\n        .set('Authorization', customerToken.id)\n        .expect(401);\n    });\n  });\n\n  function setupAppAndModels() {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.set('_verifyAuthModelRelations', false);\n    app.set('remoting', {rest: {handleErrors: false}});\n    app.dataSource('db', {connector: 'memory'});\n\n    Seller = app.registry.createModel({\n      name: 'Seller',\n      base: 'User',\n      saltWorkFactor: 4,\n      relations: {\n        accessTokens: {\n          type: 'hasMany',\n          model: 'CustomAccessToken',\n          polymorphic: {\n            foreignKey: 'userId',\n            discriminator: 'principalType',\n          },\n        },\n        buildings: {\n          type: 'hasMany',\n          model: 'Building',\n        },\n        customers: {\n          type: 'hasMany',\n          model: 'Customer',\n        },\n      },\n    });\n\n    Customer = app.registry.createModel({\n      name: 'Customer',\n      base: 'User',\n      saltWorkFactor: 4,\n      relations: {\n        accessTokens: {\n          type: 'hasMany',\n          model: 'CustomAccessToken',\n          polymorphic: {\n            foreignKey: 'userId',\n            discriminator: 'principalType',\n          },\n        },\n        seller: {\n          type: 'belongsTo',\n          model: 'Seller',\n        },\n      },\n    });\n\n    Building = app.registry.createModel({\n      name: 'Building',\n      properties: {\n        name: {type: 'string', required: true},\n      },\n      relations: {\n        seller: {\n          type: 'belongsTo',\n          model: 'Seller',\n        },\n      },\n      acls: [\n        {\n          accessType: '*',\n          principalType: 'ROLE',\n          principalId: '$everyone',\n          permission: 'DENY',\n        },\n        {\n          accessType: '*',\n          principalType: 'ROLE',\n          principalId: '$owner',\n          permission: 'ALLOW',\n        },\n      ],\n    });\n\n    CustomAccessToken = app.registry.createModel({\n      name: 'CustomAccessToken',\n      base: 'AccessToken',\n      relations: {\n        user: {\n          type: 'belongsTo',\n          idName: 'id',\n          polymorphic: {\n            idType: 'string',\n            foreignKey: 'userId',\n            discriminator: 'principalType',\n          },\n        },\n      },\n    });\n\n    app.model(CustomAccessToken, {dataSource: 'db', public: false});\n    app.model(Seller, {dataSource: 'db'});\n    app.model(Customer, {dataSource: 'db'});\n    app.model(Building, {dataSource: 'db'});\n\n    const builtinModels = app.registry.modelBuilder.models;\n    app.model(builtinModels.ACL, {dataSource: 'db', public: false});\n    app.model(builtinModels.Role, {dataSource: 'db', public: false});\n    app.model(builtinModels.RoleMapping, {dataSource: 'db', public: false});\n\n    app.enableAuth();\n\n    app.use(loopback.rest());\n  }\n\n  function setupModelInstances() {\n    return Promise.all([\n      Seller.create({email: 'seniorSeller@example.com', password: 'pass'}),\n      Seller.create({email: 'juniorSeller@example.com', password: 'pass'}),\n    ]).then((sellers) => {\n      seniorSeller = sellers[0];\n      juniorSeller = sellers[1];\n\n      debug('seniorSeller', seniorSeller.toObject());\n      debug('juniorSeller', juniorSeller.toObject());\n\n      return seniorSeller.customers.create({\n        email: 'customer@example.com',\n        password: 'pass',\n      });\n    }).then(c => {\n      customer = c;\n      debug('Customer', customer.toObject());\n\n      return Promise.all([\n        seniorSeller.createAccessToken({}),\n        juniorSeller.createAccessToken({}),\n        customer.createAccessToken({}),\n      ]);\n    }).then(tokens => {\n      seniorToken = tokens[0];\n      juniorToken = tokens[1];\n      customerToken = tokens[2];\n\n      debug('seniorSeller token', seniorToken.toObject());\n      debug('juniorSeller token', juniorToken.toObject());\n      debug('customer token', customerToken.toObject());\n\n      return seniorSeller.buildings.create({name: 'Test Building'});\n    }).then(b => {\n      building = b;\n      debug('Building', b.toObject());\n    });\n  }\n});\n"
  },
  {
    "path": "test/multiple-user-principal-types.test.js",
    "content": "// Copyright IBM Corp. 2016,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst expect = require('./helpers/expect');\nconst loopback = require('../');\nconst ctx = require('../lib/access-context');\nconst extend = require('util')._extend;\nconst AccessContext = ctx.AccessContext;\nconst Principal = ctx.Principal;\nconst Promise = require('bluebird');\nconst waitForEvent = require('./helpers/wait-for-event');\nconst supertest = require('supertest');\nconst loggers = require('./helpers/error-loggers');\nconst logServerErrorsOtherThan = loggers.logServerErrorsOtherThan;\n\ndescribe('Multiple users with custom principalType', function() {\n  this.timeout(10000);\n\n  const commonCredentials = {email: 'foo@bar.com', password: 'bar'};\n  let app, OneUser, AnotherUser, AccessToken, Role,\n    userFromOneModel, userFromAnotherModel, userRole, userOneBaseContext;\n\n  beforeEach(function setupAppAndModels() {\n    // create a local app object that does not share state with other tests\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.set('_verifyAuthModelRelations', false);\n    app.set('remoting', {rest: {handleErrors: false}});\n    app.dataSource('db', {connector: 'memory'});\n\n    const userModelOptions = {\n      base: 'User',\n      // forceId is set to false for the purpose of updating the same affected user within the\n      // `Email Update` test cases.\n      forceId: false,\n      // Speed up the password hashing algorithm for tests\n      saltWorkFactor: 4,\n    };\n\n    // create and attach 2 User-based models\n    OneUser = createUserModel(app, 'OneUser', userModelOptions);\n    AnotherUser = createUserModel(app, 'AnotherUser', userModelOptions);\n\n    AccessToken = app.registry.getModel('AccessToken');\n    app.model(AccessToken, {dataSource: 'db'});\n\n    Role = app.registry.getModel('Role');\n    app.model(Role, {dataSource: 'db'});\n\n    // Update AccessToken and Users to bind them through polymorphic relations\n    AccessToken.belongsTo('user', {idName: 'id', polymorphic: {idType: 'string',\n      foreignKey: 'userId', discriminator: 'principalType'}});\n    OneUser.hasMany('accessTokens', {polymorphic: {foreignKey: 'userId',\n      discriminator: 'principalType'}});\n    AnotherUser.hasMany('accessTokens', {polymorphic: {foreignKey: 'userId',\n      discriminator: 'principalType'}});\n\n    app.enableAuth({dataSource: 'db'});\n    app.use(loopback.token({model: AccessToken}));\n    app.use(loopback.rest());\n\n    // create one user per user model to use them throughout the tests\n    return Promise.all([\n      OneUser.create(commonCredentials),\n      AnotherUser.create(commonCredentials),\n      Role.create({name: 'userRole'}),\n    ])\n      .spread(function(u1, u2, r) {\n        userFromOneModel = u1;\n        userFromAnotherModel = u2;\n        userRole = r;\n        userOneBaseContext = {\n          principalType: OneUser.modelName,\n          principalId: userFromOneModel.id,\n        };\n      });\n  });\n\n  describe('User.login', function() {\n    it('works for one user model and valid credentials', function() {\n      return OneUser.login(commonCredentials)\n        .then(function(accessToken) {\n          assertGoodToken(accessToken, userFromOneModel);\n        });\n    });\n\n    it('works for a second user model and valid credentials', function() {\n      return AnotherUser.login(commonCredentials)\n        .then(function(accessToken) {\n          assertGoodToken(accessToken, userFromAnotherModel);\n        });\n    });\n\n    it('fails when credentials are not correct', function() {\n      return OneUser.login({email: 'foo@bar.com', password: 'invalid'})\n        .then(\n          function onSuccess() {\n            throw new Error('OneUser.login() should have failed');\n          },\n          function onError(err) {\n            expect(err).to.have.property('code', 'LOGIN_FAILED');\n          },\n        );\n    });\n  });\n\n  function assertGoodToken(accessToken, user) {\n    if (accessToken instanceof AccessToken) {\n      accessToken = accessToken.toJSON();\n    }\n    expect(accessToken.id, 'token id').to.have.lengthOf(64);\n    expect(accessToken).to.have.property('userId', user.id);\n    expect(accessToken).to.have.property('principalType', user.constructor.definition.name);\n  }\n\n  describe('User.logout', function() {\n    it('logs out a user from user model 1 without logging out user from model 2',\n      function() {\n        let tokenOfOneUser;\n        return Promise.all([\n          OneUser.login(commonCredentials),\n          AnotherUser.login(commonCredentials),\n        ])\n          .spread(function(t1, t2) {\n            tokenOfOneUser = t1;\n            return OneUser.logout(tokenOfOneUser.id);\n          })\n          .then(function() {\n            return AccessToken.find({});\n          })\n          .then(function(allTokens) {\n            const data = allTokens.map(function(token) {\n              return {userId: token.userId, principalType: token.principalType};\n            });\n            expect(data).to.eql([\n              // no token for userFromAnotherModel\n              {userId: userFromAnotherModel.id, principalType: 'AnotherUser'},\n            ]);\n          });\n      });\n  });\n\n  describe('Password Reset', function() {\n    describe('User.resetPassword(options)', function() {\n      const options = {\n        email: 'foo@bar.com',\n        redirect: 'http://foobar.com/reset-password',\n      };\n\n      it('creates a temp accessToken to allow a user to change password',\n        function() {\n          return Promise.all([\n            OneUser.resetPassword({email: options.email}),\n            waitForResetRequestAndVerify,\n          ]);\n        });\n\n      function waitForResetRequestAndVerify() {\n        return waitForEvent(OneUser, 'resetPasswordRequest')\n          .then(function(info) {\n            assertGoodToken(info.accessToken, userFromOneModel);\n            return info.accessToken.user.getAsync();\n          })\n          .then(function(user) {\n            expect(user).to.have.property('id', userFromOneModel.id);\n            expect(user).to.have.property('email', userFromOneModel.email);\n          });\n      }\n    });\n  });\n\n  describe('AccessToken (session) invalidation when changing email', function() {\n    let anotherUserFromOneModel;\n\n    it('impact only the related user', function() {\n      return OneUser.create({email: 'original@example.com', password: 'bar'})\n        .then(function(u) {\n          anotherUserFromOneModel = u;\n          return Promise.all([\n            OneUser.login({email: 'original@example.com', password: 'bar'}),\n            OneUser.login(commonCredentials),\n            AnotherUser.login(commonCredentials),\n          ]);\n        })\n        .then(function() {\n          return anotherUserFromOneModel.updateAttribute('email', 'updated@example.com');\n        })\n        .then(function() {\n          // we need to sort on principalType to ensure stability in results' order\n          return AccessToken.find({'order': 'principalType ASC'});\n        })\n        .then(function(allTokens) {\n          const data = allTokens.map(function(token) {\n            return {userId: token.userId, principalType: token.principalType};\n          });\n          expect(data).to.eql([\n            // no token for anotherUserFromOneModel\n            {userId: userFromAnotherModel.id, principalType: 'AnotherUser'},\n            {userId: userFromOneModel.id, principalType: 'OneUser'},\n          ]);\n        });\n    });\n  });\n\n  describe('AccessContext', function() {\n    let ThirdUser, userFromThirdModel, accessContext;\n\n    beforeEach(function() {\n      accessContext = new AccessContext({registry: OneUser.registry});\n    });\n\n    describe('getUser()', function() {\n      it('returns user although principals contain non USER principals',\n        function() {\n          return Promise.try(function() {\n            addToAccessContext([\n              {type: Principal.ROLE},\n              {type: Principal.APP},\n              {type: Principal.SCOPE},\n              {type: OneUser.modelName, id: userFromOneModel.id},\n            ]);\n            const user = accessContext.getUser();\n            expect(user).to.eql({\n              id: userFromOneModel.id,\n              principalType: OneUser.modelName,\n            });\n          });\n        });\n\n      it('returns user although principals contain invalid principals',\n        function() {\n          return Promise.try(function() {\n            addToAccessContext([\n              {type: 'AccessToken'},\n              {type: 'invalidModelName'},\n              {type: OneUser.modelName, id: userFromOneModel.id},\n            ]);\n            const user = accessContext.getUser();\n            expect(user).to.eql({\n              id: userFromOneModel.id,\n              principalType: OneUser.modelName,\n            });\n          });\n        });\n\n      it('supports any level of built-in User model inheritance',\n        function() {\n          ThirdUser = createUserModel(app, 'ThirdUser', {base: 'OneUser'});\n          return ThirdUser.create(commonCredentials)\n            .then(function(userFromThirdModel) {\n              accessContext.addPrincipal(ThirdUser.modelName, userFromThirdModel.id);\n              const user = accessContext.getUser();\n              expect(user).to.eql({\n                id: userFromThirdModel.id,\n                principalType: ThirdUser.modelName,\n              });\n            });\n        });\n    });\n\n    // helper\n    function addToAccessContext(list) {\n      list.forEach(function(principal) {\n        expect(principal).to.exist();\n        accessContext.addPrincipal(principal.type, principal.id);\n      });\n    }\n  });\n\n  describe('Role model', function() {\n    this.timeout(10000);\n\n    let RoleMapping, ACL, user;\n\n    beforeEach(function() {\n      ACL = app.registry.getModel('ACL');\n      app.model(ACL, {dataSource: 'db'});\n\n      RoleMapping = app.registry.getModel('RoleMapping');\n      app.model(RoleMapping, {dataSource: 'db'});\n    });\n\n    describe('role.users()', function() {\n      it('returns users when using custom user principalType', function() {\n        return userRole.principals.create(\n          {principalType: OneUser.modelName, principalId: userFromOneModel.id},\n        )\n          .then(function() {\n            return userRole.users({where: {principalType: OneUser.modelName}});\n          })\n          .then(getIds)\n          .then(function(userIds) {\n            expect(userIds).to.eql([userFromOneModel.id]);\n          });\n      });\n\n      it('returns empty array when using invalid principalType', function() {\n        return userRole.principals.create(\n          {principalType: 'invalidModelName', principalId: userFromOneModel.id},\n        )\n          .then(function() {\n            return userRole.users({where: {principalType: 'invalidModelName'}});\n          })\n          .then(function(users) {\n            expect(users).to.be.empty();\n          });\n      });\n    });\n\n    describe('principal.user()', function() {\n      it('returns the correct user instance', function() {\n        return userRole.principals.create(\n          {principalType: OneUser.modelName, principalId: userFromOneModel.id},\n        ).then(function(principal) {\n          return principal.user();\n        }).then(function(user) {\n          expect(user).to.have.property('id', userFromOneModel.id);\n        });\n      });\n\n      it('returns null when created with invalid principalType', function() {\n        return userRole.principals.create(\n          {principalType: 'invalidModelName', principalId: userFromOneModel.id},\n        ).then(function(principal) {\n          return principal.user();\n        }).then(function(user) {\n          expect(user).to.not.exist();\n        });\n      });\n    });\n\n    describe('isInRole() & getRole()', function() {\n      beforeEach(function() {\n        return userRole.principals.create({principalType: OneUser.modelName,\n          principalId: userFromOneModel.id});\n      });\n\n      it('supports isInRole()', function() {\n        return Role.isInRole('userRole', userOneBaseContext)\n          .then(function(isInRole) {\n            expect(isInRole).to.be.true();\n          });\n      });\n\n      it('supports getRoles()', function() {\n        return Role.getRoles(\n          userOneBaseContext,\n        ).then(function(roles) {\n          expect(roles).to.eql([\n            Role.AUTHENTICATED,\n            Role.EVERYONE,\n            userRole.id,\n          ]);\n        });\n      });\n    });\n\n    describe('built-in role resolvers', function() {\n      it('supports $authenticated', function() {\n        return Role.isInRole(Role.AUTHENTICATED, userOneBaseContext)\n          .then(function(isInRole) {\n            expect(isInRole).to.be.true();\n          });\n      });\n\n      it('supports $unauthenticated', function() {\n        return Role.isInRole(Role.UNAUTHENTICATED, userOneBaseContext)\n          .then(function(isInRole) {\n            expect(isInRole).to.be.false();\n          });\n      });\n\n      describe('$owner', function() {\n        it('supports legacy behavior with relations', function() {\n          const Album = app.registry.createModel('Album', {\n            name: String,\n            userId: Number,\n          }, {\n            relations: {\n              user: {\n                type: 'belongsTo',\n                model: 'OneUser',\n                foreignKey: 'userId',\n              },\n            },\n          });\n          app.model(Album, {dataSource: 'db'});\n\n          return Album.create({name: 'album', userId: userFromOneModel.id})\n            .then(function(album) {\n              const validContext = {\n                principalType: OneUser.modelName,\n                principalId: userFromOneModel.id,\n                model: Album,\n                id: album.id,\n              };\n              return Role.isInRole(Role.OWNER, validContext);\n            })\n            .then(function(isOwner) {\n              expect(isOwner).to.be.true();\n            });\n        });\n\n        // With multiple users config, we cannot resolve a user based just on\n        // his id, as many users from different models could have the same id.\n        it('legacy behavior resolves false without belongsTo relation', function() {\n          const Album = app.registry.createModel('Album', {\n            name: String,\n            userId: Number,\n            owner: Number,\n          });\n          app.model(Album, {dataSource: 'db'});\n\n          return Album.create({\n            name: 'album',\n            userId: userFromOneModel.id,\n            owner: userFromOneModel.id,\n          })\n            .then(function(album) {\n              const authContext = {\n                principalType: OneUser.modelName,\n                principalId: userFromOneModel.id,\n                model: Album,\n                id: album.id,\n              };\n              return Role.isInRole(Role.OWNER, authContext);\n            })\n            .then(function(isOwner) {\n              expect(isOwner).to.be.false();\n            });\n        });\n\n        it('legacy behavior resolves false if owner has incorrect principalType', function() {\n          const Album = app.registry.createModel('Album', {\n            name: String,\n            userId: Number,\n          }, {\n            relations: {\n              user: {\n                type: 'belongsTo',\n                model: 'OneUser',\n                foreignKey: 'userId',\n              },\n            },\n          });\n          app.model(Album, {dataSource: 'db'});\n\n          return Album.create({name: 'album', userId: userFromOneModel.id})\n            .then(function(album) {\n              const invalidPrincipalTypes = [\n                'invalidContextName',\n                'USER',\n                AnotherUser.modelName,\n              ];\n              const invalidContexts = invalidPrincipalTypes.map(principalType => {\n                return {\n                  principalType,\n                  principalId: userFromOneModel.id,\n                  model: Album,\n                  id: album.id,\n                };\n              });\n              return Promise.map(invalidContexts, context => {\n                return Role.isInRole(Role.OWNER, context)\n                  .then(isOwner => {\n                    return {\n                      principalType: context.principalType,\n                      isOwner,\n                    };\n                  });\n              });\n            })\n            .then(result => {\n              expect(result).to.eql([\n                {principalType: 'invalidContextName', isOwner: false},\n                {principalType: 'USER', isOwner: false},\n                {principalType: AnotherUser.modelName, isOwner: false},\n              ]);\n            });\n        });\n\n        it.skip('resolves the owner using the corrent belongsTo relation',\n          function() {\n          // passing {ownerRelations: true} will enable the new $owner role resolver\n          // with any belongsTo relation allowing to resolve truthy\n            const Message = createModelWithOptions(\n              'ModelWithAllRelations',\n              {ownerRelations: true},\n            );\n\n            const messages = [\n              {content: 'firstMessage', customerId: userFromOneModel.id},\n              {\n                content: 'secondMessage',\n                customerId: userFromOneModel.id,\n                shopKeeperId: userFromAnotherModel.id,\n              },\n\n              // this is the incriminated message where two foreignKeys have the\n              // same id but points towards two different user models. Although\n              // customers should come from userFromOneModel and shopKeeperIds should\n              // come from userFromAnotherModel. The inverted situation still resolves\n              // isOwner true for both the customer and the shopKeeper\n              {\n                content: 'thirdMessage',\n                customerId: userFromAnotherModel.id,\n                shopKeeperId: userFromOneModel.id,\n              },\n\n              {content: 'fourthMessage', customerId: userFromAnotherModel.id},\n              {content: 'fifthMessage'},\n            ];\n            return Promise.map(messages, msg => {\n              return Message.create(msg);\n            })\n              .then(messages => {\n                return Promise.all([\n                  isOwnerForMessage(userFromOneModel, messages[0]),\n                  isOwnerForMessage(userFromAnotherModel, messages[0]),\n                  isOwnerForMessage(userFromOneModel, messages[1]),\n                  isOwnerForMessage(userFromAnotherModel, messages[1]),\n\n                  isOwnerForMessage(userFromOneModel, messages[2]),\n                  isOwnerForMessage(userFromAnotherModel, messages[2]),\n\n                  isOwnerForMessage(userFromAnotherModel, messages[3]),\n                  isOwnerForMessage(userFromOneModel, messages[4]),\n                ]);\n              })\n              .then(result => {\n                expect(result).to.eql([\n                  {userFrom: 'OneUser', msg: 'firstMessage', isOwner: true},\n                  {userFrom: 'AnotherUser', msg: 'firstMessage', isOwner: false},\n                  {userFrom: 'OneUser', msg: 'secondMessage', isOwner: true},\n                  {userFrom: 'AnotherUser', msg: 'secondMessage', isOwner: true},\n\n                  // these 2 tests fail because we cannot resolve ownership with\n                  // multiple owners on a single model instance with a classic\n                  // belongsTo relation, we need to use belongsTo with polymorphic\n                  // discriminator to distinguish between the 2 models\n                  {userFrom: 'OneUser', msg: 'thirdMessage', isOwner: false},\n                  {userFrom: 'AnotherUser', msg: 'thirdMessage', isOwner: false},\n\n                  {userFrom: 'AnotherUser', msg: 'fourthMessage', isOwner: false},\n                  {userFrom: 'OneUser', msg: 'fifthMessage', isOwner: false},\n                ]);\n              });\n          });\n      });\n\n      // helpers\n      function isOwnerForMessage(user, msg) {\n        const accessContext = {\n          principalType: user.constructor.modelName,\n          principalId: user.id,\n          model: msg.constructor,\n          id: msg.id,\n        };\n        return Role.isInRole(Role.OWNER, accessContext)\n          .then(isOwner => {\n            return {\n              userFrom: user.constructor.modelName,\n              msg: msg.content,\n              isOwner,\n            };\n          });\n      }\n\n      function createModelWithOptions(name, options) {\n        const baseOptions = {\n          relations: {\n            sender: {\n              type: 'belongsTo',\n              model: 'OneUser',\n              foreignKey: 'customerId',\n            },\n            receiver: {\n              type: 'belongsTo',\n              model: 'AnotherUser',\n              foreignKey: 'shopKeeperId',\n            },\n          },\n        };\n        options = extend(baseOptions, options);\n        const Model = app.registry.createModel(\n          name,\n          {content: String, senderType: String},\n          options,\n        );\n        app.model(Model, {dataSource: 'db'});\n        return Model;\n      }\n    });\n\n    describe('isMappedToRole()', function() {\n      beforeEach(function() {\n        return userRole.principals.create(userOneBaseContext);\n      });\n\n      it('resolves user by id using custom user principalType', function() {\n        return ACL.resolvePrincipal(OneUser.modelName, userFromOneModel.id)\n          .then(function(principal) {\n            expect(principal.id).to.eql(userFromOneModel.id);\n          });\n      });\n\n      it('throws error with code \\'INVALID_PRINCIPAL_TYPE\\' when principalType is incorrect',\n        function() {\n          return ACL.resolvePrincipal('incorrectPrincipalType', userFromOneModel.id)\n            .then(\n              function onSuccess() {\n                throw new Error('ACL.resolvePrincipal() should have failed');\n              },\n              function onError(err) {\n                expect(err).to.have.property('statusCode', 400);\n                expect(err).to.have.property('code', 'INVALID_PRINCIPAL_TYPE');\n              },\n            );\n        });\n\n      it('reports isMappedToRole by user.username using custom user principalType',\n        function() {\n          return ACL.isMappedToRole(OneUser.modelName, userFromOneModel.username, 'userRole')\n            .then(function(isMappedToRole) {\n              expect(isMappedToRole).to.be.true();\n            });\n        });\n    });\n  });\n\n  describe('setPassword', () => {\n    let resetToken;\n    beforeEach(givenResetPasswordTokenForOneUser);\n\n    it('sets password when the access token belongs to the user', () => {\n      return supertest(app)\n        .post('/OneUsers/reset-password')\n        .set('Authorization', resetToken.id)\n        .send({newPassword: 'new-pass'})\n        .expect(204)\n        .then(() => {\n          return supertest(app)\n            .post('/OneUsers/login')\n            .send({email: commonCredentials.email, password: 'new-pass'})\n            .expect(200);\n        });\n    });\n\n    it('fails when the access token belongs to a different user mode', () => {\n      logServerErrorsOtherThan(403, app);\n      return supertest(app)\n        .post('/AnotherUsers/reset-password')\n        .set('Authorization', resetToken.id)\n        .send({newPassword: 'new-pass'})\n        .expect(403)\n        .then(() => {\n          return supertest(app)\n            .post('/AnotherUsers/login')\n            .send(commonCredentials)\n            .expect(200);\n        });\n    });\n\n    function givenResetPasswordTokenForOneUser() {\n      return Promise.all([\n        OneUser.resetPassword({email: commonCredentials.email}),\n        waitForEvent(OneUser, 'resetPasswordRequest'),\n      ])\n        .spread((reset, info) => resetToken = info.accessToken);\n    }\n  });\n\n  describe('changePassword', () => {\n    let token;\n    beforeEach(givenTokenForOneUser);\n\n    it('changes password when the access token belongs to the user', () => {\n      return supertest(app)\n        .post('/OneUsers/change-password')\n        .set('Authorization', token.id)\n        .send({\n          oldPassword: commonCredentials.password,\n          newPassword: 'new-pass',\n        })\n        .expect(204)\n        .then(() => {\n          return supertest(app)\n            .post('/OneUsers/login')\n            .send({email: commonCredentials.email, password: 'new-pass'})\n            .expect(200);\n        });\n    });\n\n    it('fails when the access token belongs to a different user mode', () => {\n      logServerErrorsOtherThan(403, app);\n      return supertest(app)\n        .post('/AnotherUsers/change-password')\n        .set('Authorization', token.id)\n        .send({\n          oldPassword: commonCredentials.password,\n          newPassword: 'new-pass',\n        })\n        .expect(403)\n        .then(() => {\n          return supertest(app)\n            .post('/AnotherUsers/login')\n            .send(commonCredentials)\n            .expect(200);\n        });\n    });\n\n    function givenTokenForOneUser() {\n      return OneUser.login(commonCredentials).then(t => token = t);\n    }\n  });\n\n  describe('authorization', () => {\n    beforeEach(givenProductModelAllowingOnlyUserRoleAccess);\n\n    it('allows users belonging to authorized role', () => {\n      logServerErrorsOtherThan(200, app);\n      return userFromOneModel.createAccessToken()\n        .then(token => {\n          return supertest(app)\n            .get('/Products')\n            .set('Authorization', token.id)\n            .expect(200, []);\n        });\n    });\n\n    it('rejects other users', () => {\n      logServerErrorsOtherThan(401, app);\n      return userFromAnotherModel.createAccessToken()\n        .then(token => {\n          return supertest(app)\n            .get('/Products')\n            .set('Authorization', token.id)\n            .expect(401);\n        });\n    });\n\n    function givenProductModelAllowingOnlyUserRoleAccess() {\n      const Product = app.registry.createModel({\n        name: 'Product',\n        acls: [\n          {\n            'principalType': 'ROLE',\n            'principalId': '$everyone',\n            'permission': 'DENY',\n          },\n          {\n            'principalType': 'ROLE',\n            'principalId': userRole.name,\n            'permission': 'ALLOW',\n          },\n        ],\n      });\n      app.model(Product, {dataSource: 'db'});\n\n      return userRole.principals.create({\n        principalType: OneUser.modelName,\n        principalId: userFromOneModel.id,\n      });\n    }\n  });\n\n  // helpers\n  function createUserModel(app, name, options) {\n    const model = app.registry.createModel(Object.assign({name: name}, options));\n    app.model(model, {dataSource: 'db'});\n    model.setMaxListeners(0); // allow many User.afterRemote's to be called\n    return model;\n  }\n\n  function waitForEvent(emitter, name) {\n    return new Promise(function(resolve, reject) {\n      emitter.once(name, resolve);\n    });\n  }\n\n  function getIds(array) {\n    return array.map(function(it) { return it.id; });\n  }\n});\n"
  },
  {
    "path": "test/registries.test.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst expect = require('./helpers/expect');\nconst loopback = require('../');\n\ndescribe('Registry', function() {\n  describe('createModel', function() {\n    it('should throw error upon extending non-exist base model', function() {\n      const app = loopback();\n      const props = {};\n      const opts = {base: 'nonexistent'};\n      expect(function() { app.registry.createModel('aModel', props, opts); })\n        .to.throw(/model\\s`aModel`(.*)unknown\\smodel\\s`nonexistent`/);\n    });\n  });\n\n  describe('one per app', function() {\n    it('should allow two apps to reuse the same model name', function(done) {\n      const appFoo = loopback();\n      const appBar = loopback();\n      const modelName = 'MyModel';\n      const subModelName = 'Sub' + modelName;\n      const settings = {base: 'PersistedModel'};\n      appFoo.set('perAppRegistries', true);\n      appBar.set('perAppRegistries', true);\n      const dsFoo = appFoo.dataSource('dsFoo', {connector: 'memory'});\n      const dsBar = appFoo.dataSource('dsBar', {connector: 'memory'});\n\n      const FooModel = appFoo.registry.createModel(modelName, {}, settings);\n      appFoo.model(FooModel, {dataSource: dsFoo});\n\n      const FooSubModel = appFoo.registry.createModel(subModelName, {}, settings);\n      appFoo.model(FooSubModel, {dataSource: dsFoo});\n\n      const BarModel = appBar.registry.createModel(modelName, {}, settings);\n      appBar.model(BarModel, {dataSource: dsBar});\n\n      const BarSubModel = appBar.registry.createModel(subModelName, {}, settings);\n      appBar.model(BarSubModel, {dataSource: dsBar});\n\n      FooModel.hasMany(FooSubModel);\n      BarModel.hasMany(BarSubModel);\n\n      expect(appFoo.models[modelName]).to.not.equal(appBar.models[modelName]);\n\n      BarModel.create({name: 'bar'}, function(err, bar) {\n        assert(!err);\n        bar.subMyModels.create({parent: 'bar'}, function(err) {\n          assert(!err);\n          FooSubModel.find(function(err, foos) {\n            assert(!err);\n            expect(foos).to.eql([]);\n            BarSubModel.find(function(err, bars) {\n              assert(!err);\n              expect(bars.map(function(f) {\n                return f.parent;\n              })).to.eql(['bar']);\n\n              done();\n            });\n          });\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/relations.integration.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../');\nconst lt = require('./helpers/loopback-testing-helper');\nconst path = require('path');\nconst SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-integration-app');\nconst app = require(path.join(SIMPLE_APP, 'server/server.js'));\nconst assert = require('assert');\nconst expect = require('./helpers/expect');\nconst debug = require('debug')('loopback:test:relations.integration');\nconst async = require('async');\n\ndescribe('relations - integration', function() {\n  lt.beforeEach.withApp(app);\n\n  lt.beforeEach.givenModel('store');\n  beforeEach(function(done) {\n    this.widgetName = 'foo';\n    this.store.widgets.create({\n      name: this.widgetName,\n    }, function() {\n      done();\n    });\n  });\n  afterEach(function(done) {\n    this.app.models.widget.destroyAll(done);\n  });\n\n  describe('polymorphicHasMany', function() {\n    before(function defineProductAndCategoryModels() {\n      const Team = app.registry.createModel('Team', {name: 'string'});\n      const Reader = app.registry.createModel('Reader', {name: 'string'});\n      const Picture = app.registry.createModel('Picture',\n        {name: 'string', imageableId: 'number', imageableType: 'string'});\n\n      app.model(Team, {dataSource: 'db'});\n      app.model(Reader, {dataSource: 'db'});\n      app.model(Picture, {dataSource: 'db'});\n\n      Reader.hasMany(Picture, {polymorphic: { // alternative syntax\n        as: 'imageable', // if not set, default to: reference\n        foreignKey: 'imageableId', // defaults to 'as + Id'\n        discriminator: 'imageableType', // defaults to 'as + Type'\n      }});\n\n      Picture.belongsTo('imageable', {polymorphic: {\n        foreignKey: 'imageableId',\n        discriminator: 'imageableType',\n      }});\n\n      Reader.belongsTo(Team);\n    });\n\n    before(function createEvent(done) {\n      const test = this;\n      app.models.Team.create({name: 'Team 1'},\n        function(err, team) {\n          if (err) return done(err);\n\n          test.team = team;\n          app.models.Reader.create({name: 'Reader 1'},\n            function(err, reader) {\n              if (err) return done(err);\n\n              test.reader = reader;\n              reader.pictures.create({name: 'Picture 1'});\n              reader.pictures.create({name: 'Picture 2'});\n              reader.team(test.team);\n              reader.save(done);\n            });\n        });\n    });\n\n    after(function(done) {\n      this.app.models.Reader.destroyAll(done);\n    });\n\n    it('includes the related child model', function(done) {\n      const url = '/api/readers/' + this.reader.id;\n      this.get(url)\n        .query({'filter': {'include': 'pictures'}})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.name).to.be.equal('Reader 1');\n          expect(res.body.pictures).to.be.eql([\n            {name: 'Picture 1', id: 1, imageableId: 1, imageableType: 'Reader'},\n            {name: 'Picture 2', id: 2, imageableId: 1, imageableType: 'Reader'},\n          ]);\n\n          done();\n        });\n    });\n\n    it('includes the related parent model', function(done) {\n      const url = '/api/pictures';\n      this.get(url)\n        .query({'filter': {'include': 'imageable'}})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body[0].name).to.be.equal('Picture 1');\n          expect(res.body[1].name).to.be.equal('Picture 2');\n          expect(res.body[0].imageable).to.be.eql({name: 'Reader 1', id: 1, teamId: 1});\n\n          done();\n        });\n    });\n\n    it('includes related models scoped to the related parent model', function(done) {\n      const url = '/api/pictures';\n      this.get(url)\n        .query({'filter': {'include': {\n          'relation': 'imageable',\n          'scope': {'include': 'team'},\n        }}})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body[0].name).to.be.equal('Picture 1');\n          expect(res.body[1].name).to.be.equal('Picture 2');\n          expect(res.body[0].imageable.name).to.be.eql('Reader 1');\n          expect(res.body[0].imageable.team).to.be.eql({name: 'Team 1', id: 1});\n\n          done();\n        });\n    });\n  });\n\n  describe('/store/superStores', function() {\n    it('should invoke scoped methods remotely', function(done) {\n      this.get('/api/stores/superStores')\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.an('array');\n\n          done();\n        });\n    });\n  });\n\n  describe('/store/:id/widgets', function() {\n    beforeEach(function() {\n      this.url = '/api/stores/' + this.store.id + '/widgets';\n    });\n    lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets', function() {\n      it('should succeed with statusCode 200', function() {\n        assert.equal(this.res.statusCode, 200);\n      });\n      describe('widgets (response.body)', function() {\n        beforeEach(function() {\n          debug('GET /api/stores/:id/widgets response: %s' +\n              '\\nheaders: %j\\nbody string: %s',\n          this.res.statusCode,\n          this.res.headers,\n          this.res.text);\n          this.widgets = this.res.body;\n          this.widget = this.res.body && this.res.body[0];\n        });\n        it('should be an array', function() {\n          assert(Array.isArray(this.widgets));\n        });\n        it('should include a single widget', function() {\n          assert(this.widgets.length === 1);\n          assert(this.widget);\n        });\n        it('should be a valid widget', function() {\n          assert(this.widget.id);\n          assert.equal(this.widget.storeId, this.store.id);\n          assert.equal(this.widget.name, this.widgetName);\n        });\n      });\n    });\n    describe('POST /api/store/:id/widgets', function() {\n      beforeEach(function() {\n        this.newWidgetName = 'baz';\n        this.newWidget = {\n          name: this.newWidgetName,\n        };\n      });\n      beforeEach(function(done) {\n        this.http = this.post(this.url, this.newWidget);\n        this.http.send(this.newWidget);\n        this.http.end(function(err) {\n          if (err) return done(err);\n\n          this.req = this.http.req;\n          this.res = this.http.response;\n\n          done();\n        }.bind(this));\n      });\n      it('should succeed with statusCode 200', function() {\n        assert.equal(this.res.statusCode, 200);\n      });\n      describe('widget (response.body)', function() {\n        beforeEach(function() {\n          this.widget = this.res.body;\n        });\n        it('should be an object', function() {\n          assert(typeof this.widget === 'object');\n          assert(!Array.isArray(this.widget));\n        });\n        it('should be a valid widget', function() {\n          assert(this.widget.id);\n          assert.equal(this.widget.storeId, this.store.id);\n          assert.equal(this.widget.name, this.newWidgetName);\n        });\n      });\n      it('should have a single widget with storeId', function(done) {\n        this.app.models.widget.count({\n          storeId: this.store.id,\n        }, function(err, count) {\n          if (err) return done(err);\n\n          assert.equal(count, 2);\n\n          done();\n        });\n      });\n    });\n\n    describe('PUT /api/store/:id/widgets/:fk', function() {\n      beforeEach(function(done) {\n        const self = this;\n        this.store.widgets.create({\n          name: this.widgetName,\n        }, function(err, widget) {\n          self.widget = widget;\n          self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id;\n          done();\n        });\n      });\n      it('does not add default properties to request body', function(done) {\n        const self = this;\n        self.request.put(self.url)\n          .send({active: true})\n          .end(function(err) {\n            if (err) return done(err);\n            app.models.Widget.findById(self.widget.id, function(err, w) {\n              if (err) return done(err);\n              expect(w.name).to.equal(self.widgetName);\n              done();\n            });\n          });\n      });\n    });\n  });\n\n  describe('/stores/:id/widgets/:fk - 200', function() {\n    beforeEach(function(done) {\n      const self = this;\n      this.store.widgets.create({\n        name: this.widgetName,\n      }, function(err, widget) {\n        self.widget = widget;\n        self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id;\n\n        done();\n      });\n    });\n    lt.describe.whenCalledRemotely('GET', '/stores/:id/widgets/:fk', function() {\n      it('should succeed with statusCode 200', function() {\n        assert.equal(this.res.statusCode, 200);\n        assert.equal(this.res.body.id, this.widget.id);\n      });\n    });\n  });\n\n  describe('/stores/:id/widgets/:fk - 404', function() {\n    beforeEach(function() {\n      this.url = '/api/stores/' + this.store.id + '/widgets/123456';\n    });\n    lt.describe.whenCalledRemotely('GET', '/stores/:id/widgets/:fk', function() {\n      it('should fail with statusCode 404', function() {\n        assert.equal(this.res.statusCode, 404);\n        assert.equal(this.res.body.error.statusCode, 404);\n      });\n    });\n  });\n\n  describe('/store/:id/widgets/count', function() {\n    beforeEach(function() {\n      this.url = '/api/stores/' + this.store.id + '/widgets/count';\n    });\n    lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets/count', function() {\n      it('should succeed with statusCode 200', function() {\n        assert.equal(this.res.statusCode, 200);\n      });\n      it('should return the count', function() {\n        assert.equal(this.res.body.count, 1);\n      });\n    });\n  });\n\n  describe('/store/:id/widgets/count - filtered (matches)', function() {\n    beforeEach(function() {\n      this.url = '/api/stores/' + this.store.id + '/widgets/count?where[name]=foo';\n    });\n    lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets/count?where[name]=foo', function() {\n      it('should succeed with statusCode 200', function() {\n        assert.equal(this.res.statusCode, 200);\n      });\n      it('should return the count', function() {\n        assert.equal(this.res.body.count, 1);\n      });\n    });\n  });\n\n  describe('/store/:id/widgets/count - filtered (no matches)', function() {\n    beforeEach(function() {\n      this.url = '/api/stores/' + this.store.id + '/widgets/count?where[name]=bar';\n    });\n    lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets/count?where[name]=bar', function() {\n      it('should succeed with statusCode 200', function() {\n        assert.equal(this.res.statusCode, 200);\n      });\n      it('should return the count', function() {\n        assert.equal(this.res.body.count, 0);\n      });\n    });\n  });\n\n  describe('/widgets/:id/store', function() {\n    beforeEach(function(done) {\n      const self = this;\n      this.store.widgets.create({\n        name: this.widgetName,\n      }, function(err, widget) {\n        self.widget = widget;\n        self.url = '/api/widgets/' + self.widget.id + '/store';\n\n        done();\n      });\n    });\n    lt.describe.whenCalledRemotely('GET', '/api/widgets/:id/store', function() {\n      it('should succeed with statusCode 200', function() {\n        assert.equal(this.res.statusCode, 200);\n        assert.equal(this.res.body.id, this.store.id);\n      });\n    });\n  });\n\n  describe('hasMany through', function() {\n    function setup(connecting, cb) {\n      const root = {};\n\n      async.series([\n        // Clean up models\n        function(done) {\n          app.models.physician.destroyAll(function(err) {\n            app.models.patient.destroyAll(function(err) {\n              app.models.appointment.destroyAll(function(err) {\n                done();\n              });\n            });\n          });\n        },\n\n        // Create a physician\n        function(done) {\n          app.models.physician.create({\n            name: 'ph1',\n          }, function(err, physician) {\n            root.physician = physician;\n\n            done();\n          });\n        },\n\n        // Create a patient\n        connecting ? function(done) {\n          root.physician.patients.create({\n            name: 'pa1',\n          }, function(err, patient) {\n            root.patient = patient;\n            root.relUrl = '/api/physicians/' + root.physician.id +\n              '/patients/rel/' + root.patient.id;\n\n            done();\n          });\n        } : function(done) {\n          app.models.patient.create({\n            name: 'pa1',\n          }, function(err, patient) {\n            root.patient = patient;\n            root.relUrl = '/api/physicians/' + root.physician.id +\n              '/patients/rel/' + root.patient.id;\n\n            done();\n          });\n        }], function(err, done) {\n        cb(err, root);\n      });\n    }\n\n    describe('PUT /physicians/:id/patients/rel/:fk', function() {\n      before(function(done) {\n        const self = this;\n        setup(false, function(err, root) {\n          self.url = root.relUrl;\n          self.patient = root.patient;\n          self.physician = root.physician;\n\n          done(err);\n        });\n      });\n\n      lt.describe.whenCalledRemotely('PUT', '/api/physicians/:id/patients/rel/:fk', function() {\n        it('should succeed with statusCode 200', function() {\n          assert.equal(this.res.statusCode, 200);\n          assert.equal(this.res.body.patientId, this.patient.id);\n          assert.equal(this.res.body.physicianId, this.physician.id);\n        });\n\n        it('should create a record in appointment', function(done) {\n          const self = this;\n          app.models.appointment.find(function(err, apps) {\n            assert.equal(apps.length, 1);\n            assert.equal(apps[0].patientId, self.patient.id);\n\n            done();\n          });\n        });\n\n        it('should connect physician to patient', function(done) {\n          const self = this;\n          self.physician.patients(function(err, patients) {\n            assert.equal(patients.length, 1);\n            assert.equal(patients[0].id, self.patient.id);\n\n            done();\n          });\n        });\n      });\n    });\n\n    describe('PUT /physicians/:id/patients/rel/:fk with data', function() {\n      before(function(done) {\n        const self = this;\n        setup(false, function(err, root) {\n          self.url = root.relUrl;\n          self.patient = root.patient;\n          self.physician = root.physician;\n\n          done(err);\n        });\n      });\n\n      const NOW = Date.now();\n      const data = {date: new Date(NOW)};\n\n      lt.describe.whenCalledRemotely('PUT', '/api/physicians/:id/patients/rel/:fk', data, function() {\n        it('should succeed with statusCode 200', function() {\n          assert.equal(this.res.statusCode, 200);\n          assert.equal(this.res.body.patientId, this.patient.id);\n          assert.equal(this.res.body.physicianId, this.physician.id);\n          assert.equal(new Date(this.res.body.date).getTime(), NOW);\n        });\n\n        it('should create a record in appointment', function(done) {\n          const self = this;\n          app.models.appointment.find(function(err, apps) {\n            assert.equal(apps.length, 1);\n            assert.equal(apps[0].patientId, self.patient.id);\n            assert.equal(apps[0].physicianId, self.physician.id);\n            assert.equal(apps[0].date.getTime(), NOW);\n\n            done();\n          });\n        });\n\n        it('should connect physician to patient', function(done) {\n          const self = this;\n          self.physician.patients(function(err, patients) {\n            assert.equal(patients.length, 1);\n            assert.equal(patients[0].id, self.patient.id);\n\n            done();\n          });\n        });\n      });\n    });\n\n    describe('HEAD /physicians/:id/patients/rel/:fk', function() {\n      before(function(done) {\n        const self = this;\n        setup(true, function(err, root) {\n          self.url = root.relUrl;\n          self.patient = root.patient;\n          self.physician = root.physician;\n\n          done(err);\n        });\n      });\n\n      lt.describe.whenCalledRemotely('HEAD', '/api/physicians/:id/patients/rel/:fk', function() {\n        it('should succeed with statusCode 200', function() {\n          assert.equal(this.res.statusCode, 200);\n        });\n      });\n    });\n\n    describe('HEAD /physicians/:id/patients/rel/:fk that does not exist', function() {\n      before(function(done) {\n        const self = this;\n        setup(true, function(err, root) {\n          self.url = '/api/physicians/' + root.physician.id +\n            '/patients/rel/' + '999';\n          self.patient = root.patient;\n          self.physician = root.physician;\n\n          done(err);\n        });\n      });\n\n      lt.describe.whenCalledRemotely('HEAD', '/api/physicians/:id/patients/rel/:fk', function() {\n        it('should succeed with statusCode 404', function() {\n          assert.equal(this.res.statusCode, 404);\n        });\n      });\n    });\n\n    describe('DELETE /physicians/:id/patients/rel/:fk', function() {\n      before(function(done) {\n        const self = this;\n        setup(true, function(err, root) {\n          self.url = root.relUrl;\n          self.patient = root.patient;\n          self.physician = root.physician;\n\n          done(err);\n        });\n      });\n\n      it('should create a record in appointment', function(done) {\n        const self = this;\n        app.models.appointment.find(function(err, apps) {\n          assert.equal(apps.length, 1);\n          assert.equal(apps[0].patientId, self.patient.id);\n\n          done();\n        });\n      });\n\n      it('should connect physician to patient', function(done) {\n        const self = this;\n        self.physician.patients(function(err, patients) {\n          assert.equal(patients.length, 1);\n          assert.equal(patients[0].id, self.patient.id);\n\n          done();\n        });\n      });\n\n      lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/rel/:fk', function() {\n        it('should succeed with statusCode 204', function() {\n          assert.equal(this.res.statusCode, 204);\n        });\n\n        it('should remove the record in appointment', function(done) {\n          const self = this;\n          app.models.appointment.find(function(err, apps) {\n            assert.equal(apps.length, 0);\n\n            done();\n          });\n        });\n\n        it('should remove the connection between physician and patient', function(done) {\n          const self = this;\n          // Need to refresh the cache\n          self.physician.patients(true, function(err, patients) {\n            assert.equal(patients.length, 0);\n\n            done();\n          });\n        });\n      });\n    });\n\n    describe('GET /physicians/:id/patients/:fk', function() {\n      before(function(done) {\n        const self = this;\n        setup(true, function(err, root) {\n          self.url = '/api/physicians/' + root.physician.id +\n            '/patients/' + root.patient.id;\n          self.patient = root.patient;\n          self.physician = root.physician;\n\n          done(err);\n        });\n      });\n\n      lt.describe.whenCalledRemotely('GET', '/api/physicians/:id/patients/:fk', function() {\n        it('should succeed with statusCode 200', function() {\n          assert.equal(this.res.statusCode, 200);\n          assert.equal(this.res.body.id, this.physician.id);\n        });\n      });\n    });\n\n    describe('DELETE /physicians/:id/patients/:fk', function() {\n      before(function(done) {\n        const self = this;\n        setup(true, function(err, root) {\n          self.url = '/api/physicians/' + root.physician.id +\n            '/patients/' + root.patient.id;\n          self.patient = root.patient;\n          self.physician = root.physician;\n\n          done(err);\n        });\n      });\n\n      lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/:fk', function() {\n        it('should succeed with statusCode 204', function() {\n          assert.equal(this.res.statusCode, 204);\n        });\n\n        it('should remove the record in appointment', function(done) {\n          const self = this;\n          app.models.appointment.find(function(err, apps) {\n            assert.equal(apps.length, 0);\n\n            done();\n          });\n        });\n\n        it('should remove the connection between physician and patient', function(done) {\n          const self = this;\n          // Need to refresh the cache\n          self.physician.patients(true, function(err, patients) {\n            assert.equal(patients.length, 0);\n\n            done();\n          });\n        });\n\n        it('should remove the record in patient', function(done) {\n          const self = this;\n          app.models.patient.find(function(err, patients) {\n            assert.equal(patients.length, 0);\n\n            done();\n          });\n        });\n      });\n    });\n  });\n\n  describe('hasAndBelongsToMany', function() {\n    beforeEach(function defineProductAndCategoryModels() {\n      // Disable \"Warning: overriding remoting type product\"\n      this.app.remotes()._typeRegistry._options.warnWhenOverridingType = false;\n\n      const product = app.registry.createModel(\n        'product',\n        {id: 'string', name: 'string'},\n      );\n      const category = app.registry.createModel(\n        'category',\n        {id: 'string', name: 'string'},\n      );\n      app.model(product, {dataSource: 'db'});\n      app.model(category, {dataSource: 'db'});\n\n      product.hasAndBelongsToMany(category);\n      category.hasAndBelongsToMany(product);\n    });\n\n    lt.beforeEach.givenModel('category');\n\n    beforeEach(function createProductsInCategory(done) {\n      const test = this;\n      this.category.products.create({\n        name: 'a-product',\n      }, function(err, product) {\n        if (err) return done(err);\n\n        test.product = product;\n\n        done();\n      });\n    });\n\n    beforeEach(function createAnotherCategoryAndProduct(done) {\n      app.models.category.create({name: 'another-category'},\n        function(err, cat) {\n          if (err) return done(err);\n\n          cat.products.create({name: 'another-product'}, done);\n        });\n    });\n\n    afterEach(function(done) {\n      this.app.models.product.destroyAll(done);\n    });\n\n    it.skip('allows to find related objects via where filter', function(done) {\n      // TODO https://github.com/strongloop/loopback-datasource-juggler/issues/94\n      const expectedProduct = this.product;\n      this.get('/api/products?filter[where][categoryId]=' + this.category.id)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.eql([\n            {\n              id: expectedProduct.id,\n              name: expectedProduct.name,\n            },\n          ]);\n\n          done();\n        });\n    });\n\n    it('allows to find related object via URL scope', function(done) {\n      const expectedProduct = this.product;\n      this.get('/api/categories/' + this.category.id + '/products')\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.eql([\n            {\n              id: expectedProduct.id,\n              name: expectedProduct.name,\n            },\n          ]);\n\n          done();\n        });\n    });\n\n    it('includes requested related models in `find`', function(done) {\n      const expectedProduct = this.product;\n      const url = '/api/categories/findOne?filter[where][id]=' +\n        this.category.id + '&filter[include]=products';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.have.property('products');\n          expect(res.body.products).to.eql([\n            {\n              id: expectedProduct.id,\n              name: expectedProduct.name,\n            },\n          ]);\n\n          done();\n        });\n    });\n\n    it.skip('includes requested related models in `findById`', function(done) {\n      // TODO https://github.com/strongloop/loopback-datasource-juggler/issues/93\n      const expectedProduct = this.product;\n      // Note: the URL format is not final\n      const url = '/api/categories/' + this.category.id + '?include=products';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.have.property('products');\n          expect(res.body.products).to.eql([\n            {\n              id: expectedProduct.id,\n              name: expectedProduct.name,\n            },\n          ]);\n\n          done();\n        });\n    });\n  });\n\n  describe('embedsOne', function() {\n    before(function defineGroupAndPosterModels() {\n      const group = app.registry.createModel(\n        'group',\n        {name: 'string'},\n        {plural: 'groups'},\n      );\n      app.model(group, {dataSource: 'db'});\n\n      const poster = app.registry.createModel(\n        'poster',\n        {url: 'string'},\n      );\n      app.model(poster, {dataSource: 'db'});\n\n      group.embedsOne(poster, {as: 'cover'});\n    });\n\n    before(function createImage(done) {\n      const test = this;\n      app.models.group.create({name: 'Group 1'},\n        function(err, group) {\n          if (err) return done(err);\n\n          test.group = group;\n\n          done();\n        });\n    });\n\n    after(function(done) {\n      this.app.models.group.destroyAll(done);\n    });\n\n    it('creates an embedded model', function(done) {\n      const url = '/api/groups/' + this.group.id + '/cover';\n\n      this.post(url)\n        .send({url: 'http://image.url'})\n        .expect(200, function(err, res) {\n          expect(res.body).to.be.eql(\n            {url: 'http://image.url'},\n          );\n\n          done();\n        });\n    });\n\n    it('includes the embedded models', function(done) {\n      const url = '/api/groups/' + this.group.id;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.name).to.be.equal('Group 1');\n          expect(res.body.poster).to.be.eql(\n            {url: 'http://image.url'},\n          );\n\n          done();\n        });\n    });\n\n    it('returns the embedded model', function(done) {\n      const url = '/api/groups/' + this.group.id + '/cover';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql(\n            {url: 'http://image.url'},\n          );\n\n          done();\n        });\n    });\n\n    it('updates an embedded model', function(done) {\n      const url = '/api/groups/' + this.group.id + '/cover';\n\n      this.put(url)\n        .send({url: 'http://changed.url'})\n        .expect(200, function(err, res) {\n          expect(res.body.url).to.be.equal('http://changed.url');\n\n          done();\n        });\n    });\n\n    it('returns the updated embedded model', function(done) {\n      const url = '/api/groups/' + this.group.id + '/cover';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql(\n            {url: 'http://changed.url'},\n          );\n\n          done();\n        });\n    });\n\n    it('deletes an embedded model', function(done) {\n      const url = '/api/groups/' + this.group.id + '/cover';\n      this.del(url).expect(204, done);\n    });\n\n    it('deleted the embedded model', function(done) {\n      const url = '/api/groups/' + this.group.id + '/cover';\n      this.get(url).expect(404, done);\n    });\n  });\n\n  describe('embedsMany', function() {\n    before(function defineProductAndCategoryModels() {\n      const todoList = app.registry.createModel(\n        'todoList',\n        {name: 'string'},\n        {plural: 'todo-lists'},\n      );\n      app.model(todoList, {dataSource: 'db'});\n\n      const todoItem = app.registry.createModel(\n        'todoItem',\n        {content: 'string'}, {forceId: false},\n      );\n      app.model(todoItem, {dataSource: 'db'});\n\n      todoList.embedsMany(todoItem, {as: 'items'});\n    });\n\n    before(function createTodoList(done) {\n      const test = this;\n      app.models.todoList.create({name: 'List A'},\n        function(err, list) {\n          if (err) return done(err);\n\n          test.todoList = list;\n          list.items.build({content: 'Todo 1'});\n          list.items.build({content: 'Todo 2'});\n          list.save(done);\n        });\n    });\n\n    after(function(done) {\n      this.app.models.todoList.destroyAll(done);\n    });\n\n    it('includes the embedded models', function(done) {\n      const url = '/api/todo-lists/' + this.todoList.id;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.name).to.be.equal('List A');\n          expect(res.body.todoItems).to.be.eql([\n            {content: 'Todo 1', id: 1},\n            {content: 'Todo 2', id: 2},\n          ]);\n\n          done();\n        });\n    });\n\n    it('returns the embedded models', function(done) {\n      const url = '/api/todo-lists/' + this.todoList.id + '/items';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {content: 'Todo 1', id: 1},\n            {content: 'Todo 2', id: 2},\n          ]);\n\n          done();\n        });\n    });\n\n    it('filters the embedded models', function(done) {\n      let url = '/api/todo-lists/' + this.todoList.id + '/items';\n      url += '?filter[where][id]=2';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {content: 'Todo 2', id: 2},\n          ]);\n\n          done();\n        });\n    });\n\n    it('creates embedded models', function(done) {\n      const url = '/api/todo-lists/' + this.todoList.id + '/items';\n\n      const expected = {content: 'Todo 3', id: 3};\n\n      this.post(url)\n        .send({content: 'Todo 3'})\n        .expect(200, function(err, res) {\n          expect(res.body).to.be.eql(expected);\n\n          done();\n        });\n    });\n\n    it('includes the created embedded model', function(done) {\n      const url = '/api/todo-lists/' + this.todoList.id + '/items';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {content: 'Todo 1', id: 1},\n            {content: 'Todo 2', id: 2},\n            {content: 'Todo 3', id: 3},\n          ]);\n\n          done();\n        });\n    });\n\n    it('returns an embedded model by (internal) id', function(done) {\n      const url = '/api/todo-lists/' + this.todoList.id + '/items/3';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql(\n            {content: 'Todo 3', id: 3},\n          );\n\n          done();\n        });\n    });\n\n    it('removes an embedded model', function(done) {\n      const expectedProduct = this.product;\n      const url = '/api/todo-lists/' + this.todoList.id + '/items/2';\n\n      this.del(url)\n        .expect(200, function(err, res) {\n          done();\n        });\n    });\n\n    it('returns the embedded models - verify', function(done) {\n      const url = '/api/todo-lists/' + this.todoList.id + '/items';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {content: 'Todo 1', id: 1},\n            {content: 'Todo 3', id: 3},\n          ]);\n\n          done();\n        });\n    });\n\n    it('returns a 404 response when embedded model is not found', function(done) {\n      const url = '/api/todo-lists/' + this.todoList.id + '/items/2';\n      this.get(url).expect(404, function(err, res) {\n        if (err) return done(err);\n\n        expect(res.body.error.status).to.be.equal(404);\n        expect(res.body.error.message).to.be.equal('Unknown \"todoItem\" id \"2\".');\n        expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND');\n\n        done();\n      });\n    });\n\n    it.skip('checks if an embedded model exists - ok', function(done) {\n      const url = '/api/todo-lists/' + this.todoList.id + '/items/3';\n\n      this.head(url)\n        .expect(200, function(err, res) {\n          done();\n        });\n    });\n\n    it.skip('checks if an embedded model exists - fail', function(done) {\n      const url = '/api/todo-lists/' + this.todoList.id + '/items/2';\n\n      this.head(url)\n        .expect(404, function(err, res) {\n          done();\n        });\n    });\n  });\n\n  describe('referencesMany', function() {\n    before(function defineProductAndCategoryModels() {\n      const recipe = app.registry.createModel(\n        'recipe',\n        {name: 'string'},\n      );\n      app.model(recipe, {dataSource: 'db'});\n\n      const ingredient = app.registry.createModel(\n        'ingredient',\n        {name: 'string'},\n      );\n      app.model(ingredient, {dataSource: 'db'});\n\n      const photo = app.registry.createModel(\n        'photo',\n        {name: 'string'},\n      );\n      app.model(photo, {dataSource: 'db'});\n\n      recipe.referencesMany(ingredient);\n      // contrived example for test:\n      recipe.hasOne(photo, {as: 'picture', options: {\n        http: {path: 'image'},\n      }});\n    });\n\n    before(function createRecipe(done) {\n      const test = this;\n      app.models.recipe.create({name: 'Recipe'},\n        function(err, recipe) {\n          if (err) return done(err);\n\n          test.recipe = recipe;\n          recipe.ingredients.create({\n            name: 'Chocolate'},\n          function(err, ing) {\n            test.ingredient1 = ing.id;\n            recipe.picture.create({name: 'Photo 1'}, done);\n          });\n        });\n    });\n\n    before(function createIngredient(done) {\n      const test = this;\n      app.models.ingredient.create({name: 'Sugar'}, function(err, ing) {\n        test.ingredient2 = ing.id;\n\n        done();\n      });\n    });\n\n    after(function(done) {\n      const app = this.app;\n      app.models.recipe.destroyAll(function() {\n        app.models.ingredient.destroyAll(function() {\n          app.models.photo.destroyAll(done);\n        });\n      });\n    });\n\n    it('keeps an array of ids', function(done) {\n      const url = '/api/recipes/' + this.recipe.id;\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.ingredientIds).to.eql([test.ingredient1]);\n          expect(res.body).to.not.have.property('ingredients');\n\n          done();\n        });\n    });\n\n    it('creates referenced models', function(done) {\n      const url = '/api/recipes/' + this.recipe.id + '/ingredients';\n      const test = this;\n\n      this.post(url)\n        .send({name: 'Butter'})\n        .expect(200, function(err, res) {\n          expect(res.body.name).to.be.eql('Butter');\n          test.ingredient3 = res.body.id;\n\n          done();\n        });\n    });\n\n    it('has created models', function(done) {\n      const url = '/api/ingredients';\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {name: 'Chocolate', id: test.ingredient1},\n            {name: 'Sugar', id: test.ingredient2},\n            {name: 'Butter', id: test.ingredient3},\n          ]);\n\n          done();\n        });\n    });\n\n    it('returns the referenced models', function(done) {\n      const url = '/api/recipes/' + this.recipe.id + '/ingredients';\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {name: 'Chocolate', id: test.ingredient1},\n            {name: 'Butter', id: test.ingredient3},\n          ]);\n\n          done();\n        });\n    });\n\n    it('filters the referenced models', function(done) {\n      let url = '/api/recipes/' + this.recipe.id + '/ingredients';\n      url += '?filter[where][name]=Butter';\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {name: 'Butter', id: test.ingredient3},\n          ]);\n\n          done();\n        });\n    });\n\n    it('includes the referenced models', function(done) {\n      let url = '/api/recipes/findOne?filter[where][id]=' + this.recipe.id;\n      url += '&filter[include]=ingredients';\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.ingredientIds).to.eql([\n            test.ingredient1, test.ingredient3,\n          ]);\n          expect(res.body.ingredients).to.eql([\n            {name: 'Chocolate', id: test.ingredient1},\n            {name: 'Butter', id: test.ingredient3},\n          ]);\n\n          done();\n        });\n    });\n\n    it('returns a referenced model by id', function(done) {\n      let url = '/api/recipes/' + this.recipe.id + '/ingredients/';\n      url += this.ingredient3;\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql(\n            {name: 'Butter', id: test.ingredient3},\n          );\n\n          done();\n        });\n    });\n\n    it('keeps an array of ids - verify', function(done) {\n      const url = '/api/recipes/' + this.recipe.id;\n      const test = this;\n\n      const expected = [test.ingredient1, test.ingredient3];\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.ingredientIds).to.eql(expected);\n          expect(res.body).to.not.have.property('ingredients');\n\n          done();\n        });\n    });\n\n    it('destroys a referenced model', function(done) {\n      const expectedProduct = this.product;\n      let url = '/api/recipes/' + this.recipe.id + '/ingredients/';\n      url += this.ingredient3;\n\n      this.del(url)\n        .expect(200, function(err, res) {\n          done();\n        });\n    });\n\n    it('has destroyed a referenced model', function(done) {\n      const url = '/api/ingredients';\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {name: 'Chocolate', id: test.ingredient1},\n            {name: 'Sugar', id: test.ingredient2},\n          ]);\n\n          done();\n        });\n    });\n\n    it('returns the referenced models without the deleted one', function(done) {\n      const url = '/api/recipes/' + this.recipe.id + '/ingredients';\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {name: 'Chocolate', id: test.ingredient1},\n          ]);\n\n          done();\n        });\n    });\n\n    it('creates/links a reference by id', function(done) {\n      let url = '/api/recipes/' + this.recipe.id + '/ingredients';\n      url += '/rel/' + this.ingredient2;\n      const test = this;\n\n      this.put(url)\n        .expect(200, function(err, res) {\n          expect(res.body).to.be.eql(\n            {name: 'Sugar', id: test.ingredient2},\n          );\n\n          done();\n        });\n    });\n\n    it('returns the referenced models - verify', function(done) {\n      const url = '/api/recipes/' + this.recipe.id + '/ingredients';\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {name: 'Chocolate', id: test.ingredient1},\n            {name: 'Sugar', id: test.ingredient2},\n          ]);\n\n          done();\n        });\n    });\n\n    it('removes/unlinks a reference by id', function(done) {\n      let url = '/api/recipes/' + this.recipe.id + '/ingredients';\n      url += '/rel/' + this.ingredient1;\n      const test = this;\n\n      this.del(url)\n        .expect(200, function(err, res) {\n          done();\n        });\n    });\n\n    it('returns the referenced models without the unlinked one', function(done) {\n      const url = '/api/recipes/' + this.recipe.id + '/ingredients';\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {name: 'Sugar', id: test.ingredient2},\n          ]);\n\n          done();\n        });\n    });\n\n    it('has not destroyed an unlinked model', function(done) {\n      const url = '/api/ingredients';\n      const test = this;\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.eql([\n            {name: 'Chocolate', id: test.ingredient1},\n            {name: 'Sugar', id: test.ingredient2},\n          ]);\n\n          done();\n        });\n    });\n\n    it('uses a custom relation path', function(done) {\n      const url = '/api/recipes/' + this.recipe.id + '/image';\n\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(err).to.not.exist();\n          expect(res.body.name).to.equal('Photo 1');\n\n          done();\n        });\n    });\n\n    it.skip('checks if a referenced model exists - ok', function(done) {\n      let url = '/api/recipes/' + this.recipe.id + '/ingredients/';\n      url += this.ingredient1;\n\n      this.head(url)\n        .expect(200, function(err, res) {\n          done();\n        });\n    });\n\n    it.skip('checks if an referenced model exists - fail', function(done) {\n      let url = '/api/recipes/' + this.recipe.id + '/ingredients/';\n      url += this.ingredient3;\n\n      this.head(url)\n        .expect(404, function(err, res) {\n          done();\n        });\n    });\n  });\n\n  describe('nested relations', function() {\n    let accessOptions;\n\n    before(function defineModels() {\n      const Book = app.registry.createModel(\n        'Book',\n        {name: 'string'},\n        {plural: 'books'},\n      );\n      app.model(Book, {dataSource: 'db'});\n\n      const Page = app.registry.createModel(\n        'Page',\n        {name: 'string'},\n        {plural: 'pages'},\n      );\n      app.model(Page, {dataSource: 'db'});\n\n      const Image = app.registry.createModel(\n        'Image',\n        {name: 'string'},\n        {plural: 'images'},\n      );\n      app.model(Image, {dataSource: 'db'});\n\n      const Note = app.registry.createModel(\n        'Note',\n        {text: 'string'},\n        {plural: 'notes'},\n      );\n      app.model(Note, {dataSource: 'db'});\n\n      const Chapter = app.registry.createModel(\n        'Chapter',\n        {name: 'string'},\n        {plural: 'chapters'},\n      );\n      app.model(Chapter, {dataSource: 'db'});\n\n      Book.hasMany(Page, {options: {nestRemoting: true}});\n      Book.hasMany(Chapter);\n      Page.hasMany(Note);\n      Page.belongsTo(Book, {options: {nestRemoting: true}});\n      Chapter.hasMany(Note);\n      Image.belongsTo(Book);\n\n      // fake a remote method that match the filter in Model.nestRemoting()\n      Page.prototype['__throw__errors'] = function() {\n        throw new Error('This should not crash the app');\n      };\n\n      Page.remoteMethod('__throw__errors', {isStatic: false, http: {path: '/throws', verb: 'get'},\n        accepts: [{arg: 'options', type: 'object', http: 'optionsFromRequest'}]});\n\n      // Now `pages` has nestRemoting set to true and no need to call nestRemoting()\n      // Book.nestRemoting('pages');\n      Book.nestRemoting('chapters');\n      Image.nestRemoting('book');\n\n      expect(Book.prototype['__findById__pages']).to.be.a('function');\n      expect(Image.prototype['__get__book']).to.be.a('function');\n\n      Page.beforeRemote('prototype.__findById__notes', function(ctx, result, next) {\n        ctx.res.set('x-before', 'before');\n\n        next();\n      });\n\n      Page.afterRemote('prototype.__findById__notes', function(ctx, result, next) {\n        ctx.res.set('x-after', 'after');\n\n        next();\n      });\n\n      Page.observe('access', function(ctx, next) {\n        accessOptions = ctx.options;\n        next();\n      });\n    });\n\n    beforeEach(function resetAccessOptions() {\n      accessOptions = 'access hook not triggered';\n    });\n\n    before(function createBook(done) {\n      const test = this;\n      app.models.Book.create({name: 'Book 1'},\n        function(err, book) {\n          if (err) return done(err);\n\n          test.book = book;\n          book.pages.create({name: 'Page 1'},\n            function(err, page) {\n              if (err) return done(err);\n\n              test.page = page;\n              page.notes.create({text: 'Page Note 1'},\n                function(err, note) {\n                  test.note = note;\n\n                  done();\n                });\n            });\n        });\n    });\n\n    before(function createChapters(done) {\n      const test = this;\n      test.book.chapters.create({name: 'Chapter 1'},\n        function(err, chapter) {\n          if (err) return done(err);\n\n          test.chapter = chapter;\n          chapter.notes.create({text: 'Chapter Note 1'}, function(err, note) {\n            test.cnote = note;\n\n            done();\n          });\n        });\n    });\n\n    before(function createCover(done) {\n      const test = this;\n      app.models.Image.create({name: 'Cover 1', book: test.book},\n        function(err, image) {\n          if (err) return done(err);\n\n          test.image = image;\n\n          done();\n        });\n    });\n\n    it('has regular relationship routes - pages', function(done) {\n      const test = this;\n      this.get('/api/books/' + test.book.id + '/pages')\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.an('array');\n          expect(res.body).to.have.length(1);\n          expect(res.body[0].name).to.equal('Page 1');\n\n          done();\n        });\n    });\n\n    it('has regular relationship routes - notes', function(done) {\n      const test = this;\n      this.get('/api/pages/' + test.page.id + '/notes/' + test.note.id)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.headers['x-before']).to.equal('before');\n          expect(res.headers['x-after']).to.equal('after');\n          expect(res.body).to.be.an('object');\n          expect(res.body.text).to.equal('Page Note 1');\n\n          done();\n        });\n    });\n\n    it('has a basic error handler', function(done) {\n      const test = this;\n      this.get('/api/books/unknown/pages/' + test.page.id + '/notes')\n        .expect(404, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.error).to.be.an('object');\n          const expected = 'could not find a model with id unknown';\n          expect(res.body.error.message).to.equal(expected);\n          expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND');\n\n          done();\n        });\n    });\n\n    it('enables nested relationship routes - belongsTo find', function(done) {\n      const test = this;\n      this.get('/api/images/' + test.image.id + '/book/pages')\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.an('array');\n          expect(res.body).to.have.length(1);\n          expect(res.body[0].name).to.equal('Page 1');\n\n          done();\n        });\n    });\n\n    it('enables nested relationship routes - belongsTo findById', function(done) {\n      const test = this;\n      this.get('/api/images/' + test.image.id + '/book/pages/' + test.page.id)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.an('object');\n          expect(res.body.name).to.equal('Page 1');\n\n          done();\n        });\n    });\n\n    it('enables nested relationship routes - hasMany find', function(done) {\n      const test = this;\n      this.get('/api/books/' + test.book.id + '/pages/' + test.page.id + '/notes')\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.an('array');\n          expect(res.body).to.have.length(1);\n          expect(res.body[0].text).to.equal('Page Note 1');\n\n          done();\n        });\n    });\n\n    it('enables nested relationship routes - hasMany findById', function(done) {\n      const test = this;\n      this.get('/api/books/' + test.book.id + '/pages/' + test.page.id + '/notes/' + test.note.id)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.headers['x-before']).to.equal('before');\n          expect(res.headers['x-after']).to.equal('after');\n          expect(res.body).to.be.an('object');\n          expect(res.body.text).to.equal('Page Note 1');\n\n          done();\n        });\n    });\n\n    it('passes options to nested relationship routes', function() {\n      return this.get(`/api/books/${this.book.id}/pages/${this.page.id}/notes/${this.note.id}`)\n        .expect(200)\n        .then(res => {\n          expect(accessOptions).to.have.property('accessToken');\n        });\n    });\n\n    it('should nest remote hooks of ModelTo - hasMany findById', function(done) {\n      const test = this;\n      this.get('/api/books/' + test.book.id + '/chapters/' + test.chapter.id + '/notes/' + test.cnote.id)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.headers['x-before']).to.be.undefined();\n          expect(res.headers['x-after']).to.be.undefined();\n\n          done();\n        });\n    });\n\n    it('should have proper http.path for remoting', function() {\n      [app.models.Book, app.models.Image].forEach(function(Model) {\n        Model.sharedClass.methods().forEach(function(method) {\n          const http = Array.isArray(method.http) ? method.http : [method.http];\n          http.forEach(function(opt) {\n            // destroyAll has been shared but missing http property\n            if (opt.path === undefined) return;\n\n            expect(opt.path, method.stringName).to.match(/^\\/.*/);\n          });\n        });\n      });\n    });\n\n    it('should catch error if nested function throws', function(done) {\n      const test = this;\n      this.get('/api/books/' + test.book.id + '/pages/' + this.page.id + '/throws')\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.be.an('object');\n          expect(res.body.error).to.be.an('object');\n          expect(res.body.error.name).to.equal('Error');\n          expect(res.body.error.statusCode).to.equal(500);\n          expect(res.body.error.message).to.equal('This should not crash the app');\n\n          done();\n        });\n    });\n  });\n\n  describe('hasOne', function() {\n    let cust;\n\n    before(function createCustomer(done) {\n      const test = this;\n      app.models.customer.create({name: 'John'}, function(err, c) {\n        if (err) return done(err);\n\n        cust = c;\n\n        done();\n      });\n    });\n\n    after(function(done) {\n      const self = this;\n      this.app.models.customer.destroyAll(function(err) {\n        if (err) return done(err);\n\n        self.app.models.profile.destroyAll(done);\n      });\n    });\n\n    it('should create the referenced model', function(done) {\n      const url = '/api/customers/' + cust.id + '/profile';\n\n      this.post(url)\n        .send({points: 10})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.points).to.be.eql(10);\n          expect(res.body.customerId).to.be.eql(cust.id);\n\n          done();\n        });\n    });\n\n    it('should find the referenced model', function(done) {\n      const url = '/api/customers/' + cust.id + '/profile';\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.points).to.be.eql(10);\n          expect(res.body.customerId).to.be.eql(cust.id);\n\n          done();\n        });\n    });\n\n    it('should not create the referenced model twice', function(done) {\n      const url = '/api/customers/' + cust.id + '/profile';\n      this.post(url)\n        .send({points: 20})\n        .expect(500, function(err, res) {\n          done(err);\n        });\n    });\n\n    it('should update the referenced model', function(done) {\n      const url = '/api/customers/' + cust.id + '/profile';\n      this.put(url)\n        .send({points: 100})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.points).to.be.eql(100);\n          expect(res.body.customerId).to.be.eql(cust.id);\n\n          done();\n        });\n    });\n\n    it('should delete the referenced model', function(done) {\n      const url = '/api/customers/' + cust.id + '/profile';\n      this.del(url)\n        .expect(204, function(err, res) {\n          done(err);\n        });\n    });\n\n    it('should not find the referenced model', function(done) {\n      const url = '/api/customers/' + cust.id + '/profile';\n      this.get(url)\n        .expect(404, function(err, res) {\n          const expected = 'No \"profile\" instance(s) found';\n          expect(res.body.error.message).to.be.equal(\n            expected,\n          );\n          expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND');\n          done(err);\n        });\n    });\n  });\n});\n"
  },
  {
    "path": "test/remote-connector.test.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst loopback = require('../');\nconst defineModelTestsWithDataSource = require('./util/model-tests');\n\ndescribe('RemoteConnector', function() {\n  this.timeout(10000);\n\n  let remoteApp, remote;\n\n  defineModelTestsWithDataSource({\n    beforeEach: function(done) {\n      const test = this;\n      remoteApp = loopback();\n      remoteApp.set('remoting', {\n        errorHandler: {debug: true, log: false},\n        types: {warnWhenOverridingType: false},\n      });\n      remoteApp.use(loopback.rest());\n      remoteApp.listen(0, function() {\n        test.dataSource = loopback.createDataSource({\n          host: 'localhost',\n          port: remoteApp.get('port'),\n          connector: loopback.Remote,\n        });\n\n        done();\n      });\n    },\n\n    // We are defining the model attached to the remote connector datasource,\n    // therefore change tracking must be disabled, only the remote API for\n    // replication should be present\n    trackChanges: false,\n    enableRemoteReplication: true,\n\n    onDefine: function(Model) {\n      const ServerModel = Model.extend('Server' + Model.modelName, {}, {\n        plural: Model.pluralModelName,\n        // This is the model running on the server & attached to a real\n        // datasource, that's the place where to keep track of changes\n        trackChanges: true,\n      });\n      ServerModel.attachTo(loopback.createDataSource({\n        connector: loopback.Memory,\n      }));\n      remoteApp.model(ServerModel);\n    },\n  });\n\n  beforeEach(function(done) {\n    const test = this;\n    remoteApp = this.remoteApp = loopback();\n    remoteApp.set('remoting', {\n      types: {warnWhenOverridingType: false},\n    });\n    remoteApp.use(loopback.rest());\n    const ServerModel = this.ServerModel = loopback.PersistedModel.extend('TestModel');\n\n    remoteApp.model(ServerModel);\n\n    remoteApp.listen(0, function() {\n      test.remote = loopback.createDataSource({\n        host: 'localhost',\n        port: remoteApp.get('port'),\n        connector: loopback.Remote,\n      });\n\n      done();\n    });\n  });\n\n  it('should support the save method', function(done) {\n    let calledServerCreate = false;\n    const RemoteModel = loopback.PersistedModel.extend('TestModel');\n    RemoteModel.attachTo(this.remote);\n\n    const ServerModel = this.ServerModel;\n\n    ServerModel.create = function(data, options, cb) {\n      calledServerCreate = true;\n      data.id = 1;\n      cb(null, data);\n    };\n\n    ServerModel.setupRemoting();\n\n    const m = new RemoteModel({foo: 'bar'});\n    m.save(function(err, inst) {\n      if (err) return done(err);\n\n      assert(inst instanceof RemoteModel);\n      assert(calledServerCreate);\n\n      done();\n    });\n  });\n});\n"
  },
  {
    "path": "test/remoting-coercion.test.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst loopback = require('../');\nconst request = require('supertest');\n\ndescribe('remoting coercion', function() {\n  it('should coerce arguments based on the type', function(done) {\n    let called = false;\n    const app = loopback();\n    app.use(loopback.rest());\n\n    const TestModel = app.registry.createModel('TestModel',\n      {},\n      {base: 'Model'});\n    app.model(TestModel, {public: true});\n\n    TestModel.test = function(inst, cb) {\n      called = true;\n      assert(inst instanceof TestModel);\n      assert(inst.foo === 'bar');\n      cb();\n    };\n    TestModel.remoteMethod('test', {\n      accepts: {arg: 'inst', type: 'TestModel', http: {source: 'body'}},\n      http: {path: '/test', verb: 'post'},\n    });\n\n    request(app)\n      .post('/TestModels/test')\n      .set('Content-Type', 'application/json')\n      .send({\n        foo: 'bar',\n      })\n      .end(function(err) {\n        if (err) return done(err);\n\n        assert(called);\n\n        done();\n      });\n  });\n});\n"
  },
  {
    "path": "test/remoting.integration.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../');\nconst lt = require('./helpers/loopback-testing-helper');\nconst path = require('path');\nconst SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-integration-app');\nconst app = require(path.join(SIMPLE_APP, 'server/server.js'));\nconst assert = require('assert');\nconst expect = require('./helpers/expect');\n\ndescribe('remoting - integration', function() {\n  lt.beforeEach.withApp(app);\n  lt.beforeEach.givenModel('store');\n\n  afterEach(function(done) {\n    this.app.models.store.destroyAll(done);\n  });\n\n  describe('app.remotes.options', function() {\n    it('should load remoting options', function() {\n      const remotes = app.remotes();\n      assert.deepEqual(remotes.options, {'json': {'limit': '1kb', 'strict': false},\n        'urlencoded': {'limit': '8kb', 'extended': true},\n        'errorHandler': {'debug': true, log: false}});\n    });\n\n    it('rest handler', function() {\n      const handler = app.handler('rest');\n      assert(handler);\n    });\n\n    it('should accept request that has entity below 1kb', function(done) {\n      // Build an object that is smaller than 1kb\n      let name = '';\n      for (let i = 0; i < 256; i++) {\n        name += '11';\n      }\n      this.http = this.post('/api/stores');\n      this.http.send({\n        'name': name,\n      });\n      this.http.end(function(err) {\n        if (err) return done(err);\n        this.req = this.http.req;\n        this.res = this.http.res;\n        assert.equal(this.res.statusCode, 200);\n\n        done();\n      }.bind(this));\n    });\n\n    it('should reject request that has entity beyond 1kb', function(done) {\n      // Build an object that is larger than 1kb\n      let name = '';\n      for (let i = 0; i < 2048; i++) {\n        name += '11111111111';\n      }\n      this.http = this.post('/api/stores');\n      this.http.send({\n        'name': name,\n      });\n      this.http.end(function(err) {\n        if (err) return done(err);\n        this.req = this.http.req;\n        this.res = this.http.res;\n        // Request is rejected with 413\n        assert.equal(this.res.statusCode, 413);\n\n        done();\n      }.bind(this));\n    });\n  });\n\n  describe('Model shared classes', function() {\n    it('has expected remote methods with default model.settings.replaceOnPUT' +\n      'set to true (3.x)',\n    function() {\n      const storeClass = findClass('store');\n      const methods = getFormattedMethodsExcludingRelations(storeClass.methods);\n\n      const expectedMethods = [\n        'create(data:object:store):store POST /stores',\n        'patchOrCreate(data:object:store):store PATCH /stores',\n        'replaceOrCreate(data:object:store):store PUT /stores',\n        'replaceOrCreate(data:object:store):store POST /stores/replaceOrCreate',\n        'exists(id:any):boolean GET /stores/:id/exists',\n        'findById(id:any,filter:object):store GET /stores/:id',\n        'replaceById(id:any,data:object:store):store PUT /stores/:id',\n        'replaceById(id:any,data:object:store):store POST /stores/:id/replace',\n        'find(filter:object):store GET /stores',\n        'findOne(filter:object):store GET /stores/findOne',\n        'updateAll(where:object,data:object:store):object POST /stores/update',\n        'deleteById(id:any):object DELETE /stores/:id',\n        'count(where:object):number GET /stores/count',\n        'prototype.patchAttributes(data:object:store):store PATCH /stores/:id',\n        'createChangeStream(options:object):ReadableStream POST /stores/change-stream',\n      ];\n\n      // The list of methods is from docs:\n      // http://loopback.io/doc/en/lb2/Exposing-models-over-REST.html\n      expect(methods).to.include.members(expectedMethods);\n    });\n\n    it('has expected remote methods for scopes', function() {\n      const storeClass = findClass('store');\n      const methods = getFormattedScopeMethods(storeClass.methods);\n\n      const expectedMethods = [\n        '__get__superStores(filter:object):store GET /stores/superStores',\n        '__create__superStores(data:object:store):store POST /stores/superStores',\n        '__delete__superStores() DELETE /stores/superStores',\n        '__count__superStores(where:object):number GET /stores/superStores/count',\n      ];\n\n      expect(methods).to.include.members(expectedMethods);\n    });\n\n    it('should have correct signatures for belongsTo methods',\n      function() {\n        const widgetClass = findClass('widget');\n        const methods = getFormattedPrototypeMethods(widgetClass.methods);\n\n        const expectedMethods = [\n          'prototype.__get__store(refresh:boolean):store ' +\n            'GET /widgets/:id/store',\n        ];\n        expect(methods).to.include.members(expectedMethods);\n      });\n\n    it('should have correct signatures for hasMany methods',\n      function() {\n        const storeClass = findClass('store');\n        const methods = getFormattedPrototypeMethods(storeClass.methods);\n\n        const expectedMethods = [\n          'prototype.__findById__widgets(fk:any):widget ' +\n              'GET /stores/:id/widgets/:fk',\n          'prototype.__destroyById__widgets(fk:any) ' +\n              'DELETE /stores/:id/widgets/:fk',\n          'prototype.__updateById__widgets(fk:any,data:object:widget):widget ' +\n              'PUT /stores/:id/widgets/:fk',\n          'prototype.__get__widgets(filter:object):widget ' +\n              'GET /stores/:id/widgets',\n          'prototype.__create__widgets(data:object:widget):widget ' +\n              'POST /stores/:id/widgets',\n          'prototype.__delete__widgets() ' +\n              'DELETE /stores/:id/widgets',\n          'prototype.__count__widgets(where:object):number ' +\n              'GET /stores/:id/widgets/count',\n        ];\n        expect(methods).to.include.members(expectedMethods);\n      });\n\n    it('should have correct signatures for hasMany-through methods',\n      function() { // jscs:disable validateIndentation\n        const physicianClass = findClass('physician');\n        const methods = getFormattedPrototypeMethods(physicianClass.methods);\n\n        const expectedMethods = [\n          'prototype.__findById__patients(fk:any):patient ' +\n          'GET /physicians/:id/patients/:fk',\n          'prototype.__destroyById__patients(fk:any) ' +\n          'DELETE /physicians/:id/patients/:fk',\n          'prototype.__updateById__patients(fk:any,data:object:patient):patient ' +\n          'PUT /physicians/:id/patients/:fk',\n          'prototype.__link__patients(fk:any,data:object:appointment):appointment ' +\n          'PUT /physicians/:id/patients/rel/:fk',\n          'prototype.__unlink__patients(fk:any) ' +\n          'DELETE /physicians/:id/patients/rel/:fk',\n          'prototype.__exists__patients(fk:any):boolean ' +\n          'HEAD /physicians/:id/patients/rel/:fk',\n          'prototype.__get__patients(filter:object):patient ' +\n          'GET /physicians/:id/patients',\n          'prototype.__create__patients(data:object:patient):patient ' +\n          'POST /physicians/:id/patients',\n          'prototype.__delete__patients() ' +\n          'DELETE /physicians/:id/patients',\n          'prototype.__count__patients(where:object):number ' +\n          'GET /physicians/:id/patients/count',\n        ];\n        expect(methods).to.include.members(expectedMethods);\n      });\n  });\n\n  it('has upsertWithWhere remote method', function() {\n    const storeClass = findClass('store');\n    const methods = getFormattedMethodsExcludingRelations(storeClass.methods);\n    const expectedMethods = [\n      'upsertWithWhere(where:object,data:object:store):store POST /stores/upsertWithWhere',\n    ];\n    expect(methods).to.include.members(expectedMethods);\n  });\n\n  describe('createOnlyInstance', function() {\n    it('sets createOnlyInstance to true if id is generated and forceId is not set to false',\n      function() {\n        const storeClass = findClass('store');\n        const createMethod = getCreateMethod(storeClass.methods);\n        assert(createMethod.accepts[0].createOnlyInstance === true);\n      });\n\n    it('sets createOnlyInstance to false if forceId is set to false in the model', function() {\n      const customerClass = findClass('customerforceidfalse');\n      const createMethod = getCreateMethod(customerClass.methods);\n      assert(createMethod.accepts[0].createOnlyInstance === false);\n    });\n\n    it('sets createOnlyInstance based on target model for scoped or related methods',\n      function() {\n        const userClass = findClass('user');\n        const createMethod = userClass.methods.find(function(m) {\n          return (m.name === 'prototype.__create__accessTokens');\n        });\n        assert(createMethod.accepts[0].createOnlyInstance === false);\n      });\n  });\n});\n\ndescribe('With model.settings.replaceOnPUT false', function() {\n  lt.beforeEach.withApp(app);\n  lt.beforeEach.givenModel('storeWithReplaceOnPUTfalse');\n  afterEach(function(done) {\n    this.app.models.storeWithReplaceOnPUTfalse.destroyAll(done);\n  });\n\n  it('should have expected remote methods',\n    function() {\n      const storeClass = findClass('storeWithReplaceOnPUTfalse');\n      const methods = getFormattedMethodsExcludingRelations(storeClass.methods);\n\n      const expectedMethods = [\n        'create(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse POST /stores-updating',\n        'patchOrCreate(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse PUT /stores-updating',\n        'patchOrCreate(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse PATCH /stores-updating',\n        'replaceOrCreate(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse POST /stores-updating/replaceOrCreate',\n        'upsertWithWhere(where:object,data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse POST /stores-updating/upsertWithWhere',\n        'exists(id:any):boolean GET /stores-updating/:id/exists',\n        'exists(id:any):boolean HEAD /stores-updating/:id',\n        'findById(id:any,filter:object):storeWithReplaceOnPUTfalse GET /stores-updating/:id',\n        'replaceById(id:any,data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse POST /stores-updating/:id/replace',\n        'find(filter:object):storeWithReplaceOnPUTfalse GET /stores-updating',\n        'findOne(filter:object):storeWithReplaceOnPUTfalse GET /stores-updating/findOne',\n        'updateAll(where:object,data:object:storeWithReplaceOnPUTfalse):object POST /stores-updating/update',\n        'deleteById(id:any):object DELETE /stores-updating/:id',\n        'count(where:object):number GET /stores-updating/count',\n        'prototype.patchAttributes(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse PUT /stores-updating/:id',\n        'prototype.patchAttributes(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse PATCH /stores-updating/:id',\n        'createChangeStream(options:object):ReadableStream POST /stores-updating/change-stream',\n        'createChangeStream(options:object):ReadableStream GET /stores-updating/change-stream',\n      ];\n\n      expect(methods).to.eql(expectedMethods);\n    });\n});\n\ndescribe('With model.settings.replaceOnPUT true', function() {\n  lt.beforeEach.withApp(app);\n  lt.beforeEach.givenModel('storeWithReplaceOnPUTtrue');\n  afterEach(function(done) {\n    this.app.models.storeWithReplaceOnPUTtrue.destroyAll(done);\n  });\n\n  it('should have expected remote methods',\n    function() {\n      const storeClass = findClass('storeWithReplaceOnPUTtrue');\n      const methods = getFormattedMethodsExcludingRelations(storeClass.methods);\n\n      const expectedMethods = [\n        'patchOrCreate(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue PATCH /stores-replacing',\n        'replaceOrCreate(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue POST /stores-replacing/replaceOrCreate',\n        'replaceOrCreate(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue PUT /stores-replacing',\n        'replaceById(id:any,data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue POST /stores-replacing/:id/replace',\n        'replaceById(id:any,data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue PUT /stores-replacing/:id',\n        'prototype.patchAttributes(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue PATCH /stores-replacing/:id',\n      ];\n\n      expect(methods).to.include.members(expectedMethods);\n    });\n});\n\nfunction formatReturns(m) {\n  const returns = m.returns;\n  if (!returns || returns.length === 0) {\n    return '';\n  }\n  let type = returns[0].type;\n\n  // handle anonymous type definitions, e.g\n  // { arg: 'info', type: { count: 'number' } }\n  if (typeof type === 'object' && !Array.isArray(type))\n    type = 'object';\n\n  return type ? ':' + type : '';\n}\n\nfunction formatMethod(m) {\n  const arr = [];\n  const endpoints = m.getEndpoints();\n  for (let i = 0; i < endpoints.length; i++) {\n    arr.push([\n      m.name,\n      '(',\n      m.accepts.filter(function(a) {\n        return !(a.http && typeof a.http === 'function');\n      }).map(function(a) {\n        return a.arg + ':' + a.type + (a.model ? ':' + a.model : '');\n      }).join(','),\n      ')',\n      formatReturns(m),\n      ' ',\n      endpoints[i].verb,\n      ' ',\n      endpoints[i].fullPath,\n    ].join(''));\n  }\n  return arr;\n}\n\nfunction findClass(name) {\n  return app.handler('rest').adapter\n    .getClasses()\n    .filter(function(c) {\n      return c.name === name;\n    })[0];\n}\n\nfunction getFormattedMethodsExcludingRelations(methods) {\n  return methods.filter(function(m) {\n    return m.name.indexOf('__') === -1;\n  })\n    .map(function(m) {\n      return formatMethod(m);\n    })\n    .reduce(function(p, c) {\n      return p.concat(c);\n    });\n}\n\nfunction getCreateMethod(methods) {\n  return methods.find(function(m) {\n    return (m.name === 'create');\n  });\n}\n\nfunction getFormattedScopeMethods(methods) {\n  return methods.filter(function(m) {\n    return m.name.indexOf('__') === 0;\n  })\n    .map(function(m) {\n      return formatMethod(m);\n    })\n    .reduce(function(p, c) {\n      return p.concat(c);\n    });\n}\n\nfunction getFormattedPrototypeMethods(methods) {\n  return methods.filter(function(m) {\n    return m.name.indexOf('prototype.__') === 0;\n  })\n    .map(function(m) {\n      return formatMethod(m);\n    })\n    .reduce(function(p, c) {\n      return p.concat(c);\n    });\n}\n"
  },
  {
    "path": "test/replication.rest.test.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst async = require('async');\nconst debug = require('debug')('test');\nconst extend = require('util')._extend;\nconst loopback = require('../');\nconst expect = require('./helpers/expect');\nconst supertest = require('supertest');\n\ndescribe('Replication over REST', function() {\n  this.timeout(10000);\n\n  const ALICE = {id: 'a', username: 'alice', email: 'a@t.io', password: 'p'};\n  const PETER = {id: 'p', username: 'peter', email: 'p@t.io', password: 'p'};\n  const EMERY = {id: 'e', username: 'emery', email: 'e@t.io', password: 'p'};\n\n  /* eslint-disable one-var */\n  let serverApp, serverUrl, ServerUser, ServerCar, serverCars;\n  let aliceId, peterId, aliceToken, peterToken, emeryToken, request;\n  let clientApp, LocalUser, LocalCar, RemoteUser, RemoteCar, clientCars;\n  let conflictedCarId;\n  /* eslint-enable one-var */\n\n  before(setupServer);\n  before(setupClient);\n  beforeEach(seedServerData);\n  beforeEach(seedClientData);\n\n  describe('the replication scenario scaffolded for the tests', function() {\n    describe('Car model', function() {\n      it('rejects anonymous READ', function(done) {\n        listCars().expect(401, done);\n      });\n\n      it('rejects anonymous WRITE', function(done) {\n        createCar().expect(401, done);\n      });\n\n      it('allows EMERY to READ', function(done) {\n        listCars()\n          .set('Authorization', emeryToken)\n          .expect(200, done);\n      });\n\n      it('denies EMERY to WRITE', function(done) {\n        createCar()\n          .set('Authorization', emeryToken)\n          .expect(401, done);\n      });\n\n      it('allows ALICE to READ', function(done) {\n        listCars()\n          .set('Authorization', aliceToken)\n          .expect(200, done);\n      });\n\n      it('denies ALICE to WRITE', function(done) {\n        createCar()\n          .set('Authorization', aliceToken)\n          .expect(401, done);\n      });\n\n      it('allows PETER to READ', function(done) {\n        listCars()\n          .set('Authorization', peterToken)\n          .expect(200, done);\n      });\n\n      it('allows PETER to WRITE', function(done) {\n        createCar()\n          .set('Authorization', peterToken)\n          .expect(200, done);\n      });\n\n      function listCars() {\n        return request.get('/Cars');\n      }\n\n      function createCar() {\n        return request.post('/Cars').send({model: 'a-model'});\n      }\n    });\n  });\n\n  describe('sync with model-level permissions', function() {\n    describe('as anonymous user', function() {\n      it('rejects pull from server', function(done) {\n        RemoteCar.replicate(LocalCar, expectHttpError(401, done));\n      });\n\n      it('rejects push to the server', function(done) {\n        LocalCar.replicate(RemoteCar, expectHttpError(401, done));\n      });\n    });\n\n    describe('as user with READ-only permissions', function() {\n      beforeEach(function() {\n        setAccessToken(emeryToken);\n      });\n\n      it('rejects pull from server', function(done) {\n        RemoteCar.replicate(LocalCar, expectHttpError(401, done));\n      });\n\n      it('rejects push to the server', function(done) {\n        LocalCar.replicate(RemoteCar, expectHttpError(401, done));\n      });\n    });\n\n    describe('as user with REPLICATE-only permissions', function() {\n      beforeEach(function() {\n        setAccessToken(aliceToken);\n      });\n\n      it('allows pull from server', function(done) {\n        RemoteCar.replicate(LocalCar, function(err, conflicts, cps) {\n          if (err) return done(err);\n\n          if (conflicts.length) return done(conflictError(conflicts));\n\n          LocalCar.find(function(err, list) {\n            if (err) return done(err);\n\n            expect(list.map(carToString)).to.include.members(serverCars);\n\n            done();\n          });\n        });\n      });\n\n      it('rejects push to the server', function(done) {\n        LocalCar.replicate(RemoteCar, expectHttpError(401, done));\n      });\n    });\n\n    describe('as user with READ and WRITE permissions', function() {\n      beforeEach(function() {\n        setAccessToken(peterToken);\n      });\n\n      it('allows pull from server', function(done) {\n        RemoteCar.replicate(LocalCar, function(err, conflicts, cps) {\n          if (err) return done(err);\n\n          if (conflicts.length) return done(conflictError(conflicts));\n\n          LocalCar.find(function(err, list) {\n            if (err) return done(err);\n\n            expect(list.map(carToString)).to.include.members(serverCars);\n\n            done();\n          });\n        });\n      });\n\n      it('allows push to the server', function(done) {\n        LocalCar.replicate(RemoteCar, function(err, conflicts, cps) {\n          if (err) return done(err);\n\n          if (conflicts.length) return done(conflictError(conflicts));\n\n          ServerCar.find(function(err, list) {\n            if (err) return done(err);\n\n            expect(list.map(carToString)).to.include.members(clientCars);\n\n            done();\n          });\n        });\n      });\n    });\n  });\n\n  describe('conflict resolution with model-level permissions', function() {\n    let LocalConflict, RemoteConflict;\n\n    before(function setupConflictModels() {\n      LocalConflict = LocalCar.getChangeModel().Conflict;\n      RemoteConflict = RemoteCar.getChangeModel().Conflict;\n    });\n\n    beforeEach(seedConflict);\n\n    describe('as anonymous user', function() {\n      it('rejects resolve() on the client', function(done) {\n        // simulate replication Client->Server\n        const conflict = new LocalConflict(\n          conflictedCarId,\n          LocalCar,\n          RemoteCar,\n        );\n        conflict.resolveUsingSource(expectHttpError(401, done));\n      });\n\n      it('rejects resolve() on the server', function(done) {\n        // simulate replication Server->Client\n        const conflict = new RemoteConflict(\n          conflictedCarId,\n          RemoteCar,\n          LocalCar,\n        );\n        conflict.resolveUsingSource(expectHttpError(401, done));\n      });\n    });\n\n    describe('as user with READ-only permissions', function() {\n      beforeEach(function() {\n        setAccessToken(emeryToken);\n      });\n\n      it('allows resolve() on the client', function(done) {\n        // simulate replication Client->Server\n        const conflict = new LocalConflict(\n          conflictedCarId,\n          LocalCar,\n          RemoteCar,\n        );\n        conflict.resolveUsingSource(done);\n      });\n\n      it('rejects resolve() on the server', function(done) {\n        // simulate replication Server->Client\n        const conflict = new RemoteConflict(\n          conflictedCarId,\n          RemoteCar,\n          LocalCar,\n        );\n        conflict.resolveUsingSource(expectHttpError(401, done));\n      });\n    });\n\n    describe('as user with REPLICATE-only permissions', function() {\n      beforeEach(function() {\n        setAccessToken(aliceToken);\n      });\n\n      it('allows reverse resolve() on the client', function(done) {\n        RemoteCar.replicate(LocalCar, function(err, conflicts) {\n          if (err) return done(err);\n\n          expect(conflicts, 'conflicts').to.have.length(1);\n\n          // By default, conflicts are always resolved by modifying\n          // the source datasource, so that the next replication run\n          // replicates the resolved data.\n          // However, when replicating changes from a datasource that\n          // users are not authorized to change, the users have to resolve\n          // conflicts at the target, discarding any changes made in\n          // the target datasource only.\n          conflicts[0].swapParties().resolveUsingTarget(function(err) {\n            if (err) return done(err);\n\n            RemoteCar.replicate(LocalCar, function(err, conflicts) {\n              if (err) return done(err);\n\n              if (conflicts.length) return done(conflictError(conflicts));\n\n              done();\n            });\n          });\n        });\n      });\n\n      it('rejects resolve() on the server', function(done) {\n        RemoteCar.replicate(LocalCar, function(err, conflicts) {\n          if (err) return done(err);\n\n          expect(conflicts, 'conflicts').to.have.length(1);\n          conflicts[0].resolveUsingSource(expectHttpError(401, done));\n        });\n      });\n    });\n\n    describe('as user with READ and WRITE permissions', function() {\n      beforeEach(function() {\n        setAccessToken(peterToken);\n      });\n\n      it('allows resolve() on the client', function(done) {\n        LocalCar.replicate(RemoteCar, function(err, conflicts) {\n          if (err) return done(err);\n\n          expect(conflicts).to.have.length(1);\n\n          conflicts[0].resolveUsingSource(function(err) {\n            if (err) return done(err);\n\n            LocalCar.replicate(RemoteCar, function(err, conflicts) {\n              if (err) return done(err);\n\n              if (conflicts.length) return done(conflictError(conflicts));\n\n              done();\n            });\n          });\n        });\n      });\n\n      it('allows resolve() on the server', function(done) {\n        RemoteCar.replicate(LocalCar, function(err, conflicts) {\n          if (err) return done(err);\n\n          expect(conflicts).to.have.length(1);\n\n          conflicts[0].resolveUsingSource(function(err) {\n            if (err) return done(err);\n\n            RemoteCar.replicate(LocalCar, function(err, conflicts) {\n              if (err) return done(err);\n\n              if (conflicts.length) return done(conflictError(conflicts));\n\n              done();\n            });\n          });\n        });\n      });\n    });\n  });\n\n  describe.skip('sync with instance-level permissions', function() {\n    it('pulls only authorized records', function(done) {\n      setAccessToken(aliceToken);\n      RemoteUser.replicate(LocalUser, function(err, conflicts, cps) {\n        if (err) return done(err);\n\n        if (conflicts.length) return done(conflictError(conflicts));\n\n        LocalUser.find(function(err, users) {\n          const userNames = users.map(function(u) { return u.username; });\n          expect(userNames).to.eql([ALICE.username]);\n\n          done();\n        });\n      });\n    });\n\n    it('allows push of authorized records', function(done) {\n      async.series([\n        setupModifiedLocalCopyOfAlice,\n\n        function replicateAsCurrentUser(next) {\n          setAccessToken(aliceToken);\n          LocalUser.replicate(RemoteUser, function(err, conflicts) {\n            if (err) return next(err);\n\n            if (conflicts.length) return next(conflictError(conflicts));\n\n            next();\n          });\n        },\n\n        function verify(next) {\n          RemoteUser.findById(aliceId, function(err, found) {\n            if (err) return next(err);\n\n            expect(found.toObject())\n              .to.have.property('fullname', 'Alice Smith');\n\n            next();\n          });\n        },\n      ], done);\n    });\n\n    it('rejects push of unauthorized records', function(done) {\n      async.series([\n        setupModifiedLocalCopyOfAlice,\n\n        function replicateAsDifferentUser(next) {\n          setAccessToken(peterToken);\n          LocalUser.replicate(RemoteUser, function(err, conflicts) {\n            if (!err)\n              return next(new Error('Replicate should have failed.'));\n\n            expect(err).to.have.property('statusCode', 401); // or 403?\n\n            next();\n          });\n        },\n\n        function verify(next) {\n          ServerUser.findById(aliceId, function(err, found) {\n            if (err) return next(err);\n\n            expect(found.toObject())\n              .to.not.have.property('fullname');\n\n            next();\n          });\n        },\n      ], done);\n    });\n\n    // TODO(bajtos) verify conflict resolution\n\n    function setupModifiedLocalCopyOfAlice(done) {\n      // Replicate directly, bypassing REST+AUTH layers\n      replicateServerToLocal(function(err) {\n        if (err) return done(err);\n\n        LocalUser.updateAll(\n          {id: aliceId},\n          {fullname: 'Alice Smith'},\n          done,\n        );\n      });\n    }\n  });\n\n  const USER_PROPS = {\n    id: {type: 'string', id: true},\n  };\n\n  const USER_OPTS = {\n    base: 'User',\n    plural: 'Users', // use the same REST path in all models\n    trackChanges: true,\n    strict: 'throw',\n    persistUndefinedAsNull: true,\n    // Speed up the password hashing algorithm for tests\n    saltWorkFactor: 4,\n  };\n\n  const CAR_PROPS = {\n    id: {type: 'string', id: true, defaultFn: 'guid'},\n    model: {type: 'string', required: true},\n    maker: {type: 'string'},\n  };\n\n  const CAR_OPTS = {\n    base: 'PersistedModel',\n    plural: 'Cars', // use the same REST path in all models\n    trackChanges: true,\n    strict: 'throw',\n    persistUndefinedAsNull: true,\n    acls: [\n      // disable anonymous access\n      {\n        principalType: 'ROLE',\n        principalId: '$everyone',\n        permission: 'DENY',\n      },\n      // allow all authenticated users to read data\n      {\n        principalType: 'ROLE',\n        principalId: '$authenticated',\n        permission: 'ALLOW',\n        accessType: 'READ',\n      },\n      // allow Alice to pull changes\n      {\n        principalType: 'USER',\n        principalId: ALICE.id,\n        permission: 'ALLOW',\n        accessType: 'REPLICATE',\n      },\n      // allow Peter to write data\n      {\n        principalType: 'USER',\n        principalId: PETER.id,\n        permission: 'ALLOW',\n        accessType: 'WRITE',\n      },\n    ],\n  };\n\n  function setupServer(done) {\n    serverApp = loopback({localRegistry: true, loadBuiltinModels: true});\n    serverApp.set('remoting', {errorHandler: {debug: true, log: false}});\n    serverApp.dataSource('db', {connector: 'memory'});\n\n    // Setup a custom access-token model that is not shared\n    // with the client app\n    const ServerToken = serverApp.registry.createModel('ServerToken', {}, {\n      base: 'AccessToken',\n      relations: {\n        user: {\n          type: 'belongsTo',\n          model: 'ServerUser',\n          foreignKey: 'userId',\n        },\n      },\n    });\n    serverApp.model(ServerToken, {dataSource: 'db', public: false});\n\n    ServerUser = serverApp.registry.createModel('ServerUser', USER_PROPS, USER_OPTS);\n    serverApp.model(ServerUser, {\n      dataSource: 'db',\n      public: true,\n      relations: {accessTokens: {model: 'ServerToken'}},\n    });\n\n    serverApp.enableAuth({dataSource: 'db'});\n\n    ServerCar = serverApp.registry.createModel('ServerCar', CAR_PROPS, CAR_OPTS);\n    serverApp.model(ServerCar, {dataSource: 'db', public: true});\n\n    serverApp.use(function(req, res, next) {\n      debug(req.method + ' ' + req.path);\n\n      next();\n    });\n    serverApp.use(loopback.token({model: ServerToken}));\n    serverApp.use(loopback.rest());\n\n    serverApp.set('port', 0);\n    serverApp.set('host', '127.0.0.1');\n    serverApp.listen(function() {\n      serverUrl = serverApp.get('url').replace(/\\/+$/, '');\n      request = supertest(serverUrl);\n\n      done();\n    });\n  }\n\n  function setupClient() {\n    clientApp = loopback({localRegistry: true, loadBuiltinModels: true});\n    clientApp.dataSource('db', {connector: 'memory'});\n    clientApp.dataSource('remote', {\n      connector: 'remote',\n      url: serverUrl,\n    });\n\n    // NOTE(bajtos) At the moment, all models share the same Checkpoint\n    // model. This causes the in-process replication to work differently\n    // than client-server replication.\n    // As a workaround, we manually setup unique Checkpoint for ClientModel.\n    const ClientCheckpoint = clientApp.registry.createModel({\n      name: 'ClientCheckpoint',\n      base: 'Checkpoint',\n    });\n    ClientCheckpoint.attachTo(clientApp.dataSources.db);\n\n    LocalUser = clientApp.registry.createModel('LocalUser', USER_PROPS, USER_OPTS);\n    if (LocalUser.Change) LocalUser.Change.Checkpoint = ClientCheckpoint;\n    clientApp.model(LocalUser, {dataSource: 'db'});\n\n    LocalCar = clientApp.registry.createModel('LocalCar', CAR_PROPS, CAR_OPTS);\n    LocalCar.Change.Checkpoint = ClientCheckpoint;\n    clientApp.model(LocalCar, {dataSource: 'db'});\n\n    let remoteOpts = createRemoteModelOpts(USER_OPTS);\n    RemoteUser = clientApp.registry.createModel('RemoteUser', USER_PROPS, remoteOpts);\n    clientApp.model(RemoteUser, {dataSource: 'remote'});\n\n    remoteOpts = createRemoteModelOpts(CAR_OPTS);\n    RemoteCar = clientApp.registry.createModel('RemoteCar', CAR_PROPS, remoteOpts);\n    clientApp.model(RemoteCar, {dataSource: 'remote'});\n  }\n\n  function createRemoteModelOpts(modelOpts) {\n    return extend(modelOpts, {\n      // Disable change tracking, server will call rectify/rectifyAll\n      // after each change, because it's tracking the changes too.\n      trackChanges: false,\n      // Enable remote replication in order to get remoting API metadata\n      // used by the remoting connector\n      enableRemoteReplication: true,\n    });\n  }\n\n  function seedServerData(done) {\n    async.series([\n      function(next) {\n        serverApp.dataSources.db.automigrate(next);\n      },\n      function(next) {\n        ServerUser.create([ALICE, PETER, EMERY], function(err, created) {\n          if (err) return next(err);\n\n          aliceId = created[0].id;\n          peterId = created[1].id;\n\n          next();\n        });\n      },\n      function(next) {\n        ServerUser.login(ALICE, function(err, token) {\n          if (err) return next(err);\n\n          aliceToken = token.id;\n\n          ServerUser.login(PETER, function(err, token) {\n            if (err) return next(err);\n\n            peterToken = token.id;\n\n            ServerUser.login(EMERY, function(err, token) {\n              emeryToken = token.id;\n\n              next();\n            });\n          });\n        });\n      },\n      function(next) {\n        ServerCar.create(\n          [\n            {id: 'Ford-Mustang', maker: 'Ford', model: 'Mustang'},\n            {id: 'Audi-R8', maker: 'Audi', model: 'R8'},\n          ],\n          function(err, cars) {\n            if (err) return next(err);\n\n            serverCars = cars.map(carToString);\n\n            next();\n          },\n        );\n      },\n    ], done);\n  }\n\n  function seedClientData(done) {\n    async.series([\n      function(next) {\n        clientApp.dataSources.db.automigrate(next);\n      },\n      function(next) {\n        LocalCar.create(\n          [{maker: 'Local', model: 'Custom'}],\n          function(err, cars) {\n            if (err) return next(err);\n\n            clientCars = cars.map(carToString);\n\n            next();\n          },\n        );\n      },\n    ], done);\n  }\n\n  function seedConflict(done) {\n    LocalCar.replicate(ServerCar, function(err, conflicts) {\n      if (err) return done(err);\n\n      if (conflicts.length) return done(conflictError(conflicts));\n\n      ServerCar.replicate(LocalCar, function(err, conflicts) {\n        if (err) return done(err);\n\n        if (conflicts.length) return done(conflictError(conflicts));\n\n        // Hard-coded, see the seed data above\n        conflictedCarId = 'Ford-Mustang';\n\n        new LocalCar({id: conflictedCarId})\n          .updateAttributes({model: 'Client'}, function(err, c) {\n            if (err) return done(err);\n\n            new ServerCar({id: conflictedCarId})\n              .updateAttributes({model: 'Server'}, done);\n          });\n      });\n    });\n  }\n\n  function setAccessToken(token) {\n    clientApp.dataSources.remote.connector.remotes.auth = {\n      bearer: new Buffer(token).toString('base64'),\n      sendImmediately: true,\n    };\n  }\n\n  function expectHttpError(code, done) {\n    return function(err) {\n      if (!err) return done(new Error('The method should have failed.'));\n\n      expect(err).to.have.property('statusCode', code);\n\n      done();\n    };\n  }\n\n  function replicateServerToLocal(next) {\n    ServerUser.replicate(LocalUser, function(err, conflicts) {\n      if (err) return next(err);\n\n      if (conflicts.length) return next(conflictError(conflicts));\n\n      next();\n    });\n  }\n\n  function conflictError(conflicts) {\n    const err = new Error('Unexpected conflicts\\n' +\n      conflicts.map(JSON.stringify).join('\\n'));\n    err.name = 'ConflictError';\n  }\n\n  function carToString(c) {\n    return c.maker ? c.maker + ' ' + c.model : c.model;\n  }\n});\n"
  },
  {
    "path": "test/replication.test.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst async = require('async');\nconst loopback = require('../');\nconst Change = loopback.Change;\nconst defineModelTestsWithDataSource = require('./util/model-tests');\nconst PersistedModel = loopback.PersistedModel;\nconst expect = require('./helpers/expect');\nconst debug = require('debug')('test');\nconst runtime = require('./../lib/runtime');\n\ndescribe('Replication / Change APIs', function() {\n  this.timeout(10000);\n\n  let dataSource, SourceModel, TargetModel, useSinceFilter;\n  let tid = 0; // per-test unique id used e.g. to build unique model names\n\n  beforeEach(function() {\n    tid++;\n    useSinceFilter = false;\n    const test = this;\n    dataSource = this.dataSource = loopback.createDataSource({\n      connector: loopback.Memory,\n    });\n    SourceModel = this.SourceModel = PersistedModel.extend(\n      'SourceModel-' + tid,\n      {id: {id: true, type: String, defaultFn: 'guid'}},\n      {trackChanges: true},\n    );\n\n    SourceModel.attachTo(dataSource);\n\n    TargetModel = this.TargetModel = PersistedModel.extend(\n      'TargetModel-' + tid,\n      {id: {id: true, type: String, defaultFn: 'guid'}},\n      {trackChanges: true},\n    );\n\n    // NOTE(bajtos) At the moment, all models share the same Checkpoint\n    // model. This causes the in-process replication to work differently\n    // than client-server replication.\n    // As a workaround, we manually setup unique Checkpoint for TargetModel.\n    const TargetChange = TargetModel.Change;\n    TargetChange.Checkpoint = loopback.Checkpoint.extend('TargetCheckpoint');\n    TargetChange.Checkpoint.attachTo(dataSource);\n\n    TargetModel.attachTo(dataSource);\n\n    test.startingCheckpoint = -1;\n\n    this.createInitalData = function(cb) {\n      SourceModel.create({name: 'foo'}, function(err, inst) {\n        if (err) return cb(err);\n\n        test.model = inst;\n        SourceModel.replicate(TargetModel, cb);\n      });\n    };\n  });\n\n  describe('cleanup check for enableChangeTracking', function() {\n    describe('when no changeCleanupInterval set', function() {\n      it('should call rectifyAllChanges if running on server', function(done) {\n        const calls = mockRectifyAllChanges(SourceModel);\n        SourceModel.enableChangeTracking();\n\n        if (runtime.isServer) {\n          expect(calls).to.eql(['rectifyAllChanges']);\n        } else {\n          expect(calls).to.eql([]);\n        }\n\n        done();\n      });\n    });\n\n    describe('when changeCleanupInterval set to -1', function() {\n      let Model;\n      beforeEach(function() {\n        Model = this.Model = PersistedModel.extend(\n          'Model-' + tid,\n          {id: {id: true, type: String, defaultFn: 'guid'}},\n          {trackChanges: true, changeCleanupInterval: -1},\n        );\n\n        Model.attachTo(dataSource);\n      });\n\n      it('should not call rectifyAllChanges', function(done) {\n        const calls = mockRectifyAllChanges(Model);\n        Model.enableChangeTracking();\n        expect(calls).to.eql([]);\n        done();\n      });\n    });\n\n    describe('when changeCleanupInterval set to 10000', function() {\n      let Model;\n      beforeEach(function() {\n        Model = this.Model = PersistedModel.extend(\n          'Model-' + tid,\n          {id: {id: true, type: String, defaultFn: 'guid'}},\n          {trackChanges: true, changeCleanupInterval: 10000},\n        );\n\n        Model.attachTo(dataSource);\n      });\n\n      it('should call rectifyAllChanges if running on server', function(done) {\n        const calls = mockRectifyAllChanges(Model);\n        Model.enableChangeTracking();\n        if (runtime.isServer) {\n          expect(calls).to.eql(['rectifyAllChanges']);\n        } else {\n          expect(calls).to.eql([]);\n        }\n\n        done();\n      });\n    });\n\n    function mockRectifyAllChanges(Model) {\n      const calls = [];\n\n      Model.rectifyAllChanges = function(cb) {\n        calls.push('rectifyAllChanges');\n        process.nextTick(cb);\n      };\n\n      return calls;\n    }\n  });\n\n  describe('optimization check rectifyChange Vs rectifyAllChanges', function() {\n    beforeEach(function initialData(done) {\n      const data = [{name: 'John', surname: 'Doe'}, {name: 'Jane', surname: 'Roe'}];\n      async.waterfall([\n        function(callback) {\n          SourceModel.create(data, callback);\n        },\n        function(data, callback) {\n          SourceModel.replicate(TargetModel, callback);\n        }], function(err, result) {\n        done(err);\n      });\n    });\n\n    it('should call rectifyAllChanges if no id is passed for rectifyOnDelete', function(done) {\n      const calls = mockSourceModelRectify();\n      SourceModel.destroyAll({name: 'John'}, function(err, data) {\n        if (err) return done(err);\n\n        expect(calls).to.eql(['rectifyAllChanges']);\n\n        done();\n      });\n    });\n\n    it('should call rectifyAllChanges if no id is passed for rectifyOnSave', function(done) {\n      const calls = mockSourceModelRectify();\n      const newData = {'name': 'Janie'};\n      SourceModel.update({name: 'Jane'}, newData, function(err, data) {\n        if (err) return done(err);\n\n        expect(calls).to.eql(['rectifyAllChanges']);\n\n        done();\n      });\n    });\n\n    it('rectifyOnDelete for Delete should call rectifyChange instead of rectifyAllChanges',\n      function(done) {\n        const calls = mockTargetModelRectify();\n        async.waterfall([\n          function(callback) {\n            SourceModel.destroyAll({name: 'John'}, callback);\n          },\n          function(data, callback) {\n            SourceModel.replicate(TargetModel, callback);\n          // replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation\n          },\n        ], function(err, results) {\n          if (err) return done(err);\n\n          expect(calls).to.eql(['rectifyChange']);\n\n          done();\n        });\n      });\n\n    it('rectifyOnSave for Update should call rectifyChange instead of rectifyAllChanges',\n      function(done) {\n        const calls = mockTargetModelRectify();\n        const newData = {'name': 'Janie'};\n        async.waterfall([\n          function(callback) {\n            SourceModel.update({name: 'Jane'}, newData, callback);\n          },\n          function(data, callback) {\n            SourceModel.replicate(TargetModel, callback);\n          // replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation\n          },\n        ], function(err, result) {\n          if (err) return done(err);\n\n          expect(calls).to.eql(['rectifyChange']);\n\n          done();\n        });\n      });\n\n    it('rectifyOnSave for Create should call rectifyChange instead of rectifyAllChanges',\n      function(done) {\n        const calls = mockTargetModelRectify();\n        const newData = [{name: 'Janie', surname: 'Doe'}];\n        async.waterfall([\n          function(callback) {\n            SourceModel.create(newData, callback);\n          },\n          function(data, callback) {\n            SourceModel.replicate(TargetModel, callback);\n          // replicate should call `rectifyOnSave` and then `rectifyChange` not `rectifyAllChanges` through `after save` operation\n          },\n        ], function(err, result) {\n          if (err) return done(err);\n\n          expect(calls).to.eql(['rectifyChange']);\n\n          done();\n        });\n      });\n\n    function mockSourceModelRectify() {\n      const calls = [];\n\n      SourceModel.rectifyChange = function(id, cb) {\n        calls.push('rectifyChange');\n        process.nextTick(cb);\n      };\n\n      SourceModel.rectifyAllChanges = function(cb) {\n        calls.push('rectifyAllChanges');\n        process.nextTick(cb);\n      };\n\n      return calls;\n    }\n\n    function mockTargetModelRectify() {\n      const calls = [];\n\n      TargetModel.rectifyChange = function(id, cb) {\n        calls.push('rectifyChange');\n        process.nextTick(cb);\n      };\n\n      TargetModel.rectifyAllChanges = function(cb) {\n        calls.push('rectifyAllChanges');\n        process.nextTick(cb);\n      };\n\n      return calls;\n    }\n  });\n\n  describe('Model.changes(since, filter, callback)', function() {\n    it('Get changes since the given checkpoint', function(done) {\n      const test = this;\n      this.SourceModel.create({name: 'foo'}, function(err) {\n        if (err) return done(err);\n\n        setTimeout(function() {\n          test.SourceModel.changes(test.startingCheckpoint, {}, function(err, changes) {\n            assert.equal(changes.length, 1);\n\n            done();\n          });\n        }, 1);\n      });\n    });\n\n    it('excludes changes from older checkpoints', function(done) {\n      const FUTURE_CHECKPOINT = 999;\n\n      SourceModel.create({name: 'foo'}, function(err) {\n        if (err) return done(err);\n        SourceModel.changes(FUTURE_CHECKPOINT, {}, function(err, changes) {\n          if (err) return done(err);\n\n          expect(changes).to.be.empty();\n\n          done();\n        });\n      });\n    });\n  });\n\n  describe('Model.replicate(since, targetModel, options, callback)', function() {\n    it('Replicate data using the target model', function(done) {\n      const test = this;\n      const options = {};\n\n      this.SourceModel.create({name: 'foo'}, function(err) {\n        if (err) return done(err);\n\n        test.SourceModel.replicate(test.startingCheckpoint, test.TargetModel,\n          options, function(err, conflicts) {\n            if (err) return done(err);\n\n            assertTargetModelEqualsSourceModel(conflicts, test.SourceModel,\n              test.TargetModel, done);\n          });\n      });\n    });\n\n    it('Replicate data using the target model - promise variant', function(done) {\n      const test = this;\n      const options = {};\n\n      this.SourceModel.create({name: 'foo'}, function(err) {\n        if (err) return done(err);\n\n        test.SourceModel.replicate(test.startingCheckpoint, test.TargetModel,\n          options)\n          .then(function(conflicts) {\n            assertTargetModelEqualsSourceModel(conflicts, test.SourceModel,\n              test.TargetModel, done);\n          })\n          .catch(function(err) {\n            done(err);\n          });\n      });\n    });\n\n    it('applies \"since\" filter on source changes', function(done) {\n      async.series([\n        function createModelInSourceCp1(next) {\n          SourceModel.create({id: '1'}, next);\n        },\n        function checkpoint(next) {\n          SourceModel.checkpoint(next);\n        },\n        function createModelInSourceCp2(next) {\n          SourceModel.create({id: '2'}, next);\n        },\n        function replicateLastChangeOnly(next) {\n          SourceModel.currentCheckpoint(function(err, cp) {\n            if (err) return done(err);\n            SourceModel.replicate(cp, TargetModel, next);\n          });\n        },\n        function verify(next) {\n          TargetModel.find(function(err, list) {\n            if (err) return done(err);\n            // '1' should be skipped by replication\n            expect(getIds(list)).to.eql(['2']);\n\n            next();\n          });\n        },\n      ], done);\n    });\n\n    it('applies \"since\" filter on source changes - promise variant', function(done) {\n      async.series([\n        function createModelInSourceCp1(next) {\n          SourceModel.create({id: '1'}, next);\n        },\n        function checkpoint(next) {\n          SourceModel.checkpoint(next);\n        },\n        function createModelInSourceCp2(next) {\n          SourceModel.create({id: '2'}, next);\n        },\n        function replicateLastChangeOnly(next) {\n          SourceModel.currentCheckpoint(function(err, cp) {\n            if (err) return done(err);\n            SourceModel.replicate(cp, TargetModel, {})\n              .then(function(next) {\n                done();\n              })\n              .catch(err);\n          });\n        },\n        function verify(next) {\n          TargetModel.find(function(err, list) {\n            if (err) return done(err);\n            // '1' should be skipped by replication\n            expect(getIds(list)).to.eql(['2']);\n\n            next();\n          });\n        },\n      ], done);\n    });\n\n    it('applies \"since\" filter on target changes', function(done) {\n      // Because the \"since\" filter is just an optimization,\n      // there isn't really any observable behaviour we could\n      // check to assert correct implementation.\n      const diffSince = [];\n      spyAndStoreSinceArg(TargetModel, 'diff', diffSince);\n\n      SourceModel.replicate(10, TargetModel, function(err) {\n        if (err) return done(err);\n\n        expect(diffSince).to.eql([10]);\n\n        done();\n      });\n    });\n\n    it('applies \"since\" filter on target changes - promise variant', function(done) {\n      // Because the \"since\" filter is just an optimization,\n      // there isn't really any observable behaviour we could\n      // check to assert correct implementation.\n      const diffSince = [];\n      spyAndStoreSinceArg(TargetModel, 'diff', diffSince);\n\n      SourceModel.replicate(10, TargetModel, {})\n        .then(function() {\n          expect(diffSince).to.eql([10]);\n\n          done();\n        })\n        .catch(function(err) {\n          done(err);\n        });\n    });\n\n    it('uses different \"since\" value for source and target', function(done) {\n      const sourceSince = [];\n      const targetSince = [];\n\n      spyAndStoreSinceArg(SourceModel, 'changes', sourceSince);\n      spyAndStoreSinceArg(TargetModel, 'diff', targetSince);\n\n      const since = {source: 1, target: 2};\n      SourceModel.replicate(since, TargetModel, function(err) {\n        if (err) return done(err);\n\n        expect(sourceSince).to.eql([1]);\n        expect(targetSince).to.eql([2]);\n\n        done();\n      });\n    });\n\n    it('uses different \"since\" value for source and target - promise variant', function(done) {\n      const sourceSince = [];\n      const targetSince = [];\n\n      spyAndStoreSinceArg(SourceModel, 'changes', sourceSince);\n      spyAndStoreSinceArg(TargetModel, 'diff', targetSince);\n\n      const since = {source: 1, target: 2};\n      SourceModel.replicate(since, TargetModel, {})\n        .then(function() {\n          expect(sourceSince).to.eql([1]);\n          expect(targetSince).to.eql([2]);\n\n          done();\n        })\n        .catch(function(err) {\n          done(err);\n        });\n    });\n\n    it('picks up changes made during replication', function(done) {\n      setupRaceConditionInReplication(function(cb) {\n        // simulate the situation when another model is created\n        // while a replication run is in progress\n        SourceModel.create({id: 'racer'}, cb);\n      });\n\n      let lastCp;\n      async.series([\n        function buildSomeDataToReplicate(next) {\n          SourceModel.create({id: 'init'}, next);\n        },\n        function getLastCp(next) {\n          SourceModel.currentCheckpoint(function(err, cp) {\n            if (err) return done(err);\n\n            lastCp = cp;\n\n            next();\n          });\n        },\n        function replicate(next) {\n          SourceModel.replicate(TargetModel, next);\n        },\n        function verifyAssumptions(next) {\n          SourceModel.find(function(err, list) {\n            expect(getIds(list), 'source ids')\n              .to.eql(['init', 'racer']);\n\n            TargetModel.find(function(err, list) {\n              expect(getIds(list), 'target ids after first sync')\n                .to.include.members(['init']);\n\n              next();\n            });\n          });\n        },\n        function replicateAgain(next) {\n          SourceModel.replicate(lastCp + 1, TargetModel, next);\n        },\n        function verify(next) {\n          TargetModel.find(function(err, list) {\n            expect(getIds(list), 'target ids').to.eql(['init', 'racer']);\n\n            next();\n          });\n        },\n      ], done);\n    });\n\n    it('returns new current checkpoints to callback', function(done) {\n      let sourceCp, targetCp;\n      async.series([\n        bumpSourceCheckpoint,\n        bumpTargetCheckpoint,\n        bumpTargetCheckpoint,\n        function replicate(cb) {\n          expect(sourceCp).to.not.equal(targetCp);\n\n          SourceModel.replicate(\n            TargetModel,\n            function(err, conflicts, newCheckpoints) {\n              if (err) return cb(err);\n\n              expect(conflicts, 'conflicts').to.eql([]);\n              expect(newCheckpoints, 'currentCheckpoints').to.eql({\n                source: sourceCp + 1,\n                target: targetCp + 1,\n              });\n\n              cb();\n            },\n          );\n        },\n      ], done);\n\n      function bumpSourceCheckpoint(cb) {\n        SourceModel.checkpoint(function(err, inst) {\n          if (err) return cb(err);\n\n          sourceCp = inst.seq;\n\n          cb();\n        });\n      }\n\n      function bumpTargetCheckpoint(cb) {\n        TargetModel.checkpoint(function(err, inst) {\n          if (err) return cb(err);\n\n          targetCp = inst.seq;\n\n          cb();\n        });\n      }\n    });\n\n    it('leaves current target checkpoint empty', function(done) {\n      async.series([\n        function createTestData(next) {\n          SourceModel.create({}, next);\n        },\n        replicateExpectingSuccess(),\n        function verify(next) {\n          TargetModel.currentCheckpoint(function(err, cp) {\n            if (err) return next(err);\n\n            TargetModel.getChangeModel().find(\n              {where: {checkpoint: {gte: cp}}},\n              function(err, changes) {\n                if (err) return done(err);\n\n                expect(changes).to.have.length(0);\n\n                done();\n              },\n            );\n          });\n        },\n      ], done);\n    });\n\n    describe('with 3rd-party changes', function() {\n      it('detects UPDATE made during UPDATE', function(done) {\n        async.series([\n          createModel(SourceModel, {id: '1'}),\n          replicateExpectingSuccess(),\n          function updateModel(next) {\n            SourceModel.updateAll({id: '1'}, {name: 'source'}, next);\n          },\n          function replicateWith3rdPartyModifyingData(next) {\n            setupRaceConditionInReplication(function(cb) {\n              const connector = TargetModel.dataSource.connector;\n              if (connector.updateAttributes.length <= 4) {\n                connector.updateAttributes(\n                  TargetModel.modelName,\n                  '1',\n                  {name: '3rd-party'},\n                  cb,\n                );\n              } else {\n                // 2.x connectors require `options`\n                connector.updateAttributes(\n                  TargetModel.modelName,\n                  '1',\n                  {name: '3rd-party'},\n                  {}, // options\n                  cb,\n                );\n              }\n            });\n\n            SourceModel.replicate(\n              TargetModel,\n              function(err, conflicts, cps, updates) {\n                if (err) return next(err);\n\n                const conflictedIds = getPropValue(conflicts || [], 'modelId');\n                expect(conflictedIds).to.eql(['1']);\n\n                // resolve the conflict using ours\n                conflicts[0].resolve(next);\n              },\n            );\n          },\n\n          replicateExpectingSuccess(),\n          verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),\n        ], done);\n      });\n\n      it('detects CREATE made during CREATE', function(done) {\n        async.series([\n          // FIXME(bajtos) Remove the 'name' property once the implementation\n          // of UPDATE is fixed to correctly remove properties\n          createModel(SourceModel, {id: '1', name: 'source'}),\n          function replicateWith3rdPartyModifyingData(next) {\n            const connector = TargetModel.dataSource.connector;\n            setupRaceConditionInReplication(function(cb) {\n              if (connector.create.length <= 3) {\n                connector.create(\n                  TargetModel.modelName,\n                  {id: '1', name: '3rd-party'},\n                  cb,\n                );\n              } else {\n                // 2.x connectors require `options`\n                connector.create(\n                  TargetModel.modelName,\n                  {id: '1', name: '3rd-party'},\n                  {}, // options\n                  cb,\n                );\n              }\n            });\n\n            SourceModel.replicate(\n              TargetModel,\n              function(err, conflicts, cps, updates) {\n                if (err) return next(err);\n\n                const conflictedIds = getPropValue(conflicts || [], 'modelId');\n                expect(conflictedIds).to.eql(['1']);\n\n                // resolve the conflict using ours\n                conflicts[0].resolve(next);\n              },\n            );\n          },\n\n          replicateExpectingSuccess(),\n          verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),\n        ], done);\n      });\n\n      it('detects UPDATE made during DELETE', function(done) {\n        async.series([\n          createModel(SourceModel, {id: '1'}),\n          replicateExpectingSuccess(),\n          function deleteModel(next) {\n            SourceModel.deleteById('1', next);\n          },\n          function replicateWith3rdPartyModifyingData(next) {\n            setupRaceConditionInReplication(function(cb) {\n              const connector = TargetModel.dataSource.connector;\n              if (connector.updateAttributes.length <= 4) {\n                connector.updateAttributes(\n                  TargetModel.modelName,\n                  '1',\n                  {name: '3rd-party'},\n                  cb,\n                );\n              } else {\n                // 2.x connectors require `options`\n                connector.updateAttributes(\n                  TargetModel.modelName,\n                  '1',\n                  {name: '3rd-party'},\n                  {}, // options\n                  cb,\n                );\n              }\n            });\n\n            SourceModel.replicate(\n              TargetModel,\n              function(err, conflicts, cps, updates) {\n                if (err) return next(err);\n\n                const conflictedIds = getPropValue(conflicts || [], 'modelId');\n                expect(conflictedIds).to.eql(['1']);\n\n                // resolve the conflict using ours\n                conflicts[0].resolve(next);\n              },\n            );\n          },\n\n          replicateExpectingSuccess(),\n          verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),\n        ], done);\n      });\n\n      it('handles DELETE made during DELETE', function(done) {\n        async.series([\n          createModel(SourceModel, {id: '1'}),\n          replicateExpectingSuccess(),\n          function deleteModel(next) {\n            SourceModel.deleteById('1', next);\n          },\n          function setup3rdPartyModifyingData(next) {\n            const connector = TargetModel.dataSource.connector;\n            setupRaceConditionInReplication(function(cb) {\n              if (connector.destroy.length <= 3) {\n                connector.destroy(\n                  TargetModel.modelName,\n                  '1',\n                  cb,\n                );\n              } else {\n                // 2.x connectors require `options`\n                connector.destroy(\n                  TargetModel.modelName,\n                  '1',\n                  {}, // options\n                  cb,\n                );\n              }\n            });\n\n            next();\n          },\n          replicateExpectingSuccess(),\n          verifyInstanceWasReplicated(SourceModel, TargetModel, '1'),\n        ], done);\n      });\n    });\n  });\n\n  describe('conflict detection - both updated', function() {\n    beforeEach(function(done) {\n      const SourceModel = this.SourceModel;\n      const TargetModel = this.TargetModel;\n      const test = this;\n\n      test.createInitalData(createConflict);\n\n      function createConflict(err, conflicts) {\n        async.parallel([\n          function(cb) {\n            SourceModel.findOne(function(err, inst) {\n              if (err) return cb(err);\n              inst.name = 'source update';\n              inst.save(cb);\n            });\n          },\n          function(cb) {\n            TargetModel.findOne(function(err, inst) {\n              if (err) return cb(err);\n              inst.name = 'target update';\n              inst.save(cb);\n            });\n          },\n        ], function(err) {\n          if (err) return done(err);\n\n          SourceModel.replicate(TargetModel, function(err, conflicts) {\n            if (err) return done(err);\n\n            test.conflicts = conflicts;\n            test.conflict = conflicts[0];\n\n            done();\n          });\n        });\n      }\n    });\n    it('should detect a single conflict', function() {\n      assert.equal(this.conflicts.length, 1);\n      assert(this.conflict);\n    });\n    it('type should be UPDATE', function(done) {\n      this.conflict.type(function(err, type) {\n        assert.equal(type, Change.UPDATE);\n\n        done();\n      });\n    });\n    it('conflict.changes()', function(done) {\n      const test = this;\n      this.conflict.changes(function(err, sourceChange, targetChange) {\n        assert.equal(typeof sourceChange.id, 'string');\n        assert.equal(typeof targetChange.id, 'string');\n        assert.equal(test.model.getId(), sourceChange.getModelId());\n        assert.equal(sourceChange.type(), Change.UPDATE);\n        assert.equal(targetChange.type(), Change.UPDATE);\n\n        done();\n      });\n    });\n    it('conflict.models()', function(done) {\n      const test = this;\n      this.conflict.models(function(err, source, target) {\n        assert.deepEqual(source.toJSON(), {\n          id: test.model.id,\n          name: 'source update',\n        });\n        assert.deepEqual(target.toJSON(), {\n          id: test.model.id,\n          name: 'target update',\n        });\n\n        done();\n      });\n    });\n  });\n\n  describe('conflict detection - source deleted', function() {\n    beforeEach(function(done) {\n      const SourceModel = this.SourceModel;\n      const TargetModel = this.TargetModel;\n      const test = this;\n\n      test.createInitalData(createConflict);\n\n      function createConflict() {\n        async.parallel([\n          function(cb) {\n            SourceModel.findOne(function(err, inst) {\n              if (err) return cb(err);\n\n              test.model = inst;\n              inst.remove(cb);\n            });\n          },\n          function(cb) {\n            TargetModel.findOne(function(err, inst) {\n              if (err) return cb(err);\n\n              inst.name = 'target update';\n              inst.save(cb);\n            });\n          },\n        ], function(err) {\n          if (err) return done(err);\n\n          SourceModel.replicate(TargetModel, function(err, conflicts) {\n            if (err) return done(err);\n\n            test.conflicts = conflicts;\n            test.conflict = conflicts[0];\n\n            done();\n          });\n        });\n      }\n    });\n    it('should detect a single conflict', function() {\n      assert.equal(this.conflicts.length, 1);\n      assert(this.conflict);\n    });\n    it('type should be DELETE', function(done) {\n      this.conflict.type(function(err, type) {\n        assert.equal(type, Change.DELETE);\n\n        done();\n      });\n    });\n    it('conflict.changes()', function(done) {\n      const test = this;\n      this.conflict.changes(function(err, sourceChange, targetChange) {\n        assert.equal(typeof sourceChange.id, 'string');\n        assert.equal(typeof targetChange.id, 'string');\n        assert.equal(test.model.getId(), sourceChange.getModelId());\n        assert.equal(sourceChange.type(), Change.DELETE);\n        assert.equal(targetChange.type(), Change.UPDATE);\n\n        done();\n      });\n    });\n    it('conflict.models()', function(done) {\n      const test = this;\n      this.conflict.models(function(err, source, target) {\n        assert.equal(source, null);\n        assert.deepEqual(target.toJSON(), {\n          id: test.model.id,\n          name: 'target update',\n        });\n\n        done();\n      });\n    });\n  });\n\n  describe('conflict detection - target deleted', function() {\n    beforeEach(function(done) {\n      const SourceModel = this.SourceModel;\n      const TargetModel = this.TargetModel;\n      const test = this;\n\n      test.createInitalData(createConflict);\n\n      function createConflict() {\n        async.parallel([\n          function(cb) {\n            SourceModel.findOne(function(err, inst) {\n              if (err) return cb(err);\n              test.model = inst;\n              inst.name = 'source update';\n              inst.save(cb);\n            });\n          },\n          function(cb) {\n            TargetModel.findOne(function(err, inst) {\n              if (err) return cb(err);\n\n              inst.remove(cb);\n            });\n          },\n        ], function(err) {\n          if (err) return done(err);\n\n          SourceModel.replicate(TargetModel, function(err, conflicts) {\n            if (err) return done(err);\n\n            test.conflicts = conflicts;\n            test.conflict = conflicts[0];\n\n            done();\n          });\n        });\n      }\n    });\n    it('should detect a single conflict', function() {\n      assert.equal(this.conflicts.length, 1);\n      assert(this.conflict);\n    });\n    it('type should be DELETE', function(done) {\n      this.conflict.type(function(err, type) {\n        assert.equal(type, Change.DELETE);\n\n        done();\n      });\n    });\n    it('conflict.changes()', function(done) {\n      const test = this;\n      this.conflict.changes(function(err, sourceChange, targetChange) {\n        assert.equal(typeof sourceChange.id, 'string');\n        assert.equal(typeof targetChange.id, 'string');\n        assert.equal(test.model.getId(), sourceChange.getModelId());\n        assert.equal(sourceChange.type(), Change.UPDATE);\n        assert.equal(targetChange.type(), Change.DELETE);\n\n        done();\n      });\n    });\n    it('conflict.models()', function(done) {\n      const test = this;\n      this.conflict.models(function(err, source, target) {\n        assert.equal(target, null);\n        assert.deepEqual(source.toJSON(), {\n          id: test.model.id,\n          name: 'source update',\n        });\n\n        done();\n      });\n    });\n  });\n\n  describe('conflict detection - both deleted', function() {\n    beforeEach(function(done) {\n      const SourceModel = this.SourceModel;\n      const TargetModel = this.TargetModel;\n      const test = this;\n\n      test.createInitalData(createConflict);\n\n      function createConflict() {\n        async.parallel([\n          function(cb) {\n            SourceModel.findOne(function(err, inst) {\n              if (err) return cb(err);\n\n              test.model = inst;\n              inst.remove(cb);\n            });\n          },\n          function(cb) {\n            TargetModel.findOne(function(err, inst) {\n              if (err) return cb(err);\n\n              inst.remove(cb);\n            });\n          },\n        ], function(err) {\n          if (err) return done(err);\n\n          SourceModel.replicate(TargetModel, function(err, conflicts) {\n            if (err) return done(err);\n\n            test.conflicts = conflicts;\n            test.conflict = conflicts[0];\n\n            done();\n          });\n        });\n      }\n    });\n    it('should not detect a conflict', function() {\n      assert.equal(this.conflicts.length, 0);\n      assert(!this.conflict);\n    });\n  });\n\n  describe('change detection', function() {\n    it('detects \"create\"', function(done) {\n      SourceModel.create({}, function(err, inst) {\n        if (err) return done(err);\n\n        assertChangeRecordedForId(inst.id, done);\n      });\n    });\n\n    it('detects \"updateOrCreate\"', function(done) {\n      givenReplicatedInstance(function(err, created) {\n        if (err) return done(err);\n\n        const data = created.toObject();\n        created.name = 'updated';\n        SourceModel.updateOrCreate(created, function(err, inst) {\n          if (err) return done(err);\n\n          assertChangeRecordedForId(inst.id, done);\n        });\n      });\n    });\n\n    it('detects \"upsertWithWhere\"', function(done) {\n      givenReplicatedInstance(function(err, inst) {\n        if (err) return done(err);\n        SourceModel.upsertWithWhere(\n          {name: inst.name},\n          {name: 'updated'},\n          function(err) {\n            if (err) return done(err);\n            assertChangeRecordedForId(inst.id, done);\n          },\n        );\n      });\n    });\n\n    it('detects \"findOrCreate\"', function(done) {\n      // make sure we bypass find+create and call the connector directly\n      SourceModel.dataSource.connector.findOrCreate =\n        function(model, query, data, callback) {\n          if (this.all.length <= 3) {\n            this.all(model, query, function(err, list) {\n              if (err || (list && list[0]))\n                return callback(err, list && list[0], false);\n\n              this.create(model, data, function(err) {\n                callback(err, data, true);\n              });\n            }.bind(this));\n          } else {\n            // 2.x connectors requires `options`\n            this.all(model, query, {}, function(err, list) {\n              if (err || (list && list[0]))\n                return callback(err, list && list[0], false);\n\n              this.create(model, data, {}, function(err) {\n                callback(err, data, true);\n              });\n            }.bind(this));\n          }\n        };\n\n      SourceModel.findOrCreate(\n        {where: {name: 'does-not-exist'}},\n        {name: 'created'},\n        function(err, inst) {\n          if (err) return done(err);\n\n          assertChangeRecordedForId(inst.id, done);\n        },\n      );\n    });\n\n    it('detects \"deleteById\"', function(done) {\n      givenReplicatedInstance(function(err, inst) {\n        if (err) return done(err);\n\n        SourceModel.deleteById(inst.id, function(err) {\n          assertChangeRecordedForId(inst.id, done);\n        });\n      });\n    });\n\n    it('detects \"deleteAll\"', function(done) {\n      givenReplicatedInstance(function(err, inst) {\n        if (err) return done(err);\n\n        SourceModel.deleteAll({name: inst.name}, function(err) {\n          if (err) return done(err);\n\n          assertChangeRecordedForId(inst.id, done);\n        });\n      });\n    });\n\n    it('detects \"updateAll\"', function(done) {\n      givenReplicatedInstance(function(err, inst) {\n        if (err) return done(err);\n\n        SourceModel.updateAll(\n          {name: inst.name},\n          {name: 'updated'},\n          function(err) {\n            if (err) return done(err);\n\n            assertChangeRecordedForId(inst.id, done);\n          },\n        );\n      });\n    });\n\n    it('detects \"prototype.save\"', function(done) {\n      givenReplicatedInstance(function(err, inst) {\n        if (err) return done(err);\n\n        inst.name = 'updated';\n        inst.save(function(err) {\n          if (err) return done(err);\n\n          assertChangeRecordedForId(inst.id, done);\n        });\n      });\n    });\n\n    it('detects \"prototype.updateAttributes\"', function(done) {\n      givenReplicatedInstance(function(err, inst) {\n        if (err) return done(err);\n\n        inst.updateAttributes({name: 'updated'}, function(err) {\n          if (err) return done(err);\n\n          assertChangeRecordedForId(inst.id, done);\n        });\n      });\n    });\n\n    it('detects \"prototype.delete\"', function(done) {\n      givenReplicatedInstance(function(err, inst) {\n        if (err) return done(err);\n\n        inst.delete(function(err) {\n          assertChangeRecordedForId(inst.id, done);\n        });\n      });\n    });\n\n    function givenReplicatedInstance(cb) {\n      SourceModel.create({name: 'a-name'}, function(err, inst) {\n        if (err) return cb(err);\n\n        SourceModel.checkpoint(function(err) {\n          if (err) return cb(err);\n\n          cb(null, inst);\n        });\n      });\n    }\n\n    function assertChangeRecordedForId(id, cb) {\n      SourceModel.getChangeModel().getCheckpointModel()\n        .current(function(err, cp) {\n          if (err) return cb(err);\n\n          SourceModel.changes(cp - 1, {}, function(err, pendingChanges) {\n            if (err) return cb(err);\n\n            expect(pendingChanges, 'list of changes').to.have.length(1);\n            const change = pendingChanges[0].toObject();\n            expect(change).to.have.property('checkpoint', cp); // sanity check\n            expect(change).to.have.property('modelName', SourceModel.modelName);\n            // NOTE(bajtos) Change.modelId is always String\n            // regardless of the type of the changed model's id property\n            expect(change).to.have.property('modelId', '' + id);\n\n            cb();\n          });\n        });\n    }\n  });\n\n  describe('complex setup', function() {\n    let sourceInstance, sourceInstanceId, AnotherModel;\n\n    beforeEach(function createReplicatedInstance(done) {\n      async.series([\n        function createInstance(next) {\n          SourceModel.create({id: 'test-instance'}, function(err, result) {\n            sourceInstance = result;\n            sourceInstanceId = result.id;\n\n            next(err);\n          });\n        },\n        replicateExpectingSuccess(),\n        verifySourceWasReplicated(),\n      ], done);\n    });\n\n    beforeEach(function setupThirdModel() {\n      AnotherModel = this.AnotherModel = PersistedModel.extend(\n        'AnotherModel-' + tid,\n        {id: {id: true, type: String, defaultFn: 'guid'}},\n        {trackChanges: true},\n      );\n\n      // NOTE(bajtos) At the moment, all models share the same Checkpoint\n      // model. This causes the in-process replication to work differently\n      // than client-server replication.\n      // As a workaround, we manually setup unique Checkpoint for AnotherModel.\n      const AnotherChange = AnotherModel.Change;\n      AnotherChange.Checkpoint = loopback.Checkpoint.extend('AnotherCheckpoint');\n      AnotherChange.Checkpoint.attachTo(dataSource);\n\n      AnotherModel.attachTo(dataSource);\n    });\n\n    it('correctly replicates without checkpoint filter', function(done) {\n      async.series([\n        updateSourceInstanceNameTo('updated'),\n        replicateExpectingSuccess(),\n        verifySourceWasReplicated(),\n\n        function deleteInstance(next) {\n          sourceInstance.remove(next);\n        },\n        replicateExpectingSuccess(),\n        function verifyTargetModelWasDeleted(next) {\n          TargetModel.find(function(err, list) {\n            if (err) return next(err);\n\n            expect(getIds(list)).to.not.contain(sourceInstance.id);\n\n            next();\n          });\n        },\n      ], done);\n    });\n\n    it('replicates multiple updates within the same CP', function(done) {\n      async.series([\n        replicateExpectingSuccess(),\n        verifySourceWasReplicated(),\n\n        updateSourceInstanceNameTo('updated'),\n        updateSourceInstanceNameTo('again'),\n        replicateExpectingSuccess(),\n        verifySourceWasReplicated(),\n      ], done);\n    });\n\n    describe('clientA-server-clientB', function() {\n      let ClientA, Server, ClientB;\n\n      beforeEach(function() {\n        ClientA = SourceModel;\n        Server = TargetModel;\n        ClientB = AnotherModel;\n\n        // NOTE(bajtos) The tests should ideally pass without the since\n        // filter too. Unfortunately that's not possible with the current\n        // implementation that remembers only the last two changes made.\n        useSinceFilter = true;\n      });\n\n      it('replicates new models', function(done) {\n        async.series([\n          // Note that ClientA->Server was already replicated during setup\n          replicateExpectingSuccess(Server, ClientB),\n          verifySourceWasReplicated(ClientB),\n        ], done);\n      });\n\n      it('propagates updates with no false conflicts', function(done) {\n        async.series([\n          updateSourceInstanceNameTo('v2'),\n          replicateExpectingSuccess(ClientA, Server),\n\n          replicateExpectingSuccess(Server, ClientB),\n\n          updateSourceInstanceNameTo('v3'),\n          replicateExpectingSuccess(ClientA, Server),\n          updateSourceInstanceNameTo('v4'),\n          replicateExpectingSuccess(ClientA, Server),\n\n          replicateExpectingSuccess(Server, ClientB),\n          verifySourceWasReplicated(ClientB),\n        ], done);\n      });\n\n      it('propagates deletes with no false conflicts', function(done) {\n        async.series([\n          deleteSourceInstance(),\n          replicateExpectingSuccess(ClientA, Server),\n          replicateExpectingSuccess(Server, ClientB),\n          verifySourceWasReplicated(ClientB),\n        ], done);\n      });\n\n      describe('bidirectional sync', function() {\n        beforeEach(function finishInitialSync(next) {\n          // The fixture setup creates a new model instance and replicates\n          // it from ClientA to Server. Since we are performing bidirectional\n          // synchronization in this suite, we must complete the first sync,\n          // otherwise some of the tests may fail.\n          replicateExpectingSuccess(Server, ClientA)(next);\n        });\n\n        it('propagates CREATE', function(done) {\n          async.series([\n            sync(ClientA, Server),\n            sync(ClientB, Server),\n          ], done);\n        });\n\n        it('propagates CREATE+UPDATE', function(done) {\n          async.series([\n            // NOTE: ClientB has not fetched the new model instance yet\n            updateSourceInstanceNameTo('v2'),\n            sync(ClientA, Server),\n\n            // ClientB fetches the created & updated instance from the server\n            sync(ClientB, Server),\n          ], done);\n        });\n\n        it('propagates DELETE', function(done) {\n          async.series([\n            // NOTE: ClientB has not fetched the new model instance yet\n            updateSourceInstanceNameTo('v2'),\n            sync(ClientA, Server),\n\n            // ClientB fetches the created & updated instance from the server\n            sync(ClientB, Server),\n          ], done);\n        });\n\n        it('does not report false conflicts', function(done) {\n          async.series([\n            // client A makes some work\n            updateSourceInstanceNameTo('v2'),\n            sync(ClientA, Server),\n\n            // ClientB fetches the change from the server\n            sync(ClientB, Server),\n            verifySourceWasReplicated(ClientB),\n\n            // client B makes some work\n            updateClientB('v5'),\n            sync(Server, ClientB),\n            updateClientB('v6'),\n            sync(ClientB, Server),\n\n            // client A fetches the changes\n            sync(ClientA, Server),\n          ], done);\n        });\n\n        it('handles UPDATE conflict resolved using \"ours\"', function(done) {\n          testUpdateConflictIsResolved(\n            function resolveUsingOurs(conflict, cb) {\n              conflict.resolveUsingSource(cb);\n            },\n            done,\n          );\n        });\n\n        it('handles UPDATE conflict resolved using \"theirs\"', function(done) {\n          testUpdateConflictIsResolved(\n            function resolveUsingTheirs(conflict, cb) {\n              // We sync ClientA->Server first\n              expect(conflict.SourceModel.modelName)\n                .to.equal(ClientB.modelName);\n              conflict.resolveUsingTarget(cb);\n            },\n            done,\n          );\n        });\n\n        it('handles UPDATE conflict resolved manually', function(done) {\n          testUpdateConflictIsResolved(\n            function resolveManually(conflict, cb) {\n              conflict.resolveManually({name: 'manual'}, cb);\n            },\n            done,\n          );\n        });\n\n        it('handles DELETE conflict resolved using \"ours\"', function(done) {\n          testDeleteConflictIsResolved(\n            function resolveUsingOurs(conflict, cb) {\n              conflict.resolveUsingSource(cb);\n            },\n            done,\n          );\n        });\n\n        it('handles DELETE conflict resolved using \"theirs\"', function(done) {\n          testDeleteConflictIsResolved(\n            function resolveUsingTheirs(conflict, cb) {\n              // We sync ClientA->Server first\n              expect(conflict.SourceModel.modelName)\n                .to.equal(ClientB.modelName);\n              conflict.resolveUsingTarget(cb);\n            },\n            done,\n          );\n        });\n\n        it('handles DELETE conflict resolved as manual delete', function(done) {\n          testDeleteConflictIsResolved(\n            function resolveManually(conflict, cb) {\n              conflict.resolveManually(null, cb);\n            },\n            done,\n          );\n        });\n\n        it('handles DELETE conflict resolved manually', function(done) {\n          testDeleteConflictIsResolved(\n            function resolveManually(conflict, cb) {\n              conflict.resolveManually({name: 'manual'}, cb);\n            },\n            done,\n          );\n        });\n      });\n\n      function testUpdateConflictIsResolved(resolver, cb) {\n        async.series([\n          // sync the new model to ClientB\n          sync(ClientB, Server),\n          verifyInstanceWasReplicated(ClientA, ClientB, sourceInstanceId),\n\n          // ClientA makes a change\n          updateSourceInstanceNameTo('a'),\n          sync(ClientA, Server),\n\n          // ClientB changes the same instance\n          updateClientB('b'),\n\n          function syncAndResolveConflict(next) {\n            replicate(ClientB, Server, function(err, conflicts, cps) {\n              if (err) return next(err);\n\n              expect(conflicts).to.have.length(1);\n              expect(conflicts[0].SourceModel.modelName)\n                .to.equal(ClientB.modelName);\n\n              debug('Resolving the conflict %j', conflicts[0]);\n              resolver(conflicts[0], next);\n            });\n          },\n\n          // repeat the last sync, it should pass now\n          sync(ClientB, Server),\n          // and sync back to ClientA too\n          sync(ClientA, Server),\n\n          verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId),\n        ], cb);\n      }\n\n      function testDeleteConflictIsResolved(resolver, cb) {\n        async.series([\n          // sync the new model to ClientB\n          sync(ClientB, Server),\n          verifyInstanceWasReplicated(ClientA, ClientB, sourceInstanceId),\n\n          // ClientA makes a change\n          function deleteInstanceOnClientA(next) {\n            ClientA.deleteById(sourceInstanceId, next);\n          },\n\n          sync(ClientA, Server),\n\n          // ClientB changes the same instance\n          updateClientB('b'),\n\n          function syncAndResolveConflict(next) {\n            replicate(ClientB, Server, function(err, conflicts, cps) {\n              if (err) return next(err);\n\n              expect(conflicts).to.have.length(1);\n              expect(conflicts[0].SourceModel.modelName)\n                .to.equal(ClientB.modelName);\n\n              debug('Resolving the conflict %j', conflicts[0]);\n              resolver(conflicts[0], next);\n            });\n          },\n\n          // repeat the last sync, it should pass now\n          sync(ClientB, Server),\n          // and sync back to ClientA too\n          sync(ClientA, Server),\n\n          verifyInstanceWasReplicated(ClientB, ClientA, sourceInstanceId),\n        ], cb);\n      }\n\n      function updateClientB(name) {\n        return function updateInstanceB(next) {\n          ClientB.findById(sourceInstanceId, function(err, instance) {\n            if (err) return next(err);\n\n            instance.name = name;\n            instance.save(next);\n          });\n        };\n      }\n\n      function sync(client, server) {\n        return function syncBothWays(next) {\n          async.series([\n            // NOTE(bajtos) It's important to replicate from the client to the\n            // server first, so that we can resolve any conflicts at the client\n            replicateExpectingSuccess(client, server),\n            replicateExpectingSuccess(server, client),\n          ], next);\n        };\n      }\n    });\n\n    function updateSourceInstanceNameTo(value) {\n      return function updateInstance(next) {\n        debug('update source instance name to %j', value);\n        sourceInstance.name = value;\n        sourceInstance.save(next);\n      };\n    }\n\n    function deleteSourceInstance(value) {\n      return function deleteInstance(next) {\n        debug('delete source instance', value);\n        sourceInstance.remove(function(err) {\n          sourceInstance = null;\n\n          next(err);\n        });\n      };\n    }\n\n    function verifySourceWasReplicated(target) {\n      if (!target) target = TargetModel;\n\n      return function verify(next) {\n        target.findById(sourceInstanceId, function(err, targetInstance) {\n          if (err) return next(err);\n\n          expect(targetInstance && targetInstance.toObject())\n            .to.eql(sourceInstance && sourceInstance.toObject());\n\n          next();\n        });\n      };\n    }\n  });\n\n  describe('ensure options object is set on context during bulkUpdate', function() {\n    let syncPropertyExists = false;\n    let OptionsSourceModel;\n\n    beforeEach(function() {\n      OptionsSourceModel = PersistedModel.extend(\n        'OptionsSourceModel-' + tid,\n        {id: {id: true, type: String, defaultFn: 'guid'}},\n        {trackChanges: true},\n      );\n\n      OptionsSourceModel.attachTo(dataSource);\n\n      OptionsSourceModel.observe('before save', function updateTimestamp(ctx, next) {\n        if (ctx.options.sync) {\n          syncPropertyExists = true;\n        } else {\n          syncPropertyExists = false;\n        }\n        next();\n      });\n    });\n\n    it('bulkUpdate should call Model updates with the provided options object', function(done) {\n      const testData = {name: 'Janie', surname: 'Doe'};\n      const updates = [\n        {\n          data: null,\n          change: null,\n          type: 'create',\n        },\n      ];\n\n      const options = {\n        sync: true,\n      };\n\n      async.waterfall([\n        function(callback) {\n          TargetModel.create(testData, callback);\n        },\n        function(data, callback) {\n          updates[0].data = data;\n          TargetModel.getChangeModel().find({where: {modelId: data.id}}, callback);\n        },\n        function(data, callback) {\n          updates[0].change = data;\n          OptionsSourceModel.bulkUpdate(updates, options, callback);\n        }],\n      function(err, result) {\n        if (err) return done(err);\n\n        expect(syncPropertyExists).to.eql(true);\n\n        done();\n      });\n    });\n  });\n\n  describe('ensure bulkUpdate works with just 2 args', function() {\n    it('bulkUpdate should successfully finish without options', function(done) {\n      const testData = {name: 'Janie', surname: 'Doe'};\n      const updates = [{\n        data: null,\n        change: null,\n        type: 'create',\n      }];\n\n      async.waterfall([\n        function(callback) {\n          TargetModel.create(testData, callback);\n        },\n        function(data, callback) {\n          updates[0].data = data;\n          TargetModel.getChangeModel().find({where: {modelId: data.id}}, callback);\n        },\n        function(data, callback) {\n          updates[0].change = data;\n          SourceModel.bulkUpdate(updates, callback);\n        },\n      ], function(err, result) {\n        if (err) return done(err);\n        done();\n      });\n    });\n  });\n\n  describe('Replication with chunking', function() {\n    beforeEach(function() {\n      const test = this;\n      SourceModel = this.SourceModel = PersistedModel.extend(\n        'SourceModel-' + tid,\n        {id: {id: true, type: String, defaultFn: 'guid'}},\n        {trackChanges: true, replicationChunkSize: 1},\n      );\n\n      SourceModel.attachTo(dataSource);\n\n      TargetModel = this.TargetModel = PersistedModel.extend(\n        'TargetModel-' + tid,\n        {id: {id: true, type: String, defaultFn: 'guid'}},\n        {trackChanges: true, replicationChunkSize: 1},\n      );\n\n      const TargetChange = TargetModel.Change;\n      TargetChange.Checkpoint = loopback.Checkpoint.extend('TargetCheckpoint');\n      TargetChange.Checkpoint.attachTo(dataSource);\n\n      TargetModel.attachTo(dataSource);\n\n      test.startingCheckpoint = -1;\n    });\n\n    describe('Model.replicate(since, targetModel, options, callback)', function() {\n      it('calls bulkUpdate multiple times', function(done) {\n        const test = this;\n        const options = {};\n        const calls = mockBulkUpdate(TargetModel);\n\n        SourceModel.create([{name: 'foo'}, {name: 'bar'}], function(err) {\n          if (err) return done(err);\n\n          test.SourceModel.replicate(test.startingCheckpoint, test.TargetModel,\n            options, function(err, conflicts) {\n              if (err) return done(err);\n\n              assertTargetModelEqualsSourceModel(conflicts, test.SourceModel,\n                test.TargetModel, done);\n              expect(calls.length).to.eql(2);\n            });\n        });\n      });\n    });\n  });\n\n  describe('Replication without chunking', function() {\n    beforeEach(function() {\n      const test = this;\n      SourceModel = this.SourceModel = PersistedModel.extend(\n        'SourceModel-' + tid,\n        {id: {id: true, type: String, defaultFn: 'guid'}},\n        {trackChanges: true},\n      );\n\n      SourceModel.attachTo(dataSource);\n\n      TargetModel = this.TargetModel = PersistedModel.extend(\n        'TargetModel-' + tid,\n        {id: {id: true, type: String, defaultFn: 'guid'}},\n        {trackChanges: true},\n      );\n\n      const TargetChange = TargetModel.Change;\n      TargetChange.Checkpoint = loopback.Checkpoint.extend('TargetCheckpoint');\n      TargetChange.Checkpoint.attachTo(dataSource);\n\n      TargetModel.attachTo(dataSource);\n\n      test.startingCheckpoint = -1;\n    });\n\n    describe('Model.replicate(since, targetModel, options, callback)', function() {\n      it('calls bulkUpdate only once', function(done) {\n        const test = this;\n        const options = {};\n        const calls = mockBulkUpdate(TargetModel);\n\n        SourceModel.create([{name: 'foo'}, {name: 'bar'}], function(err) {\n          if (err) return done(err);\n\n          test.SourceModel.replicate(test.startingCheckpoint, test.TargetModel,\n            options, function(err, conflicts) {\n              if (err) return done(err);\n\n              assertTargetModelEqualsSourceModel(conflicts, test.SourceModel,\n                test.TargetModel, done);\n              expect(calls.length).to.eql(1);\n            });\n        });\n      });\n    });\n  });\n\n  function mockBulkUpdate(modelToMock) {\n    const calls = [];\n\n    const originalBulkUpdateFunction = modelToMock.bulkUpdate;\n\n    modelToMock.bulkUpdate = function(since, filter, callback) {\n      calls.push('bulkUpdate');\n      originalBulkUpdateFunction.call(this, since, filter, callback);\n    };\n\n    return calls;\n  }\n\n  const _since = {};\n  function replicate(source, target, since, next) {\n    if (typeof since === 'function') {\n      next = since;\n      since = undefined;\n    }\n\n    const sinceIx = source.modelName + ':to:' + target.modelName;\n    if (since === undefined) {\n      since = useSinceFilter ?\n        _since[sinceIx] || -1 :\n        -1;\n    }\n\n    debug('replicate from %s to %s since %j',\n      source.modelName, target.modelName, since);\n\n    source.replicate(since, target, function(err, conflicts, cps) {\n      if (err) return next(err);\n\n      if (conflicts.length === 0) {\n        _since[sinceIx] = cps;\n      }\n\n      next(err, conflicts, cps);\n    });\n  }\n\n  function createModel(Model, data) {\n    return function create(next) {\n      Model.create(data, next);\n    };\n  }\n\n  function replicateExpectingSuccess(source, target, since) {\n    if (!source) source = SourceModel;\n    if (!target) target = TargetModel;\n\n    return function doReplicate(next) {\n      replicate(source, target, since, function(err, conflicts, cps) {\n        if (err) return next(err);\n\n        if (conflicts.length) {\n          return next(new Error('Unexpected conflicts\\n' +\n            conflicts.map(JSON.stringify).join('\\n')));\n        }\n\n        next();\n      });\n    };\n  }\n\n  function setupRaceConditionInReplication(fn) {\n    const bulkUpdate = TargetModel.bulkUpdate;\n    TargetModel.bulkUpdate = function(data, options, cb) {\n      // simulate the situation when a 3rd party modifies the database\n      // while a replication run is in progress\n      const self = this;\n      fn(function(err) {\n        if (err) return cb(err);\n\n        bulkUpdate.call(self, data, options, cb);\n      });\n\n      // apply the 3rd party modification only once\n      TargetModel.bulkUpdate = bulkUpdate;\n    };\n  }\n\n  function verifyInstanceWasReplicated(source, target, id) {\n    return function verify(next) {\n      source.findById(id, function(err, expected) {\n        if (err) return next(err);\n\n        target.findById(id, function(err, actual) {\n          if (err) return next(err);\n\n          expect(actual && actual.toObject())\n            .to.eql(expected && expected.toObject());\n          debug('replicated instance: %j', actual);\n\n          next();\n        });\n      });\n    };\n  }\n\n  function spyAndStoreSinceArg(Model, methodName, store) {\n    const orig = Model[methodName];\n    Model[methodName] = function(since) {\n      store.push(since);\n      orig.apply(this, arguments);\n    };\n  }\n\n  function getPropValue(obj, name) {\n    return Array.isArray(obj) ?\n      obj.map(function(it) { return getPropValue(it, name); }) :\n      obj[name];\n  }\n\n  function getIds(list) {\n    return getPropValue(list, 'id');\n  }\n\n  function assertTargetModelEqualsSourceModel(conflicts, sourceModel,\n    targetModel, done) {\n    let sourceData, targetData;\n\n    assert(conflicts.length === 0);\n    async.parallel([\n      function(cb) {\n        sourceModel.find(function(err, result) {\n          if (err) return cb(err);\n\n          sourceData = result;\n          cb();\n        });\n      },\n      function(cb) {\n        targetModel.find(function(err, result) {\n          if (err) return cb(err);\n\n          targetData = result;\n          cb();\n        });\n      },\n    ], function(err) {\n      if (err) return done(err);\n\n      assert.deepEqual(sourceData, targetData);\n\n      done();\n    });\n  }\n});\n\ndescribe('Replication / Change APIs with custom change properties', function() {\n  this.timeout(10000);\n  let dataSource, useSinceFilter, SourceModel, TargetModel, startingCheckpoint;\n  let tid = 0; // per-test unique id used e.g. to build unique model names\n\n  beforeEach(function() {\n    tid++;\n    useSinceFilter = false;\n    const test = this;\n\n    dataSource = this.dataSource = loopback.createDataSource({\n      connector: loopback.Memory,\n    });\n    SourceModel = this.SourceModel = PersistedModel.extend(\n      'SourceModelWithCustomChangeProperties-' + tid,\n      {\n        id: {id: true, type: String, defaultFn: 'guid'},\n        customProperty: {type: 'string'},\n      },\n      {\n        trackChanges: true,\n        additionalChangeModelProperties: {customProperty: {type: 'string'}},\n      },\n    );\n\n    SourceModel.createChangeFilter = function(since, modelFilter) {\n      const filter = this.base.createChangeFilter.apply(this, arguments);\n      if (modelFilter && modelFilter.where && modelFilter.where.customProperty)\n        filter.where.customProperty = modelFilter.where.customProperty;\n      return filter;\n    };\n\n    SourceModel.prototype.fillCustomChangeProperties = function(change, cb) {\n      const customProperty = this.customProperty;\n      const base = this.constructor.base;\n      base.prototype.fillCustomChangeProperties.call(this, change, err => {\n        if (err) return cb(err);\n        change.customProperty = customProperty;\n        cb();\n      });\n    };\n\n    SourceModel.attachTo(dataSource);\n\n    TargetModel = this.TargetModel = PersistedModel.extend(\n      'TargetModelWithCustomChangeProperties-' + tid,\n      {\n        id: {id: true, type: String, defaultFn: 'guid'},\n        customProperty: {type: 'string'},\n      },\n      {\n        trackChanges: true,\n        additionalChangeModelProperties: {customProperty: {type: 'string'}},\n      },\n    );\n\n    const ChangeModelForTarget = TargetModel.Change;\n    ChangeModelForTarget.Checkpoint = loopback.Checkpoint.extend('TargetCheckpoint');\n    ChangeModelForTarget.Checkpoint.attachTo(dataSource);\n\n    TargetModel.attachTo(dataSource);\n\n    startingCheckpoint = -1;\n  });\n\n  describe('Model._defineChangeModel()', function() {\n    it('defines change model with custom properties', function() {\n      const changeModel = SourceModel.getChangeModel();\n      const changeModelProperties = changeModel.definition.properties;\n\n      expect(changeModelProperties).to.have.property('customProperty');\n    });\n  });\n\n  describe('Model.changes(since, filter, callback)', function() {\n    beforeEach(givenSomeSourceModelInstances);\n\n    it('queries changes using customized filter', function(done) {\n      const filterUsed = mockChangeFind(this.SourceModel);\n\n      SourceModel.changes(\n        startingCheckpoint,\n        {where: {customProperty: '123'}},\n        function(err, changes) {\n          if (err) return done(err);\n          expect(filterUsed[0]).to.eql({\n            where: {\n              checkpoint: {gte: -1},\n              modelName: SourceModel.modelName,\n              customProperty: '123',\n            },\n          });\n          done();\n        },\n      );\n    });\n\n    it('query returns the matching changes', function(done) {\n      SourceModel.changes(\n        startingCheckpoint,\n        {where: {customProperty: '123'}},\n        function(err, changes) {\n          expect(changes).to.have.length(1);\n          expect(changes[0]).to.have.property('customProperty', '123');\n          done();\n        },\n      );\n    });\n\n    function givenSomeSourceModelInstances(done) {\n      const data = [\n        {name: 'foo', customProperty: '123'},\n        {name: 'foo', customPropertyValue: '456'},\n      ];\n      this.SourceModel.create(data, done);\n    }\n  });\n\n  function mockChangeFind(Model) {\n    const filterUsed = [];\n\n    Model.getChangeModel().find = function(filter, cb) {\n      filterUsed.push(filter);\n      if (cb) {\n        process.nextTick(cb);\n      }\n    };\n\n    return filterUsed;\n  }\n});\n"
  },
  {
    "path": "test/rest.middleware.test.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst expect = require('./helpers/expect');\nconst loopback = require('../');\nconst path = require('path');\nconst request = require('supertest');\n\ndescribe('loopback.rest', function() {\n  this.timeout(10000);\n  let app, MyModel;\n\n  beforeEach(function() {\n    // override the global app object provided by test/support.js\n    // and create a local one that does not share state with other tests\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.set('remoting', {errorHandler: {debug: true, log: false}});\n    const db = app.dataSource('db', {connector: 'memory'});\n    MyModel = app.registry.createModel('MyModel');\n    MyModel.attachTo(db);\n  });\n\n  it('works out-of-the-box', function(done) {\n    app.model(MyModel);\n    app.use(loopback.rest());\n    request(app).get('/mymodels')\n      .expect(200)\n      .end(done);\n  });\n\n  it('should report 200 for DELETE /:id found', function(done) {\n    app.model(MyModel);\n    app.use(loopback.rest());\n    MyModel.create({name: 'm1'}, function(err, inst) {\n      request(app)\n        .del('/mymodels/' + inst.id)\n        .expect(200, function(err, res) {\n          expect(res.body.count).to.equal(1);\n\n          done();\n        });\n    });\n  });\n\n  it('should report 404 for GET /:id not found', function(done) {\n    app.model(MyModel);\n    app.use(loopback.rest());\n    request(app).get('/mymodels/1')\n      .expect(404)\n      .end(function(err, res) {\n        if (err) return done(err);\n\n        const errorResponse = res.body.error;\n        assert(errorResponse);\n        assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');\n\n        done();\n      });\n  });\n\n  it('should report 404 for HEAD /:id not found', function(done) {\n    app.model(MyModel);\n    app.use(loopback.rest());\n    request(app).head('/mymodels/1')\n      .expect(404)\n      .end(done);\n  });\n\n  it('should report 200 for GET /:id/exists not found', function(done) {\n    app.model(MyModel);\n    app.use(loopback.rest());\n    request(app).get('/mymodels/1/exists')\n      .expect(200)\n      .end(function(err, res) {\n        if (err) return done(err);\n\n        expect(res.body).to.eql({exists: false});\n\n        done();\n      });\n  });\n\n  it('should report 200 for GET /:id found', function(done) {\n    app.model(MyModel);\n    app.use(loopback.rest());\n    MyModel.create({name: 'm1'}, function(err, inst) {\n      request(app).get('/mymodels/' + inst.id)\n        .expect(200)\n        .end(done);\n    });\n  });\n\n  it('should report 200 for HEAD /:id found', function(done) {\n    app.model(MyModel);\n    app.use(loopback.rest());\n    MyModel.create({name: 'm2'}, function(err, inst) {\n      request(app).head('/mymodels/' + inst.id)\n        .expect(200)\n        .end(done);\n    });\n  });\n\n  it('should report 200 for GET /:id/exists found', function(done) {\n    app.model(MyModel);\n    app.use(loopback.rest());\n    MyModel.create({name: 'm2'}, function(err, inst) {\n      request(app).get('/mymodels/' + inst.id + '/exists')\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.eql({exists: true});\n\n          done();\n        });\n    });\n  });\n\n  it('should honour `remoting.rest.supportedTypes`', function(done) {\n    const app = loopback({localRegistry: true});\n\n    // NOTE it is crucial to set `remoting` before creating any models\n    const supportedTypes = ['json', 'application/javascript', 'text/javascript'];\n    app.set('remoting', {rest: {supportedTypes: supportedTypes}});\n\n    app.model(MyModel);\n    app.use(loopback.rest());\n\n    request(app).get('/mymodels')\n      .set('Accept', 'text/html,application/xml;q= 0.9,*/*;q= 0.8')\n      .expect('Content-Type', 'application/json; charset=utf-8')\n      .expect(200, done);\n  });\n\n  it('allows models to provide a custom HTTP path', function(done) {\n    const CustomModel = app.registry.createModel('CustomModel',\n      {name: String},\n      {http: {'path': 'domain1/CustomModelPath'}});\n\n    app.model(CustomModel, {dataSource: 'db'});\n    app.use(loopback.rest());\n\n    request(app).get('/domain1/CustomModelPath').expect(200).end(done);\n  });\n\n  it('should report 200 for url-encoded HTTP path', function(done) {\n    const CustomModel = app.registry.createModel('CustomModel',\n      {name: String},\n      {http: {path: 'domain%20one/CustomModelPath'}});\n\n    app.model(CustomModel, {dataSource: 'db'});\n    app.use(loopback.rest());\n\n    request(app).get('/domain%20one/CustomModelPath').expect(200).end(done);\n  });\n\n  it('includes loopback.token when necessary', function(done) {\n    givenUserModelWithAuth();\n    app.enableAuth({dataSource: 'db'});\n    app.use(loopback.rest());\n\n    givenLoggedInUser(function(err, token) {\n      if (err) return done(err);\n      expect(token).instanceOf(app.models.AccessToken);\n      request(app).get('/users/' + token.userId)\n        .set('Authorization', token.id)\n        .expect(200)\n        .end(done);\n    }, done);\n  });\n\n  it('does not include loopback.token when auth not enabled', function(done) {\n    const User = givenUserModelWithAuth();\n    User.getToken = function(req, cb) {\n      cb(null, req.accessToken ? req.accessToken.id : null);\n    };\n    loopback.remoteMethod(User.getToken, {\n      accepts: [{type: 'object', http: {source: 'req'}}],\n      returns: [{type: 'object', name: 'id'}],\n    });\n\n    app.use(loopback.rest());\n    givenLoggedInUser(function(err, token) {\n      if (err) return done(err);\n\n      request(app).get('/users/getToken')\n        .set('Authorization', token.id)\n        .expect(200)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.id).to.equal(null);\n\n          done();\n        });\n    }, done);\n  });\n\n  it('rebuilds REST endpoints after a model was added', () => {\n    app.use(loopback.rest());\n\n    return request(app).get('/mymodels').expect(404).then(() => {\n      app.model(MyModel);\n\n      return request(app).get('/mymodels').expect(200);\n    });\n  });\n\n  it('rebuilds REST endpoints after a model was deleted', () => {\n    app.model(MyModel);\n    app.use(loopback.rest());\n\n    return request(app).get('/mymodels').expect(200)\n      .then(() => {\n        app.deleteModelByName('MyModel');\n\n        return request(app).get('/mymodels').expect(404);\n      });\n  });\n\n  it('rebuilds REST endpoints after a remoteMethod was added', () => {\n    app.model(MyModel);\n    app.use(loopback.rest());\n\n    return request(app).get('/mymodels/customMethod').expect(404)\n      .then(() => {\n        MyModel.customMethod = function(req, cb) {\n          cb(null, true);\n        };\n        MyModel.remoteMethod('customMethod', {\n          http: {verb: 'get'},\n          accepts: [{type: 'object', http: {source: 'req'}}],\n          returns: [{type: 'boolean', name: 'success'}],\n        });\n\n        return request(app).get('/mymodels/customMethod').expect(200);\n      });\n  });\n\n  it('rebuilds REST endpoints after a remoteMethod was disabled', () => {\n    app.model(MyModel);\n    app.use(loopback.rest());\n    MyModel.customMethod = function(req, cb) {\n      cb(null, true);\n    };\n    MyModel.remoteMethod('customMethod', {\n      http: {verb: 'get'},\n      accepts: [{type: 'object', http: {source: 'req'}}],\n      returns: [{type: 'boolean', name: 'success'}],\n    });\n    return request(app).get('/mymodels/customMethod').expect(200)\n      .then(() => {\n        MyModel.disableRemoteMethodByName('customMethod');\n\n        return request(app).get('/mymodels/customMethod').expect(404);\n      });\n  });\n\n  function givenUserModelWithAuth() {\n    const AccessToken = app.registry.getModel('AccessToken');\n    app.model(AccessToken, {dataSource: 'db'});\n    const User = app.registry.getModel('User');\n    // Speed up the password hashing algorithm for tests\n    User.settings.saltWorkFactor = 4;\n    app.model(User, {dataSource: 'db'});\n\n    // NOTE(bajtos) This is puzzling to me. The built-in User & AccessToken\n    // models should come with both relations already set up, i.e. the\n    // following two lines should not be neccessary.\n    // And it does behave that way when only tests in this file are run.\n    // However, when I run the full test suite (all files), the relations\n    // get broken.\n    AccessToken.belongsTo(User, {as: 'user', foreignKey: 'userId'});\n    User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});\n\n    return User;\n  }\n\n  function givenLoggedInUser(cb, done) {\n    const credentials = {email: 'user@example.com', password: 'pwd'};\n    const User = app.models.User;\n    User.create(credentials,\n      function(err, user) {\n        if (err) return done(err);\n\n        User.login(credentials, cb);\n      });\n  }\n\n  describe('shared methods', function() {\n    function getFixturePath(dirName) {\n      return path.join(__dirname, 'fixtures/shared-methods/' + dirName +\n          '/server/server.js');\n    }\n\n    describe('with specific definitions in model-config.json', function() {\n      it('should not be exposed when the definition value is false',\n        function(done) {\n          const app = require(getFixturePath('model-config-defined-false'));\n          request(app)\n            .get('/todos')\n            .expect(404, done);\n        });\n\n      it('should be exposed when the definition value is true', function(done) {\n        const app = require(getFixturePath('model-config-defined-true'));\n        request(app)\n          .get('/todos')\n          .expect(200, done);\n      });\n    });\n\n    describe('with default definitions in model-config.json', function() {\n      it('should not be exposed when the definition value is false',\n        function(done) {\n          const app = require(getFixturePath('model-config-default-false'));\n          request(app)\n            .get('/todos')\n            .expect(404, done);\n        });\n\n      it('should be exposed when the definition value is true', function(done) {\n        const app = require(getFixturePath('model-config-default-true'));\n        app.models.Todo.create([\n          {content: 'a'},\n          {content: 'b'},\n          {content: 'c'},\n        ], function() {\n          request(app)\n            .del('/todos')\n            .expect(200)\n            .end(function(err, res) {\n              if (err) return done(err);\n\n              expect(res.body.count).to.equal(3);\n\n              done();\n            });\n        });\n      });\n    });\n\n    describe('with specific definitions in config.json', function() {\n      it('should not be exposed when the definition value is false',\n        function(done) {\n          const app = require(getFixturePath('config-defined-false'));\n          request(app)\n            .get('/todos')\n            .expect(404, done);\n        });\n\n      it('should be exposed when the definition value is true',\n        function(done) {\n          const app = require(getFixturePath('config-defined-true'));\n          request(app)\n            .get('/todos')\n            .expect(200, done);\n        });\n    });\n\n    describe('with default definitions in config.json', function() {\n      it('should not be exposed when the definition value is false',\n        function(done) {\n          const app = require(getFixturePath('config-default-false'));\n          request(app)\n            .get('/todos')\n            .expect(404, done);\n        });\n\n      it('should be exposed when the definition value is true', function(done) {\n        const app = require(getFixturePath('config-default-true'));\n        app.models.Todo.create([\n          {content: 'a'},\n          {content: 'b'},\n          {content: 'c'},\n        ], function() {\n          request(app)\n            .del('/todos')\n            .expect(200)\n            .end(function(err, res) {\n              if (err) return done(err);\n\n              expect(res.body.count).to.equal(3);\n\n              done();\n            });\n        });\n      });\n    });\n\n    // The fixture in `shared-method/both-configs-set/config.json` has `*:false`\n    // set which disables the REST endpoints for built-in models such as User as\n    // a side effect since tests share the same loopback instance. As a\n    // consequence, this causes the tests in user.integration to fail.\n    describe.skip('with definitions in both config.json and model-config.json',\n      function() {\n        it('should prioritize the settings in model-config.json', function(done) {\n          const app = require(getFixturePath('both-configs-set'));\n          request(app)\n            .del('/todos')\n            .expect(404, done);\n        });\n\n        it('should fall back to config.json settings if setting is not found in' +\n          'model-config.json', function(done) {\n          const app = require(getFixturePath('both-configs-set'));\n          request(app)\n            .get('/todos')\n            .expect(404, done);\n        });\n      });\n  });\n});\n"
  },
  {
    "path": "test/role-mapping.test.js",
    "content": "// Copyright IBM Corp. 2017,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst expect = require('./helpers/expect');\nconst loopback = require('../');\nconst Promise = require('bluebird');\n\ndescribe('role-mapping model', function() {\n  this.timeout(10000);\n\n  let app, oneUser, anApp, aRole;\n  const models = {};\n\n  beforeEach(function() {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.dataSource('db', {connector: 'memory'});\n\n    // setup models\n    ['User', 'Role', 'RoleMapping', 'Application'].map(setupModel);\n\n    // create generic instances\n    return Promise.all([\n      models.User.create({\n        username: 'oneUser',\n        email: 'user@email.com',\n        password: 'password',\n      }),\n      models.Application.create({name: 'anApp'}),\n      models.Role.create({name: 'aRole'}),\n    ])\n      .spread(function(u, a, r) {\n        oneUser = u;\n        anApp = a;\n        aRole = r;\n      });\n\n    // helper\n    function setupModel(modelName) {\n      const model = app.registry.getModel(modelName);\n      app.model(model, {dataSource: 'db'});\n      models[modelName] = model;\n    }\n  });\n\n  it('supports .user() with a callback', function(done) {\n    models.RoleMapping.create(\n      {principalType: 'USER', principalId: oneUser.id},\n      function(err, mapping) {\n        if (err) done(err);\n        mapping.user(function(err, user) {\n          if (err) done(err);\n          expect(user.id).to.equal(oneUser.id);\n          done();\n        });\n      },\n    );\n  });\n\n  it('supports .user() returning a promise', function() {\n    return models.RoleMapping.create({principalType: 'USER', principalId: oneUser.id})\n      .then(function(mapping) {\n        return mapping.user();\n      })\n      .then(function(user) {\n        expect(user.id).to.equal(oneUser.id);\n      });\n  });\n\n  it('supports .application() with a callback', function(done) {\n    models.RoleMapping.create(\n      {principalType: 'APP', principalId: anApp.id},\n      function(err, mapping) {\n        if (err) done(err);\n        mapping.application(function(err, app) {\n          if (err) done(err);\n          expect(app.id).to.equal(anApp.id);\n          done();\n        });\n      },\n    );\n  });\n\n  it('supports .application() returning a promise', function() {\n    return models.RoleMapping.create({principalType: 'APP', principalId: anApp.id})\n      .then(function(mapping) {\n        return mapping.application();\n      })\n      .then(function(app) {\n        expect(app.id).to.equal(anApp.id);\n      });\n  });\n\n  it('supports .childRole() with a callback', function(done) {\n    models.RoleMapping.create(\n      {principalType: 'ROLE', principalId: aRole.id},\n      function(err, mapping) {\n        if (err) done(err);\n        mapping.childRole(function(err, role) {\n          if (err) done(err);\n          expect(role.id).to.equal(aRole.id);\n          done();\n        });\n      },\n    );\n  });\n\n  it('supports .childRole() returning a promise', function() {\n    return models.RoleMapping.create({principalType: 'ROLE', principalId: aRole.id})\n      .then(function(mapping) {\n        return mapping.childRole();\n      })\n      .then(function(role) {\n        expect(role.id).to.equal(aRole.id);\n      });\n  });\n});\n"
  },
  {
    "path": "test/role.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst sinon = require('sinon');\nconst loopback = require('../index');\nconst async = require('async');\nconst extend = require('util')._extend;\nconst expect = require('./helpers/expect');\nconst Promise = require('bluebird');\n\nfunction checkResult(err, result) {\n  assert(!err);\n}\n\ndescribe('role model', function() {\n  this.timeout(10000);\n\n  let app, Role, RoleMapping, User, Application, ACL;\n\n  beforeEach(function() {\n    // Use local app registry to ensure models are isolated to avoid\n    // pollutions from other tests\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.dataSource('db', {connector: 'memory'});\n\n    ACL = app.registry.getModel('ACL');\n    app.model(ACL, {dataSource: 'db'});\n\n    User = app.registry.getModel('User');\n    // Speed up the password hashing algorithm for tests\n    User.settings.saltWorkFactor = 4;\n    app.model(User, {dataSource: 'db'});\n\n    Role = app.registry.getModel('Role');\n    app.model(Role, {dataSource: 'db'});\n\n    RoleMapping = app.registry.getModel('RoleMapping');\n    app.model(RoleMapping, {dataSource: 'db'});\n\n    Application = app.registry.getModel('Application');\n    app.model(Application, {dataSource: 'db'});\n\n    ACL.roleModel = Role;\n    ACL.roleMappingModel = RoleMapping;\n    ACL.userModel = User;\n    ACL.applicationModel = Application;\n  });\n\n  it('should define role/role relations', function(done) {\n    Role.create({name: 'user'}, function(err, userRole) {\n      if (err) return done(err);\n      Role.create({name: 'admin'}, function(err, adminRole) {\n        if (err) return done(err);\n        userRole.principals.create(\n          {principalType: RoleMapping.ROLE, principalId: adminRole.id},\n          function(err, mapping) {\n            if (err) return done(err);\n\n            async.parallel([\n              function(next) {\n                Role.find(function(err, roles) {\n                  if (err) return next(err);\n                  assert.equal(roles.length, 2);\n                  next();\n                });\n              },\n              function(next) {\n                RoleMapping.find(function(err, mappings) {\n                  if (err) return next(err);\n                  assert.equal(mappings.length, 1);\n                  assert.equal(mappings[0].principalType, RoleMapping.ROLE);\n                  assert.equal(mappings[0].principalId, adminRole.id);\n                  next();\n                });\n              },\n              function(next) {\n                userRole.principals(function(err, principals) {\n                  if (err) return next(err);\n                  assert.equal(principals.length, 1);\n                  next();\n                });\n              },\n              function(next) {\n                userRole.roles(function(err, roles) {\n                  if (err) return next(err);\n                  assert.equal(roles.length, 1);\n                  next();\n                });\n              },\n            ], done);\n          },\n        );\n      });\n    });\n  });\n\n  it('should generate created/modified properties', () => {\n    return Role.create({name: 'ADMIN'})\n      .then(role => {\n        expect(role.toJSON().created).to.be.instanceOf(Date);\n        expect(role.toJSON().modified).to.be.instanceOf(Date);\n      });\n  });\n\n  it('should define role/user relations', function(done) {\n    User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {\n      if (err) return done(err);\n      Role.create({name: 'userRole'}, function(err, role) {\n        if (err) return done(err);\n        role.principals.create({principalType: RoleMapping.USER, principalId: user.id},\n          function(err, p) {\n            if (err) return done(err);\n            async.parallel([\n              function(next) {\n                Role.find(function(err, roles) {\n                  if (err) return next(err);\n                  assert.equal(roles.length, 1);\n                  assert.equal(roles[0].name, 'userRole');\n                  next();\n                });\n              },\n              function(next) {\n                role.principals(function(err, principals) {\n                  if (err) return next(err);\n                  assert.equal(principals.length, 1);\n                  assert.equal(principals[0].principalType, RoleMapping.USER);\n                  assert.equal(principals[0].principalId, user.id);\n                  next();\n                });\n              },\n              function(next) {\n                role.users(function(err, users) {\n                  if (err) return next(err);\n                  assert.equal(users.length, 1);\n                  assert.equal(users[0].id, user.id);\n                  next();\n                });\n              },\n            ], done);\n          });\n      });\n    });\n  });\n\n  it('should not allow duplicate role name', function(done) {\n    Role.create({name: 'userRole'}, function(err, role) {\n      if (err) return done(err);\n\n      Role.create({name: 'userRole'}, function(err, role) {\n        expect(err).to.exist();\n        expect(err).to.have.property('name', 'ValidationError');\n        expect(err).to.have.nested.property('details.codes.name');\n        expect(err.details.codes.name).to.contain('uniqueness');\n        expect(err).to.have.property('statusCode', 422);\n\n        done();\n      });\n    });\n  });\n\n  it('should automatically generate role id', function(done) {\n    User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {\n      if (err) return done(err);\n      Role.create({name: 'userRole'}, function(err, role) {\n        if (err) return done(err);\n        assert(role.id);\n        role.principals.create({principalType: RoleMapping.USER, principalId: user.id},\n          function(err, p) {\n            if (err) return done(err);\n            assert(p.id);\n            assert.equal(p.roleId, role.id);\n            async.parallel([\n              function(next) {\n                Role.find(function(err, roles) {\n                  if (err) return next(err);\n                  assert.equal(roles.length, 1);\n                  assert.equal(roles[0].name, 'userRole');\n                  next();\n                });\n              },\n              function(next) {\n                role.principals(function(err, principals) {\n                  if (err) return next(err);\n                  assert.equal(principals.length, 1);\n                  assert.equal(principals[0].principalType, RoleMapping.USER);\n                  assert.equal(principals[0].principalId, user.id);\n                  next();\n                });\n              },\n              function(next) {\n                role.users(function(err, users) {\n                  if (err) return next(err);\n                  assert.equal(users.length, 1);\n                  assert.equal(users[0].id, user.id);\n                });\n                next();\n              },\n            ], done);\n          });\n      });\n    });\n  });\n\n  it('should support getRoles() and isInRole()', function(done) {\n    User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {\n      if (err) return done(err);\n      Role.create({name: 'userRole'}, function(err, role) {\n        if (err) return done(err);\n        role.principals.create({principalType: RoleMapping.USER, principalId: user.id},\n          function(err, p) {\n            if (err) return done(err);\n            async.series([\n              function(next) {\n                Role.isInRole(\n                  'userRole',\n                  {principalType: RoleMapping.USER, principalId: user.id},\n                  function(err, inRole) {\n                    if (err) return next(err);\n                    // NOTE(bajtos) Apparently isRole is not a boolean,\n                    // but the matchin role object instead\n                    assert(!!inRole);\n                    next();\n                  },\n                );\n              },\n              function(next) {\n                Role.isInRole(\n                  'userRole',\n                  {principalType: RoleMapping.APP, principalId: user.id},\n                  function(err, inRole) {\n                    if (err) return next(err);\n                    assert(!inRole);\n                    next();\n                  },\n                );\n              },\n              function(next) {\n                Role.isInRole(\n                  'userRole',\n                  {principalType: RoleMapping.USER, principalId: 100},\n                  function(err, inRole) {\n                    if (err) return next(err);\n                    assert(!inRole);\n                    next();\n                  },\n                );\n              },\n              function(next) {\n                Role.getRoles(\n                  {principalType: RoleMapping.USER, principalId: user.id},\n                  function(err, roles) {\n                    if (err) return next(err);\n                    expect(roles).to.eql([\n                      Role.AUTHENTICATED,\n                      Role.EVERYONE,\n                      role.id,\n                    ]);\n                    next();\n                  },\n                );\n              },\n              function(next) {\n                Role.getRoles(\n                  {principalType: RoleMapping.USER, principalId: user.id},\n                  {returnOnlyRoleNames: true},\n                  function(err, roles) {\n                    if (err) return next(err);\n                    expect(roles).to.eql([\n                      Role.AUTHENTICATED,\n                      Role.EVERYONE,\n                      role.name,\n                    ]);\n                    next();\n                  },\n                );\n              },\n              function(next) {\n                Role.getRoles(\n                  {principalType: RoleMapping.APP, principalId: user.id},\n                  function(err, roles) {\n                    if (err) return next(err);\n                    expect(roles).to.eql([\n                      Role.AUTHENTICATED,\n                      Role.EVERYONE,\n                    ]);\n                    next();\n                  },\n                );\n              },\n              function(next) {\n                Role.getRoles(\n                  {principalType: RoleMapping.USER, principalId: 100},\n                  function(err, roles) {\n                    if (err) return next(err);\n                    expect(roles).to.eql([\n                      Role.AUTHENTICATED,\n                      Role.EVERYONE,\n                    ]);\n                    next();\n                  },\n                );\n              },\n              function(next) {\n                Role.getRoles(\n                  {principalType: RoleMapping.USER, principalId: null},\n                  function(err, roles) {\n                    if (err) return next(err);\n                    expect(roles).to.eql([\n                      Role.UNAUTHENTICATED,\n                      Role.EVERYONE,\n                    ]);\n                    next();\n                  },\n                );\n              },\n            ], done);\n          });\n      });\n    });\n  });\n\n  it('supports isInRole() returning a Promise', function(done) {\n    const userData = {name: 'Raymond', email: 'x@y.com', password: 'foobar'};\n    User.create(userData, function(err, user) {\n      if (err) return done(err);\n      Role.create({name: 'userRole'}, function(err, role) {\n        if (err) return done(err);\n        const principalData = {\n          principalType: RoleMapping.USER,\n          principalId: user.id,\n        };\n        role.principals.create(principalData, function(err, p) {\n          if (err) return done(err);\n          Role.isInRole('userRole', principalData)\n            .then(function(inRole) {\n              expect(inRole).to.be.true();\n              done();\n            })\n            .catch(done);\n        });\n      });\n    });\n  });\n\n  it('supports getRole() returning a Promise', function(done) {\n    const userData = {name: 'Raymond', email: 'x@y.com', password: 'foobar'};\n    User.create(userData, function(err, user) {\n      if (err) return done(err);\n      Role.create({name: 'userRole'}, function(err, role) {\n        if (err) return done(err);\n        const principalData = {\n          principalType: RoleMapping.USER,\n          principalId: user.id,\n        };\n        role.principals.create(principalData, function(err, p) {\n          if (err) return done(err);\n          Role.getRoles(principalData)\n            .then(function(roles) {\n              expect(roles).to.eql([\n                Role.AUTHENTICATED,\n                Role.EVERYONE,\n                role.id,\n              ]);\n              done();\n            })\n            .catch(done);\n        });\n      });\n    });\n  });\n\n  it('should be properly authenticated with 0 userId', function(done) {\n    const userData = {name: 'Raymond', email: 'x@y.com', password: 'foobar', id: 0};\n    const TestUser = app.registry.createModel({\n      name: 'TestUser',\n      base: 'User',\n      // forceId is set to false so we can create a user with a known ID,\n      // in this case 0 - which used to fail the falsy checks.\n      forceId: false,\n    });\n    app.model(TestUser, {dataSource: 'db'});\n\n    TestUser.create(userData, function(err, user) {\n      if (err) return done(err);\n      Role.create({name: 'userRole'}, function(err, role) {\n        if (err) return done(err);\n        role.principals.create({principalType: RoleMapping.USER, principalId: user.id},\n          function(err, p) {\n            if (err) return done(err);\n            async.series([\n              function(next) {\n                Role.isInRole(\n                  'userRole',\n                  {principalType: RoleMapping.USER, principalId: user.id},\n                  function(err, inRole) {\n                    if (err) return next(err);\n                    assert(!!inRole);\n                    next();\n                  },\n                );\n              },\n              function(next) {\n                Role.isInRole(\n                  'userRole',\n                  {principalType: RoleMapping.APP, principalId: user.id},\n                  function(err, inRole) {\n                    if (err) return next(err);\n                    assert(!inRole);\n                    next();\n                  },\n                );\n              },\n              function(next) {\n                Role.getRoles(\n                  {principalType: RoleMapping.USER, principalId: user.id},\n                  function(err, roles) {\n                    if (err) return next(err);\n                    expect(roles).to.eql([\n                      Role.AUTHENTICATED,\n                      Role.EVERYONE,\n                      role.id,\n                    ]);\n                    next();\n                  },\n                );\n              },\n            ], done);\n          });\n      });\n    });\n  });\n\n  // this test should be split to address one resolver at a time\n  it('supports built-in role resolvers', function(done) {\n    Role.registerResolver('returnPromise', function(role, context) {\n      return new Promise(function(resolve) {\n        process.nextTick(function() {\n          resolve(true);\n        });\n      });\n    });\n\n    const Album = app.registry.createModel('Album', {\n      name: String,\n      userId: Number,\n    }, {\n      relations: {\n        user: {\n          type: 'belongsTo',\n          model: 'User',\n          foreignKey: 'userId',\n        },\n      },\n    });\n    app.model(Album, {dataSource: 'db'});\n\n    User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {\n      if (err) return done(err);\n      async.parallel([\n        function(next) {\n          Role.isInRole(\n            'returnPromise',\n            {principalType: ACL.USER, principalId: user.id},\n            function(err, yes) {\n              if (err) return next(err);\n              assert(yes);\n              next();\n            },\n          );\n        },\n        function(next) {\n          Role.isInRole(\n            Role.AUTHENTICATED,\n            {principalType: ACL.USER, principalId: user.id},\n            function(err, yes) {\n              if (err) next(err);\n              assert(yes);\n              next();\n            },\n          );\n        },\n        function(next) {\n          Role.isInRole(\n            Role.AUTHENTICATED,\n            {principalType: ACL.USER, principalId: null},\n            function(err, yes) {\n              if (err) next(err);\n              assert(!yes);\n              next();\n            },\n          );\n        },\n        function(next) {\n          Role.isInRole(\n            Role.UNAUTHENTICATED,\n            {principalType: ACL.USER, principalId: user.id},\n            function(err, yes) {\n              if (err) return next(err);\n              assert(!yes);\n              next();\n            },\n          );\n        },\n        function(next) {\n          Role.isInRole(\n            Role.UNAUTHENTICATED,\n            {principalType: ACL.USER, principalId: null},\n            function(err, yes) {\n              if (err) return next(err);\n              assert(yes);\n              next();\n            },\n          );\n        },\n        function(next) {\n          Role.isInRole(\n            Role.EVERYONE,\n            {principalType: ACL.USER, principalId: user.id},\n            function(err, yes) {\n              if (err) return next(err);\n              assert(yes);\n              next();\n            },\n          );\n        },\n        function(next) {\n          Role.isInRole(\n            Role.EVERYONE,\n            {principalType: ACL.USER, principalId: null},\n            function(err, yes) {\n              if (err) return next(err);\n              assert(yes);\n              next();\n            },\n          );\n        },\n        function(next) {\n          Album.create({name: 'Album 1', userId: user.id}, function(err, album1) {\n            if (err) return done(err);\n            let role = {\n              principalType: ACL.USER, principalId: user.id,\n              model: Album, id: album1.id,\n            };\n            Role.isInRole(Role.OWNER, role, function(err, yes) {\n              if (err) return next(err);\n              assert(yes);\n\n              Album.create({name: 'Album 2'}, function(err, album2) {\n                if (err) return next(err);\n                role = {\n                  principalType: ACL.USER, principalId: user.id,\n                  model: Album, id: album2.id,\n                };\n                Role.isInRole(Role.OWNER, role, function(err, yes) {\n                  if (err) return next(err);\n                  assert(!yes);\n                  next();\n                });\n              });\n            });\n          });\n        },\n      ], done);\n    });\n  });\n\n  describe('$owner role resolver', function() {\n    let sender, receiver;\n    const users = [\n      {username: 'sender', email: 'sender@example.com', password: 'pass'},\n      {username: 'receiver', email: 'receiver@example.com', password: 'pass'},\n    ];\n\n    describe('ownerRelations not set (legacy behaviour)', () => {\n      it('resolves the owner via property \"userId\"', function() {\n        let user;\n        const Album = app.registry.createModel('Album', {\n          name: String,\n          userId: Number,\n        });\n        app.model(Album, {dataSource: 'db'});\n\n        return User.create({email: 'test@example.com', password: 'pass'})\n          .then(u => {\n            user = u;\n            return Album.create({name: 'Album 1', userId: user.id});\n          })\n          .then(album => {\n            return Role.isInRole(Role.OWNER, {\n              principalType: ACL.USER,\n              principalId: user.id,\n              model: Album,\n              id: album.id,\n            });\n          })\n          .then(isInRole => expect(isInRole).to.be.true());\n      });\n\n      it('resolves the owner via property \"owner\"', function() {\n        let user;\n        const Album = app.registry.createModel('Album', {\n          name: String,\n          owner: Number,\n        });\n        app.model(Album, {dataSource: 'db'});\n\n        return User.create({email: 'test@example.com', password: 'pass'})\n          .then(u => {\n            user = u;\n            return Album.create({name: 'Album 1', owner: user.id});\n          })\n          .then(album => {\n            return Role.isInRole(Role.OWNER, {\n              principalType: ACL.USER,\n              principalId: user.id,\n              model: Album,\n              id: album.id,\n            });\n          })\n          .then(isInRole => expect(isInRole).to.be.true());\n      });\n\n      it('resolves the owner via a belongsTo relation', function() {\n        // passing no options will result calling\n        // the legacy $owner role resolver behavior\n        const Message = givenModelWithSenderReceiverRelations('ModelWithNoOptions');\n\n        return givenUsers()\n          .then(() => {\n            const messages = [\n              {content: 'firstMessage', senderId: sender.id},\n              {content: 'secondMessage', receiverId: receiver.id},\n              {content: 'thirdMessage'},\n            ];\n            return Promise.map(messages, msg => {\n              return Message.create(msg);\n            });\n          })\n          .then(messages => {\n            return Promise.all([\n              isOwnerForMessage(sender, messages[0]),\n              isOwnerForMessage(receiver, messages[1]),\n              isOwnerForMessage(receiver, messages[2]),\n            ]);\n          })\n          .then(result => {\n            expect(result).to.eql([\n              {user: 'sender', msg: 'firstMessage', isOwner: true},\n              {user: 'receiver', msg: 'secondMessage', isOwner: false},\n              {user: 'receiver', msg: 'thirdMessage', isOwner: false},\n            ]);\n          });\n      });\n    });\n\n    it('resolves as false without belongsTo relation', function() {\n      let user;\n      const Album = app.registry.createModel(\n        'Album',\n        {\n          name: String,\n          userId: Number,\n          owner: Number,\n        },\n        // passing {ownerRelations: true} will enable the new $owner role resolver\n        // and hence resolve false when no belongsTo relation is defined\n        {ownerRelations: true},\n      );\n      app.model(Album, {dataSource: 'db'});\n\n      return User.create({email: 'test@example.com', password: 'pass'})\n        .then(u => {\n          user = u;\n          return Album.create({name: 'Album 1', userId: user.id, owner: user.id});\n        })\n        .then(album => {\n          return Role.isInRole(Role.OWNER, {\n            principalType: ACL.USER,\n            principalId: user.id,\n            model: Album,\n            id: album.id,\n          });\n        })\n        .then(isInRole => expect(isInRole).to.be.false());\n    });\n\n    it('resolves the owner using the corrent belongsTo relation', function() {\n      // passing {ownerRelations: true} will enable the new $owner role resolver\n      // with any belongsTo relation allowing to resolve truthy\n      const Message = givenModelWithSenderReceiverRelations(\n        'ModelWithAllRelations',\n        {ownerRelations: true},\n      );\n\n      return givenUsers()\n        .then(() => {\n          const messages = [\n            {content: 'firstMessage', senderId: sender.id},\n            {content: 'secondMessage', receiverId: receiver.id},\n            {content: 'thirdMessage'},\n          ];\n          return Promise.map(messages, msg => {\n            return Message.create(msg);\n          });\n        })\n        .then(messages => {\n          return Promise.all([\n            isOwnerForMessage(sender, messages[0]),\n            isOwnerForMessage(receiver, messages[1]),\n            isOwnerForMessage(receiver, messages[2]),\n          ]);\n        })\n        .then(result => {\n          expect(result).to.eql([\n            {user: 'sender', msg: 'firstMessage', isOwner: true},\n            {user: 'receiver', msg: 'secondMessage', isOwner: true},\n            {user: 'receiver', msg: 'thirdMessage', isOwner: false},\n          ]);\n        });\n    });\n\n    it('allows fine-grained control of which relations grant ownership',\n      function() {\n      // passing {ownerRelations: true} will enable the new $owner role resolver\n      // with a specified list of belongsTo relations allowing to resolve truthy\n        const Message = givenModelWithSenderReceiverRelations(\n          'ModelWithCoercedRelations',\n          {ownerRelations: ['receiver']},\n        );\n\n        return givenUsers()\n          .then(() => {\n            const messages = [\n              {content: 'firstMessage', senderId: sender.id},\n              {content: 'secondMessage', receiverId: receiver.id},\n              {content: 'thirdMessage'},\n            ];\n            return Promise.map(messages, msg => {\n              return Message.create(msg);\n            });\n          })\n          .then(messages => {\n            return Promise.all([\n              isOwnerForMessage(sender, messages[0]),\n              isOwnerForMessage(receiver, messages[1]),\n              isOwnerForMessage(receiver, messages[2]),\n            ]);\n          })\n          .then(result => {\n            expect(result).to.eql([\n              {user: 'sender', msg: 'firstMessage', isOwner: false},\n              {user: 'receiver', msg: 'secondMessage', isOwner: true},\n              {user: 'receiver', msg: 'thirdMessage', isOwner: false},\n            ]);\n          });\n      });\n\n    // helpers\n    function givenUsers() {\n      return Promise.map(users, user => {\n        return User.create(user);\n      })\n        .then(users => {\n          sender = users[0];\n          receiver = users[1];\n        });\n    }\n\n    function isOwnerForMessage(user, msg) {\n      const accessContext = {\n        principalType: ACL.USER,\n        principalId: user.id,\n        model: msg.constructor,\n        id: msg.id,\n      };\n      return Role.isInRole(Role.OWNER, accessContext)\n        .then(isOwner => {\n          return {\n            user: user.username,\n            msg: msg.content,\n            isOwner,\n          };\n        });\n    }\n\n    function givenModelWithSenderReceiverRelations(name, options) {\n      const baseOptions = {\n        relations: {\n          sender: {\n            type: 'belongsTo',\n            model: 'User',\n            foreignKey: 'senderId',\n          },\n          receiver: {\n            type: 'belongsTo',\n            model: 'User',\n            foreignKey: 'receiverId',\n          },\n        },\n      };\n      options = extend(baseOptions, options);\n      const Model = app.registry.createModel(\n        name,\n        {content: String},\n        options,\n      );\n      app.model(Model, {dataSource: 'db'});\n      return Model;\n    }\n  });\n\n  it('passes accessToken to modelClass.findById when resolving OWNER', () => {\n    const Album = app.registry.createModel('Album', {name: String});\n    app.model(Album, {dataSource: 'db'});\n    Album.belongsTo(User);\n\n    let observedOptions = null;\n    Album.observe('access', ctx => {\n      observedOptions = ctx.options;\n      return Promise.resolve();\n    });\n\n    let user, token;\n    return User.create({email: 'test@example.com', password: 'pass'})\n      .then(u => {\n        user = u;\n        return Album.create({name: 'Album 1', userId: user.id});\n      })\n      .then(album => {\n        return Role.isInRole(Role.OWNER, {\n          principalType: ACL.USER, principalId: user.id,\n          model: Album, id: album.id,\n          accessToken: 'test-token',\n        });\n      })\n      .then(isInRole => {\n        expect(observedOptions).to.eql({accessToken: 'test-token'});\n      });\n  });\n\n  describe('isMappedToRole', function() {\n    let user, app, role;\n\n    beforeEach(function(done) {\n      User.create({\n        username: 'john',\n        email: 'john@gmail.com',\n        password: 'jpass',\n      }, function(err, u) {\n        if (err) return done(err);\n\n        user = u;\n        User.create({\n          username: 'mary',\n          email: 'mary@gmail.com',\n          password: 'mpass',\n        }, function(err, u) {\n          if (err) return done(err);\n\n          Application.create({\n            name: 'demo',\n          }, function(err, a) {\n            if (err) return done(err);\n\n            app = a;\n            Role.create({\n              name: 'admin',\n            }, function(err, r) {\n              if (err) return done(err);\n\n              role = r;\n              const principals = [\n                {\n                  principalType: ACL.USER,\n                  principalId: user.id,\n                },\n                {\n                  principalType: ACL.APP,\n                  principalId: app.id,\n                },\n              ];\n              async.each(principals, function(p, done) {\n                role.principals.create(p, done);\n              }, done);\n            });\n          });\n        });\n      });\n    });\n\n    it('supports ACL.resolvePrincipal() returning a promise', function() {\n      return ACL.resolvePrincipal(ACL.USER, user.id)\n        .then(function(u) {\n          expect(u.id).to.eql(user.id);\n        });\n    });\n\n    it('should resolve user by id', function(done) {\n      ACL.resolvePrincipal(ACL.USER, user.id, function(err, u) {\n        if (err) return done(err);\n\n        expect(u.id).to.eql(user.id);\n\n        done();\n      });\n    });\n\n    it('should resolve user by username', function(done) {\n      ACL.resolvePrincipal(ACL.USER, user.username, function(err, u) {\n        if (err) return done(err);\n\n        expect(u.username).to.eql(user.username);\n\n        done();\n      });\n    });\n\n    it('should resolve user by email', function(done) {\n      ACL.resolvePrincipal(ACL.USER, user.email, function(err, u) {\n        if (err) return done(err);\n\n        expect(u.email).to.eql(user.email);\n\n        done();\n      });\n    });\n\n    it('should resolve app by id', function(done) {\n      ACL.resolvePrincipal(ACL.APP, app.id, function(err, a) {\n        if (err) return done(err);\n\n        expect(a.id).to.eql(app.id);\n\n        done();\n      });\n    });\n\n    it('should resolve app by name', function(done) {\n      ACL.resolvePrincipal(ACL.APP, app.name, function(err, a) {\n        if (err) return done(err);\n\n        expect(a.name).to.eql(app.name);\n\n        done();\n      });\n    });\n\n    it('supports ACL.isMappedToRole() returning a promise', function() {\n      return ACL.isMappedToRole(ACL.USER, user.username, 'admin')\n        .then(function(flag) {\n          expect(flag).to.be.true();\n        });\n    });\n\n    it('should report isMappedToRole by user.username', function(done) {\n      ACL.isMappedToRole(ACL.USER, user.username, 'admin', function(err, flag) {\n        if (err) return done(err);\n\n        expect(flag).to.eql(true);\n\n        done();\n      });\n    });\n\n    it('should report isMappedToRole by user.email', function(done) {\n      ACL.isMappedToRole(ACL.USER, user.email, 'admin', function(err, flag) {\n        if (err) return done(err);\n\n        expect(flag).to.eql(true);\n\n        done();\n      });\n    });\n\n    it('should report isMappedToRole by user.username for mismatch',\n      function(done) {\n        ACL.isMappedToRole(ACL.USER, 'mary', 'admin', function(err, flag) {\n          if (err) return done(err);\n\n          expect(flag).to.eql(false);\n\n          done();\n        });\n      });\n\n    it('should report isMappedToRole by app.name', function(done) {\n      ACL.isMappedToRole(ACL.APP, app.name, 'admin', function(err, flag) {\n        if (err) return done(err);\n\n        expect(flag).to.eql(true);\n\n        done();\n      });\n    });\n  });\n\n  describe('listByPrincipalType', function() {\n    let sandbox;\n\n    beforeEach(function() {\n      sandbox = sinon.sandbox.create();\n    });\n\n    afterEach(function() {\n      sandbox.restore();\n    });\n\n    it('should fetch all models assigned to the role', function(done) {\n      const principalTypesToModels = {};\n      let runs = 0;\n\n      principalTypesToModels[RoleMapping.USER] = User;\n      principalTypesToModels[RoleMapping.APPLICATION] = Application;\n      principalTypesToModels[RoleMapping.ROLE] = Role;\n\n      const mappings = Object.keys(principalTypesToModels);\n\n      mappings.forEach(function(principalType) {\n        const Model = principalTypesToModels[principalType];\n        Model.create({name: 'test', email: 'x@y.com', password: 'foobar'}, function(err, model) {\n          if (err) return done(err);\n          const uniqueRoleName = 'testRoleFor' + principalType;\n          Role.create({name: uniqueRoleName}, function(err, role) {\n            if (err) return done(err);\n            role.principals.create({principalType: principalType, principalId: model.id},\n              function(err, p) {\n                if (err) return done(err);\n                const pluralName = Model.pluralModelName.toLowerCase();\n                role[pluralName](function(err, models) {\n                  if (err) return done(err);\n                  assert.equal(models.length, 1);\n\n                  if (++runs === mappings.length) {\n                    done();\n                  }\n                });\n              });\n          });\n        });\n      });\n    });\n\n    it('should fetch all models only assigned to the role', function(done) {\n      const principalTypesToModels = {};\n\n      principalTypesToModels[RoleMapping.USER] = User;\n      principalTypesToModels[RoleMapping.APPLICATION] = Application;\n      principalTypesToModels[RoleMapping.ROLE] = Role;\n\n      const mappings = Object.keys(principalTypesToModels);\n\n      async.each(mappings, function(principalType, eachCallback) {\n        const Model = principalTypesToModels[principalType];\n\n        async.waterfall([\n          // Create models\n          function(next) {\n            Model.create([\n              {name: 'test', email: 'x@y.com', password: 'foobar'},\n              {name: 'test2', email: 'f@v.com', password: 'bargoo'},\n              {name: 'test3', email: 'd@t.com', password: 'bluegoo'}],\n            function(err, models) {\n              if (err) return next(err);\n              next(null, models);\n            });\n          },\n\n          // Create Roles\n          function(models, next) {\n            const uniqueRoleName = 'testRoleFor' + principalType;\n            const otherUniqueRoleName = 'otherTestRoleFor' + principalType;\n            Role.create([\n              {name: uniqueRoleName},\n              {name: otherUniqueRoleName}],\n            function(err, roles) {\n              if (err) return next(err);\n              next(null, models, roles);\n            });\n          },\n\n          // Create principles\n          function(models, roles, next) {\n            async.parallel([\n              function(callback) {\n                roles[0].principals.create(\n                  {principalType: principalType, principalId: models[0].id},\n                  function(err, p) {\n                    if (err) return callback(err);\n                    callback(p);\n                  },\n                );\n              },\n              function(callback) {\n                roles[1].principals.create(\n                  {principalType: principalType, principalId: models[1].id},\n                  function(err, p) {\n                    if (err) return callback(err);\n                    callback(p);\n                  },\n                );\n              }],\n            function(err, principles) {\n              next(null, models, roles, principles);\n            });\n          },\n\n          // Run tests against unique Role\n          function(models, roles, principles, next) {\n            const pluralName = Model.pluralModelName.toLowerCase();\n            const uniqueRole = roles[0];\n            uniqueRole[pluralName](function(err, models) {\n              if (err) return done(err);\n              assert.equal(models.length, 1);\n              next();\n            });\n          }],\n        eachCallback);\n      }, function(err) {\n        done();\n      });\n    });\n\n    it('should apply query', function(done) {\n      User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {\n        if (err) return done(err);\n        Role.create({name: 'userRole'}, function(err, role) {\n          if (err) return done(err);\n          role.principals.create({principalType: RoleMapping.USER, principalId: user.id},\n            function(err, p) {\n              if (err) return done(err);\n              const query = {fields: ['id', 'name']};\n              sandbox.spy(User, 'find');\n              role.users(query, function(err, users) {\n                if (err) return done(err);\n                assert.equal(users.length, 1);\n                assert.equal(users[0].id, user.id);\n                assert(User.find.calledWith(query));\n\n                done();\n              });\n            });\n        });\n      });\n    });\n\n    it('supports Promise API', function(done) {\n      const userData = {name: 'Raymond', email: 'x@y.com', password: 'foobar'};\n      User.create(userData, function(err, user) {\n        if (err) return done(err);\n        Role.create({name: 'userRole'}, function(err, role) {\n          if (err) return done(err);\n          const principalData = {\n            principalType: RoleMapping.USER,\n            principalId: user.id,\n          };\n          role.principals.create(principalData, function(err, p) {\n            if (err) return done(err);\n            role.users()\n              .then(function(users) {\n                const userIds = users.map(function(u) { return u.id; });\n                expect(userIds).to.eql([user.id]);\n                done();\n              })\n              .catch(done);\n          });\n        });\n      });\n    });\n  });\n\n  describe('isOwner', function() {\n    it('supports app-local model registry', function(done) {\n      const app = loopback({localRegistry: true, loadBuiltinModels: true});\n      app.dataSource('db', {connector: 'memory'});\n      // attach all auth-related models to 'db' datasource\n      app.enableAuth({dataSource: 'db'});\n\n      const Role = app.models.Role;\n      const User = app.models.User;\n\n      // Speed up the password hashing algorithm for tests\n      User.settings.saltWorkFactor = 4;\n\n      const u = app.registry.findModel('User');\n      const credentials = {email: 'test@example.com', password: 'pass'};\n      User.create(credentials, function(err, user) {\n        if (err) return done(err);\n\n        Role.isOwner(User, user.id, user.id, function(err, result) {\n          if (err) return done(err);\n\n          expect(result, 'isOwner result').to.equal(true);\n\n          done();\n        });\n      });\n    });\n\n    it('supports Promise API', function(done) {\n      const credentials = {email: 'test@example.com', password: 'pass'};\n      User.create(credentials, function(err, user) {\n        if (err) return done(err);\n\n        Role.isOwner(User, user.id, user.id)\n          .then(function(result) {\n            expect(result, 'isOwner result').to.equal(true);\n            done();\n          })\n          .catch(done);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/user-password.test.js",
    "content": "// Copyright IBM Corp. 2017,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst expect = require('./helpers/expect');\nconst errorHandler = require('strong-error-handler');\nconst loopback = require('../');\nconst Promise = require('bluebird');\nconst request = require('supertest');\nconst waitForEvent = require('./helpers/wait-for-event');\n\ndescribe('User.password', () => {\n  const credentials = {email: 'test@example.com', password: 'pass'};\n\n  let app, User, testUser, regularToken, resetToken;\n\n  context('restrict reset password token scope', () => {\n    beforeEach(givenAppWithRestrictionEnabled);\n\n    context('using regular access token', () => {\n      beforeEach(givenRegularAccessToken);\n\n      it('allows patching user name', () => {\n        return changeName(regularToken).expect(200);\n      });\n\n      it('allows patching user password', () => {\n        return patchPassword(regularToken).expect(200);\n      });\n\n      it('allows changing user password', () => {\n        return changePassword(regularToken).expect(204);\n      });\n\n      it('denies resetting user password', () => {\n        return resetPassword(regularToken).expect(401);\n      });\n    });\n\n    context('using password-reset token', () => {\n      beforeEach(givenResetPasswordToken);\n\n      it('denies patching user name', () => {\n        return changeName(resetToken).expect(401);\n      });\n\n      it('denies patching user password', () => {\n        return patchPassword(resetToken).expect(401);\n      });\n\n      it('denies changing user password', () => {\n        return changePassword(resetToken).expect(401);\n      });\n\n      it('allows resetting user password', () => {\n        return resetPassword(resetToken).expect(204);\n      });\n    });\n\n    function givenAppWithRestrictionEnabled() {\n      return givenAppWithUser({restrictResetPasswordTokenScope: true});\n    }\n  });\n\n  context('reject password changes via patch or replace', () => {\n    beforeEach(givenAppWithRejectionEnabled);\n    beforeEach(givenRegularAccessToken);\n\n    it('allows patching user name', () => {\n      return changeName(regularToken).expect(200);\n    });\n\n    it('denies patching user password', () => {\n      return patchPassword(regularToken).expect(401);\n    });\n\n    it('allows changing user password', () => {\n      return changePassword(regularToken).expect(204);\n    });\n\n    it('denies setPassword-like call with non-password changes', () => {\n      return patchNameAndPasswordDirectly().then(\n        function onSuccess() {\n          throw new Error('patchAttributes() should have failed');\n        },\n        function onError(err) {\n          expect(err.message).to.match(/Invalid use.*options.setPassword/);\n        },\n      );\n    });\n\n    function givenAppWithRejectionEnabled() {\n      return givenAppWithUser({rejectPasswordChangesViaPatchOrReplace: true});\n    }\n  });\n\n  context('all feature flags disabled', () => {\n    beforeEach(givenAppWithNoRestrictions);\n\n    context('using regular access token', () => {\n      beforeEach(givenRegularAccessToken);\n\n      it('allows changing user name', () => {\n        return changeName(regularToken).expect(200);\n      });\n\n      it('allows patching user password', () => {\n        return patchPassword(regularToken).expect(200);\n      });\n\n      it('allows changing user password', () => {\n        return changePassword(regularToken).expect(204);\n      });\n\n      it('allows resetting user password', () => {\n        return resetPassword(regularToken).expect(204);\n      });\n    });\n\n    context('using password-reset token', () => {\n      beforeEach(givenResetPasswordToken);\n\n      it('allows changing user name', () => {\n        return changeName(resetToken).expect(200);\n      });\n\n      it('allows patching user password', () => {\n        return patchPassword(resetToken).expect(200);\n      });\n\n      it('allows changing user password', () => {\n        return changePassword(resetToken).expect(204);\n      });\n\n      it('allows resetting user password', () => {\n        return resetPassword(resetToken).expect(204);\n      });\n    });\n\n    it('allows setPassword-like call with non-password changes', () => {\n      return patchNameAndPasswordDirectly().then(() => {\n        // test passed\n      });\n    });\n\n    function givenAppWithNoRestrictions() {\n      return givenAppWithUser({\n        rejectPasswordChangesViaPatchOrReplace: false,\n        restrictResetPasswordTokenScope: false,\n      });\n    }\n  });\n\n  function givenAppWithUser(userSettings) {\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.set('remoting', {rest: {handleErrors: false}});\n    app.dataSource('db', {connector: 'memory'});\n\n    userSettings = Object.assign({\n      name: 'PwdUser',\n      base: 'User',\n      properties: {\n        name: 'string',\n      },\n\n      // Speed up the password hashing algorithm for tests\n      saltWorkFactor: 4,\n\n      http: {path: '/users'},\n    }, userSettings);\n\n    User = app.registry.createModel(userSettings);\n    app.model(User, {dataSource: 'db'});\n\n    const AccessToken = app.registry.getModel('AccessToken');\n    AccessToken.settings.relations.user.model = User.modelName;\n\n    app.enableAuth({dataSource: 'db'});\n\n    app.use(loopback.token());\n    app.use(loopback.rest());\n    app.use(function logUnexpectedError(err, req, res, next) {\n      const statusCode = err.statusCode || err.status;\n      if (statusCode > 400 && statusCode !== 401) {\n        console.log('Unexpected error for %s %s: %s %s',\n          req.method, req.path, statusCode, err.stack || err);\n      }\n      next(err);\n    });\n    app.use(errorHandler({debug: true, log: false}));\n\n    return User.create(credentials)\n      .then(u => {\n        testUser = u;\n        return u.setAttribute('emailVerified', true);\n      });\n  }\n\n  function givenRegularAccessToken() {\n    return User.login(credentials).then(t => regularToken = t);\n  }\n\n  function givenResetPasswordToken() {\n    return Promise.all([\n      User.resetPassword({email: credentials.email}),\n      waitForEvent(User, 'resetPasswordRequest'),\n    ])\n      .spread((reset, info) => resetToken = info.accessToken);\n  }\n\n  function changeName(token) {\n    return request(app).patch(`/users/${testUser.id}`)\n      .set('Authorization', token.id)\n      .send({name: 'New Name'});\n  }\n\n  function patchPassword(token) {\n    return request(app).patch(`/users/${testUser.id}`)\n      .set('Authorization', token.id)\n      .send({password: 'new-pass'});\n  }\n\n  function changePassword(token) {\n    return request(app).post('/users/change-password')\n      .set('Authorization', token.id)\n      .send({oldPassword: credentials.password, newPassword: 'new-pass'});\n  }\n\n  function resetPassword(token) {\n    return request(app).post('/users/reset-password')\n      .set('Authorization', token.id)\n      .send({newPassword: 'new-pass'});\n  }\n\n  function patchNameAndPasswordDirectly() {\n    return testUser.patchAttributes(\n      {password: 'new-pass', name: 'New Name'},\n      {setPassword: true},\n    );\n  }\n});\n"
  },
  {
    "path": "test/user.integration.js",
    "content": "// Copyright IBM Corp. 2015,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../');\nconst lt = require('./helpers/loopback-testing-helper');\nconst path = require('path');\nconst SIMPLE_APP = path.join(__dirname, 'fixtures', 'user-integration-app');\nconst app = require(path.join(SIMPLE_APP, 'server/server.js'));\nconst expect = require('./helpers/expect');\nconst Promise = require('bluebird');\nconst waitForEvent = require('./helpers/wait-for-event');\n\ndescribe('users - integration', function() {\n  lt.beforeEach.withApp(app);\n\n  before(function(done) {\n    app.models.User.destroyAll(function(err) {\n      if (err) return done(err);\n\n      app.models.Post.destroyAll(function(err) {\n        if (err) return done(err);\n\n        app.models.blog.destroyAll(function(err) {\n          if (err) return done(err);\n\n          done();\n        });\n      });\n    });\n  });\n\n  describe('base-user', function() {\n    let userId, accessToken;\n\n    it('should create a new user', function(done) {\n      this.post('/api/users')\n        .send({username: 'x', email: 'x@y.com', password: 'x'})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.id).to.exist();\n          userId = res.body.id;\n\n          done();\n        });\n    });\n\n    it('should log into the user', function(done) {\n      const url = '/api/users/login';\n\n      this.post(url)\n        .send({username: 'x', email: 'x@y.com', password: 'x'})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.id).to.exist();\n          accessToken = res.body.id;\n\n          done();\n        });\n    });\n\n    it('returns error when replacing user that does not exist', function() {\n      const credentials = {email: 'temp@example.com', password: 'pass'};\n      const User = app.models.User;\n      let user;\n\n      let hookEnabled = true;\n      User.beforeRemote('replaceOrCreate', (ctx, unused, next) => {\n        // don't affect subsequent tests!\n        if (!hookEnabled) return;\n        hookEnabled = false;\n\n        // Delete the user *AFTER* the PUT request was authorized\n        // but *BEFORE* replaceOrCreate is invoked\n        User.deleteById(user.id, next);\n      });\n\n      return User.create(credentials)\n        .then(u => {\n          user = u;\n          return User.login(credentials);\n        })\n        .then(token => {\n          return this.put('/api/users')\n            .set('Authorization', token.id)\n            .send({\n              id: user.id,\n              email: 'x@x.com',\n              password: 'x',\n            })\n            .expect(404);\n        });\n    });\n\n    it('should create post for a given user', function(done) {\n      const url = '/api/users/' + userId + '/posts?access_token=' + accessToken;\n      this.post(url)\n        .send({title: 'T1', content: 'C1'})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.title).to.be.eql('T1');\n          expect(res.body.content).to.be.eql('C1');\n          expect(res.body.userId).to.be.eql(userId);\n\n          done();\n        });\n    });\n\n    // FIXME: [rfeng] The test is passing if run alone. But it fails with\n    // `npm test` as the loopback models are polluted by other tests\n    it('should prevent access tokens from being included', function(done) {\n      const url = '/api/posts?filter={\"include\":{\"user\":\"accessTokens\"}}';\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.have.property('length', 1);\n          const post = res.body[0];\n          expect(post.user).to.have.property('username', 'x');\n          expect(post.user).to.not.have.property('accessTokens');\n\n          done();\n        });\n    });\n\n    it('should preserve current session when invalidating tokens', function(done) {\n      const url = '/api/users/' + userId;\n      const self = this;\n      this.patch(url)\n        .send({email: 'new@example.com'})\n        .set('Authorization', accessToken)\n        .expect(200, function(err) {\n          if (err) return done(err);\n          self.get(url)\n            .set('Authorization', accessToken)\n            .expect(200, done);\n        });\n    });\n\n    it('returns 401 on logout with no access token', function(done) {\n      this.post('/api/users/logout')\n        .expect(401, done);\n    });\n\n    it('returns 401 on logout with invalid access token', function(done) {\n      this.post('/api/users/logout')\n        .set('Authorization', 'unknown-token')\n        .expect(401, done);\n    });\n\n    it('updates the user\\'s password', function() {\n      const User = app.models.User;\n      const credentials = {email: 'change@example.com', password: 'pass'};\n      return User.create(credentials)\n        .then(u => {\n          this.user = u;\n          return User.login(credentials);\n        })\n        .then(token => {\n          return this.post('/api/users/change-password')\n            .set('Authorization', token.id)\n            .send({\n              oldPassword: credentials.password,\n              newPassword: 'new password',\n            })\n            .expect(204);\n        })\n        .then(() => {\n          return User.findById(this.user.id);\n        })\n        .then(user => {\n          return user.hasPassword('new password');\n        })\n        .then(isMatch => expect(isMatch, 'user has new password').to.be.true());\n    });\n\n    it('rejects unauthenticated change password request', function() {\n      return this.post('/api/users/change-password')\n        .send({\n          oldPassword: 'old password',\n          newPassword: 'new password',\n        })\n        .expect(401);\n    });\n\n    it('uses change password options provided by the remoting context', function() {\n      const User = app.models.User;\n      const credentials = {email: 'inject@example.com', password: 'pass'};\n\n      let observedOptions;\n      User.observe('before save', (ctx, next) => {\n        observedOptions = ctx.options;\n        next();\n      });\n\n      return User.create(credentials)\n        .then(u => User.login(credentials))\n        .then(token => {\n          return this.post('/api/users/change-password')\n            .set('Authorization', token.id)\n            .send({\n              oldPassword: credentials.password,\n              newPassword: 'new password',\n            })\n            .expect(204);\n        })\n        .then(() => {\n          expect(observedOptions).to.have.property('accessToken');\n        });\n    });\n\n    it('resets the user\\'s password', function() {\n      const User = app.models.User;\n      const credentials = {email: 'reset@example.com', password: 'pass'};\n      return User.create(credentials)\n        .then(u => {\n          this.user = u;\n          return triggerPasswordReset(credentials.email);\n        })\n        .then(info => {\n          return this.post('/api/users/reset-password')\n            .set('Authorization', info.accessToken.id)\n            .send({\n              newPassword: 'new password',\n            })\n            .expect(204);\n        })\n        .then(() => {\n          return User.findById(this.user.id);\n        })\n        .then(user => {\n          return user.hasPassword('new password');\n        })\n        .then(isMatch => expect(isMatch, 'user has new password').to.be.true());\n    });\n\n    it('rejects unauthenticated reset password requests', function() {\n      return this.post('/api/users/reset-password')\n        .send({\n          newPassword: 'new password',\n        })\n        .expect(401);\n    });\n\n    it('uses password reset options provided by the remoting context', function() {\n      const User = app.models.User;\n      const credentials = {email: 'inject-reset@example.com', password: 'pass'};\n\n      let observedOptions;\n      User.observe('before save', (ctx, next) => {\n        observedOptions = ctx.options;\n        next();\n      });\n\n      return User.create(credentials)\n        .then(u => triggerPasswordReset(credentials.email))\n        .then(info => {\n          return this.post('/api/users/reset-password')\n            .set('Authorization', info.accessToken.id)\n            .send({\n              newPassword: 'new password',\n            })\n            .expect(204);\n        })\n        .then(() => {\n          expect(observedOptions).to.have.property('accessToken');\n        });\n    });\n  });\n\n  describe('sub-user', function() {\n    let userId, accessToken;\n\n    it('should create a new user', function(done) {\n      const url = '/api/myUsers';\n\n      this.post(url)\n        .send({username: 'x', email: 'x@y.com', password: 'x'})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.id).to.exist();\n          userId = res.body.id;\n\n          done();\n        });\n    });\n\n    it('should log into the user', function(done) {\n      const url = '/api/myUsers/login';\n\n      this.post(url)\n        .send({username: 'x', email: 'x@y.com', password: 'x'})\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body.id).to.exist();\n          accessToken = res.body.id;\n\n          done();\n        });\n    });\n\n    it('should create blog for a given user', function(done) {\n      const url = '/api/myUsers/' + userId + '/blogs?access_token=' + accessToken;\n      this.post(url)\n        .send({title: 'T1', content: 'C1'})\n        .expect(200, function(err, res) {\n          if (err) {\n            console.error(err);\n            return done(err);\n          }\n\n          expect(res.body.title).to.be.eql('T1');\n          expect(res.body.content).to.be.eql('C1');\n          expect(res.body.userId).to.be.eql(userId);\n\n          done();\n        });\n    });\n\n    it('should prevent access tokens from being included', function(done) {\n      const url = '/api/blogs?filter={\"include\":{\"user\":\"accessTokens\"}}';\n      this.get(url)\n        .expect(200, function(err, res) {\n          if (err) return done(err);\n\n          expect(res.body).to.have.property('length', 1);\n          const blog = res.body[0];\n          expect(blog.user).to.have.property('username', 'x');\n          expect(blog.user).to.not.have.property('accessTokens');\n\n          done();\n        });\n    });\n  });\n\n  function triggerPasswordReset(email) {\n    const User = app.models.User;\n    return Promise.all([\n      User.resetPassword({email: email}),\n      waitForEvent(app.models.User, 'resetPasswordRequest'),\n    ])\n      .spread((reset, info) => info);\n  }\n});\n"
  },
  {
    "path": "test/user.test.js",
    "content": "// Copyright IBM Corp. 2013,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst expect = require('./helpers/expect');\nconst request = require('supertest');\nconst loopback = require('../');\nconst async = require('async');\nconst url = require('url');\nconst extend = require('util')._extend;\nconst Promise = require('bluebird');\nconst waitForEvent = require('./helpers/wait-for-event');\n\nlet User, AccessToken;\n\ndescribe('User', function() {\n  this.timeout(10000);\n\n  const validCredentialsEmail = 'foo@bar.com';\n  const validCredentials = {email: validCredentialsEmail, password: 'bar'};\n  const validCredentialsEmailVerified = {\n    email: 'foo1@bar.com', password: 'bar1', emailVerified: true};\n  const validCredentialsEmailVerifiedOverREST = {\n    email: 'foo2@bar.com', password: 'bar2', emailVerified: true};\n  const validCredentialsWithRealm = {\n    email: 'foo3@bar.com', password: 'bar', realm: 'foobar'};\n  const validCredentialsWithTTL = {email: 'foo@bar.com', password: 'bar', ttl: 3600};\n  const validCredentialsWithTTLAndScope = {\n    email: 'foo@bar.com', password: 'bar', ttl: 3600, scope: 'all'};\n  const validMixedCaseEmailCredentials = {email: 'Foo@bar.com', password: 'bar'};\n  const invalidCredentials = {email: 'foo1@bar.com', password: 'invalid'};\n  const incompleteCredentials = {password: 'bar1'};\n  let validCredentialsUser, validCredentialsEmailVerifiedUser, user;\n\n  // Create a local app variable to prevent clashes with the global\n  // variable shared by all tests. While this should not be necessary if\n  // the tests were written correctly, it turns out that's not the case :(\n  let app = null;\n\n  beforeEach(function setupAppAndModels() {\n    // override the global app object provided by test/support.js\n    // and create a local one that does not share state with other tests\n    app = loopback({localRegistry: true, loadBuiltinModels: true});\n    app.set('remoting', {errorHandler: {debug: true, log: false}});\n    app.dataSource('db', {connector: 'memory'});\n\n    // setup Email model, it's needed by User tests\n    app.dataSource('email', {\n      connector: loopback.Mail,\n      transports: [{type: 'STUB'}],\n    });\n    const Email = app.registry.getModel('Email');\n    app.model(Email, {dataSource: 'email'});\n\n    // attach User and related models\n    User = app.registry.createModel({\n      name: 'TestUser',\n      base: 'User',\n      properties: {\n        // Use a custom id property to verify that User methods\n        // are correctly looking up the primary key\n        pk: {type: 'String', defaultFn: 'guid', id: true},\n      },\n      http: {path: 'test-users'},\n      // forceId is set to false for the purpose of updating the same affected user within the\n      // `Email Update` test cases.\n      forceId: false,\n      // Speed up the password hashing algorithm for tests\n      saltWorkFactor: 4,\n    });\n    app.model(User, {dataSource: 'db'});\n\n    AccessToken = app.registry.getModel('AccessToken');\n    app.model(AccessToken, {dataSource: 'db'});\n\n    User.email = Email;\n\n    // Update the AccessToken relation to use the subclass of User\n    AccessToken.belongsTo(User, {as: 'user', foreignKey: 'userId'});\n    User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});\n\n    // Speed up the password hashing algorithm\n    // for tests using the built-in User model\n    User.settings.saltWorkFactor = 4;\n\n    // allow many User.afterRemote's to be called\n    User.setMaxListeners(0);\n\n    app.enableAuth({dataSource: 'db'});\n    app.use(loopback.token({model: AccessToken}));\n    app.use(loopback.rest());\n\n    // create 2 users: with and without verified email\n    return Promise.map(\n      [validCredentials, validCredentialsEmailVerified],\n      credentials => User.create(credentials),\n    ).then(users => {\n      validCredentialsUser = user = users[0];\n      validCredentialsEmailVerifiedUser = users[1];\n    });\n  });\n\n  describe('User.create', function() {\n    it('Create a new user', function(done) {\n      User.create({email: 'f@b.com', password: 'bar'}, function(err, user) {\n        assert(!err);\n        assert(user.pk);\n        assert(user.email);\n\n        done();\n      });\n    });\n\n    it('Create a new user (email case-sensitivity off)', function(done) {\n      User.settings.caseSensitiveEmail = false;\n      User.create({email: 'F@b.com', password: 'bar'}, function(err, user) {\n        if (err) return done(err);\n\n        assert(user.pk);\n        assert.equal(user.email, user.email.toLowerCase());\n\n        done();\n      });\n    });\n\n    it('Create a new user (email case-sensitive)', function(done) {\n      User.create({email: 'F@b.com', password: 'bar'}, function(err, user) {\n        if (err) return done(err);\n\n        assert(user.pk);\n        assert(user.email);\n        assert.notEqual(user.email, user.email.toLowerCase());\n\n        done();\n      });\n    });\n\n    it('fails when the required email is missing (case-sensitivity on)', () => {\n      User.create({password: '123'})\n        .then(\n          success => { throw new Error('create should have failed'); },\n          err => {\n            expect(err.name).to.equal('ValidationError');\n            expect(err.statusCode).to.equal(422);\n            expect(err.details.context).to.equal(User.modelName);\n            expect(err.details.codes.email).to.deep.equal(['presence']);\n          },\n        );\n    });\n\n    it('fails when the required email is missing (case-sensitivity off)', () => {\n      User.settings.caseSensitiveEmail = false;\n      User.create({email: undefined, password: '123'})\n        .then(\n          success => { throw new Error('create should have failed'); },\n          err => {\n            expect(err.name).to.equal('ValidationError');\n            expect(err.statusCode).to.equal(422);\n            expect(err.details.context).to.equal(User.modelName);\n            expect(err.details.codes.email).to.deep.equal(['presence']);\n          },\n        );\n    });\n\n    // will change in future versions where password will be optional by default\n    it('Password is required', function(done) {\n      const u = new User({email: '123@456.com'});\n\n      User.create({email: 'c@d.com'}, function(err) {\n        assert(err);\n\n        done();\n      });\n    });\n\n    it('Requires a valid email', function(done) {\n      User.create({email: 'foo@', password: '123'}, function(err) {\n        assert(err);\n        assert.equal(err.name, 'ValidationError');\n        assert.equal(err.statusCode, 422);\n        assert.equal(err.details.context, User.modelName);\n        assert.deepEqual(err.details.codes.email, ['custom.email']);\n        done();\n      });\n    });\n\n    it('allows TLD domains in email', function() {\n      return User.create({\n        email: 'local@com',\n        password: '123',\n      });\n    });\n\n    it('Requires a unique email', function(done) {\n      User.create({email: 'a@b.com', password: 'foobar'}, function() {\n        User.create({email: 'a@b.com', password: 'batbaz'}, function(err) {\n          assert(err, 'should error because the email is not unique!');\n\n          done();\n        });\n      });\n    });\n\n    it('Requires a unique email (email case-sensitivity off)', function(done) {\n      User.settings.caseSensitiveEmail = false;\n      User.create({email: 'A@b.com', password: 'foobar'}, function(err) {\n        if (err) return done(err);\n\n        User.create({email: 'a@b.com', password: 'batbaz'}, function(err) {\n          assert(err, 'should error because the email is not unique!');\n\n          done();\n        });\n      });\n    });\n\n    it('Requires a unique email (email case-sensitive)', function(done) {\n      User.create({email: 'A@b.com', password: 'foobar'}, function(err, user1) {\n        User.create({email: 'a@b.com', password: 'batbaz'}, function(err, user2) {\n          if (err) return done(err);\n\n          assert.notEqual(user1.email, user2.email);\n\n          done();\n        });\n      });\n    });\n\n    it('Requires a unique username', function(done) {\n      User.create({email: 'a@b.com', username: 'abc', password: 'foobar'}, function() {\n        User.create({email: 'b@b.com', username: 'abc', password: 'batbaz'}, function(err) {\n          assert(err, 'should error because the username is not unique!');\n\n          done();\n        });\n      });\n    });\n\n    it('Requires a password to login with basic auth', function(done) {\n      User.create({email: 'b@c.com'}, function(err) {\n        User.login({email: 'b@c.com'}, function(err, accessToken) {\n          assert(!accessToken, 'should not create a accessToken without a valid password');\n          assert(err, 'should not login without a password');\n          assert.equal(err.code, 'LOGIN_FAILED');\n\n          done();\n        });\n      });\n    });\n\n    it('Hashes the given password', function() {\n      const u = new User({username: 'foo', password: 'bar'});\n      assert(u.password !== 'bar');\n    });\n\n    it('does not hash the password if it\\'s already hashed', function() {\n      const u1 = new User({username: 'foo', password: 'bar'});\n      assert(u1.password !== 'bar');\n      const u2 = new User({username: 'foo', password: u1.password});\n      assert(u2.password === u1.password);\n    });\n\n    it('invalidates the user\\'s accessToken when the user is deleted By id', function(done) {\n      let usersId;\n      async.series([\n        function(next) {\n          User.create({email: 'b@c.com', password: 'bar'}, function(err, user) {\n            usersId = user.pk;\n            next(err);\n          });\n        },\n        function(next) {\n          User.login({email: 'b@c.com', password: 'bar'}, function(err, accessToken) {\n            if (err) return next(err);\n            assert(accessToken.userId);\n            next();\n          });\n        },\n        function(next) {\n          User.deleteById(usersId, function(err) {\n            next(err);\n          });\n        },\n        function(next) {\n          User.findById(usersId, function(err, userFound) {\n            if (err) return next(err);\n            expect(userFound).to.equal(null);\n            AccessToken.find({where: {userId: usersId}}, function(err, tokens) {\n              if (err) return next(err);\n              expect(tokens.length).to.equal(0);\n              next();\n            });\n          });\n        },\n      ], function(err) {\n        if (err) return done(err);\n        done();\n      });\n    });\n\n    it('skips token invalidation when the relation is not configured', () => {\n      const app = loopback({localRegistry: true, loadBuiltinModels: true});\n      app.dataSource('db', {connector: 'memory'});\n\n      const PrivateUser = app.registry.createModel({\n        name: 'PrivateUser',\n        base: 'User',\n        // Speed up the password hashing algorithm for tests\n        saltWorkFactor: 4,\n      });\n      app.model(PrivateUser, {dataSource: 'db'});\n\n      return PrivateUser.create({email: 'private@example.com', password: 'pass'})\n        .then(u => PrivateUser.deleteById(u.id));\n      // the test passed when the operation did not crash\n    });\n\n    it('invalidates the user\\'s accessToken when the user is deleted all', function(done) {\n      let userIds = [];\n      let users;\n      async.series([\n        function(next) {\n          User.create([\n            {name: 'myname', email: 'b@c.com', password: 'bar'},\n            {name: 'myname', email: 'd@c.com', password: 'bar'},\n          ], function(err, createdUsers) {\n            users = createdUsers;\n            userIds = createdUsers.map(function(u) {\n              return u.pk;\n            });\n            next(err);\n          });\n        },\n        function(next) {\n          User.login({email: 'b@c.com', password: 'bar'}, function(err, accessToken) {\n            if (err) return next(err);\n            assertGoodToken(accessToken, users[0]);\n            next();\n          });\n        },\n        function(next) {\n          User.login({email: 'd@c.com', password: 'bar'}, function(err, accessToken) {\n            if (err) return next(err);\n            assertGoodToken(accessToken, users[1]);\n            next();\n          });\n        },\n        function(next) {\n          User.deleteAll({name: 'myname'}, function(err, user) {\n            next(err);\n          });\n        },\n        function(next) {\n          User.find({where: {name: 'myname'}}, function(err, userFound) {\n            if (err) return next(err);\n            expect(userFound.length).to.equal(0);\n            AccessToken.find({where: {userId: {inq: userIds}}}, function(err, tokens) {\n              if (err) return next(err);\n              expect(tokens.length).to.equal(0);\n              next();\n            });\n          });\n        },\n      ], function(err) {\n        if (err) return done(err);\n        done();\n      });\n    });\n\n    describe('custom password hash', function() {\n      let defaultHashPassword, defaultValidatePassword;\n\n      beforeEach(function() {\n        defaultHashPassword = User.hashPassword;\n        defaultValidatePassword = User.validatePassword;\n\n        User.hashPassword = function(plain) {\n          return plain.toUpperCase();\n        };\n\n        User.validatePassword = function(plain) {\n          if (!plain || plain.length < 3) {\n            throw new Error('Password must have at least 3 chars');\n          }\n          return true;\n        };\n      });\n\n      afterEach(function() {\n        User.hashPassword = defaultHashPassword;\n        User.validatePassword = defaultValidatePassword;\n      });\n\n      it('Reports invalid password', function() {\n        try {\n          const u = new User({username: 'foo', password: 'aa'});\n          assert(false, 'Error should have been thrown');\n        } catch (e) {\n          // Ignore\n        }\n      });\n\n      it('Hashes the given password', function() {\n        const u = new User({username: 'foo', password: 'bar'});\n        assert(u.password === 'BAR');\n      });\n    });\n\n    it('Create a user over REST should remove emailVerified property', function(done) {\n      request(app)\n        .post('/test-users')\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .send(validCredentialsEmailVerifiedOverREST)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          assert(!res.body.emailVerified);\n\n          done();\n        });\n    });\n  });\n\n  describe('Password length validation', function() {\n    const pass72Char = new Array(70).join('a') + '012';\n    const pass73Char = pass72Char + '3';\n    const passTooLong = pass72Char + 'WXYZ1234';\n\n    it('rejects empty passwords creation', function(done) {\n      User.create({email: 'b@c.com', password: ''}, function(err) {\n        expect(err.code).to.equal('INVALID_PASSWORD');\n        expect(err.statusCode).to.equal(422);\n        done();\n      });\n    });\n\n    it('rejects updating with empty password', function(done) {\n      User.create({email: 'blank@c.com', password: pass72Char}, function(err, userCreated) {\n        if (err) return done(err);\n        userCreated.updateAttribute('password', '', function(err, userUpdated) {\n          expect(err.code).to.equal('INVALID_PASSWORD');\n          expect(err.statusCode).to.equal(422);\n          done();\n        });\n      });\n    });\n\n    it('rejects updating with empty password using replaceAttributes', function(done) {\n      User.create({email: 'b@example.com', password: pass72Char}, function(err, userCreated) {\n        if (err) return done(err);\n        userCreated.replaceAttributes({'password': ''}, function(err, userUpdated) {\n          expect(err.code).to.equal('INVALID_PASSWORD');\n          expect(err.statusCode).to.equal(422);\n          done();\n        });\n      });\n    });\n\n    it('rejects updating with empty password using updateOrCreate', function(done) {\n      User.create({email: 'b@example.com', password: pass72Char}, function(err, userCreated) {\n        if (err) return done(err);\n        User.updateOrCreate({id: userCreated.id, 'password': ''}, function(err, userUpdated) {\n          expect(err.code).to.equal('INVALID_PASSWORD');\n          expect(err.statusCode).to.equal(422);\n          done();\n        });\n      });\n    });\n\n    it('rejects updating with empty password using updateAll', function(done) {\n      User.create({email: 'b@example.com', password: pass72Char}, function(err, userCreated) {\n        if (err) return done(err);\n        User.updateAll({where: {id: userCreated.id}}, {'password': ''}, function(err, userUpdated) {\n          expect(err.code).to.equal('INVALID_PASSWORD');\n          expect(err.statusCode).to.equal(422);\n          done();\n        });\n      });\n    });\n\n    it('rejects passwords longer than 72 characters', function(done) {\n      User.create({email: 'b@c.com', password: pass73Char}, function(err) {\n        expect(err.code).to.equal('PASSWORD_TOO_LONG');\n        expect(err.statusCode).to.equal(422);\n        done();\n      });\n    });\n\n    it('rejects a new user with password longer than 72 characters', function(done) {\n      try {\n        const u = new User({username: 'foo', password: pass73Char});\n        assert(false, 'Error should have been thrown');\n      } catch (e) {\n        expect(e).to.match(/password entered was too long/);\n        done();\n      }\n    });\n\n    it('accepts passwords that are exactly 72 characters long', function(done) {\n      User.create({email: 'b@c.com', password: pass72Char}, function(err, user) {\n        if (err) return done(err);\n        User.findById(user.pk, function(err, userFound) {\n          if (err) return done(err);\n          assert(userFound);\n          done();\n        });\n      });\n    });\n\n    it('allows login with password exactly 72 characters long', function(done) {\n      User.create({email: 'b@c.com', password: pass72Char}, function(err, user) {\n        if (err) return done(err);\n        User.login({email: 'b@c.com', password: pass72Char}, function(err, accessToken) {\n          if (err) return done(err);\n          assertGoodToken(accessToken, user);\n          done();\n        });\n      });\n    });\n\n    it('rejects password reset when password is more than 72 chars', function(done) {\n      User.create({email: 'b@c.com', password: pass72Char}, function(err) {\n        if (err) return done(err);\n        User.resetPassword({email: 'b@c.com', password: pass73Char}, function(err) {\n          assert(err);\n          expect(err).to.match(/password entered was too long/);\n          done();\n        });\n      });\n    });\n\n    it('rejects changePassword when new password is longer than 72 chars', function() {\n      return User.create({email: 'test@example.com', password: pass72Char})\n        .then(u => u.changePassword(pass72Char, pass73Char))\n        .then(\n          success => { throw new Error('changePassword should have failed'); },\n          err => {\n            expect(err.message).to.match(/password entered was too long/);\n\n            // workaround for chai problem\n            //   object tested must be an array, an object, or a string,\n            //   but error given\n            const props = Object.assign({}, err);\n            expect(props).to.contain({\n              code: 'PASSWORD_TOO_LONG',\n              statusCode: 422,\n            });\n          },\n        );\n    });\n\n    it('rejects setPassword when new password is longer than 72 chars', function() {\n      return User.create({email: 'test@example.com', password: pass72Char})\n        .then(u => u.setPassword(pass73Char))\n        .then(\n          success => { throw new Error('setPassword should have failed'); },\n          err => {\n            expect(err.message).to.match(/password entered was too long/);\n\n            // workaround for chai problem\n            //   object tested must be an array, an object, or a string,\n            //   but error given\n            const props = Object.assign({}, err);\n            expect(props).to.contain({\n              code: 'PASSWORD_TOO_LONG',\n              statusCode: 422,\n            });\n          },\n        );\n    });\n  });\n\n  describe('Access-hook for queries with email NOT case-sensitive', function() {\n    it('Should not throw an error if the query does not contain {where: }', function(done) {\n      User.find({}, function(err) {\n        if (err) done(err);\n\n        done();\n      });\n    });\n\n    it('Should be able to find lowercase email with mixed-case email query', function(done) {\n      User.settings.caseSensitiveEmail = false;\n      User.find({where: {email: validMixedCaseEmailCredentials.email}}, function(err, result) {\n        if (err) done(err);\n\n        assert(result[0], 'The query did not find the user');\n        assert.equal(result[0].email, validCredentialsEmail);\n\n        done();\n      });\n    });\n\n    it('Should be able to use query filters (email case-sensitivity off)', function(done) {\n      User.settings.caseSensitiveEmail = false;\n      const insensitiveUser = {email: 'insensitive@example.com', password: 'abc'};\n      User.create(insensitiveUser, function(err, user) {\n        User.find({where: {email:\n          {inq: [insensitiveUser.email]},\n        }}, function(err, result) {\n          if (err) done(err);\n          assert(result[0], 'The query did not find the user');\n          assert.equal(result[0].email, insensitiveUser.email);\n          done();\n        });\n      });\n    });\n\n    it('Should be able to use query filters (email case-sensitivity on)', function(done) {\n      User.settings.caseSensitiveEmail = true;\n      const sensitiveUser = {email: 'senSiTive@example.com', password: 'abc'};\n      User.create(sensitiveUser, function(err, user) {\n        User.find({where: {email:\n          {inq: [sensitiveUser.email]},\n        }}, function(err, result) {\n          if (err) done(err);\n          assert(result[0], 'The query did not find the user');\n          assert.equal(result[0].email, sensitiveUser.email);\n          done();\n        });\n      });\n    });\n  });\n\n  describe('User.login', function() {\n    it('Login a user by providing credentials', function(done) {\n      User.login(validCredentials, function(err, accessToken) {\n        assertGoodToken(accessToken, validCredentialsUser);\n\n        done();\n      });\n    });\n\n    it('Login a user by providing email credentials (email case-sensitivity off)', function(done) {\n      User.settings.caseSensitiveEmail = false;\n      User.login(validMixedCaseEmailCredentials, function(err, accessToken) {\n        assertGoodToken(accessToken, validCredentialsUser);\n\n        done();\n      });\n    });\n\n    it('Try to login with invalid email case', function(done) {\n      User.login(validMixedCaseEmailCredentials, function(err, accessToken) {\n        assert(err);\n\n        done();\n      });\n    });\n\n    it('should not allow queries in email field', function(done) {\n      User.login({email: {'neq': 'x'}, password: 'x'}, function(err, accessToken) {\n        assert(err);\n        assert.equal(err.code, 'INVALID_EMAIL');\n        assert(!accessToken);\n\n        done();\n      });\n    });\n\n    it('should not allow queries in username field', function(done) {\n      User.login({username: {'neq': 'x'}, password: 'x'}, function(err, accessToken) {\n        assert(err);\n        assert.equal(err.code, 'INVALID_USERNAME');\n        assert(!accessToken);\n\n        done();\n      });\n    });\n\n    it('should not allow queries in realm field', function(done) {\n      User.settings.realmRequired = true;\n      User.login({username: 'x', password: 'x', realm: {'neq': 'x'}}, function(err, accessToken) {\n        assert(err);\n        assert.equal(err.code, 'INVALID_REALM');\n        assert(!accessToken);\n\n        done();\n      });\n    });\n\n    it('Login a user by providing credentials with TTL', function(done) {\n      User.login(validCredentialsWithTTL, function(err, accessToken) {\n        assertGoodToken(accessToken, validCredentialsUser);\n        assert.equal(accessToken.ttl, validCredentialsWithTTL.ttl);\n\n        done();\n      });\n    });\n\n    it('honors default `createAccessToken` implementation', function(done) {\n      User.login(validCredentialsWithTTL, function(err, accessToken) {\n        assert(accessToken.userId);\n        assert(accessToken.id);\n\n        User.findById(accessToken.userId, function(err, user) {\n          user.createAccessToken(120, function(err, accessToken) {\n            assertGoodToken(accessToken, validCredentialsUser);\n            assert.equal(accessToken.ttl, 120);\n\n            done();\n          });\n        });\n      });\n    });\n\n    it('honors default `createAccessToken` implementation - promise variant', function(done) {\n      User.login(validCredentialsWithTTL, function(err, accessToken) {\n        assert(accessToken.userId);\n        assert(accessToken.id);\n\n        User.findById(accessToken.userId, function(err, user) {\n          user.createAccessToken(120)\n            .then(function(accessToken) {\n              assertGoodToken(accessToken, validCredentialsUser);\n              assert.equal(accessToken.ttl, 120);\n\n              done();\n            })\n            .catch(function(err) {\n              done(err);\n            });\n        });\n      });\n    });\n\n    it('Login a user using a custom createAccessToken', function(done) {\n      const createToken = User.prototype.createAccessToken; // Save the original method\n      // Override createAccessToken\n      User.prototype.createAccessToken = function(ttl, cb) {\n        // Reduce the ttl by half for testing purpose\n        this.accessTokens.create({ttl: ttl / 2}, cb);\n      };\n      User.login(validCredentialsWithTTL, function(err, accessToken) {\n        assertGoodToken(accessToken, validCredentialsUser);\n        assert.equal(accessToken.ttl, 1800);\n\n        User.findById(accessToken.userId, function(err, user) {\n          user.createAccessToken(120, function(err, accessToken) {\n            assertGoodToken(accessToken, validCredentialsUser);\n            assert.equal(accessToken.ttl, 60);\n            // Restore create access token\n            User.prototype.createAccessToken = createToken;\n\n            done();\n          });\n        });\n      });\n    });\n\n    it('Login a user using a custom createAccessToken with options',\n      function(done) {\n        const createToken = User.prototype.createAccessToken; // Save the original method\n        // Override createAccessToken\n        User.prototype.createAccessToken = function(ttl, options, cb) {\n          // Reduce the ttl by half for testing purpose\n          this.accessTokens.create({ttl: ttl / 2, scopes: [options.scope]}, cb);\n        };\n        User.login(validCredentialsWithTTLAndScope, function(err, accessToken) {\n          assertGoodToken(accessToken, validCredentialsUser);\n          assert.equal(accessToken.ttl, 1800);\n          assert.deepEqual(accessToken.scopes, ['all']);\n\n          User.findById(accessToken.userId, function(err, user) {\n            user.createAccessToken(120, {scope: 'default'}, function(err, accessToken) {\n              assertGoodToken(accessToken, validCredentialsUser);\n              assert.equal(accessToken.ttl, 60);\n              assert.deepEqual(accessToken.scopes, ['default']);\n              // Restore create access token\n              User.prototype.createAccessToken = createToken;\n\n              done();\n            });\n          });\n        });\n      });\n\n    it('Login should only allow correct credentials', function(done) {\n      User.login(invalidCredentials, function(err, accessToken) {\n        assert(err);\n        assert.equal(err.code, 'LOGIN_FAILED');\n        assert(!accessToken);\n\n        done();\n      });\n    });\n\n    it('Login should only allow correct credentials - promise variant', function(done) {\n      User.login(invalidCredentials)\n        .then(function(accessToken) {\n          expect(accessToken, 'accessToken').to.not.exist();\n\n          done();\n        })\n        .catch(function(err) {\n          expect(err, 'err').to.exist();\n          expect(err).to.have.property('code', 'LOGIN_FAILED');\n\n          done();\n        });\n    });\n\n    it('Login a user providing incomplete credentials', function(done) {\n      User.login(incompleteCredentials, function(err, accessToken) {\n        expect(err, 'err').to.exist();\n        expect(err).to.have.property('code', 'USERNAME_EMAIL_REQUIRED');\n\n        done();\n      });\n    });\n\n    it('Login a user providing incomplete credentials - promise variant', function(done) {\n      User.login(incompleteCredentials)\n        .then(function(accessToken) {\n          expect(accessToken, 'accessToken').to.not.exist();\n\n          done();\n        })\n        .catch(function(err) {\n          expect(err, 'err').to.exist();\n          expect(err).to.have.property('code', 'USERNAME_EMAIL_REQUIRED');\n\n          done();\n        });\n    });\n\n    it('Login a user over REST by providing credentials', function(done) {\n      request(app)\n        .post('/test-users/login')\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .send(validCredentials)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const accessToken = res.body;\n\n          assertGoodToken(accessToken, validCredentialsUser);\n          assert(accessToken.user === undefined);\n\n          done();\n        });\n    });\n\n    it('Login a user over REST by providing invalid credentials', function(done) {\n      request(app)\n        .post('/test-users/login')\n        .expect('Content-Type', /json/)\n        .expect(401)\n        .send(invalidCredentials)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const errorResponse = res.body.error;\n          assert.equal(errorResponse.code, 'LOGIN_FAILED');\n\n          done();\n        });\n    });\n\n    it('Login a user over REST by providing incomplete credentials', function(done) {\n      request(app)\n        .post('/test-users/login')\n        .expect('Content-Type', /json/)\n        .expect(400)\n        .send(incompleteCredentials)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const errorResponse = res.body.error;\n          assert.equal(errorResponse.code, 'USERNAME_EMAIL_REQUIRED');\n\n          done();\n        });\n    });\n\n    it('Login a user over REST with the wrong Content-Type', function(done) {\n      request(app)\n        .post('/test-users/login')\n        .set('Content-Type', null)\n        .expect('Content-Type', /json/)\n        .expect(400)\n        .send(JSON.stringify(validCredentials))\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const errorResponse = res.body.error;\n          assert.equal(errorResponse.code, 'USERNAME_EMAIL_REQUIRED');\n\n          done();\n        });\n    });\n\n    it('Returns current user when `include` is `USER`', function(done) {\n      request(app)\n        .post('/test-users/login?include=USER')\n        .send(validCredentials)\n        .expect(200)\n        .expect('Content-Type', /json/)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const token = res.body;\n          expect(token.user, 'body.user').to.not.equal(undefined);\n          expect(token.user, 'body.user')\n            .to.have.property('email', validCredentials.email);\n\n          done();\n        });\n    });\n\n    it('should handle multiple `include`', function(done) {\n      request(app)\n        .post('/test-users/login?include=USER&include=Post')\n        .send(validCredentials)\n        .expect(200)\n        .expect('Content-Type', /json/)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const token = res.body;\n          expect(token.user, 'body.user').to.not.equal(undefined);\n          expect(token.user, 'body.user')\n            .to.have.property('email', validCredentials.email);\n\n          done();\n        });\n    });\n\n    it('allows login with password too long but created in old LB version',\n      function(done) {\n        const bcrypt = require('bcryptjs');\n        const longPassword = new Array(80).join('a');\n        const oldHash = bcrypt.hashSync(longPassword, bcrypt.genSaltSync(1));\n\n        User.create({email: 'b@c.com', password: oldHash}, function(err) {\n          if (err) return done(err);\n          User.login({email: 'b@c.com', password: longPassword}, function(err, accessToken) {\n            if (err) return done(err);\n            assert(accessToken.id);\n            // we are logged in, the test passed\n            done();\n          });\n        });\n      });\n  });\n\n  function assertGoodToken(accessToken, user) {\n    if (accessToken instanceof AccessToken) {\n      accessToken = accessToken.toJSON();\n    }\n    expect(accessToken).to.have.property('userId', user.pk);\n    assert(accessToken.id);\n    assert.equal(accessToken.id.length, 64);\n  }\n\n  describe('User.login requiring email verification', function() {\n    beforeEach(function() {\n      User.settings.emailVerificationRequired = true;\n    });\n\n    afterEach(function() {\n      User.settings.emailVerificationRequired = false;\n    });\n\n    it('requires valid and complete credentials for email verification', function(done) {\n      User.login({email: validCredentialsEmail}, function(err, accessToken) {\n        // strongloop/loopback#931\n        // error message should be \"login failed\"\n        // and not \"login failed as the email has not been verified\"\n        assert(err && !/verified/.test(err.message),\n          'expecting \"login failed\" error message, received: \"' + err.message + '\"');\n        assert.equal(err.code, 'LOGIN_FAILED');\n        // as login is failing because of invalid credentials it should to return\n        // the user id in the error message\n        assert.equal(err.details, undefined);\n\n        done();\n      });\n    });\n\n    it('requires valid and complete credentials for email verification - promise variant',\n      function(done) {\n        User.login({email: validCredentialsEmail})\n          .then(function(accessToken) {\n            done();\n          })\n          .catch(function(err) {\n            // strongloop/loopback#931\n            // error message should be \"login failed\" and not \"login failed as the email has not been verified\"\n            assert(err && !/verified/.test(err.message),\n              'expecting \"login failed\" error message, received: \"' + err.message + '\"');\n            assert.equal(err.code, 'LOGIN_FAILED');\n            assert.equal(err.details, undefined);\n            done();\n          });\n      });\n\n    it('does not login a user with unverified email but provides userId', function() {\n      return User.login(validCredentials).then(\n        function(user) {\n          throw new Error('User.login() should have failed');\n        },\n        function(err, accessToken) {\n          err = Object.assign({}, err);\n          expect(err).to.eql({\n            statusCode: 401,\n            code: 'LOGIN_FAILED_EMAIL_NOT_VERIFIED',\n            details: {\n              userId: validCredentialsUser.pk,\n            },\n          });\n        },\n      );\n    });\n\n    it('login a user with verified email', function(done) {\n      User.login(validCredentialsEmailVerified, function(err, accessToken) {\n        assertGoodToken(accessToken, validCredentialsEmailVerifiedUser);\n\n        done();\n      });\n    });\n\n    it('login a user with verified email - promise variant', function(done) {\n      User.login(validCredentialsEmailVerified)\n        .then(function(accessToken) {\n          assertGoodToken(accessToken, validCredentialsEmailVerifiedUser);\n\n          done();\n        })\n        .catch(function(err) {\n          done(err);\n        });\n    });\n\n    it('login a user over REST when email verification is required', function(done) {\n      request(app)\n        .post('/test-users/login')\n        .expect('Content-Type', /json/)\n        .expect(200)\n        .send(validCredentialsEmailVerified)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const accessToken = res.body;\n\n          assertGoodToken(accessToken, validCredentialsEmailVerifiedUser);\n          assert(accessToken.user === undefined);\n\n          done();\n        });\n    });\n\n    it('login user over REST require complete and valid credentials ' +\n    'for email verification error message',\n    function(done) {\n      request(app)\n        .post('/test-users/login')\n        .expect('Content-Type', /json/)\n        .expect(401)\n        .send({email: validCredentialsEmail})\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          // strongloop/loopback#931\n          // error message should be \"login failed\"\n          // and not \"login failed as the email has not been verified\"\n          const errorResponse = res.body.error;\n          assert(errorResponse && !/verified/.test(errorResponse.message),\n            'expecting \"login failed\" error message, received: \"' + errorResponse.message + '\"');\n          assert.equal(errorResponse.code, 'LOGIN_FAILED');\n\n          done();\n        });\n    });\n\n    it('login a user over REST without email verification when it is required', function(done) {\n      // make sure the app is configured in production mode\n      app.set('remoting', {errorHandler: {debug: false, log: false}});\n\n      request(app)\n        .post('/test-users/login')\n        .expect('Content-Type', /json/)\n        .expect(401)\n        .send(validCredentials)\n        .end(function(err, res) {\n          if (err) return done(err);\n\n          const errorResponse = res.body.error;\n\n          // extracting code and details error response\n          const errorExcerpts = {\n            code: errorResponse.code,\n            details: errorResponse.details,\n          };\n\n          expect(errorExcerpts).to.eql({\n            code: 'LOGIN_FAILED_EMAIL_NOT_VERIFIED',\n            details: {\n              userId: validCredentialsUser.pk,\n            },\n          });\n\n          done();\n        });\n    });\n  });\n\n  describe('User.login requiring realm', function() {\n    let User, AccessToken;\n\n    beforeEach(function() {\n      User = app.registry.createModel('RealmUser', {}, {\n        base: 'TestUser',\n        realmRequired: true,\n        realmDelimiter: ':',\n      });\n\n      AccessToken = app.registry.createModel('RealmAccessToken', {}, {\n        base: 'AccessToken',\n      });\n\n      app.model(AccessToken, {dataSource: 'db'});\n      app.model(User, {dataSource: 'db'});\n\n      // Update the AccessToken relation to use the subclass of User\n      AccessToken.belongsTo(User, {as: 'user', foreignKey: 'userId'});\n      User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});\n\n      // allow many User.afterRemote's to be called\n      User.setMaxListeners(0);\n    });\n\n    const realm1User = {\n      realm: 'realm1',\n      username: 'foo100',\n      email: 'foo100@bar.com',\n      password: 'pass100',\n    };\n\n    const realm2User = {\n      realm: 'realm2',\n      username: 'foo100',\n      email: 'foo100@bar.com',\n      password: 'pass200',\n    };\n\n    const credentialWithoutRealm = {\n      username: 'foo100',\n      email: 'foo100@bar.com',\n      password: 'pass100',\n    };\n\n    const credentialWithBadPass = {\n      realm: 'realm1',\n      username: 'foo100',\n      email: 'foo100@bar.com',\n      password: 'pass001',\n    };\n\n    const credentialWithBadRealm = {\n      realm: 'realm3',\n      username: 'foo100',\n      email: 'foo100@bar.com',\n      password: 'pass100',\n    };\n\n    const credentialWithRealm = {\n      realm: 'realm1',\n      username: 'foo100',\n      password: 'pass100',\n    };\n\n    const credentialRealmInUsername = {\n      username: 'realm1:foo100',\n      password: 'pass100',\n    };\n\n    const credentialRealmInEmail = {\n      email: 'realm1:foo100@bar.com',\n      password: 'pass100',\n    };\n\n    let user1 = null;\n    beforeEach(function(done) {\n      User.create(realm1User, function(err, u) {\n        if (err) return done(err);\n\n        user1 = u;\n        User.create(realm2User, done);\n      });\n    });\n\n    it('honors unique email for realm', function(done) {\n      User.create(realm1User, function(err, u) {\n        assert(err);\n        assert(err.message.match(/User already exists/) &&\n          err.message.match(/Email already exists/));\n        done();\n      });\n    });\n\n    it('rejects a user by without realm', function(done) {\n      User.login(credentialWithoutRealm, function(err, accessToken) {\n        assert(err);\n        assert.equal(err.code, 'REALM_REQUIRED');\n\n        done();\n      });\n    });\n\n    it('rejects a user by with bad realm', function(done) {\n      User.login(credentialWithBadRealm, function(err, accessToken) {\n        assert(err);\n        assert.equal(err.code, 'LOGIN_FAILED');\n\n        done();\n      });\n    });\n\n    it('rejects a user by with bad pass', function(done) {\n      User.login(credentialWithBadPass, function(err, accessToken) {\n        assert(err);\n        assert.equal(err.code, 'LOGIN_FAILED');\n\n        done();\n      });\n    });\n\n    it('logs in a user by with realm', function(done) {\n      User.login(credentialWithRealm, function(err, accessToken) {\n        assertGoodToken(accessToken, user1);\n\n        done();\n      });\n    });\n\n    it('logs in a user by with realm in username', function(done) {\n      User.login(credentialRealmInUsername, function(err, accessToken) {\n        assertGoodToken(accessToken, user1);\n\n        done();\n      });\n    });\n\n    it('logs in a user by with realm in email', function(done) {\n      User.login(credentialRealmInEmail, function(err, accessToken) {\n        assertGoodToken(accessToken, user1);\n\n        done();\n      });\n    });\n\n    describe('User.login with realmRequired but no realmDelimiter', function() {\n      beforeEach(function() {\n        User.settings.realmDelimiter = undefined;\n      });\n\n      afterEach(function() {\n        User.settings.realmDelimiter = ':';\n      });\n\n      it('logs in a user by with realm', function(done) {\n        User.login(credentialWithRealm, function(err, accessToken) {\n          assertGoodToken(accessToken, user1);\n\n          done();\n        });\n      });\n\n      it('rejects a user by with realm in email if realmDelimiter is not set',\n        function(done) {\n          User.login(credentialRealmInEmail, function(err, accessToken) {\n            assert(err);\n            assert.equal(err.code, 'REALM_REQUIRED');\n\n            done();\n          });\n        });\n    });\n  });\n\n  describe('User.logout', function() {\n    it('Logout a user by providing the current accessToken id (using node)', function(done) {\n      login(logout);\n\n      function login(fn) {\n        User.login(validCredentials, fn);\n      }\n\n      function logout(err, accessToken) {\n        User.logout(accessToken.id, verify(accessToken.id, done));\n      }\n    });\n\n    it('Logout a user by providing the current accessToken id (using node) - promise variant',\n      function(done) {\n        login(logout);\n\n        function login(fn) {\n          User.login(validCredentials, fn);\n        }\n\n        function logout(err, accessToken) {\n          User.logout(accessToken.id)\n            .then(function() {\n              verify(accessToken.id, done);\n            })\n            .catch(done(err));\n        }\n      });\n\n    it('Logout a user by providing the current accessToken id (over rest)', function(done) {\n      login(logout);\n      function login(fn) {\n        request(app)\n          .post('/test-users/login')\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .send(validCredentials)\n          .end(function(err, res) {\n            if (err) return done(err);\n\n            const accessToken = res.body;\n            assertGoodToken(accessToken, validCredentialsUser);\n\n            fn(null, accessToken.id);\n          });\n      }\n\n      function logout(err, token) {\n        request(app)\n          .post('/test-users/logout')\n          .set('Authorization', token)\n          .expect(204)\n          .end(verify(token, done));\n      }\n    });\n\n    it('fails when accessToken is not provided', function(done) {\n      User.logout(undefined, function(err) {\n        expect(err).to.have.property('message');\n        expect(err).to.have.property('statusCode', 401);\n        done();\n      });\n    });\n\n    it('fails when accessToken is not found', function(done) {\n      User.logout('expired-access-token', function(err) {\n        expect(err).to.have.property('message');\n        expect(err).to.have.property('statusCode', 401);\n        done();\n      });\n    });\n\n    function verify(token, done) {\n      assert(token);\n\n      return function(err) {\n        if (err) return done(err);\n\n        AccessToken.findById(token, function(err, accessToken) {\n          assert(!accessToken, 'accessToken should not exist after logging out');\n\n          done(err);\n        });\n      };\n    }\n  });\n\n  describe('user.hasPassword(plain, fn)', function() {\n    it('Determine if the password matches the stored password', function(done) {\n      const u = new User({username: 'foo', password: 'bar'});\n      u.hasPassword('bar', function(err, isMatch) {\n        assert(isMatch, 'password doesnt match');\n\n        done();\n      });\n    });\n\n    it('Determine if the password matches the stored password - promise variant', function(done) {\n      const u = new User({username: 'foo', password: 'bar'});\n      u.hasPassword('bar')\n        .then(function(isMatch) {\n          assert(isMatch, 'password doesnt match');\n\n          done();\n        })\n        .catch(function(err) {\n          done(err);\n        });\n    });\n\n    it('should match a password when saved', function(done) {\n      const u = new User({username: 'a', password: 'b', email: 'z@z.net'});\n\n      u.save(function(err, user) {\n        User.findById(user.pk, function(err, uu) {\n          uu.hasPassword('b', function(err, isMatch) {\n            assert(isMatch);\n\n            done();\n          });\n        });\n      });\n    });\n\n    it('should match a password after it is changed', function(done) {\n      User.create({email: 'foo@baz.net', username: 'bat', password: 'baz'}, function(err, user) {\n        User.findById(user.pk, function(err, foundUser) {\n          assert(foundUser);\n          foundUser.hasPassword('baz', function(err, isMatch) {\n            assert(isMatch);\n            foundUser.password = 'baz2';\n            foundUser.save(function(err, updatedUser) {\n              updatedUser.hasPassword('baz2', function(err, isMatch) {\n                assert(isMatch);\n                User.findById(user.pk, function(err, uu) {\n                  uu.hasPassword('baz2', function(err, isMatch) {\n                    assert(isMatch);\n\n                    done();\n                  });\n                });\n              });\n            });\n          });\n        });\n      });\n    });\n  });\n\n  describe('User.changePassword()', () => {\n    let userId, currentPassword;\n    beforeEach(givenUserIdAndPassword);\n\n    it('changes the password - callback-style', done => {\n      User.changePassword(userId, currentPassword, 'new password', (err) => {\n        if (err) return done(err);\n        expect(arguments.length, 'changePassword callback arguments length')\n          .to.be.at.most(1);\n\n        User.findById(userId, (err, user) => {\n          if (err) return done(err);\n          user.hasPassword('new password', (err, isMatch) => {\n            if (err) return done(err);\n            expect(isMatch, 'user has new password').to.be.true();\n            done();\n          });\n        });\n      });\n    });\n\n    it('changes the password - Promise-style', () => {\n      return User.changePassword(userId, currentPassword, 'new password')\n        .then(() => {\n          expect(arguments.length, 'changePassword promise resolution')\n            .to.equal(0);\n          return User.findById(userId);\n        })\n        .then(user => {\n          return user.hasPassword('new password');\n        })\n        .then(isMatch => {\n          expect(isMatch, 'user has new password').to.be.true();\n        });\n    });\n\n    it('changes the password - instance method', () => {\n      return validCredentialsUser.changePassword(currentPassword, 'new password')\n        .then(() => {\n          expect(arguments.length, 'changePassword promise resolution')\n            .to.equal(0);\n          return User.findById(userId);\n        })\n        .then(user => {\n          return user.hasPassword('new password');\n        })\n        .then(isMatch => {\n          expect(isMatch, 'user has new password').to.be.true();\n        });\n    });\n\n    it('fails when current password does not match', () => {\n      return User.changePassword(userId, 'bad password', 'new password').then(\n        success => { throw new Error('changePassword should have failed'); },\n        err => {\n          // workaround for chai problem\n          //   object tested must be an array, an object,\n          //   or a string, but error given\n          const props = Object.assign({}, err);\n          expect(props).to.contain({\n            code: 'INVALID_PASSWORD',\n            statusCode: 400,\n          });\n        },\n      );\n    });\n\n    it('fails with 401 for unknown user id', () => {\n      return User.changePassword('unknown-id', 'pass', 'pass').then(\n        success => { throw new Error('changePassword should have failed'); },\n        err => {\n          // workaround for chai problem\n          //   object tested must be an array, an object, or a string,\n          //   but error given\n          const props = Object.assign({}, err);\n          expect(props).to.contain({\n            code: 'USER_NOT_FOUND',\n            statusCode: 401,\n          });\n        },\n      );\n    });\n\n    it('forwards the \"options\" argument', () => {\n      const options = {testFlag: true};\n      const observedOptions = [];\n\n      saveObservedOptionsForHook('access');\n      saveObservedOptionsForHook('before save');\n\n      return User.changePassword(userId, currentPassword, 'new', options)\n        .then(() => expect(observedOptions).to.eql([\n          // findById\n          {hook: 'access', testFlag: true},\n\n          // \"before save\" hook prepareForTokenInvalidation\n          {hook: 'access', setPassword: true, testFlag: true},\n\n          // updateAttributes\n          {hook: 'before save', setPassword: true, testFlag: true},\n\n          // validate uniqueness of User.email\n          {hook: 'access', setPassword: true, testFlag: true},\n        ]));\n\n      function saveObservedOptionsForHook(name) {\n        User.observe(name, (ctx, next) => {\n          observedOptions.push(Object.assign({hook: name}, ctx.options));\n          next();\n        });\n      }\n    });\n\n    function givenUserIdAndPassword() {\n      userId = validCredentialsUser.id;\n      currentPassword = validCredentials.password;\n    }\n  });\n\n  describe('User.setPassword()', () => {\n    let userId;\n    beforeEach(givenUserId);\n\n    it('changes the password - callback-style', done => {\n      User.setPassword(userId, 'new password', (err) => {\n        if (err) return done(err);\n        expect(arguments.length, 'changePassword callback arguments length')\n          .to.be.at.most(1);\n\n        User.findById(userId, (err, user) => {\n          if (err) return done(err);\n          user.hasPassword('new password', (err, isMatch) => {\n            if (err) return done(err);\n            expect(isMatch, 'user has new password').to.be.true();\n            done();\n          });\n        });\n      });\n    });\n\n    it('changes the password - Promise-style', () => {\n      return User.setPassword(userId, 'new password')\n        .then(() => {\n          expect(arguments.length, 'changePassword promise resolution')\n            .to.equal(0);\n          return User.findById(userId);\n        })\n        .then(user => {\n          return user.hasPassword('new password');\n        })\n        .then(isMatch => {\n          expect(isMatch, 'user has new password').to.be.true();\n        });\n    });\n\n    it('fails with 401 for unknown users', () => {\n      return User.setPassword('unknown-id', 'pass').then(\n        success => { throw new Error('setPassword should have failed'); },\n        err => {\n          // workaround for chai problem\n          //   object tested must be an array, an object, or a string,\n          //   but error given\n          const props = Object.assign({}, err);\n          expect(props).to.contain({\n            code: 'USER_NOT_FOUND',\n            statusCode: 401,\n          });\n        },\n      );\n    });\n\n    it('forwards the \"options\" argument', () => {\n      const options = {testFlag: true};\n      const observedOptions = [];\n\n      saveObservedOptionsForHook('access');\n      saveObservedOptionsForHook('before save');\n\n      return User.setPassword(userId, 'new', options)\n        .then(() => expect(observedOptions).to.eql([\n          // findById\n          {hook: 'access', testFlag: true},\n\n          // \"before save\" hook prepareForTokenInvalidation\n          {hook: 'access', setPassword: true, testFlag: true},\n\n          // updateAttributes\n          {hook: 'before save', setPassword: true, testFlag: true},\n\n          // validate uniqueness of User.email\n          {hook: 'access', setPassword: true, testFlag: true},\n        ]));\n\n      function saveObservedOptionsForHook(name) {\n        User.observe(name, (ctx, next) => {\n          observedOptions.push(Object.assign({hook: name}, ctx.options));\n          next();\n        });\n      }\n    });\n\n    function givenUserId() {\n      userId = validCredentialsUser.id;\n    }\n  });\n\n  describe('Identity verification', function() {\n    describe('user.verify(verifyOptions, options, cb)', function() {\n      const ctxOptions = {testFlag: true};\n      let verifyOptions;\n\n      beforeEach(function() {\n        // reset verifyOptions before each test\n        verifyOptions = {\n          type: 'email',\n          from: 'noreply@example.org',\n        };\n      });\n\n      describe('User.getVerifyOptions()', function() {\n        it('returns default verify options', function(done) {\n          const verifyOptions = User.getVerifyOptions();\n          expect(verifyOptions).to.eql({\n            type: 'email',\n            from: 'noreply@example.com',\n          });\n          done();\n        });\n\n        it('handles custom verify options defined via model.settings', function(done) {\n          User.settings.verifyOptions = {\n            type: 'email',\n            from: 'test@example.com',\n          };\n          const verifyOptions = User.getVerifyOptions();\n          expect(verifyOptions).to.eql(User.settings.verifyOptions);\n          done();\n        });\n\n        it('returns same verifyOptions after verify user model', () => {\n          const defaultOptions = {\n            type: 'email',\n            from: 'test@example.com',\n          };\n          const verifyOptions = Object.assign({}, defaultOptions);\n          const user = new User({\n            email: 'example@example.com',\n            password: 'pass',\n            verificationToken: 'example-token',\n          });\n          return user\n            .verify(verifyOptions)\n            .then(res => expect(verifyOptions).to.eql(defaultOptions));\n        });\n\n        it('getVerifyOptions() always returns the same', () => {\n          const defaultOptions = {\n            type: 'email',\n            from: 'test@example.com',\n          };\n          User.settings.verifyOptions = Object.assign({}, defaultOptions);\n          const verifyOptions = User.getVerifyOptions();\n          verifyOptions.from = 'newTest@example.com';\n          verifyOptions.test = 'test';\n          expect(User.getVerifyOptions()).to.eql(defaultOptions);\n        });\n\n        it('can be extended by user', function(done) {\n          User.getVerifyOptions = function() {\n            const base = User.base.getVerifyOptions();\n            return Object.assign({}, base, {\n              redirect: '/redirect',\n            });\n          };\n          const verifyOptions = User.getVerifyOptions();\n          expect(verifyOptions).to.eql({\n            type: 'email',\n            from: 'noreply@example.com',\n            redirect: '/redirect',\n          });\n          done();\n        });\n      });\n\n      it('verifies a user\\'s email address', function(done) {\n        User.afterRemote('create', function(ctx, user, next) {\n          assert(user, 'afterRemote should include result');\n\n          user.verify(verifyOptions, function(err, result) {\n            assert(result.email);\n            assert(result.email.response);\n            assert(result.token);\n            const msg = result.email.response.toString('utf-8');\n            assert(~msg.indexOf('/api/test-users/confirm'));\n            assert(~msg.indexOf('To: bar@bat.com'));\n\n            done();\n          });\n        });\n\n        request(app)\n          .post('/test-users')\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .send({email: 'bar@bat.com', password: 'bar'})\n          .end(function(err, res) {\n            if (err) return done(err);\n          });\n      });\n\n      it('verifies a user\\'s email address - promise variant', function(done) {\n        User.afterRemote('create', function(ctx, user, next) {\n          assert(user, 'afterRemote should include result');\n\n          user.verify(verifyOptions)\n            .then(function(result) {\n              assert(result.email);\n              assert(result.email.response);\n              assert(result.token);\n              const msg = result.email.response.toString('utf-8');\n              assert(~msg.indexOf('/api/test-users/confirm'));\n              assert(~msg.indexOf('To: bar@bat.com'));\n\n              done();\n            })\n            .catch(function(err) {\n              done(err);\n            });\n        });\n\n        request(app)\n          .post('/test-users')\n          .send({email: 'bar@bat.com', password: 'bar'})\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .end(function(err, res) {\n            if (err) return done(err);\n          });\n      });\n\n      it('verifies a user\\'s email address with custom header', function(done) {\n        User.afterRemote('create', function(ctx, user, next) {\n          assert(user, 'afterRemote should include result');\n\n          verifyOptions.headers = {'message-id': 'custom-header-value'};\n\n          user.verify(verifyOptions, function(err, result) {\n            assert(result.email);\n            assert.equal(result.email.messageId, 'custom-header-value');\n\n            done();\n          });\n        });\n\n        request(app)\n          .post('/test-users')\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .send({email: 'bar@bat.com', password: 'bar'})\n          .end(function(err, res) {\n            if (err) return done(err);\n          });\n      });\n\n      it('verifies a user\\'s email address with custom template function', function(done) {\n        User.afterRemote('create', function(ctx, user, next) {\n          assert(user, 'afterRemote should include result');\n\n          verifyOptions.templateFn = function(verifyOptions, cb) {\n            cb(null, 'custom template  - verify url: ' + verifyOptions.verifyHref);\n          };\n\n          user.verify(verifyOptions, function(err, result) {\n            assert(result.email);\n            assert(result.email.response);\n            assert(result.token);\n            const msg = result.email.response.toString('utf-8');\n            assert(~msg.indexOf('/api/test-users/confirm'));\n            assert(~msg.indexOf('custom template'));\n            assert(~msg.indexOf('To: bar@bat.com'));\n\n            done();\n          });\n        });\n\n        request(app)\n          .post('/test-users')\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .send({email: 'bar@bat.com', password: 'bar'})\n          .end(function(err, res) {\n            if (err) return done(err);\n          });\n      });\n\n      it('converts uid value to string', function(done) {\n        const idString = '58be263abc88dd483956030a';\n        let actualVerifyHref;\n\n        User.afterRemote('create', function(ctx, user, next) {\n          assert(user, 'afterRemote should include result');\n\n          verifyOptions.templateFn = function(verifyOptions, cb) {\n            actualVerifyHref = verifyOptions.verifyHref;\n            cb(null, 'dummy body');\n          };\n\n          // replace the string id with an object\n          // TODO: find a better way to do this\n          Object.defineProperty(user, 'pk', {\n            get: function() { return this.__data.pk; },\n            set: function(value) { this.__data.pk = value; },\n          });\n          user.pk = {toString: function() { return idString; }};\n\n          user.verify(verifyOptions, function(err, result) {\n            expect(result.uid).to.exist().and.be.an('object');\n            expect(result.uid.toString()).to.equal(idString);\n            const parsed = url.parse(actualVerifyHref, true);\n            expect(parsed.query.uid, 'uid query field').to.eql(idString);\n            done();\n          });\n        });\n\n        request(app)\n          .post('/test-users')\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .send({email: 'bar@bat.com', password: 'bar', pk: idString})\n          .end(function(err, res) {\n            if (err) return done(err);\n          });\n      });\n\n      it('verifies a user\\'s email address with custom token generator', function(done) {\n        User.afterRemote('create', function(ctx, user, next) {\n          assert(user, 'afterRemote should include result');\n\n          verifyOptions.generateVerificationToken = function(user, cb) {\n            assert(user);\n            assert.equal(user.email, 'bar@bat.com');\n            assert(cb);\n            assert.equal(typeof cb, 'function');\n            // let's ensure async execution works on this one\n            process.nextTick(function() {\n              cb(null, 'token-123456');\n            });\n          };\n\n          user.verify(verifyOptions, function(err, result) {\n            assert(result.email);\n            assert(result.email.response);\n            assert(result.token);\n            assert.equal(result.token, 'token-123456');\n            const msg = result.email.response.toString('utf-8');\n            assert(~msg.indexOf('token-123456'));\n\n            done();\n          });\n        });\n\n        request(app)\n          .post('/test-users')\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .send({email: 'bar@bat.com', password: 'bar'})\n          .end(function(err, res) {\n            if (err) return done(err);\n          });\n      });\n\n      it('fails if custom token generator returns error', function(done) {\n        User.afterRemote('create', function(ctx, user, next) {\n          assert(user, 'afterRemote should include result');\n\n          verifyOptions.generateVerificationToken = function(user, cb) {\n            // let's ensure async execution works on this one\n            process.nextTick(function() {\n              cb(new Error('Fake error'));\n            });\n          };\n\n          user.verify(verifyOptions, function(err, result) {\n            assert(err);\n            assert.equal(err.message, 'Fake error');\n            assert.equal(result, undefined);\n\n            done();\n          });\n        });\n\n        request(app)\n          .post('/test-users')\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .send({email: 'bar@bat.com', password: 'bar'})\n          .end(function(err, res) {\n            if (err) return done(err);\n          });\n      });\n\n      describe('Verification link port-squashing', function() {\n        it('does not squash non-80 ports for HTTP links', function(done) {\n          User.afterRemote('create', function(ctx, user, next) {\n            assert(user, 'afterRemote should include result');\n\n            Object.assign(verifyOptions, {\n              host: 'myapp.org',\n              port: 3000,\n            });\n\n            user.verify(verifyOptions, function(err, result) {\n              const msg = result.email.response.toString('utf-8');\n              assert(~msg.indexOf('http://myapp.org:3000/'));\n\n              done();\n            });\n          });\n\n          request(app)\n            .post('/test-users')\n            .expect('Content-Type', /json/)\n            .expect(200)\n            .send({email: 'bar@bat.com', password: 'bar'})\n            .end(function(err, res) {\n              if (err) return done(err);\n            });\n        });\n\n        it('squashes port 80 for HTTP links', function(done) {\n          User.afterRemote('create', function(ctx, user, next) {\n            assert(user, 'afterRemote should include result');\n\n            Object.assign(verifyOptions, {\n              host: 'myapp.org',\n              port: 80,\n            });\n\n            user.verify(verifyOptions, function(err, result) {\n              const msg = result.email.response.toString('utf-8');\n              assert(~msg.indexOf('http://myapp.org/'));\n\n              done();\n            });\n          });\n\n          request(app)\n            .post('/test-users')\n            .expect('Content-Type', /json/)\n            .expect(200)\n            .send({email: 'bar@bat.com', password: 'bar'})\n            .end(function(err, res) {\n              if (err) return done(err);\n            });\n        });\n\n        it('does not squash non-443 ports for HTTPS links', function(done) {\n          User.afterRemote('create', function(ctx, user, next) {\n            assert(user, 'afterRemote should include result');\n\n            Object.assign(verifyOptions, {\n              host: 'myapp.org',\n              port: 3000,\n              protocol: 'https',\n            });\n\n            user.verify(verifyOptions, function(err, result) {\n              const msg = result.email.response.toString('utf-8');\n              assert(~msg.indexOf('https://myapp.org:3000/'));\n\n              done();\n            });\n          });\n\n          request(app)\n            .post('/test-users')\n            .expect('Content-Type', /json/)\n            .expect(200)\n            .send({email: 'bar@bat.com', password: 'bar'})\n            .end(function(err, res) {\n              if (err) return done(err);\n            });\n        });\n\n        it('squashes port 443 for HTTPS links', function(done) {\n          User.afterRemote('create', function(ctx, user, next) {\n            assert(user, 'afterRemote should include result');\n\n            Object.assign(verifyOptions, {\n              host: 'myapp.org',\n              protocol: 'https',\n              port: 443,\n            });\n\n            user.verify(verifyOptions, function(err, result) {\n              const msg = result.email.response.toString('utf-8');\n              assert(~msg.indexOf('https://myapp.org/'));\n\n              done();\n            });\n          });\n\n          request(app)\n            .post('/test-users')\n            .expect('Content-Type', /json/)\n            .expect(200)\n            .send({email: 'bar@bat.com', password: 'bar'})\n            .end(function(err, res) {\n              if (err) return done(err);\n            });\n        });\n      });\n\n      it('hides verification tokens from user JSON', function(done) {\n        const user = new User({\n          email: 'bar@bat.com',\n          password: 'bar',\n          verificationToken: 'a-token',\n        });\n        const data = user.toJSON();\n        assert(!('verificationToken' in data));\n\n        done();\n      });\n\n      it('squashes \"//\" when restApiRoot is \"/\"', function(done) {\n        let emailBody;\n        User.afterRemote('create', function(ctx, user, next) {\n          assert(user, 'afterRemote should include result');\n\n          Object.assign(verifyOptions, {\n            host: 'myapp.org',\n            port: 3000,\n            restApiRoot: '/',\n          });\n\n          user.verify(verifyOptions, function(err, result) {\n            if (err) return next(err);\n            emailBody = result.email.response.toString('utf-8');\n            next();\n          });\n        });\n\n        request(app)\n          .post('/test-users')\n          .expect('Content-Type', /json/)\n          .expect(200)\n          .send({email: 'user@example.com', password: 'pass'})\n          .end(function(err, res) {\n            if (err) return done(err);\n            expect(emailBody)\n              .to.contain('http://myapp.org:3000/test-users/confirm');\n            done();\n          });\n      });\n\n      it('removes \"verifyOptions.template\" from Email payload', function() {\n        const MailerMock = {\n          send: function(verifyOptions, cb) { cb(null, verifyOptions); },\n        };\n        verifyOptions.mailer = MailerMock;\n\n        return user.verify(verifyOptions)\n          .then(function(result) {\n            expect(result.email).to.not.have.property('template');\n          });\n      });\n\n      it('allows hash fragment in redirectUrl', function() {\n        let actualVerifyHref;\n\n        Object.assign(verifyOptions, {\n          redirect: '#/some-path?a=1&b=2',\n          templateFn: (verifyOptions, cb) => {\n            actualVerifyHref = verifyOptions.verifyHref;\n            cb(null, 'dummy body');\n          },\n        });\n\n        return user.verify(verifyOptions)\n          .then(() => actualVerifyHref)\n          .then(verifyHref => {\n            const parsed = url.parse(verifyHref, true);\n            expect(parsed.query.redirect, 'redirect query')\n              .to.equal('#/some-path?a=1&b=2');\n          });\n      });\n\n      it('verifies that verifyOptions.templateFn receives verifyOptions.verificationToken',\n        function() {\n          let actualVerificationToken;\n\n          Object.assign(verifyOptions, {\n            redirect: '#/some-path?a=1&b=2',\n            templateFn: (verifyOptions, cb) => {\n              actualVerificationToken = verifyOptions.verificationToken;\n              cb(null, 'dummy body');\n            },\n          });\n\n          return user.verify(verifyOptions)\n            .then(() => actualVerificationToken)\n            .then(token => {\n              expect(token).to.exist();\n            });\n        });\n\n      it('forwards the \"options\" argument to user.save() ' +\n        'when adding verification token', function() {\n        let onBeforeSaveCtx = {};\n\n        // before save operation hook to capture remote ctx when saving\n        // verification token in user instance\n        User.observe('before save', function(ctx, next) {\n          onBeforeSaveCtx = ctx || {};\n          next();\n        });\n\n        return user.verify(verifyOptions, ctxOptions)\n          .then(() => {\n            // not checking equality since other properties are added by user.save()\n            expect(onBeforeSaveCtx.options).to.contain({testFlag: true});\n          });\n      });\n\n      it('forwards the \"options\" argument to a custom templateFn function', function() {\n        let templateFnOptions;\n\n        // custom templateFn function accepting the options argument\n        verifyOptions.templateFn = (verifyOptions, options, cb) => {\n          templateFnOptions = options;\n          cb(null, 'dummy body');\n        };\n\n        return user.verify(verifyOptions, ctxOptions)\n          .then(() => {\n            // not checking equality since other properties are added by user.save()\n            expect(templateFnOptions).to.contain({testFlag: true});\n          });\n      });\n\n      it('forwards the \"options\" argment to a custom token generator function', function() {\n        let generateTokenOptions;\n\n        // custom generateVerificationToken function accepting the options argument\n        verifyOptions.generateVerificationToken = (user, options, cb) => {\n          generateTokenOptions = options;\n          cb(null, 'dummy token');\n        };\n\n        return user.verify(verifyOptions, ctxOptions)\n          .then(() => {\n            // not checking equality since other properties are added by user.save()\n            expect(generateTokenOptions).to.contain({testFlag: true});\n          });\n      });\n\n      it('forwards the \"options\" argument to a custom mailer function', function() {\n        let mailerOptions;\n\n        // custom mailer function accepting the options argument\n        const mailer = function() {};\n        mailer.send = function(verifyOptions, options, cb) {\n          mailerOptions = options;\n          cb(null, 'dummy result');\n        };\n        verifyOptions.mailer = mailer;\n\n        return user.verify(verifyOptions, ctxOptions)\n          .then(() => {\n            // not checking equality since other properties are added by user.save()\n            expect(mailerOptions).to.contain({testFlag: true});\n          });\n      });\n\n      it('handles the case when remote method \"confirm\" is disabled', () => {\n        let actualVerifyHref;\n        const VERIFY_HREF = 'http://example.com/a-verify-url';\n\n        Object.assign(verifyOptions, {\n          verifyHref: VERIFY_HREF,\n          templateFn: (options, cb) => {\n            actualVerifyHref = options.verifyHref;\n            cb(null, 'dummy body');\n          },\n        });\n\n        User.disableRemoteMethodByName('confirm');\n\n        return user.verify(verifyOptions)\n          .then(() => {\n            expect(actualVerifyHref.substring(0, VERIFY_HREF.length + 1))\n              .to.equal(`${VERIFY_HREF}?`);\n          });\n      });\n\n      function givenUser() {\n        return User.create({email: 'test@example.com', password: 'pass'})\n          .then(u => user = u);\n      }\n\n      it('is called over REST method /User/:id/verify', function() {\n        return User.create({email: 'bar@bat.com', password: 'bar'})\n          .then(user => {\n            return request(app)\n              .post('/test-users/' + user.pk + '/verify')\n              .expect('Content-Type', /json/)\n              // we already tested before that User.verify(id) works correctly\n              // having the remote method returning 204 is enough to make sure\n              // User.verify() was called successfully\n              .expect(204);\n          });\n      });\n\n      it('fails over REST method /User/:id/verify with invalid user id', function() {\n        return request(app)\n          .post('/test-users/' + 'invalid-id' + '/verify')\n          .expect('Content-Type', /json/)\n          .expect(404);\n      });\n    });\n\n    describe('User.confirm(options, fn)', function() {\n      let verifyOptions;\n\n      function testConfirm(testFunc, done) {\n        User.afterRemote('create', function(ctx, user, next) {\n          assert(user, 'afterRemote should include result');\n\n          verifyOptions = {\n            type: 'email',\n            to: user.email,\n            from: 'noreply@myapp.org',\n            redirect: 'http://foo.com/bar',\n            protocol: ctx.req.protocol,\n            host: ctx.req.get('host'),\n          };\n\n          user.verify(verifyOptions, function(err, result) {\n            if (err) return done(err);\n\n            testFunc(result, done);\n          });\n        });\n\n        request(app)\n          .post('/test-users')\n          .expect('Content-Type', /json/)\n          .expect(302)\n          .send({email: 'bar@bat.com', password: 'bar'})\n          .end(function(err, res) {\n            if (err) return done(err);\n          });\n      }\n\n      it('Confirm a user verification', function(done) {\n        testConfirm(function(result, done) {\n          request(app)\n            .get('/test-users/confirm?uid=' + (result.uid) +\n              '&token=' + encodeURIComponent(result.token) +\n              '&redirect=' + encodeURIComponent(verifyOptions.redirect))\n            .expect(302)\n            .end(function(err, res) {\n              if (err) return done(err);\n\n              done();\n            });\n        }, done);\n      });\n\n      it('sets verificationToken to null after confirmation', function(done) {\n        testConfirm(function(result, done) {\n          User.confirm(result.uid, result.token, false, function(err) {\n            if (err) return done(err);\n\n            // Verify by loading user data stored in the datasource\n            User.findById(result.uid, function(err, user) {\n              if (err) return done(err);\n              expect(user).to.have.property('verificationToken', null);\n              done();\n            });\n          });\n        }, done);\n      });\n\n      it('Should report 302 when redirect url is set', function(done) {\n        testConfirm(function(result, done) {\n          request(app)\n            .get('/test-users/confirm?uid=' + (result.uid) +\n              '&token=' + encodeURIComponent(result.token) +\n              '&redirect=http://foo.com/bar')\n            .expect(302)\n            .expect('Location', 'http://foo.com/bar')\n            .end(done);\n        }, done);\n      });\n\n      it('Should report 204 when redirect url is not set', function(done) {\n        testConfirm(function(result, done) {\n          request(app)\n            .get('/test-users/confirm?uid=' + (result.uid) +\n              '&token=' + encodeURIComponent(result.token))\n            .expect(204)\n            .end(done);\n        }, done);\n      });\n\n      it('Report error for invalid user id during verification', function(done) {\n        testConfirm(function(result, done) {\n          request(app)\n            .get('/test-users/confirm?uid=' + (result.uid + '_invalid') +\n               '&token=' + encodeURIComponent(result.token) +\n               '&redirect=' + encodeURIComponent(verifyOptions.redirect))\n            .expect(404)\n            .end(function(err, res) {\n              if (err) return done(err);\n\n              const errorResponse = res.body.error;\n              assert(errorResponse);\n              assert.equal(errorResponse.code, 'USER_NOT_FOUND');\n\n              done();\n            });\n        }, done);\n      });\n\n      it('Report error for invalid token during verification', function(done) {\n        testConfirm(function(result, done) {\n          request(app)\n            .get('/test-users/confirm?uid=' + result.uid +\n              '&token=' + encodeURIComponent(result.token) + '_invalid' +\n              '&redirect=' + encodeURIComponent(verifyOptions.redirect))\n            .expect(400)\n            .end(function(err, res) {\n              if (err) return done(err);\n\n              const errorResponse = res.body.error;\n              assert(errorResponse);\n              assert.equal(errorResponse.code, 'INVALID_TOKEN');\n\n              done();\n            });\n        }, done);\n      });\n    });\n  });\n\n  describe('Password Reset', function() {\n    describe('User.resetPassword(options, cb)', function() {\n      const options = {\n        email: 'foo@bar.com',\n        redirect: 'http://foobar.com/reset-password',\n      };\n\n      it('Requires email address to reset password', function(done) {\n        User.resetPassword({ }, function(err) {\n          assert(err);\n          assert.equal(err.code, 'EMAIL_REQUIRED');\n\n          done();\n        });\n      });\n\n      it('Requires email address to reset password - promise variant', function(done) {\n        User.resetPassword({ })\n          .then(function() {\n            throw new Error('Error should NOT be thrown');\n          })\n          .catch(function(err) {\n            assert(err);\n            assert.equal(err.code, 'EMAIL_REQUIRED');\n\n            done();\n          });\n      });\n\n      it('Reports when email is not found', function(done) {\n        User.resetPassword({email: 'unknown@email.com'}, function(err) {\n          assert(err);\n          assert.equal(err.code, 'EMAIL_NOT_FOUND');\n          assert.equal(err.statusCode, 404);\n\n          done();\n        });\n      });\n\n      it('Checks that options exist', function(done) {\n        let calledBack = false;\n\n        User.resetPassword(options, function() {\n          calledBack = true;\n        });\n\n        User.once('resetPasswordRequest', function(info) {\n          assert(info.options);\n          assert.equal(info.options, options);\n          assert(calledBack);\n\n          done();\n        });\n      });\n\n      it('Creates a temp accessToken to allow a user to change password', function(done) {\n        let calledBack = false;\n\n        User.resetPassword({\n          email: options.email,\n        }, function() {\n          calledBack = true;\n        });\n\n        User.once('resetPasswordRequest', function(info) {\n          assert(info.email);\n          assert(info.accessToken);\n          assert(info.accessToken.id);\n          assert.equal(info.accessToken.ttl / 60, 15);\n          assert(calledBack);\n          info.accessToken.user(function(err, user) {\n            if (err) return done(err);\n\n            assert.equal(user.email, options.email);\n\n            done();\n          });\n        });\n      });\n\n      it('calls createAccessToken() to create the token', function(done) {\n        User.prototype.createAccessToken = function(ttl, cb) {\n          cb(null, new AccessToken({id: 'custom-token'}));\n        };\n\n        User.resetPassword({email: options.email}, function() {});\n\n        User.once('resetPasswordRequest', function(info) {\n          expect(info.accessToken.id).to.equal('custom-token');\n          done();\n        });\n      });\n\n      it('Password reset over REST rejected without email address', function(done) {\n        request(app)\n          .post('/test-users/reset')\n          .expect('Content-Type', /json/)\n          .expect(400)\n          .send({ })\n          .end(function(err, res) {\n            if (err) return done(err);\n\n            const errorResponse = res.body.error;\n            assert(errorResponse);\n            assert.equal(errorResponse.code, 'EMAIL_REQUIRED');\n\n            done();\n          });\n      });\n\n      it('Password reset over REST requires email address', function(done) {\n        request(app)\n          .post('/test-users/reset')\n          .expect('Content-Type', /json/)\n          .expect(204)\n          .send({email: options.email})\n          .end(function(err, res) {\n            if (err) return done(err);\n\n            assert.deepEqual(res.body, '');\n\n            done();\n          });\n      });\n\n      it('creates token that allows patching User with new password', () => {\n        return triggerPasswordReset(options.email)\n          .then(info => {\n            // Make a REST request to change the password\n            return request(app)\n              .patch(`/test-users/${info.user.id}`)\n              .set('Authorization', info.accessToken.id)\n              .send({password: 'new-pass'})\n              .expect(200);\n          })\n          .then(() => {\n            // Call login to verify the password was changed\n            const credentials = {email: options.email, password: 'new-pass'};\n            return User.login(credentials);\n          });\n      });\n\n      it('creates token that allows calling other endpoints too', () => {\n        // Setup a test method that can be executed by $owner only\n        User.prototype.testMethod = function(cb) { cb(null, 'ok'); };\n        User.remoteMethod('prototype.testMethod', {\n          returns: {arg: 'status', type: 'string'},\n          http: {verb: 'get', path: '/test'},\n        });\n        User.settings.acls.push({\n          principalType: 'ROLE',\n          principalId: '$owner',\n          permission: 'ALLOW',\n          property: 'testMethod',\n        });\n\n        return triggerPasswordReset(options.email)\n          .then(info => request(app)\n            .get(`/test-users/${info.user.id}/test`)\n            .set('Authorization', info.accessToken.id)\n            .expect(200));\n      });\n\n      describe('User.resetPassword(options, cb) requiring realm', function() {\n        let realmUser;\n\n        beforeEach(function(done) {\n          User.create(validCredentialsWithRealm, function(err, u) {\n            if (err) return done(err);\n\n            realmUser = u;\n            done();\n          });\n        });\n\n        it('Reports when email is not found in realm', function(done) {\n          User.resetPassword({\n            email: realmUser.email,\n            realm: 'unknown',\n          }, function(err) {\n            assert(err);\n            assert.equal(err.code, 'EMAIL_NOT_FOUND');\n            assert.equal(err.statusCode, 404);\n\n            done();\n          });\n        });\n\n        it('Creates a temp accessToken to allow user in realm to change password', function(done) {\n          let calledBack = false;\n\n          User.resetPassword({\n            email: realmUser.email,\n            realm: realmUser.realm,\n          }, function() {\n            calledBack = true;\n          });\n\n          User.once('resetPasswordRequest', function(info) {\n            assert(info.email);\n            assert(info.accessToken);\n            assert(info.accessToken.id);\n            assert.equal(info.accessToken.ttl / 60, 15);\n            assert(calledBack);\n            info.accessToken.user(function(err, user) {\n              if (err) return done(err);\n\n              assert.equal(user.email, realmUser.email);\n\n              done();\n            });\n          });\n        });\n      });\n    });\n  });\n\n  describe('AccessToken (session) invalidation', function() {\n    let user, originalUserToken1, originalUserToken2, newUserCreated;\n    const currentEmailCredentials = {email: 'original@example.com', password: 'bar'};\n    const updatedEmailCredentials = {email: 'updated@example.com', password: 'bar'};\n    const newUserCred = {email: 'newuser@example.com', password: 'newpass'};\n\n    beforeEach('create user then login', function createAndLogin(done) {\n      async.series([\n        function createUserWithOriginalEmail(next) {\n          User.create(currentEmailCredentials, function(err, userCreated) {\n            if (err) return next(err);\n            user = userCreated;\n            next();\n          });\n        },\n        function firstLoginWithOriginalEmail(next) {\n          User.login(currentEmailCredentials, function(err, accessToken1) {\n            if (err) return next(err);\n            assert(accessToken1.userId);\n            originalUserToken1 = accessToken1;\n            next();\n          });\n        },\n        function secondLoginWithOriginalEmail(next) {\n          User.login(currentEmailCredentials, function(err, accessToken2) {\n            if (err) return next(err);\n            assert(accessToken2.userId);\n            originalUserToken2 = accessToken2;\n            next();\n          });\n        },\n      ], done);\n    });\n\n    it('invalidates sessions when email is changed using `updateAttributes`', function(done) {\n      user.updateAttributes(\n        {email: updatedEmailCredentials.email},\n        function(err, userInstance) {\n          if (err) return done(err);\n          assertNoAccessTokens(done);\n        },\n      );\n    });\n\n    it('invalidates sessions after `replaceAttributes`', function(done) {\n      // The way how the invalidation is implemented now, all sessions\n      // are invalidated on a full replace\n      user.replaceAttributes(currentEmailCredentials, function(err, userInstance) {\n        if (err) return done(err);\n        assertNoAccessTokens(done);\n      });\n    });\n\n    it('invalidates sessions when email is changed using `updateOrCreate`', function(done) {\n      User.updateOrCreate({\n        pk: user.pk,\n        email: updatedEmailCredentials.email,\n      }, function(err, userInstance) {\n        if (err) return done(err);\n        assertNoAccessTokens(done);\n      });\n    });\n\n    it('invalidates sessions after `replaceById`', function(done) {\n      // The way how the invalidation is implemented now, all sessions\n      // are invalidated on a full replace\n      User.replaceById(user.pk, currentEmailCredentials, function(err, userInstance) {\n        if (err) return done(err);\n        assertNoAccessTokens(done);\n      });\n    });\n\n    it('invalidates sessions after `replaceOrCreate`', function(done) {\n      // The way how the invalidation is implemented now, all sessions\n      // are invalidated on a full replace\n      User.replaceOrCreate({\n        pk: user.pk,\n        email: currentEmailCredentials.email,\n        password: currentEmailCredentials.password,\n      }, function(err, userInstance) {\n        if (err) return done(err);\n        assertNoAccessTokens(done);\n      });\n    });\n\n    it('keeps sessions AS IS if firstName is added using `updateAttributes`', function(done) {\n      user.updateAttributes({'firstName': 'Janny'}, function(err, userInstance) {\n        if (err) return done(err);\n        assertPreservedTokens(done);\n      });\n    });\n\n    it('keeps sessions AS IS when calling save() with no changes', function(done) {\n      user.save(function(err) {\n        if (err) return done(err);\n        assertPreservedTokens(done);\n      });\n    });\n\n    it('keeps sessions AS IS if firstName is added using `updateOrCreate`', function(done) {\n      User.updateOrCreate({\n        pk: user.pk,\n        firstName: 'Loay',\n        email: currentEmailCredentials.email,\n      }, function(err, userInstance) {\n        if (err) return done(err);\n        assertPreservedTokens(done);\n      });\n    });\n\n    it('keeps sessions AS IS if a new user is created using `create`', function(done) {\n      async.series([\n        function(next) {\n          User.create(newUserCred, function(err, newUserInstance) {\n            if (err) return done(err);\n            newUserCreated = newUserInstance;\n            next();\n          });\n        },\n        function(next) {\n          User.login(newUserCred, function(err, newAccessToken) {\n            if (err) return done(err);\n            assert(newAccessToken.id);\n            assertPreservedTokens(next);\n          });\n        },\n      ], done);\n    });\n\n    it('keeps sessions AS IS if a new user is created using `updateOrCreate`', function(done) {\n      async.series([\n        function(next) {\n          User.create(newUserCred, function(err, newUserInstance2) {\n            if (err) return done(err);\n            newUserCreated = newUserInstance2;\n            next();\n          });\n        },\n        function(next) {\n          User.login(newUserCred, function(err, newAccessToken2) {\n            if (err) return done(err);\n            assert(newAccessToken2.id);\n            assertPreservedTokens(next);\n          });\n        },\n      ], done);\n    });\n\n    it('keeps sessions AS IS if non-email property is changed using updateAll', function(done) {\n      let userPartial;\n      async.series([\n        function createPartialUser(next) {\n          User.create(\n            {email: 'partial@example.com', password: 'pass1', age: 25},\n            function(err, partialInstance) {\n              if (err) return next(err);\n              userPartial = partialInstance;\n              next();\n            },\n          );\n        },\n        function loginPartiallUser(next) {\n          User.login({email: 'partial@example.com', password: 'pass1'}, function(err, ats) {\n            if (err) return next(err);\n            next();\n          });\n        },\n        function updatePartialUser(next) {\n          User.updateAll(\n            {pk: userPartial.pk},\n            {age: userPartial.age + 1},\n            function(err, info) {\n              if (err) return next(err);\n              next();\n            },\n          );\n        },\n        function verifyTokensOfPartialUser(next) {\n          AccessToken.find({where: {userId: userPartial.pk}}, function(err, tokens1) {\n            if (err) return next(err);\n            expect(tokens1.length).to.equal(1);\n            next();\n          });\n        },\n      ], done);\n    });\n\n    it('keeps sessions sessions when preserveAccessTokens is passed in options', function(done) {\n      user.updateAttributes(\n        {email: 'invalidateAccessTokens@example.com'},\n        {preserveAccessTokens: true},\n        function(err, userInstance) {\n          if (err) return done(err);\n          assertPreservedTokens(done);\n        },\n      );\n    });\n\n    it('preserves other users\\' sessions if their email is  untouched', function(done) {\n      let user1, user2, user3;\n      async.series([\n        function(next) {\n          User.create({email: 'user1@example.com', password: 'u1pass'}, function(err, u1) {\n            if (err) return done(err);\n            User.create({email: 'user2@example.com', password: 'u2pass'}, function(err, u2) {\n              if (err) return done(err);\n              User.create({email: 'user3@example.com', password: 'u3pass'}, function(err, u3) {\n                if (err) return done(err);\n                user1 = u1;\n                user2 = u2;\n                user3 = u3;\n                next();\n              });\n            });\n          });\n        },\n        function(next) {\n          User.login(\n            {email: 'user1@example.com', password: 'u1pass'},\n            function(err, accessToken1) {\n              if (err) return next(err);\n              User.login(\n                {email: 'user2@example.com', password: 'u2pass'},\n                function(err, accessToken2) {\n                  if (err) return next(err);\n                  User.login({email: 'user3@example.com', password: 'u3pass'},\n                    function(err, accessToken3) {\n                      if (err) return next(err);\n                      next();\n                    });\n                },\n              );\n            },\n          );\n        },\n        function(next) {\n          user2.updateAttribute('email', 'user2Update@b.com', function(err, userInstance) {\n            if (err) return next(err);\n            assert.equal(userInstance.email, 'user2Update@b.com');\n            next();\n          });\n        },\n        function(next) {\n          AccessToken.find({where: {userId: user1.pk}}, function(err, tokens1) {\n            if (err) return next(err);\n            AccessToken.find({where: {userId: user2.pk}}, function(err, tokens2) {\n              if (err) return next(err);\n              AccessToken.find({where: {userId: user3.pk}}, function(err, tokens3) {\n                if (err) return next(err);\n\n                expect(tokens1.length).to.equal(1);\n                expect(tokens2.length).to.equal(0);\n                expect(tokens3.length).to.equal(1);\n                next();\n              });\n            });\n          });\n        },\n      ], done);\n    });\n\n    it('invalidates correct sessions after changing email using updateAll', function(done) {\n      let userSpecial, userNormal;\n      async.series([\n        function createSpecialUser(next) {\n          User.create(\n            {email: 'special@example.com', password: 'pass1', name: 'Special'},\n            function(err, specialInstance) {\n              if (err) return next(err);\n              userSpecial = specialInstance;\n              next();\n            },\n          );\n        },\n        function loginSpecialUser(next) {\n          User.login({email: 'special@example.com', password: 'pass1'}, function(err, ats) {\n            if (err) return next(err);\n            next();\n          });\n        },\n        function updateSpecialUser(next) {\n          User.updateAll(\n            {name: 'Special'},\n            {email: 'superspecial@example.com'}, function(err, info) {\n              if (err) return next(err);\n              next();\n            },\n          );\n        },\n        function verifyTokensOfSpecialUser(next) {\n          AccessToken.find({where: {userId: userSpecial.pk}}, function(err, tokens1) {\n            if (err) return done(err);\n            expect(tokens1.length, 'tokens - special user tokens').to.equal(0);\n            next();\n          });\n        },\n        assertPreservedTokens,\n      ], done);\n    });\n\n    it('invalidates session when password is reset', function(done) {\n      user.updateAttribute('password', 'newPass', function(err, user2) {\n        if (err) return done(err);\n        assertNoAccessTokens(done);\n      });\n    });\n\n    it('preserves current session', function(done) {\n      const options = {accessToken: originalUserToken1};\n      user.updateAttribute('email', 'new@example.com', options, function(err) {\n        if (err) return done(err);\n        AccessToken.find({where: {userId: user.pk}}, function(err, tokens) {\n          if (err) return done(err);\n          const tokenIds = tokens.map(function(t) { return t.id; });\n          expect(tokenIds).to.eql([originalUserToken1.id]);\n          done();\n        });\n      });\n    });\n\n    it('forwards the \"options\" argument', function(done) {\n      const options = {testFlag: true};\n      const observedOptions = [];\n\n      saveObservedOptionsForHook('access', User);\n      saveObservedOptionsForHook('before delete', AccessToken);\n\n      user.updateAttribute('password', 'newPass', options, function(err, updated) {\n        if (err) return done(err);\n\n        expect(observedOptions).to.eql([\n          // prepareForTokenInvalidation - load current instance data\n          {hook: 'access', testFlag: true},\n\n          // validate uniqueness of User.email\n          {hook: 'access', testFlag: true},\n\n          // _invalidateAccessTokensOfUsers - deleteAll\n          {hook: 'before delete', testFlag: true},\n        ]);\n        done();\n      });\n\n      function saveObservedOptionsForHook(name, model) {\n        model.observe(name, function(ctx, next) {\n          observedOptions.push(extend({hook: name}, ctx.options));\n          next();\n        });\n      }\n    });\n\n    it('preserves other user sessions if their password is  untouched', function(done) {\n      let user1, user2, user1Token;\n      async.series([\n        function(next) {\n          User.create({email: 'user1@example.com', password: 'u1pass'}, function(err, u1) {\n            if (err) return done(err);\n            User.create({email: 'user2@example.com', password: 'u2pass'}, function(err, u2) {\n              if (err) return done(err);\n              user1 = u1;\n              user2 = u2;\n              next();\n            });\n          });\n        },\n        function(next) {\n          User.login({email: 'user1@example.com', password: 'u1pass'}, function(err, at1) {\n            User.login({email: 'user2@example.com', password: 'u2pass'}, function(err, at2) {\n              assert(at1.userId);\n              assert(at2.userId);\n              user1Token = at1.id;\n              next();\n            });\n          });\n        },\n        function(next) {\n          user2.updateAttribute('password', 'newPass', function(err, user2Instance) {\n            if (err) return next(err);\n            assert(user2Instance);\n            next();\n          });\n        },\n        function(next) {\n          AccessToken.find({where: {userId: user1.pk}}, function(err, tokens1) {\n            if (err) return next(err);\n            AccessToken.find({where: {userId: user2.pk}}, function(err, tokens2) {\n              if (err) return next(err);\n              expect(tokens1.length).to.equal(1);\n              expect(tokens2.length).to.equal(0);\n              assert.equal(tokens1[0].id, user1Token);\n              next();\n            });\n          });\n        },\n      ], function(err) {\n        done();\n      });\n    });\n\n    // See https://github.com/strongloop/loopback/issues/3215\n    it('handles subclassed user with no accessToken relation', () => {\n      // setup a new LoopBack app, we don't want to use shared models\n      app = loopback({localRegistry: true, loadBuiltinModels: true});\n      app.set('_verifyAuthModelRelations', false);\n      app.set('remoting', {errorHandler: {debug: true, log: false}});\n      app.dataSource('db', {connector: 'memory'});\n      const User = app.registry.createModel({\n        name: 'user',\n        base: 'User',\n      });\n      app.model(User, {dataSource: 'db'});\n      app.enableAuth({dataSource: 'db'});\n      expect(app.models.User.modelName).to.eql('user');\n\n      return User.create(validCredentials)\n        .then(u => {\n          u.email = 'updated@example.com';\n          return u.save();\n          // the test passes when save() does not throw any error\n        });\n    });\n\n    function assertPreservedTokens(done) {\n      AccessToken.find({where: {userId: user.pk}}, function(err, tokens) {\n        if (err) return done(err);\n        const actualIds = tokens.map(function(t) { return t.id; });\n        actualIds.sort();\n        const expectedIds = [originalUserToken1.id, originalUserToken2.id];\n        expectedIds.sort();\n        expect(actualIds).to.eql(expectedIds);\n        done();\n      });\n    }\n\n    function assertNoAccessTokens(done) {\n      AccessToken.find({where: {userId: user.pk}}, function(err, tokens) {\n        if (err) return done(err);\n        expect(tokens.length).to.equal(0);\n        done();\n      });\n    }\n  });\n\n  describe('Verification after updating email', function() {\n    const NEW_EMAIL = 'updated@example.com';\n    let userInstance;\n\n    beforeEach(createOriginalUser);\n\n    it('sets verification to false after email update if verification is required',\n      function(done) {\n        User.settings.emailVerificationRequired = true;\n        async.series([\n          function updateUser(next) {\n            userInstance.updateAttribute('email', NEW_EMAIL, function(err, info) {\n              if (err) return next(err);\n              assert.equal(info.email, NEW_EMAIL);\n              next();\n            });\n          },\n          function findUser(next) {\n            User.findById(userInstance.pk, function(err, info) {\n              if (err) return next(err);\n              assert.equal(info.email, NEW_EMAIL);\n              assert.equal(info.emailVerified, false);\n              next();\n            });\n          },\n        ], done);\n      });\n\n    it('leaves verification as is after email update if verification is not required',\n      function(done) {\n        User.settings.emailVerificationRequired = false;\n        async.series([\n          function updateUser(next) {\n            userInstance.updateAttribute('email', NEW_EMAIL, function(err, info) {\n              if (err) return next(err);\n              assert.equal(info.email, NEW_EMAIL);\n              next();\n            });\n          },\n          function findUser(next) {\n            User.findById(userInstance.pk, function(err, info) {\n              if (err) return next(err);\n              assert.equal(info.email, NEW_EMAIL);\n              assert.equal(info.emailVerified, true);\n              next();\n            });\n          },\n        ], done);\n      });\n\n    it('should not set verification to false after something other than email is updated',\n      function(done) {\n        User.settings.emailVerificationRequired = true;\n        async.series([\n          function updateUser(next) {\n            userInstance.updateAttribute('realm', 'test', function(err, info) {\n              if (err) return next(err);\n              assert.equal(info.realm, 'test');\n              next();\n            });\n          },\n          function findUser(next) {\n            User.findById(userInstance.pk, function(err, info) {\n              if (err) return next(err);\n              assert.equal(info.realm, 'test');\n              assert.equal(info.emailVerified, true);\n              next();\n            });\n          },\n        ], done);\n      });\n\n    function createOriginalUser(done) {\n      const userData = {\n        email: 'original@example.com',\n        password: 'bar',\n        emailVerified: true,\n      };\n      User.create(userData, function(err, instance) {\n        if (err) return done(err);\n        userInstance = instance;\n        done();\n      });\n    }\n  });\n\n  describe('password reset with/without email verification', function() {\n    it('allows resetPassword by email if email verification is required and done',\n      function(done) {\n        User.settings.emailVerificationRequired = true;\n        const email = validCredentialsEmailVerified.email;\n\n        User.resetPassword({email: email}, function(err, info) {\n          if (err) return done(err);\n          done();\n        });\n      });\n\n    it('disallows resetPassword by email if email verification is required and not done',\n      function(done) {\n        User.settings.emailVerificationRequired = true;\n        const email = validCredentialsEmail;\n\n        User.resetPassword({email: email}, function(err) {\n          assert(err);\n          assert.equal(err.code, 'RESET_FAILED_EMAIL_NOT_VERIFIED');\n          assert.equal(err.statusCode, 401);\n          done();\n        });\n      });\n\n    it('allows resetPassword by email if email verification is not required',\n      function(done) {\n        User.settings.emailVerificationRequired = false;\n        const email = validCredentialsEmail;\n\n        User.resetPassword({email: email}, function(err) {\n          if (err) return done(err);\n          done();\n        });\n      });\n  });\n\n  describe('ctor', function() {\n    it('exports default Email model', function() {\n      expect(User.email, 'User.email').to.be.a('function');\n      expect(User.email.modelName, 'modelName').to.eql('Email');\n    });\n\n    it('exports default AccessToken model', function() {\n      expect(User.accessToken, 'User.accessToken').to.be.a('function');\n      expect(User.accessToken.modelName, 'modelName').to.eql('AccessToken');\n    });\n  });\n\n  describe('ttl', function() {\n    let User2;\n    beforeEach(function() {\n      User2 = loopback.User.extend('User2', {}, {ttl: 10});\n    });\n    it('should override ttl setting in based User model', function() {\n      expect(User2.settings.ttl).to.equal(10);\n    });\n  });\n\n  function triggerPasswordReset(email) {\n    return Promise.all([\n      User.resetPassword({email: email}),\n      waitForEvent(User, 'resetPasswordRequest'),\n    ])\n      .spread((reset, info) => info);\n  }\n});\n"
  },
  {
    "path": "test/util/describe.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../');\n\nmodule.exports = describe;\n\ndescribe.onServer = function describeOnServer(name, fn) {\n  if (loopback.isServer) {\n    describe(name, fn);\n  } else {\n    describe.skip(name, fn);\n  }\n};\n\ndescribe.inBrowser = function describeInBrowser(name, fn) {\n  if (loopback.isBrowser) {\n    describe(name, fn);\n  } else {\n    describe.skip(name, fn);\n  }\n};\n"
  },
  {
    "path": "test/util/it.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst loopback = require('../../');\n\nmodule.exports = it;\n\nit.onServer = function itOnServer(name, fn) {\n  if (loopback.isServer) {\n    it(name, fn);\n  } else {\n    it.skip(name, fn);\n  }\n};\n\nit.inBrowser = function itInBrowser(name, fn) {\n  if (loopback.isBrowser) {\n    it(name, fn);\n  } else {\n    it.skip(name, fn);\n  }\n};\n"
  },
  {
    "path": "test/util/model-tests.js",
    "content": "// Copyright IBM Corp. 2014,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\nconst assert = require('assert');\nconst async = require('async');\nconst describe = require('./describe');\nconst loopback = require('../../');\nconst ACL = loopback.ACL;\nconst Change = loopback.Change;\nconst PersistedModel = loopback.PersistedModel;\nconst RemoteObjects = require('strong-remoting');\nconst TaskEmitter = require('strong-task-emitter');\n\nmodule.exports = function defineModelTestsWithDataSource(options) {\n  describe('Model Tests', function() {\n    let User, dataSource;\n\n    if (options.beforeEach) {\n      beforeEach(options.beforeEach);\n    }\n\n    beforeEach(function() {\n      const test = this;\n\n      // setup a model / datasource\n      dataSource = this.dataSource || loopback.createDataSource(options.dataSource);\n\n      const extend = PersistedModel.extend;\n\n      // create model hook\n      PersistedModel.extend = function() {\n        const extendedModel = extend.apply(PersistedModel, arguments);\n\n        if (options.onDefine) {\n          options.onDefine.call(test, extendedModel);\n        }\n\n        return extendedModel;\n      };\n\n      User = PersistedModel.extend('UtilUser', {\n        id: {id: true, type: String, defaultFn: 'guid'},\n        'first': String,\n        'last': String,\n        'age': Number,\n        'password': String,\n        'gender': String,\n        'domain': String,\n        'email': String,\n      }, {\n        trackChanges: options.trackChanges !== false,\n        enableRemoteReplication: options.enableRemoteReplication,\n      });\n\n      User.attachTo(dataSource);\n      User.handleChangeError = function(err) {\n        console.warn('WARNING: unhandled change-tracking error');\n        console.warn(err);\n      };\n    });\n\n    describe('Model.validatesPresenceOf(properties...)', function() {\n      it('Require a model to include a property to be considered valid', function() {\n        User.validatesPresenceOf('first', 'last', 'age');\n        const joe = new User({first: 'joe'});\n        assert(joe.isValid() === false, 'model should not validate');\n        assert(joe.errors.last, 'should have a missing last error');\n        assert(joe.errors.age, 'should have a missing age error');\n      });\n    });\n\n    describe('Model.validatesLengthOf(property, options)', function() {\n      it('Require a property length to be within a specified range', function() {\n        User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}});\n        const joe = new User({password: '1234'});\n        assert(joe.isValid() === false, 'model should not be valid');\n        assert(joe.errors.password, 'should have password error');\n      });\n    });\n\n    describe('Model.validatesInclusionOf(property, options)', function() {\n      it('Require a value for `property` to be in the specified array', function() {\n        User.validatesInclusionOf('gender', {in: ['male', 'female']});\n        const foo = new User({gender: 'bar'});\n        assert(foo.isValid() === false, 'model should not be valid');\n        assert(foo.errors.gender, 'should have gender error');\n      });\n    });\n\n    describe('Model.validatesExclusionOf(property, options)', function() {\n      it('Require a value for `property` to not exist in the specified array', function() {\n        User.validatesExclusionOf('domain', {in: ['www', 'billing', 'admin']});\n        const foo = new User({domain: 'www'});\n        const bar = new User({domain: 'billing'});\n        const bat = new User({domain: 'admin'});\n        assert(foo.isValid() === false);\n        assert(bar.isValid() === false);\n        assert(bat.isValid() === false);\n        assert(foo.errors.domain, 'model should have a domain error');\n        assert(bat.errors.domain, 'model should have a domain error');\n        assert(bat.errors.domain, 'model should have a domain error');\n      });\n    });\n\n    describe('Model.validatesNumericalityOf(property, options)', function() {\n      it('Require a value for `property` to be a specific type of `Number`', function() {\n        User.validatesNumericalityOf('age', {int: true});\n        const joe = new User({age: 10.2});\n        assert(joe.isValid() === false);\n        const bob = new User({age: 0});\n        assert(bob.isValid() === true);\n        assert(joe.errors.age, 'model should have an age error');\n      });\n    });\n\n    describe('myModel.isValid()', function() {\n      it('Validate the model instance', function() {\n        User.validatesNumericalityOf('age', {int: true});\n        const user = new User({first: 'joe', age: 'flarg'});\n        const valid = user.isValid();\n        assert(valid === false);\n        assert(user.errors.age, 'model should have age error');\n      });\n\n      it('Asynchronously validate the model', function(done) {\n        User.validatesNumericalityOf('age', {int: true});\n        const user = new User({first: 'joe', age: 'flarg'});\n        user.isValid(function(valid) {\n          assert(valid === false);\n          assert(user.errors.age, 'model should have age error');\n\n          done();\n        });\n      });\n    });\n\n    describe('Model.create([data], [callback])', function() {\n      it('Create an instance of Model with given data and save to the attached data source',\n        function(done) {\n          User.create({first: 'Joe', last: 'Bob'}, function(err, user) {\n            assert(user instanceof User);\n\n            done();\n          });\n        });\n    });\n\n    describe('model.save([options], [callback])', function() {\n      it('Save an instance of a Model to the attached data source', function(done) {\n        const joe = new User({first: 'Joe', last: 'Bob'});\n        joe.save(function(err, user) {\n          assert(user.id);\n          assert(!err);\n          assert(!user.errors);\n\n          done();\n        });\n      });\n    });\n\n    describe('model.updateAttributes(data, [callback])', function() {\n      it('Save specified attributes to the attached data source', function(done) {\n        User.create({first: 'joe', age: 100}, function(err, user) {\n          assert(!err);\n          assert.equal(user.first, 'joe');\n\n          user.updateAttributes({\n            first: 'updatedFirst',\n            last: 'updatedLast',\n          }, function(err, updatedUser) {\n            assert(!err);\n            assert.equal(updatedUser.first, 'updatedFirst');\n            assert.equal(updatedUser.last, 'updatedLast');\n            assert.equal(updatedUser.age, 100);\n\n            done();\n          });\n        });\n      });\n    });\n\n    describe('Model.upsert(data, callback)', function() {\n      it('Update when record with id=data.id found, insert otherwise', function(done) {\n        User.upsert({first: 'joe', id: 7}, function(err, user) {\n          assert(!err);\n          assert.equal(user.first, 'joe');\n\n          User.upsert({first: 'bob', id: 7}, function(err, updatedUser) {\n            assert(!err);\n            assert.equal(updatedUser.first, 'bob');\n\n            done();\n          });\n        });\n      });\n    });\n\n    describe('model.destroy([callback])', function() {\n      it('Remove a model from the attached data source', function(done) {\n        User.create({first: 'joe', last: 'bob'}, function(err, user) {\n          User.findById(user.id, function(err, foundUser) {\n            if (err) return done(err);\n\n            assert.equal(user.id, foundUser.id);\n            User.deleteById(foundUser.id, function(err) {\n              if (err) return done(err);\n\n              User.find({where: {id: user.id}}, function(err, found) {\n                if (err) return done(err);\n\n                assert.equal(found.length, 0);\n\n                done();\n              });\n            });\n          });\n        });\n      });\n    });\n\n    describe('Model.deleteById(id, [callback])', function() {\n      it('Delete a model instance from the attached data source', function(done) {\n        User.create({first: 'joe', last: 'bob'}, function(err, user) {\n          User.deleteById(user.id, function(err) {\n            User.findById(user.id, function(err, notFound) {\n              assert.equal(notFound, null);\n\n              done();\n            });\n          });\n        });\n      });\n    });\n\n    describe('Model.exists(id, [callback])', function() {\n      it('returns true when the model with the given id exists', function(done) {\n        User.create({first: 'max'}, function(err, user) {\n          if (err) return done(err);\n          User.exists(user.id, function(err, exist) {\n            if (err) return done(err);\n            assert.equal(exist, true);\n            done();\n          });\n        });\n      });\n\n      it('returns false when there is no model with the given id', function(done) {\n        User.exists('user-id-does-not-exist', function(err, exist) {\n          if (err) return done(err);\n          assert.equal(exist, false);\n          done();\n        });\n      });\n    });\n\n    describe('Model.findById(id, callback)', function() {\n      it('Find an instance by id', function(done) {\n        User.create({first: 'michael', last: 'jordan', id: 23}, function() {\n          User.findById(23, function(err, user) {\n            assert.equal(user.id, 23);\n            assert.equal(user.first, 'michael');\n            assert.equal(user.last, 'jordan');\n\n            done();\n          });\n        });\n      });\n    });\n\n    describe('Model.count([query], callback)', function() {\n      it('Query count of Model instances in data source', function(done) {\n        (new TaskEmitter())\n          .task(User, 'create', {first: 'jill', age: 100})\n          .task(User, 'create', {first: 'bob', age: 200})\n          .task(User, 'create', {first: 'jan'})\n          .task(User, 'create', {first: 'sam'})\n          .task(User, 'create', {first: 'suzy'})\n          .on('done', function() {\n            User.count({age: {gt: 99}}, function(err, count) {\n              assert.equal(count, 2);\n\n              done();\n            });\n          });\n      });\n    });\n  });\n};\n"
  },
  {
    "path": "test/utils.test.js",
    "content": "// Copyright IBM Corp. 2016,2019. All Rights Reserved.\n// Node module: loopback\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\n\n'use strict';\n\nconst utils = require('../lib/utils');\nconst assert = require('assert');\n\ndescribe('Utils', function() {\n  describe('uploadInChunks', function() {\n    it('calls process function for each chunk', function(done) {\n      const largeArray = ['item1', 'item2', 'item3'];\n      const calls = [];\n\n      utils.uploadInChunks(largeArray, 1, function processFunction(array, cb) {\n        calls.push(array);\n        cb();\n      }, function finished(err) {\n        if (err) return done(err);\n        assert.deepEqual(calls, [['item1'], ['item2'], ['item3']]);\n        done();\n      });\n    });\n\n    it('calls process function only once when array is smaller than chunk size', function(done) {\n      const largeArray = ['item1', 'item2'];\n      const calls = [];\n\n      utils.uploadInChunks(largeArray, 3, function processFunction(array, cb) {\n        calls.push(array);\n        cb();\n      }, function finished(err) {\n        if (err) return done(err);\n        assert.deepEqual(calls, [['item1', 'item2']]);\n        done();\n      });\n    });\n\n    it('concats results from each call to the process function', function(done) {\n      const largeArray = ['item1', 'item2', 'item3', 'item4'];\n\n      utils.uploadInChunks(largeArray, 2, function processFunction(array, cb) {\n        cb(null, array);\n      }, function finished(err, results) {\n        if (err) return done(err);\n        assert.deepEqual(results, ['item1', 'item2', 'item3', 'item4']);\n        done();\n      });\n    });\n  });\n\n  describe('downloadInChunks', function() {\n    let largeArray, calls, chunkSize, skip;\n\n    beforeEach(function() {\n      largeArray = ['item1', 'item2', 'item3'];\n      calls = [];\n      chunkSize = 2;\n      skip = 0;\n    });\n\n    function processFunction(filter, cb) {\n      calls.push(Object.assign({}, filter));\n      const results = [];\n\n      for (let i = 0; i < chunkSize; i++) {\n        if (largeArray[skip + i]) {\n          results.push(largeArray[skip + i]);\n        }\n      }\n\n      skip += chunkSize;\n      cb(null, results);\n    }\n\n    it('calls process function with the correct filter', function(done) {\n      const expectedFilters = [{skip: 0, limit: chunkSize}, {skip: chunkSize, limit: chunkSize}];\n      utils.downloadInChunks({}, chunkSize, processFunction, function finished(err) {\n        if (err) return done(err);\n        assert.deepEqual(calls, expectedFilters);\n        done();\n      });\n    });\n\n    it('concats the results of all calls of the process function', function(done) {\n      utils.downloadInChunks({}, chunkSize, processFunction, function finished(err, results) {\n        if (err) return done(err);\n        assert.deepEqual(results, largeArray);\n        done();\n      });\n    });\n  });\n\n  describe('concatResults', function() {\n    it('concats regular arrays', function() {\n      const array1 = ['item1', 'item2'];\n      const array2 = ['item3', 'item4'];\n\n      const concatResults = utils.concatResults(array1, array2);\n      assert.deepEqual(concatResults, ['item1', 'item2', 'item3', 'item4']);\n    });\n\n    it('concats objects containing arrays', function() {\n      const object1 = {deltas: [{change: 'change 1'}], conflict: []};\n      const object2 = {deltas: [{change: 'change 2'}], conflict: [{conflict: 'conflict 1'}]};\n      const expectedResults = {\n        deltas: [{change: 'change 1'}, {change: 'change 2'}],\n        conflict: [{conflict: 'conflict 1'}],\n      };\n\n      const concatResults = utils.concatResults(object1, object2);\n      assert.deepEqual(concatResults, expectedResults);\n    });\n  });\n});\n"
  }
]