Compare commits

...

294 Commits

Author SHA1 Message Date
Diana Lau 13371fd2a1
Merge pull request #4343 from achrinzafork/chore/update-lts
chore: update LTS status to End-of-Life
2021-03-05 20:01:03 -05:00
Rifa Achrinza 1316be55c5 chore: update LTS status to End-of-Life
see https://github.com/strongloop/loopback-next/issues/6957
Co-authored-by: Diana Lau <dhmlau@ca.ibm.com>
2021-03-04 18:21:00 +08:00
jannyHou a22d4f9197 3.28.0
* upgrade nodemailer to greater than 6.4.16 (jannyHou)
 * chore: sync LoopBack 4 with Node.js v14 EOL (Rifa Achrinza)
 * chore: add Node.js 14 to travis (Diana Lau)
 * Remove "major" and "p1" from stalebot (Miroslav Bajtoš)
2020-11-25 17:06:34 -05:00
Diana Lau 223dd5d58e
Merge pull request #4338 from achrinzafork/chore/update-module-lts-node14
chore: sync LoopBack 4 with Node.js v14 EOL
2020-11-25 09:29:27 -05:00
Janny fbd9783e47
Merge pull request #4340 from strongloop/upgrade-nodemailer
chore: upgrade nodemailer
2020-11-24 15:34:12 -05:00
jannyHou 6f04b61f88 upgrade nodemailer to greater than 6.4.16
Signed-off-by: jannyHou <juehou@ca.ibm.com>
2020-11-24 11:56:27 -05:00
Rifa Achrinza ab3e159fed chore: sync LoopBack 4 with Node.js v14 EOL
see https://github.com/strongloop/loopback-next/issues/6709
2020-11-03 17:22:07 +08:00
Diana Lau 5b61efb855
Merge pull request #4334 from strongloop/travis
chore: add Node.js 14 to travis
2020-09-10 15:24:16 -04:00
Diana Lau e457e2651d chore: add Node.js 14 to travis 2020-07-28 13:31:44 -04:00
Miroslav Bajtoš ab2e462ca6
Merge pull request #4318 from strongloop/bajtos-patch-1
Remove "major" and "p1" from stalebot
2020-03-10 16:41:24 +01:00
Miroslav Bajtoš f2f7332916
Remove "major" and "p1" from stalebot
LoopBack 3 is in Maintenance LTS mode, only critical bugs and critical
security fixes will be provided. Major and p1 issues are not qualified
and if nobody submits a pull request for them, then they should be
automatically closed as stale.
2020-03-10 15:56:10 +01:00
Miroslav Bajtoš 06fcaa144e
3.27.0
* Update LTS status in README (Miroslav Bajtoš)
 * chore: update copyright year (Diana Lau)
 * feat: change hasone relation error message (Sujesh T)
 * chore: disable security issue reporting (Nora)
 * chore: fix eslint violations (Nora)
 * fixup! manual fixes (Miroslav Bajtoš)
 * fixup! eslint --fix . (Miroslav Bajtoš)
 * chore: update eslint & eslint-config to latest (Miroslav Bajtoš)
 * chore: update dev-dependencies (Miroslav Bajtoš)
 * chore: update chai to v4, dirty-chai to v2 (Miroslav Bajtoš)
 * Updated "ismail" package to v3.2 (Stanislav Sarbinski)
 * Introduce issue templates for bugs, features, etc. (Miroslav Bajtoš)
 * Improve PULL_REQUEST_TEMPLATE (Miroslav Bajtoš)
 * test: use Chromium (not Chrome) when available (Miroslav Bajtoš)
 * test: disable Chrome sandboxing when inside Docker (Miroslav Bajtoš)
 * test: switch from PhantomJS to HeadlessChrome (Miroslav Bajtoš)
2020-03-06 10:00:20 +01:00
Miroslav Bajtoš d929b57da4
Merge pull request #4315 from strongloop/feat/maintenance-lts
Update LTS status in README
2020-03-06 09:59:09 +01:00
Miroslav Bajtoš 58f65454e9
Update LTS status in README 2020-03-05 13:40:44 +01:00
Diana Lau cdf48b648e
Merge pull request #4303 from strongloop/copyright
chore: update copyright year
2020-01-23 15:07:27 -05:00
Diana Lau f788aae464 chore: update copyright year 2020-01-21 14:19:18 -05:00
Diana Lau 6a7fc529fc
Merge pull request #4290 from sujeshthekkepatt/fix/hasone-relation-error-message
feat: change hasone relation error message
2019-12-15 19:32:43 -05:00
Sujesh T b93187b0f9 feat: change hasone relation error message
The current hasone error message is not appropriate one.
So adds a better message.
2019-12-02 23:28:02 +05:30
Nora 2db09a3d00
Merge pull request #4281 from strongloop/chore/improve-issue-templates
chore: disable security issue reporting
2019-11-19 15:41:03 -05:00
Nora 4edcc4f14c chore: disable security issue reporting 2019-11-18 23:48:24 -05:00
Nora b848bd9d9f
Merge pull request #4283 from strongloop/fix-comma-dangle
chore: fix eslint violations
2019-11-18 23:44:59 -05:00
Nora aa76a19e33 chore: fix eslint violations
Fix comma-dangle violations.
2019-11-17 14:04:57 -05:00
Miroslav Bajtoš f2f0b3aa91
Merge pull request #4267 from strongloop/update-dev-deps
Update dev dependencies + switch from "var" to "const"
2019-10-08 08:21:20 +02:00
Miroslav Bajtoš 82cd6681f6
fixup! manual fixes
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-07 11:54:59 +02:00
Miroslav Bajtoš 397d141036
fixup! eslint --fix .
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-07 11:54:59 +02:00
Miroslav Bajtoš aec86ccc3f
chore: update eslint & eslint-config to latest
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-07 11:54:59 +02:00
Miroslav Bajtoš 5dd9561cb6
chore: update dev-dependencies
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-07 11:54:59 +02:00
Miroslav Bajtoš dd6d2a5e6c
chore: update chai to v4, dirty-chai to v2
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-07 11:54:59 +02:00
Miroslav Bajtoš da51c992ba
Merge pull request #4241 from Sarbinski/issue-4239/update-ismail-lib-for-mail-validation
Updated "isemail" package to ver. 3.2.x
2019-10-07 11:39:17 +02:00
Stanislav Sarbinski 4846048e48
Updated "ismail" package to v3.2 2019-10-07 11:19:19 +02:00
Miroslav Bajtoš edb925529e
Merge pull request #4266 from strongloop/improve/github-templates
Improve issue & pull-request templates
2019-10-07 11:10:33 +02:00
Miroslav Bajtoš bd276d551a
Introduce issue templates for bugs, features, etc.
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-07 10:35:17 +02:00
Miroslav Bajtoš 83612aac8f
Improve PULL_REQUEST_TEMPLATE
Make it easier for contributors to mix the text provided by GitHub
from the commit messages with the template content.

Rework the checklist to follow the style we use in loopback-next.

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-04 10:43:05 +02:00
Miroslav Bajtoš af9f776ed0
Merge pull request #4262 from strongloop/fix/ci
test: switch from PhantomJS to HeadlessChrome
2019-10-03 15:20:30 +02:00
Miroslav Bajtoš 387835faef
test: use Chromium (not Chrome) when available
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-01 09:27:03 +02:00
Miroslav Bajtoš 8311138f3e
test: disable Chrome sandboxing when inside Docker
See the discussion in
https://github.com/docker/for-linux/issues/496

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-01 09:27:02 +02:00
Miroslav Bajtoš cceb8a3c26
test: switch from PhantomJS to HeadlessChrome
Rework browser tests to run in Headless Chrome instead of PhantomJS,
because the latter is no longer maintained.

This allows us to remove transpilation to ES5 via babelify, which
significantly improves speed of our tests.

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-10-01 09:12:17 +02:00
Miroslav Bajtoš 9b1d4885ab
3.26.0
* fix: disallow queries in username and email fields (Hage Yaapa)
 * Ignore failing downstream dependencies (Miroslav Bajtoš)
 * Upgrade nyc to version 14 (Miroslav Bajtoš)
 * Update Karma dependencies to latest versions (Miroslav Bajtoš)
 * Drop Node.js 6.x from the supported versions (Miroslav Bajtoš)
 * Fix Model.exists() to work with remote connector (Maxim Sharai)
 * chore: update copyrights years (Agnes Lin)
 * Update LTS status (Diana Lau)
 * Enable Node.js 12.x on Travis CI (Miroslav Bajtoš)
 * chore: update copyright year (Diana Lau)
 * chore: update LB3 EOL date (Diana Lau)
2019-05-31 09:04:12 +02:00
Hage Yaapa 58a0e6c8e9 fix: disallow queries in username and email fields
Username and email fields should not allow queries.
2019-05-30 19:54:01 +05:30
Miroslav Bajtoš e682914ecd
Merge pull request #4196 from strongloop/ignore-failing-downstream-builds
Ignore failing downstream dependencies
2019-05-14 18:49:25 +02:00
Miroslav Bajtoš 716347e25c
Ignore failing downstream dependencies
The following dependencies are always failing the CI builds, let's
ignore them when testing changes in `loopback` package:

- bluemix-service-broker
- gateway-director-bluemix
- plan-manager

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-05-14 16:17:23 +02:00
Miroslav Bajtoš cd8db0b5cf
Merge pull request #4194 from strongloop/update-karma-nyc
Drop Node.js 6.x + update Karma and nyc dependencies to their latest versions
2019-05-14 09:45:23 +02:00
Miroslav Bajtoš 686c6fe88e
Upgrade nyc to version 14 2019-05-14 09:03:36 +02:00
Miroslav Bajtoš c9a277884e
Update Karma dependencies to latest versions 2019-05-14 09:03:36 +02:00
Miroslav Bajtoš a685553b49
Drop Node.js 6.x from the supported versions
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-05-14 09:03:36 +02:00
Miroslav Bajtoš 4b3a3a347c
Merge pull request #4120 from angfal/fix/remote_exists
Fix Model.exists() to work with remote connector
2019-05-13 15:55:45 +02:00
Maxim Sharai e8d115bdc2
Fix Model.exists() to work with remote connector 2019-05-13 15:47:38 +02:00
Agnes Lin ae3fff9e7b
Merge pull request #4191 from strongloop/copyrights
chore: update copyrights years
2019-05-07 10:24:44 -04:00
Agnes Lin 0e0ca455ac chore: update copyrights years 2019-05-06 16:10:57 -04:00
Diana Lau e82a74cce3
Merge pull request #4189 from strongloop/eol
Update LTS status
2019-05-03 09:37:14 -04:00
Diana Lau 182ab7fb22 Update LTS status 2019-05-03 08:18:45 -04:00
Miroslav Bajtoš 3a2a645e0c
Merge pull request #4186 from strongloop/chore/add-node-12
Enable Node.js 12.x on Travis CI
2019-04-29 09:22:42 +02:00
Miroslav Bajtoš f19a8ab6fe
Enable Node.js 12.x on Travis CI 2019-04-29 09:13:05 +02:00
Diana Lau 66ec2aa57f
Merge pull request #4183 from strongloop/copyright
chore: update copyright year
2019-04-17 13:24:02 -04:00
Diana Lau f6ba1afe91 chore: update copyright year 2019-04-17 12:10:45 -04:00
Diana Lau b05d7320d7
Merge pull request #4159 from strongloop/lts
chore: update LB3 EOL date
2019-03-18 15:13:46 -04:00
Diana Lau a175b36939 chore: update LB3 EOL date 2019-03-18 12:25:14 -04:00
Miroslav Bajtoš 34acd02a7e
3.25.1
* Back-ticks added to highlight example JSON (Quentin Presley)
 * Add same change to description for findOne (Quentin Presley)
 * Update the description for persisted-models (Quentin Presley)
 * handle $2b$ in hashed password check (Sylvain Dumont)
2019-03-15 16:08:23 +01:00
Miroslav Bajtoš a9a86fe380
Merge pull request #4158 from strongloop/fixFilterDef
Update the description for persisted-models
2019-03-15 16:05:37 +01:00
Quentin Presley b83f5635cf Back-ticks added to highlight example JSON 2019-03-15 07:46:15 -07:00
Quentin Presley 6caf506769 Add same change to description for findOne 2019-03-15 07:43:13 -07:00
Quentin Presley b91938ecdf Update the description for persisted-models 2019-03-15 07:39:40 -07:00
Janny c8a31e2725
Merge pull request #4146 from sylvaindumont/patch-1
handle $2b$ in hashed password check
2019-02-25 13:01:53 -05:00
Sylvain Dumont 4226da5fc4
handle $2b$ in hashed password check
bcrypt made $2b$ the default in bcrypt 2.0.0
2019-02-23 16:14:24 +01:00
Miroslav Bajtoš 1e33ec596f
3.25.0
* Support middleware injected by AppDynamics. (Mike Li)
2019-02-05 14:23:17 +01:00
Miroslav Bajtoš c38900b785
Merge pull request #4119 from studykik/fix/appdynamics-proxy
Support middleware injected by AppDynamics
2019-02-05 14:22:15 +01:00
Mike Li edb8dbc517 Support middleware injected by AppDynamics.
AppDynamics injects a proxy object into the router stack, which it
uses for its network analysis.  This is similar to how NewRelic
adds a sentinel handler to the router stack. This commit adds a
similar workaround so that loopback can find the original layer.
2019-01-25 11:12:41 -08:00
Miroslav Bajtoš b77907ffa5
3.24.2
* Fix crash when modifying an unknown user (Matheus Horstmann)
2019-01-11 16:04:43 +01:00
Miroslav Bajtoš 242c20fecf
Merge pull request #4108 from horstmannmat/fix_4105
Fix crash when modifying a user with an unknown id
2019-01-11 16:04:15 +01:00
Matheus Horstmann 2532b0b67e
Fix crash when modifying an unknown user
Signed-off-by: Matheus Horstmann <mch15@inf.ufpr.br>
Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
2019-01-11 15:45:00 +01:00
Miroslav Bajtoš 0bb8c23e2d
3.24.1
* Update underscore.string to 3.3.5 (Francois)
 * Fix: treat empty access token string as undefined (andrey-abramow)
2019-01-08 10:48:41 +01:00
Miroslav Bajtoš 6eb8c0ed3a
Merge pull request #4107 from 3z3qu13l/master
Update underscore.string to 3.3.5
2019-01-08 10:47:42 +01:00
Francois 6b748748bd Update underscore.string to 3.3.5 2019-01-08 09:37:02 +01:00
Miroslav Bajtoš da2b8d8676
Merge pull request #4083 from andrey-abramow/master
Fix: treat empty access token string as undefined
2018-11-26 13:10:03 +01:00
andrey-abramow 71c651123f Fix: treat empty access token string as undefined
Fix AccessToken's method tokenIdForRequest to treat an empty string
as if no access token was provided.

This is needed to accomodate the changes made in
loopback-datasource-juggler@2.56.0.
2018-11-26 13:13:35 +02:00
Raymond Feng aad97c2036
Merge pull request #4065 from strongloop/set-default-remote-options
Set juggler configs in options by default
2018-11-15 15:37:10 -08:00
Raymond Feng 81d1f7406f 3.24.0
* Set juggler options for remote calls (Raymond Feng)
 * Speed up ACL tests by reducing saltWorkFactor (Miroslav Bajtoš)
2018-11-15 08:29:10 -08:00
Raymond Feng f1c613ac07 Set juggler options for remote calls 2018-11-15 08:28:46 -08:00
Miroslav Bajtoš a239a11656
Merge pull request #4039 from strongloop/speed-up-acl-tests
Speed up ACL tests by reducing saltWorkFactor
2018-10-25 15:39:18 +02:00
Miroslav Bajtoš a4d7caba1a
3.23.2
* Fix ACL check to support model wildcard (Moshe Malka)
2018-10-25 14:14:22 +02:00
Miroslav Bajtoš 818a7506d8
Speed up ACL tests by reducing saltWorkFactor 2018-10-25 14:10:49 +02:00
Miroslav Bajtoš 69df6386fc
Merge pull request #3954 from moshemal/issue-3953
Fix ACL rules with a wildcard match ('*') for model property
2018-10-25 14:09:19 +02:00
Moshe Malka 29c5f20d90
Fix ACL check to support model wildcard 2018-10-25 14:00:35 +02:00
Miroslav Bajtoš be91ba3dad
3.23.1
* README: highlight Active LTS at the top (Miroslav Bajtoš)
2018-10-18 09:27:54 +02:00
Miroslav Bajtoš b944b9d8a5
Merge pull request #4029 from strongloop/update-lts
README: highligh Active LTS at the top
2018-10-18 09:23:07 +02:00
Miroslav Bajtoš 9ea6b6ab03
README: highlight Active LTS at the top
Add a short warning at the top of README to explain that this package
is no longer accepting new features and point users to LoopBack 4.
2018-10-18 09:06:23 +02:00
Diana Lau 4c49b5fa1f 3.23.0
* Clear handler cache when a method is added/removed (Mohammed Essehemy)
 * Add `options.preserveAccessTokens` (lchaglla)
 * Update LB3 to be active LTS (Diana Lau)
 * Fix ACL tests to wait until all assertions finish (Moshe Malka)
 * chore: update to latest linting rules (virkt25)
2018-10-09 16:05:13 -07:00
Diana Lau d66c912d06
Merge pull request #4022 from strongloop/change-status
Update LB3 to be active LTS
2018-10-09 18:58:07 -04:00
Miroslav Bajtoš 0488cb7ba1
Merge pull request #4011 from MohammedEssehemy/fix/clear-cache-handler-after-method-add
clearHandlerCache when remote method added or disabled
2018-10-09 15:19:27 +02:00
Mohammed Essehemy e33d10fe44
Clear handler cache when a method is added/removed 2018-10-09 15:03:00 +02:00
Miroslav Bajtoš 97a55bf67a
Merge pull request #4021 from lchaglla/preserveAccessTokens
Add a flag to preserve access tokens on email/password change
2018-10-08 09:46:44 +02:00
lchaglla 2b7b0e1cc1
Add `options.preserveAccessTokens`
Add an option to preserve access tokens when email/password is changed.
2018-10-08 09:28:35 +02:00
Diana Lau d7a32aea70 Update LB3 to be active LTS 2018-10-05 22:31:31 -04:00
Miroslav Bajtoš 0bb7cd67ec
Merge pull request #3989 from moshemal/issue-3988
Use done function for multiple async assertions
2018-09-17 15:02:23 +02:00
Moshe Malka 186ae2ae57
Fix ACL tests to wait until all assertions finish 2018-09-17 14:12:58 +02:00
virkt25 fa644d6a31 chore: update to latest linting rules 2018-09-13 10:08:25 -04:00
virkt25 acc1cd0ee5 3.22.3
* chore: use grunt to install optional  phantomjs (virkt25)
 * [WebFM] fr translation (candytangnb)
2018-09-12 11:06:28 -04:00
virkt25 cab1ed5463 chore: use grunt to install optional phantomjs
connected to #3993
2018-09-12 11:02:14 -04:00
candytangnb c9164aed17 [WebFM] fr translation
fr translation check-in by YI TANG (tangyinb@cn.ibm.com) using WebFM
tool.
2018-08-30 09:53:13 -04:00
virkt25 9f1eb419d1 3.22.2
* [WebFM] tr translation (candytangnb)
 * [WebFM] de translation (candytangnb)
 * [WebFM] cs/es/fr/it/nl/pl/pt_BR/ru translation (candytangnb)
2018-08-29 15:10:43 -04:00
candytangnb 516fbbd91a [WebFM] tr translation
tr translation check-in by YI TANG (tangyinb@cn.ibm.com) using WebFM
tool.
2018-08-27 10:46:47 -04:00
Diana Lau d028876b94
Merge pull request #3978 from candytangnb/webfm-0824-063653-de-translation
[WebFM] de translation Check-in by YI TANG (tangyinb@cn.ibm.com)
2018-08-24 09:56:27 -04:00
candytangnb 78342ebf80 [WebFM] de translation
de translation check-in by YI TANG (tangyinb@cn.ibm.com) using WebFM
tool.
2018-08-24 06:36:53 -04:00
Diana Lau 8ad0f93552
Merge pull request #3975 from candytangnb/webfm-0823-120743-translation
[WebFM] cs/es/fr/it/nl/pl/pt_BR/ru translation Check-in by YI TANG (tangyinb@cn.ibm.com)
2018-08-23 15:04:13 -04:00
candytangnb 55e48cf572 [WebFM] cs/es/fr/it/nl/pl/pt_BR/ru translation
cs/es/fr/it/nl/pl/pt_BR/ru translation check-in by YI TANG
(tangyinb@cn.ibm.com) using WebFM tool.
2018-08-23 12:07:43 -04:00
Diana Lau 74d7043edc 3.22.1
* [WebFM] ja/ko/zh_CN/zh_TW translation (candytangnb)
 * remove unnecessary format call (Diana Lau)
 * Make desc when export-api-def translatable (Diana Lau)
2018-08-22 09:18:19 -04:00
Diana Lau 3caf65d4a6
Merge pull request #3974 from candytangnb/webfm-0822-053933-translation
[WebFM] ja/ko/zh_CN/zh_TW translation Check-in by YI TANG (tangyinb@cn.ibm.com)
2018-08-22 09:12:08 -04:00
candytangnb 700085c8d6 [WebFM] ja/ko/zh_CN/zh_TW translation
ja/ko/zh_CN/zh_TW translation check-in by YI TANG (tangyinb@cn.ibm.com)
using WebFM tool.
2018-08-22 05:39:33 -04:00
Diana Lau 1c30628a8a
Merge pull request #3971 from strongloop/fix-translation
[GVT] Get the summary and description in the `lb export-api-def` output translatable
2018-08-20 20:12:43 -04:00
Diana Lau 6b0a880efa remove unnecessary format call 2018-08-20 16:55:56 -04:00
Diana Lau 89ff760b6d Make desc when export-api-def translatable 2018-08-20 13:04:21 -04:00
virkt25 416b86a5ec 3.22.0
* fix: accessToken create default acl (virkt25)
 * add: ppc64 and s390x to not run UI tests (Thomas Leah)
 * chore: update deps + fix linting + .npmrc (virkt25)
 * Update Loopback 2.x EOL dates (Chris Bailey)
 * Fix formatting (Chris Bailey)
 * Update support badge and move LTS section (Chris Bailey)
 * Add badges and information for LTS and support (Chris Bailey)
2018-08-08 16:27:22 -04:00
virkt25 aaa1381964 fix: accessToken create default acl 2018-08-08 16:17:48 -04:00
Thomas Leah 0dac936bba add: ppc64 and s390x to not run UI tests 2018-08-08 15:27:29 -04:00
virkt25 5ee731eafd chore: update deps + fix linting + .npmrc 2018-08-08 13:31:30 -04:00
Diana Lau 6d23c2edd1
Merge pull request #3947 from seabaylea/lts-and-support
Add badges and information for LTS and support
2018-07-20 11:09:05 -04:00
Chris Bailey 962efec9cd Update Loopback 2.x EOL dates 2018-07-20 15:25:48 +01:00
Chris Bailey 951b61a2ae Fix formatting 2018-07-18 17:16:55 +01:00
Chris Bailey f90e1c5e01 Merge supported versions and LTS sections 2018-07-18 17:14:55 +01:00
Chris Bailey cdab1519af Update support badge and move LTS section 2018-07-18 09:51:48 +01:00
Chris Bailey 7e7f8facd8 Add badges and information for LTS and support 2018-07-17 22:25:46 +01:00
virkt25 72d85fa9ea 3.21.0
* Make verifyUserRelations() more robust (mcitdev)
 * Fix crash in verifyUserRelations (ryanxwelch)
 * Fix crash in User model's "before delete" hook (mcitdev)
 * [WebFM] cs/pl/ru translation (candytangnb)
 * Update strong-error-handler (shimks)
2018-07-09 17:04:16 -04:00
Miroslav Bajtoš 7ad5a2e1bc
Merge pull request #3938 from mcitdev/master
Make verifyUserRelations() more robust
2018-07-03 09:18:39 +02:00
mcitdev f9e9aaa8ce
Make verifyUserRelations() more robust
The fix introduced in previous commit was relying on the default
merge algorighm applied when a child model inherits relation config
from the parent.

This commit changes the check to use a more reliable approach
based on the relation metadata configured by the child model.
2018-07-03 09:03:44 +02:00
Miroslav Bajtoš cd1b31920e
Merge pull request #3934 from strongloop/fix/crash-in-verifyUserRelations
fix: resolve customAccessToken warning error that causes app to crash
2018-06-29 17:18:05 +02:00
ryanxwelch 9d77ba28d3
Fix crash in verifyUserRelations
In the present implementation `verifyUserRelations(Model)` assumes that
`Model.relations.accessTokens` is always set, and as a result may
crash when trying to access `polymorphic` property of that relation.

It seems the intention is to check whether the the following conditions
are met:
 1. a model has a hasMany accessTokens relation
 2. that relation is polymorphic

This commit fixes the problem by accounting for the case where the
accessTokens relation was not correctly set up.
2018-06-29 16:14:01 +02:00
Miroslav Bajtoš 95bcf035a8
Merge pull request #3917 from mcitdev/master
Fix crash in User model's "before delete" hook
2018-06-29 15:27:39 +02:00
mcitdev 37e57f6943
Fix crash in User model's "before delete" hook
Update User's "before delete" hook to take into account the case when
the related AccessToken model was not configured in the application
(attached to a datasource).
2018-06-29 14:51:41 +02:00
Miroslav Bajtoš 9c554fd4f0
Merge pull request #3933 from candytangnb/webfm-0628-225109-cs,pl,ru-translation
[WebFM] cs/pl/ru translation Check-in by YI TANG (tangyinb@cn.ibm.com)
2018-06-29 09:19:14 +02:00
candytangnb 4dd195e654 [WebFM] cs/pl/ru translation
cs/pl/ru translation check-in by YI TANG (tangyinb@cn.ibm.com) using
WebFM tool.
2018-06-28 22:51:09 -04:00
Kyusung Shim 44951a1032
Merge pull request #3912 from strongloop/update-strong-error-handler
Update strong-error-handler
2018-06-13 10:42:51 -04:00
shimks 0590bd1bb4 Update strong-error-handler 2018-06-12 12:25:33 -04:00
Miroslav Bajtoš 7d93b27a80
3.20.0
* Update strong-globalize to 4.x (Miroslav Bajtoš)
 * Update nodemailer to v4.x (Dimitris)
 * Drop support for Node.js 4.x (Miroslav Bajtoš)
2018-06-12 08:31:51 +02:00
Miroslav Bajtoš 5a87f8df16
Merge pull request #3911 from strongloop/update-strong-globalize
Update strong-globalize to 4.x
2018-06-12 08:27:39 +02:00
Miroslav Bajtoš f17346ba50
Update strong-globalize to 4.x 2018-06-11 17:37:56 +02:00
Miroslav Bajtoš 995c14eccf
Merge pull request #3585 from mitsos1os/nodemailer-update
Update nodemailer to the latest v4 version
2018-06-11 16:49:34 +02:00
Dimitris 83fc62c0af
Update nodemailer to v4.x 2018-06-11 16:01:42 +02:00
Miroslav Bajtoš 58090b4b80
Merge pull request #3908 from strongloop/drop-node-4x
Drop support for Node.js 4.x
2018-06-11 15:54:15 +02:00
Miroslav Bajtoš ec46c457d3
Drop support for Node.js 4.x 2018-06-08 09:33:55 +02:00
Miroslav Bajtoš 85aa3fd679
3.19.3
* Provide link to CODEOWNERS (Aditya Agarwal)
 * fix bug in User.verify when confirm is disabled (wolrajhti)
 * Enable Node.js 10.x on Travis CI (Miroslav Bajtoš)
2018-06-04 16:36:29 +02:00
Miroslav Bajtoš 8c4c91a5d1
Merge pull request #3904 from itaditya/patch-1
Provide URL to CODEOWNERS in stalebot comments.
2018-06-04 16:35:33 +02:00
Aditya Agarwal cf5b78fc6c
Provide link to CODEOWNERS
Currently, stale bot doesn't provide the link to the CODEOWNERS file,
a person must have to browse for the file to open it.

This commit adds a link to the CODEOWNERS file so that a person can
directly open it.
2018-06-04 16:24:42 +02:00
Miroslav Bajtoš c6d8565216
Merge pull request #3896 from wolrajhti/patch-2
Fix bug in User.verify when confirm is disabled
2018-06-04 16:20:39 +02:00
wolrajhti cc4fc2197f
fix bug in User.verify when confirm is disabled 2018-05-31 12:40:23 +02:00
Miroslav Bajtoš eb3301abf2
Merge pull request #3899 from strongloop/add-node-10
Enable Node.js 10.x on Travis CI
2018-05-29 09:48:00 +02:00
Miroslav Bajtoš 36ef35dd60
Enable Node.js 10.x on Travis CI 2018-05-29 08:55:15 +02:00
Miroslav Bajtoš 59b14093c5
3.19.2
* Add check for undefined user email in setter (Kevin Scroggins)
2018-05-29 08:54:18 +02:00
Miroslav Bajtoš c2077dfcb0
Merge pull request #3779 from nitro404/hotfix/case-insensitive-user-email
Fix bugs with case insensitive user emails
2018-05-29 08:53:23 +02:00
Kevin Scroggins b2bc449e24 Add check for undefined user email in setter 2018-05-23 20:00:45 -04:00
Miroslav Bajtoš 3e3092c017
3.19.1
* Fix isOwner() bug in multiple-principal setup (Miroslav Bajtoš)
2018-05-21 09:17:50 +02:00
Miroslav Bajtoš 07e759259e
Merge pull request #3883 from strongloop/fix/principal-type-polymorphic-user
Fix isOwner() bug in multiple-principal setup
2018-05-21 09:16:45 +02:00
Miroslav Bajtoš 2aead13f11
Fix isOwner() bug in multiple-principal setup
Fix the owner role resolver to correctly handle the case when a user
from one model (e.g. Seller) is accessing an instance of another
user-like model (e.g. Customer).
2018-05-18 15:36:59 +02:00
Miroslav Bajtoš df70f83d67
3.19.0
* feat: remove all references to a Model (Miroslav Bajtoš)
2018-04-17 10:09:11 +02:00
Miroslav Bajtoš 7a148caa26
Merge pull request #3858 from strongloop/feature/remove-model
feat: remove all references to a Model
2018-04-17 10:07:47 +02:00
Miroslav Bajtoš 0cd380c590
feat: remove all references to a Model
Add API allowing applications to hide a Model from the
REST API and remove all references to it, allowing Garbage Collector
to claim all memory used by the model.
2018-04-17 09:42:08 +02:00
Miroslav Bajtoš 77a35231dc
3.18.3
* Remove forgotten debugger statement (Miroslav Bajtoš)
 * Fix role check in apps with multiple user models (Miroslav Bajtoš)
 * Fix formatting issues reported by recent eslint (Miroslav Bajtoš)
 * CODEOWNERS: add nitro404 (Miroslav Bajtoš)
 * test: add missing "return" in a promise-style test (Miroslav Bajtoš)
2018-03-22 09:22:20 +01:00
Miroslav Bajtoš 743b2d1495
Remove forgotten debugger statement 2018-03-22 09:21:44 +01:00
Miroslav Bajtoš 77d3d57252
Merge pull request #3835 from strongloop/fix/role-acl-with-multiple-users
Fix role check in apps with multiple user models
2018-03-22 09:18:37 +01:00
Miroslav Bajtoš f4527c9c91
Fix role check in apps with multiple user models 2018-03-20 14:15:44 +01:00
Miroslav Bajtoš 6ddf268cb6
Merge pull request #3820 from strongloop/fix/eslint-issues
Fix formatting issues reported by recent eslint
2018-03-16 13:32:19 +01:00
Miroslav Bajtoš c611bbbe04
Fix formatting issues reported by recent eslint 2018-03-05 10:03:59 +01:00
Miroslav Bajtoš 8e9fd36878
Merge pull request #3799 from strongloop/welcome-nitro404
CODEOWNERS: add nitro404
2018-02-16 09:17:58 +01:00
Miroslav Bajtoš 49bdf2fe3c
CODEOWNERS: add nitro404 2018-02-15 16:00:29 +01:00
Miroslav Bajtoš 66497ead70
Merge pull request #3787 from strongloop/fix/dangling-promise-in-test
test: add missing "return" in a promise-style test
2018-02-09 08:20:10 +01:00
Kevin Delisle 38f3d728b1 3.18.2
* model: fix infinite loop on nestRemoting (Kevin Delisle)
 * Use statusCode prop for user errors (Zak Barbuto)
2018-02-08 13:26:18 -05:00
Kevin Delisle 0feda03d5b
Merge pull request #3789 from strongloop/nestRemoting/prevent-endless-relation-recursion
model: fix infinite loop on nestRemoting
2018-02-08 13:12:32 -05:00
Kevin Delisle 386615a1df model: fix infinite loop on nestRemoting
Prevent endless recursion on nestRemoting calls for
two-way model links.
2018-02-08 11:26:02 -05:00
Miroslav Bajtoš 3723f107db
test: add missing "return" in a promise-style test
Before this change, when the test failed, the rejected promise
was not reported back to mocha and triggered "unhandled promise
rejection" warning only.
2018-02-02 14:25:52 +01:00
Raymond Feng d23ff84587
Merge pull request #3784 from zbarbuto/fix/user-status-code
Use statusCode prop for user errors
2018-01-31 15:16:27 -08:00
Zak Barbuto 50e2b49efe Use statusCode prop for user errors 2018-02-01 09:40:13 +10:30
Taranveer Virk 0eeb99060f 3.18.1
* update: juggler to version including security fix. (Taranveer Virk)
2018-01-31 16:56:12 -05:00
Taranveer Virk 6b4234b18d
Merge pull request #3781 from strongloop/update-juggler
update: juggler to version including security fix.
2018-01-31 16:40:55 -05:00
Taranveer Virk 2c909b8223 update: juggler to version including security fix. 2018-01-31 14:33:37 -05:00
Miroslav Bajtoš c5ff5faf0d
3.18.0
* fix: preserve datasource name (Kevin Scroggins)
 * Update Copyright Years (Justin Ross)
 * Support options.filter in createChangeStream (Edward Choh)
 * fixup! add top-level dep on eslint-plugin-mocha (Miroslav Bajtoš)
 * Update eslint and eslint-config to latest (Miroslav Bajtoš)
2018-01-29 16:36:14 +01:00
Miroslav Bajtoš 60c9dd166b
Merge pull request #3733 from nitro404/hotfix/retain-datasource-name
Fixe data sources not retaining the correct name value
2018-01-19 17:08:22 +01:00
Kevin Scroggins ab791fc258
fix: preserve datasource name
Modify the code creating juggler DataSource objects to correctly
forward the datasource name provided by the user.
2018-01-19 15:58:33 +01:00
Miroslav Bajtoš 72d48c3bfa
Merge pull request #3746 from JustinTRoss/patch-1
Update Copyright Years
2018-01-16 14:36:14 +01:00
Justin Ross 60750b4508
Update Copyright Years
Update copyright years to include 2018
2018-01-16 13:55:02 +01:00
Miroslav Bajtoš 0a4940e31e
Merge pull request #3683 from edwardchoh/master
Support options.filter in createChangeStream
2017-12-14 14:59:14 +01:00
Edward Choh 00169d2312
Support options.filter in createChangeStream
Implement "options.filter" argument in Persisted.createChangeStream()
by leveraging loopback-filter module.
2017-12-14 13:08:28 +01:00
Miroslav Bajtoš 7c030c6900
Merge pull request #3728 from strongloop/update-eslint-config
Update eslint and eslint-config to latest
2017-12-14 13:07:14 +01:00
Miroslav Bajtoš 243af4bfc2
3.17.1
* Update nestRemoting to pass optionsFromContext (bmatson)
 * fix(test): rem exclusive test (Samuel Reed)
 * fix(test): working test with 0 userId (Samuel Reed)
 * fix(AccessContext): Tighten userid/appid checks (Samuel Reed)
 * fix(id): replace with != null (Samuel Reed)
2017-12-12 19:43:30 +01:00
Miroslav Bajtoš b045e4a6be
Merge pull request #3681 from zipitwireless/master
Update nestRemoting to pass optionsFromContext
2017-12-12 19:00:09 +01:00
bmatson 317e00d92c
Update nestRemoting to pass optionsFromContext
Fix the code invoking relation getter to correctly pass through
the "options" argument.
2017-12-12 17:24:35 +01:00
Miroslav Bajtoš 010bbc6369
fixup! add top-level dep on eslint-plugin-mocha 2017-12-12 13:08:05 +01:00
Miroslav Bajtoš 73cc950b1b
Update eslint and eslint-config to latest 2017-12-12 09:33:15 +01:00
Miroslav Bajtoš fdb453943a
Merge pull request #3725 from STRML/fix/exclusive-test
fix(test): rem exclusive test
2017-12-08 20:19:01 +01:00
Samuel Reed 3af6a1bbaa
fix(test): rem exclusive test
Ref: #3720
2017-12-08 11:14:15 -06:00
Miroslav Bajtoš 3bf84bacde
Merge pull request #3720 from STRML/fix/falsy-id-3.x
Fix handling of falsy model ids
2017-12-08 15:24:13 +01:00
Samuel Reed 2bfd67ccaa
fix(test): working test with 0 userId 2017-12-07 10:10:35 -06:00
Samuel Reed b362776e73
fix(AccessContext): Tighten userid/appid checks
An application may have a use for a falsy ID.
2017-12-05 10:03:52 -06:00
Samuel Reed 0bac0a933f
fix(id): replace with != null
Ref: #2356, #2374, #3130, #3693
2017-12-05 09:54:28 -06:00
Diana Lau 1babfcde9f 3.17.0
* Added missing DateString type in loopback index (CSLTech)
 * chore:update license (Diana Lau)
2017-11-29 15:49:28 -05:00
Kevin Delisle 5dd5a674ce
Merge pull request #3689 from CSLTech/master
Added missing DateString type in loopback index
2017-11-22 13:34:25 -05:00
CSLTech 1a2d8a4571 Added missing DateString type in loopback index 2017-11-21 11:56:36 -05:00
Diana Lau 0737f5476d
Merge pull request #3687 from strongloop/license
chore:update license
2017-11-13 14:55:53 -05:00
Diana Lau b67a096f9e chore:update license 2017-11-09 13:12:39 -05:00
Miroslav Bajtoš cb600d1470
3.16.2
* Fix "POST /change-password" for multi-user setup (Miroslav Bajtoš)
2017-10-30 09:03:15 +01:00
Miroslav Bajtoš 825d5a6373
Merge tag 'v3.16.1'
Bring in changes from #3674 that were accidentally not landed on master:

 * Fix createOnlyInstance for related methods (Raymond Feng)

Close #3674
2017-10-30 09:00:16 +01:00
Miroslav Bajtoš 91729ee550
Merge pull request #3675 from strongloop/fix/change-password-multiple-users
Fix "POST /change-password" for multi-user setup
2017-10-30 08:58:07 +01:00
Raymond Feng 010c7bcd5f 3.16.1
* Fix createOnlyInstance for related methods (Raymond Feng)
2017-10-27 21:43:40 -07:00
Raymond Feng 6570b94843 Fix createOnlyInstance for related methods
For scoped or related create method, the createOnlyInstance flag should
be calculated on the target model. For example, User.createAccessTokens
should set the flag only if AccessToken has updateonly properties.
2017-10-27 18:51:56 -07:00
Miroslav Bajtoš 3996f56ab9
Fix "POST /change-password" for multi-user setup
Fix the code extracting current user id from the access token provided
in the HTTP request, to allow only access tokens created by the target
user models to execute the action.

This fixes the following security vulnerability:

* We have two user models, e.g. Admin and Customer

* We have an Admin instance and a Customer instance with the same
  id and the same password.

* The Customer can change Admin's password using their
  regular access token.
2017-10-27 09:47:07 +02:00
Kevin Delisle 4d4070e542 3.16.0
* Fix "POST /reset-password" for multi-user setup (Miroslav Bajtoš)
 * test: extract helpers for logging HTTP errors (Miroslav Bajtoš)
 * CODEOWNERS: move @lehni to Alumni section (Miroslav Bajtoš)
2017-10-24 14:12:37 -04:00
Kevin Delisle 2761e62533 Merge pull request #3666 from strongloop/fix/multi-user-reset-password
Fix "POST /reset-password" for multi-user setup
2017-10-24 14:10:35 -04:00
Miroslav Bajtoš 0a2a45512c
Fix "POST /reset-password" for multi-user setup
Fix the code extracting current user id from the access token provided
in the HTTP request, to allow only access tokens created by the target
user models to execute the action.

This fixes the following security vulnerability:

* A UserA with id 1 (for example), requires a resetToken1

* A UserB with the same id requires a resetToken2.

* Using resetToken2, use the UserAs/reset-password endpoint and change
  the password of UserA and/or vice-versa.
2017-10-19 13:29:08 +02:00
Miroslav Bajtoš 4ebc517a78
test: extract helpers for logging HTTP errors
Extract two helpers into a shared file:

 - logAllServerErrors(app)
 - logServerErrorsOtherThan(statusCode, app)
2017-10-19 13:08:54 +02:00
Miroslav Bajtoš 083fb5d668 Merge pull request #3659 from strongloop/remove/lehni
CODEOWNERS: move @lehni to Alumni section
2017-10-19 10:44:20 +02:00
Miroslav Bajtoš c3450df4db
CODEOWNERS: move @lehni to Alumni section 2017-10-19 10:42:54 +02:00
Miroslav Bajtoš f30159cd23
3.15.0
* update strong-globalize to 3.1.0 (shimks)
 * Fix handling of user verification options (Miroslav Bajtoš)
 * Handle missing getUpdateOnlyProperties fn (Jürg Lehni)
 * test: fix too strict test assertion (Miroslav Bajtoš)
 * Fix typo (Siegfried Ehret)
2017-10-13 15:33:56 +02:00
Miroslav Bajtoš 64d60fb6f7 Merge pull request #3650 from strongloop/update-strong-globalize
update strong-globalize to 3.1.0
2017-10-13 15:27:02 +02:00
shimks 2f02fbac89 update strong-globalize to 3.1.0 2017-10-12 15:08:04 -04:00
Miroslav Bajtoš fb8f3d9df3 Merge pull request #3647 from lehni/model/fix-updateonly-props-check
Handle missing getUpdateOnlyProperties fn on Model
2017-10-09 17:00:55 +02:00
Miroslav Bajtoš 9176ee2e11 Merge pull request #3609 from sebastianfelipe/fix/user-verify-duplicated-token
Fix handling of user verification options
2017-10-09 14:00:22 +02:00
Miroslav Bajtoš d0a4941668
Fix handling of user verification options
- Fix `User.prototype.verify` to not modify properties of the supplied
   `verifyOptions` argument. This is needed to allow callers to supply
   the same options object to multiple calls of `verify`.

 - Fix `User.getVerifyOptions` to always return a new copy of the
   options object. This is needed to allow callers to modify the
   returned options object without affecting the result returned
   by subsequent calls of `getVerifyOptions`.
2017-10-09 13:42:22 +02:00
Jürg Lehni 826ee2aca8 Handle missing getUpdateOnlyProperties fn
If the current scope does not define a getUpdateOnlyProperties
function, the updateOnlyProps value will now be set to false.
2017-10-09 09:19:47 +02:00
Miroslav Bajtoš 8488da2e26 Merge pull request #3637 from strongloop/fix/build
test: fix too strict test assertion
2017-10-05 13:14:52 +02:00
Miroslav Bajtoš 33989d776c
test: fix too strict test assertion
Rework the test verifying properties of `loopback` to ignore
new express properties added after the test was written.
2017-10-04 10:31:50 +02:00
Miroslav Bajtoš 1dd0ab31e0 Merge pull request #3636 from SiegfriedEhret/patch-1
Fix typo in jsdoc
2017-10-04 10:11:04 +02:00
Siegfried Ehret db8130ac6d Fix typo
Update a jsdoc thing to match the argument name.
2017-10-04 09:26:17 +02:00
Raymond Feng c9913927e5 3.14.0
* Allow declarative nestRemoting for relations (Raymond Feng)
2017-09-28 11:07:15 -07:00
Raymond Feng c453ad52c2 Merge pull request #3628 from strongloop/declarative-nest-remoting
Allow declarative nestRemoting for relations
2017-09-28 11:06:32 -07:00
Raymond Feng c0a0f09f3a Allow declarative nestRemoting for relations
Now relation.options.nestRemoting can be set to true so that
nestRemoting will be set up automatically without explicitly
calling MyModel.nestRemoting
2017-09-27 09:22:06 -07:00
Miroslav Bajtoš fcfaf7ef53
3.13.0
* Fix OWNER role to handle multiple relations (pierreclr)
 * Fix acl.resolvePermission for wildcard req (Farid Neshat)
 * CODEOWNERS: add zbarbuto (Miroslav Bajtoš)
2017-09-27 17:45:28 +02:00
Miroslav Bajtoš 658d228789 Merge pull request #3140 from pierreclr/feature/allow-mutiple-owners-resolving
Fix OWNER role to handle multiple relations
2017-09-27 17:43:48 +02:00
pierreclr e17132d061
Fix OWNER role to handle multiple relations
Fix the code resolving OWNER role to correctly handle the situation
where the target model has multiple "belongsTo" relations to the User
model.

Introduce a new model setting "ownerRelations" that enables the new
behavior. When "ownerRelations" is set to true, then all "belongsTo"
relations are considered as granting ownership. Alternatively,
"ownerRelations" can be set to an array of the relations which
are granting ownership.

For example, a document can "belongTo" an author and a reviewer,
but only the author is an owner, the reviewer is not. In this case,
"ownerRelations" should be set to "['author']".
2017-09-27 17:11:36 +02:00
Miroslav Bajtoš ef7175a4d5 Merge pull request #3293 from alFReD-NSH/bugfix/acl-checkpermission
Fix acl.resolvePermission not working with wildcard request
2017-09-27 16:51:27 +02:00
Miroslav Bajtoš 2128ecde46 Merge pull request #3625 from strongloop/welcome-zbarbuto
CODEOWNERS: add zbarbuto
2017-09-27 11:10:40 +02:00
Farid Neshat d2d8fabb16 Fix acl.resolvePermission for wildcard req
When acl.resolvePermission was called with a request containing a
wildcard, it would return the matching acl with lowest score instead of
higher.

Fixes #2153
2017-09-27 02:48:34 +02:00
Miroslav Bajtoš 4c4430ea95
3.12.0
* Fix relation race condition in model glob (Zak Barbuto)
 * CODEOWNERS: add lehni (Miroslav Bajtoš)
2017-09-25 15:58:47 +02:00
Miroslav Bajtoš 883667ce8e
CODEOWNERS: add zbarbuto 2017-09-25 09:49:00 +02:00
Miroslav Bajtoš 0f40ca8f8e Merge pull request #3565 from zbarbuto/fix/shared-glob
Fix relation race condition in model glob
2017-09-01 09:44:13 +02:00
Zak Barbuto d405432b2d Fix relation race condition in model glob
Globs working depended on the order that models were imported.
Remote sharing is now re-calculated whenever a new model is remoted.
2017-09-01 09:18:39 +09:30
Miroslav Bajtoš 30f3161c65 Merge pull request #3595 from strongloop/welcome-lehni
CODEOWNERS: add lehni as a collaborator
2017-08-31 11:15:07 +02:00
Miroslav Bajtoš 63721fd253
CODEOWNERS: add lehni 2017-08-30 10:46:06 +02:00
rashmihunt 6ba35c297b 3.11.1
* Handle missing getUpdateOnlyProperties fn (Kevin Delisle)
2017-08-23 09:17:57 -07:00
Kevin Delisle 8bff145c65 Merge pull request #3579 from strongloop/persisted-model/fix-updateonly-props-check
Handle missing getUpdateOnlyProperties fn
2017-08-23 10:48:19 -04:00
Kevin Delisle 16ede97033 Handle missing getUpdateOnlyProperties fn
If the current scope does not define a getUpdateOnlyProperties
function, the updateOnlyProps value will now be set to false.
2017-08-23 10:21:11 -04:00
rashmihunt 29e89f50e5 3.11.0
* Support createOnlyInstance in model (#3548) (Rashmi Hunt)
 * Add stalebot configuration (Kevin Delisle)
 * Catch errors on invalidate update (loay)
 * Update Issue and PR Templates (#3568) (Sakib Hasan)
2017-08-22 17:17:24 -07:00
Rashmi Hunt 3651c09782 Support createOnlyInstance in model (#3548)
* setting up createOnlyInstance

* add comment

* fix eslint issue

* new tests

* Address code review comments
2017-08-22 17:10:55 -07:00
Kevin Delisle 661f7741cd Add stalebot configuration 2017-08-22 15:12:54 -04:00
Loay 4a8e3f1327 Merge pull request #3570 from strongloop/catch-err
Catch errors on invalidate update
2017-08-17 11:24:57 -04:00
loay bf4b5de648 Catch errors on invalidate update 2017-08-17 10:46:09 -04:00
Sakib Hasan b3602bdd18 Update Issue and PR Templates (#3568)
* update issue template

* update pr template
2017-08-16 11:54:30 -04:00
Miroslav Bajtoš b1adba1c4a
3.10.1
* fix(validatePassword): reword error message (Samuel Reed)
 * Do not add isStatic properties to method settings (Jürg Lehni)
2017-08-16 16:20:33 +02:00
Miroslav Bajtoš 2ebe38b4d5 Merge pull request #3540 from lehni/fix/isStatic-method-settings
Do not add isStatic properties to method settings
2017-08-16 15:55:11 +02:00
Miroslav Bajtoš bc8778908e Merge pull request #3556 from STRML/fix/validatePassword
fix(validatePassword): Reword password too long error.
2017-08-16 15:53:58 +02:00
Samuel Reed 44dd048036
fix(validatePassword): reword error message
Reword the error message returned when the password is too long
 - remove the plaintext password value, it looks very bad
 - include information about the maximum allowed length instead

Also add additional context to the error.
2017-08-16 14:57:57 +02:00
Jürg Lehni a736f782af Do not add isStatic properties to method settings
Closes #3529
2017-08-15 18:09:16 +02:00
Miroslav Bajtoš 5db00c6ab2
3.10.0
* Allow glob-style patterns for remote options (Zak Barbuto)
 * Fix case of values per doc issue (crandmck)
 * Update translated strings Q3 2017 (Allen Boone)
 * Revert "Validate on updateAll" (Sakib Hasan)
 * Add tests of HTTP normalization on app level (Jürg Lehni)
 * travis: drop Node.js 7.x, add 8.x (Miroslav Bajtoš)
 * Validate on updateAll (ssh24)
 * Update juggler version (loay)
 * update messages.json (Diana Lau)
 * small fix for the title (Michael Alaev)
 * Changed http to https (Michael Alaev)
 * Update Travis registry (loay)
 * Add unit test for empty password (loay)
 * Add CODEOWNER file (Diana Lau)
2017-08-14 17:45:31 +02:00
Miroslav Bajtoš 06a2b6d86b Merge pull request #3504 from zbarbuto/feature/share-method-glob
Allow glob-style patterns for remote options
2017-08-14 17:25:43 +02:00
Zak Barbuto 724a7d1928 Allow glob-style patterns for remote options 2017-08-14 12:23:26 +09:30
Rand McKinney df87c17c08 Merge pull request #3559 from strongloop/acl-apidoc-fix
Fix case of values per doc issue
2017-08-11 11:27:00 -07:00
crandmck 9f8321ad61 Fix case of values per doc issue 2017-08-11 11:10:51 -07:00
Miroslav Bajtoš 132ce4c4ae Merge pull request #3555 from kallenboone/master
Update translated strings Q3 2017
2017-08-11 13:59:01 +02:00
Allen Boone dcb190ffc8 Update translated strings Q3 2017 2017-08-10 15:15:04 -04:00
Sakib Hasan 74cbe12918 Merge pull request #3545 from strongloop/revert-3541-add-validate-updateAll
Revert "Validate on updateAll"
2017-08-04 06:33:59 -04:00
Sakib Hasan 2fd5701ede Revert "Validate on updateAll" 2017-08-03 13:46:01 -04:00
Loay 2809d62990 Merge pull request #3538 from strongloop/juggler-version
Update juggler version
2017-08-03 10:00:05 -04:00
Miroslav Bajtoš d3d0ef4ec1 Merge pull request #3527 from lehni/feature/test-http-normalization
Add tests of HTTP normalization on app level
2017-08-03 14:52:12 +02:00
Jürg Lehni 5cd95e42f2
Add tests of HTTP normalization on app level
Also improve tests on model level to include nested routes

Add a test for HTTP normalization precedence too.
2017-08-03 12:32:54 +02:00
Miroslav Bajtoš 5562dca118 Merge pull request #3543 from strongloop/update/travis-platforms
travis: drop Node.js 7.x, add 8.x
2017-08-03 12:31:25 +02:00
Miroslav Bajtoš 3915c49a89
travis: drop Node.js 7.x, add 8.x 2017-08-03 12:02:26 +02:00
Sakib Hasan 619a0e468d Merge pull request #3541 from strongloop/add-validate-updateAll
Validate on updateAll
2017-08-02 12:27:50 -04:00
ssh24 5dd0d196ee Validate on updateAll 2017-08-02 11:52:56 -04:00
loay 7879ec346a Update juggler version 2017-08-02 11:14:18 -04:00
Diana Lau deec84f3b5 Merge pull request #3537 from strongloop/translate
update messages.json
2017-08-02 10:30:31 -04:00
Diana Lau 679ecbc12d update messages.json 2017-08-02 09:39:33 -04:00
Rand McKinney 8589629e7a Merge pull request #3523 from Alaev/patch-4
Fixed broken link && Changed http to https
2017-07-28 10:44:50 -07:00
Michael Alaev 478e3f3b16 small fix for the title
small fix for the title
2017-07-28 14:09:55 +03:00
Michael Alaev 1fcc40f1d1 Changed http to https
Changed the following links from HTTP to HTTPS as it is supported by loopback and strongloop
2017-07-28 01:14:33 +03:00
Loay 131b78dc84 Merge pull request #3511 from strongloop/empty-password-lb3
Add unit test for empty password
2017-07-25 15:42:22 -04:00
loay a3bf813088 Update Travis registry 2017-07-25 14:40:05 -04:00
loay c761dc5279 Add unit test for empty password 2017-07-25 14:40:04 -04:00
Diana Lau d1f6129eaf Merge pull request #3513 from strongloop/add-codeowner
Add CODEOWNER file
2017-07-25 14:37:20 -04:00
Diana Lau 5ed74ab24b Add CODEOWNER file 2017-07-25 09:41:09 -04:00
Miroslav Bajtoš 839c58639d
3.9.0
* Remove observers from Model on end of the stream (Alexei Smirnov)
 * Fix Model#settings.acls doc type signature (Farid Nouri Neshat)
 * Use `localhost` instead of `::` for local (Daijiro Wachi)
 * Fix API doc for Model class property type (Candy)
 * Update package.json (sqlwwx)
 * Support remoting adapters with no ctx.req object (Piero Maltese)
 * update strong-error-handler (sqlwwx)
2017-07-12 16:17:00 +02:00
Miroslav Bajtoš f7b88e3435 Merge pull request #3474 from bigcup/fixChangeStreamIssue#1569
Removed observers from a Model after finish of the stream
2017-07-12 16:15:20 +02:00
Alexei Smirnov 8ed92a12e0
Remove observers from Model on end of the stream
- Remove flags and properly finish the stream.
 - Destroy emits an end event for compability with ending of
   ReadableStream now.
 - Check for default implementation of destroy() method,
   because in Node.js 8 all types of streams have a native one.
2017-07-12 10:28:27 +02:00
Miroslav Bajtoš ca3a21ddd5 Merge pull request #3444 from alFReD-NSH/patch-2
Fix Model#settings.acls doc type signature
2017-06-23 16:43:47 +02:00
Farid Nouri Neshat 065eedab7b
Fix Model#settings.acls doc type signature 2017-06-23 16:25:54 +02:00
Miroslav Bajtoš f85551b715 Merge pull request #3450 from watilde/fixes-3179
Use `localhost` instead of `::` for local
2017-06-21 17:51:24 +02:00
Daijiro Wachi 75b4a45968 Use `localhost` instead of `::` for local 2017-06-15 22:29:37 +02:00
Candy 1be0a9f3d5 Merge pull request #3448 from strongloop/fix_type
Fix API doc for Model class property type
2017-06-14 15:49:22 -04:00
Candy 04983831ca Fix API doc for Model class property type 2017-06-14 14:38:55 -04:00
Miroslav Bajtoš 359a6a5762 Merge pull request #3396 from sqlwwx/master
Update strong-error-handler to 2.x
2017-05-23 16:58:59 +02:00
sqlwwx 3a10209502 Update package.json 2017-05-23 09:14:38 +08:00
sqlwwx 375d476d28 Update package.json 2017-05-23 09:13:27 +08:00
Miroslav Bajtoš f8db64c9c3 Merge pull request #3376 from pierissimo/patch-1
Support remoting adapters with no ctx.req object
2017-05-22 15:23:51 +02:00
Piero Maltese 4735efa41f
Support remoting adapters with no ctx.req object
Fix `Model.createOptionsFromRemotingContext()` to correctly handle
the case where `ctx.req` is not defined, e.g. when using
websocket-based adapters.
2017-05-22 13:21:44 +02:00
sqlwwx ee68b8067c update strong-error-handler 2017-05-13 18:21:15 +08:00
Raymond Feng 9fb67315f9 3.8.0
* Refactor access token to make it extensible (Raymond Feng)
2017-05-02 11:16:34 -07:00
Raymond Feng a9c13a8f6c Merge pull request #3381 from strongloop/feature/refactor-access-token-id
Refactor access token to make it extensible
2017-05-02 13:14:31 -05:00
Raymond Feng 69df11bb8e Refactor access token to make it extensible
1. Make it possible to reuse getIdForRequest()
2. Introduce a flag to control if oAuth2 bearer token should be base64
encoded
3. Promote resolve() to locate/validate access tokens by id
2017-05-02 10:55:51 -07:00
149 changed files with 7640 additions and 4186 deletions

View File

@ -1,29 +0,0 @@
<!--
Questions:
https://groups.google.com/forum/#!forum/loopbackjs
https://gitter.im/strongloop/loopback
Immediate support:
https://strongloop.com/api-connect-faqs/
https://strongloop.com/node-js/subscription-plans/
-->
# Description/Steps to reproduce
<!--
If feature: A description of the feature
If bug: Steps to reproduce + link to sample repo
-->
# Expected result
<!--
Also include actual results if bug
-->
# Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->

53
.github/ISSUE_TEMPLATE/Bug_report.md vendored Normal file
View File

@ -0,0 +1,53 @@
---
name: Bug report
about: Create a report to help us improve
labels: bug
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
Are you using LoopBack version 4? Please report the bug here:
https://github.com/strongloop/loopback-next/issues/new
HELP US HELP YOU, PLEASE
- Do a quick search to avoid duplicate issues
- Provide as much information as possible (reproduction sandbox, use case for features, etc.)
- Consider using a more suitable venue for questions such as Stack Overflow, Gitter, etc.
Please fill in the *entire* template below.
-->
## Steps to reproduce
<!-- Describe how to reproduce the issue -->
## Current Behavior
<!-- Describe the observed result -->
## Expected Behavior
<!-- Describe what did you expect instead, what is the desired outcome? -->
## Link to reproduction sandbox
<!--
See https://loopback.io/doc/en/contrib/Reporting-issues.html#loopback-3x-bugs
Note: Failure to provide a sandbox application for reproduction purposes will result in the issue being closed.
-->
## Additional information
<!--
Copy+paste the output of these two commands:
node -e 'console.log(process.platform, process.arch, process.versions.node)'
npm ls --prod --depth 0 | grep loopback
-->
## Related Issues
<!-- Did you find other bugs that looked similar? -->
_See [Reporting Issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more tips on writing good issues_

View File

@ -0,0 +1,34 @@
---
name: Feature request
about: Suggest an idea for this project
labels: feature
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
LoopBack version 3 is in LTS mode, we are not accepting new features.
We are actively developing version 4, you can find the new GitHub
repository here: https://github.com/strongloop/loopback-next
-->
## Suggestion
<!-- A summary of what you'd like to see added or changed -->
## Use Cases
<!--
What do you want to use this for?
What shortcomings exist with current approaches?
-->
## Examples
<!-- Show how this would be used and what the behavior would be -->
## Acceptance criteria
TBD - will be filled by the team.

27
.github/ISSUE_TEMPLATE/Question.md vendored Normal file
View File

@ -0,0 +1,27 @@
---
name: Question
about: The issue tracker is not for questions. Please use Stack Overflow or other resources for help.
labels: question
---
<!-- 🚨 STOP 🚨 STOP 🚨 STOP 🚨
THE ISSUE TRACKER IS NOT FOR QUESTIONS.
DO NOT CREATE A NEW ISSUE TO ASK A QUESTION.
Please use one of the following resources for help:
**Questions**
- https://stackoverflow.com/tags/loopbackjs
- https://groups.google.com/forum/#!forum/loopbackjs
- https://gitter.im/strongloop/loopback
**Immediate support**
- https://strongloop.com/api-connect-faqs/
- https://strongloop.com/node-js/subscription-plans/
-->

16
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,16 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://loopback.io/doc/en/contrib/Reporting-issues.html#security-issues
about: >
LoopBack 3 has reached End-of-Life. No new security fixes will be provided
or accepted.
Do not report security vulnerabilities using GitHub issues. Please send an
email to `reachsl@us.ibm.com` instead.
- name: Get help on StackOverflow
url: https://stackoverflow.com/tags/loopbackjs
about: Please ask and answer questions on StackOverflow.
- name: Join our mailing list
url: https://groups.google.com/forum/#!forum/loopbackjs
about: You can also post your question to our mailing list.

View File

@ -1,24 +1,18 @@
### Description
#### Related issues
<!-- <!--
Please use the following link syntaxes: Please provide a high-level description of the changes made by your pull request.
- #49 (to reference issues in the current repository) Include references to all related GitHub issues and other pull requests, for example:
- strongloop/loopback#49 (to reference issues in another repository)
Fixes #123
Implements #254
See also #23
--> -->
- None ## Checklist
### Checklist 👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback) 👈
<!--
Please mark your choice with an "x" (i.e. [x], see
https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments)
-->
- [ ] `npm test` passes on your machine
- [ ] New tests added or existing tests modified to cover all changes - [ ] New tests added or existing tests modified to cover all changes
- [ ] Code conforms with the [style - [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.html)
guide](http://loopback.io/doc/en/contrib/style-guide.html) - [ ] Commit messages are following our [guidelines](https://loopback.io/doc/en/contrib/git-commit-messages.html)

21
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,21 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- critical
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been closed due to continued inactivity. Thank you for your understanding.
If you believe this to be in error, please contact one of the code owners,
listed in the [`CODEOWNERS`](https://github.com/strongloop/loopback/blob/master/CODEOWNERS) file at the top-level of this repository.

1
.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

View File

@ -1,21 +1,15 @@
sudo: false sudo: false
language: node_js language: node_js
node_js: node_js:
- "4" - "8"
- "6" - "10"
- "7" - "12"
- "14"
addons:
chrome: stable
after_success: npm run coverage after_success: npm run coverage
# see https://www.npmjs.com/package/phantomjs-prebuilt#continuous-integration
cache:
directories:
- travis_phantomjs
before_install: before_install:
# Upgrade PhantomJS to v2.1.1. - npm config set registry http://ci.strongloop.com:4873/
- "export PHANTOMJS_VERSION=2.1.1"
- "export PATH=$PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin:$PATH"
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi"
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then wget https://github.com/Medium/phantomjs/releases/download/v$PHANTOMJS_VERSION/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2; fi"
- "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi"
- "phantomjs --version"

View File

@ -1,3 +1,447 @@
2020-11-25, Version 3.28.0
==========================
* upgrade nodemailer to greater than 6.4.16 (jannyHou)
* chore: sync LoopBack 4 with Node.js v14 EOL (Rifa Achrinza)
* chore: add Node.js 14 to travis (Diana Lau)
* Remove "major" and "p1" from stalebot (Miroslav Bajtoš)
2020-03-06, Version 3.27.0
==========================
* Update LTS status in README (Miroslav Bajtoš)
* chore: update copyright year (Diana Lau)
* feat: change hasone relation error message (Sujesh T)
* chore: disable security issue reporting (Nora)
* chore: fix eslint violations (Nora)
* fixup! manual fixes (Miroslav Bajtoš)
* fixup! eslint --fix . (Miroslav Bajtoš)
* chore: update eslint & eslint-config to latest (Miroslav Bajtoš)
* chore: update dev-dependencies (Miroslav Bajtoš)
* chore: update chai to v4, dirty-chai to v2 (Miroslav Bajtoš)
* Updated "ismail" package to v3.2 (Stanislav Sarbinski)
* Introduce issue templates for bugs, features, etc. (Miroslav Bajtoš)
* Improve PULL_REQUEST_TEMPLATE (Miroslav Bajtoš)
* test: use Chromium (not Chrome) when available (Miroslav Bajtoš)
* test: disable Chrome sandboxing when inside Docker (Miroslav Bajtoš)
* test: switch from PhantomJS to HeadlessChrome (Miroslav Bajtoš)
2019-05-31, Version 3.26.0
==========================
* fix: disallow queries in username and email fields (Hage Yaapa)
* Ignore failing downstream dependencies (Miroslav Bajtoš)
* Upgrade nyc to version 14 (Miroslav Bajtoš)
* Update Karma dependencies to latest versions (Miroslav Bajtoš)
* Drop Node.js 6.x from the supported versions (Miroslav Bajtoš)
* Fix Model.exists() to work with remote connector (Maxim Sharai)
* chore: update copyrights years (Agnes Lin)
* Update LTS status (Diana Lau)
* Enable Node.js 12.x on Travis CI (Miroslav Bajtoš)
* chore: update copyright year (Diana Lau)
* chore: update LB3 EOL date (Diana Lau)
2019-03-15, Version 3.25.1
==========================
* Back-ticks added to highlight example JSON (Quentin Presley)
* Add same change to description for findOne (Quentin Presley)
* Update the description for persisted-models (Quentin Presley)
* handle $2b$ in hashed password check (Sylvain Dumont)
2019-02-05, Version 3.25.0
==========================
* Support middleware injected by AppDynamics. (Mike Li)
2019-01-11, Version 3.24.2
==========================
* Fix crash when modifying an unknown user (Matheus Horstmann)
2019-01-08, Version 3.24.1
==========================
* Update underscore.string to 3.3.5 (Francois)
* Fix: treat empty access token string as undefined (andrey-abramow)
2018-11-15, Version 3.24.0
==========================
* Set juggler options for remote calls (Raymond Feng)
* Speed up ACL tests by reducing saltWorkFactor (Miroslav Bajtoš)
2018-10-25, Version 3.23.2
==========================
* Fix ACL check to support model wildcard (Moshe Malka)
2018-10-18, Version 3.23.1
==========================
* README: highlight Active LTS at the top (Miroslav Bajtoš)
2018-10-09, Version 3.23.0
==========================
* Clear handler cache when a method is added/removed (Mohammed Essehemy)
* Add `options.preserveAccessTokens` (lchaglla)
* Update LB3 to be active LTS (Diana Lau)
* Fix ACL tests to wait until all assertions finish (Moshe Malka)
* chore: update to latest linting rules (virkt25)
2018-09-12, Version 3.22.3
==========================
* chore: use grunt to install optional phantomjs (virkt25)
* [WebFM] fr translation (candytangnb)
2018-08-29, Version 3.22.2
==========================
* [WebFM] tr translation (candytangnb)
* [WebFM] de translation (candytangnb)
* [WebFM] cs/es/fr/it/nl/pl/pt_BR/ru translation (candytangnb)
2018-08-22, Version 3.22.1
==========================
* [WebFM] ja/ko/zh_CN/zh_TW translation (candytangnb)
* remove unnecessary format call (Diana Lau)
* Make desc when export-api-def translatable (Diana Lau)
2018-08-08, Version 3.22.0
==========================
* fix: accessToken create default acl (virkt25)
* add: ppc64 and s390x to not run UI tests (Thomas Leah)
* chore: update deps + fix linting + .npmrc (virkt25)
* Update Loopback 2.x EOL dates (Chris Bailey)
* Fix formatting (Chris Bailey)
* Update support badge and move LTS section (Chris Bailey)
* Add badges and information for LTS and support (Chris Bailey)
2018-07-09, Version 3.21.0
==========================
* Make verifyUserRelations() more robust (mcitdev)
* Fix crash in verifyUserRelations (ryanxwelch)
* Fix crash in User model's "before delete" hook (mcitdev)
* [WebFM] cs/pl/ru translation (candytangnb)
* Update strong-error-handler (shimks)
2018-06-12, Version 3.20.0
==========================
* Update strong-globalize to 4.x (Miroslav Bajtoš)
* Update nodemailer to v4.x (Dimitris)
* Drop support for Node.js 4.x (Miroslav Bajtoš)
2018-06-04, Version 3.19.3
==========================
* Provide link to CODEOWNERS (Aditya Agarwal)
* fix bug in User.verify when confirm is disabled (wolrajhti)
* Enable Node.js 10.x on Travis CI (Miroslav Bajtoš)
2018-05-29, Version 3.19.2
==========================
* Add check for undefined user email in setter (Kevin Scroggins)
2018-05-21, Version 3.19.1
==========================
* Fix isOwner() bug in multiple-principal setup (Miroslav Bajtoš)
2018-04-17, Version 3.19.0
==========================
* feat: remove all references to a Model (Miroslav Bajtoš)
2018-03-22, Version 3.18.3
==========================
* Remove forgotten debugger statement (Miroslav Bajtoš)
* Fix role check in apps with multiple user models (Miroslav Bajtoš)
* Fix formatting issues reported by recent eslint (Miroslav Bajtoš)
* CODEOWNERS: add nitro404 (Miroslav Bajtoš)
* test: add missing "return" in a promise-style test (Miroslav Bajtoš)
2018-02-08, Version 3.18.2
==========================
* model: fix infinite loop on nestRemoting (Kevin Delisle)
* Use statusCode prop for user errors (Zak Barbuto)
2018-01-31, Version 3.18.1
==========================
* update: juggler to version including security fix. (Taranveer Virk)
2018-01-29, Version 3.18.0
==========================
* fix: preserve datasource name (Kevin Scroggins)
* Update Copyright Years (Justin Ross)
* Support options.filter in createChangeStream (Edward Choh)
* fixup! add top-level dep on eslint-plugin-mocha (Miroslav Bajtoš)
* Update eslint and eslint-config to latest (Miroslav Bajtoš)
2017-12-12, Version 3.17.1
==========================
* Update nestRemoting to pass optionsFromContext (bmatson)
* fix(test): rem exclusive test (Samuel Reed)
* fix(test): working test with 0 userId (Samuel Reed)
* fix(AccessContext): Tighten userid/appid checks (Samuel Reed)
* fix(id): replace with != null (Samuel Reed)
2017-11-29, Version 3.17.0
==========================
* Added missing DateString type in loopback index (CSLTech)
* chore:update license (Diana Lau)
2017-10-30, Version 3.16.2
==========================
* Fix "POST /change-password" for multi-user setup (Miroslav Bajtoš)
2017-10-27, Version 3.16.1
==========================
* Fix createOnlyInstance for related methods (Raymond Feng)
2017-10-24, Version 3.16.0
==========================
* Fix "POST /reset-password" for multi-user setup (Miroslav Bajtoš)
* test: extract helpers for logging HTTP errors (Miroslav Bajtoš)
* CODEOWNERS: move @lehni to Alumni section (Miroslav Bajtoš)
2017-10-13, Version 3.15.0
==========================
* update strong-globalize to 3.1.0 (shimks)
* Fix handling of user verification options (Miroslav Bajtoš)
* Handle missing getUpdateOnlyProperties fn (Jürg Lehni)
* test: fix too strict test assertion (Miroslav Bajtoš)
* Fix typo (Siegfried Ehret)
2017-09-28, Version 3.14.0
==========================
* Allow declarative nestRemoting for relations (Raymond Feng)
2017-09-27, Version 3.13.0
==========================
* Fix OWNER role to handle multiple relations (pierreclr)
* Fix acl.resolvePermission for wildcard req (Farid Neshat)
* CODEOWNERS: add zbarbuto (Miroslav Bajtoš)
2017-09-25, Version 3.12.0
==========================
* Fix relation race condition in model glob (Zak Barbuto)
* CODEOWNERS: add lehni (Miroslav Bajtoš)
2017-08-23, Version 3.11.1
==========================
* Handle missing getUpdateOnlyProperties fn (Kevin Delisle)
2017-08-22, Version 3.11.0
==========================
* Support createOnlyInstance in model (#3548) (Rashmi Hunt)
* Add stalebot configuration (Kevin Delisle)
* Catch errors on invalidate update (loay)
* Update Issue and PR Templates (#3568) (Sakib Hasan)
2017-08-16, Version 3.10.1
==========================
* fix(validatePassword): reword error message (Samuel Reed)
* Do not add isStatic properties to method settings (Jürg Lehni)
2017-08-14, Version 3.10.0
==========================
* Allow glob-style patterns for remote options (Zak Barbuto)
* Fix case of values per doc issue (crandmck)
* Update translated strings Q3 2017 (Allen Boone)
* Revert "Validate on updateAll" (Sakib Hasan)
* Add tests of HTTP normalization on app level (Jürg Lehni)
* travis: drop Node.js 7.x, add 8.x (Miroslav Bajtoš)
* Validate on updateAll (ssh24)
* Update juggler version (loay)
* update messages.json (Diana Lau)
* small fix for the title (Michael Alaev)
* Changed http to https (Michael Alaev)
* Update Travis registry (loay)
* Add unit test for empty password (loay)
* Add CODEOWNER file (Diana Lau)
2017-07-12, Version 3.9.0
=========================
* Remove observers from Model on end of the stream (Alexei Smirnov)
* Fix Model#settings.acls doc type signature (Farid Nouri Neshat)
* Use `localhost` instead of `::` for local (Daijiro Wachi)
* Fix API doc for Model class property type (Candy)
* Update package.json (sqlwwx)
* Support remoting adapters with no ctx.req object (Piero Maltese)
* update strong-error-handler (sqlwwx)
2017-05-02, Version 3.8.0
=========================
* Refactor access token to make it extensible (Raymond Feng)
2017-04-27, Version 3.7.0 2017-04-27, Version 3.7.0
========================= =========================

11
CODEOWNERS Normal file
View File

@ -0,0 +1,11 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners,
# the last matching pattern has the most precendence.
# Current maintainers
* @bajtos @fabien @clarkorz @ebarault @zbarbuto @nitro404
# Alumni
_ @lehni

View File

@ -1,10 +1,10 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
/* global module:false */
'use strict'; 'use strict';
module.exports = function(grunt) { module.exports = function(grunt) {
// Do not report warnings from unit-tests exercising deprecated paths // Do not report warnings from unit-tests exercising deprecated paths
process.env.NO_DEPRECATION = 'loopback'; process.env.NO_DEPRECATION = 'loopback';
@ -104,7 +104,7 @@ module.exports = function(grunt) {
karma: { karma: {
'unit-once': { 'unit-once': {
configFile: 'test/karma.conf.js', configFile: 'test/karma.conf.js',
browsers: ['PhantomJS'], browsers: ['ChromeDocker'],
singleRun: true, singleRun: true,
reporters: ['dots', 'junit'], reporters: ['dots', 'junit'],
@ -218,16 +218,17 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-karma'); grunt.loadNpmTasks('grunt-karma');
grunt.registerTask('e2e-server', function() { grunt.registerTask('e2e-server', function() {
var done = this.async(); const done = this.async();
var app = require('./test/fixtures/e2e/app'); const app = require('./test/fixtures/e2e/app');
app.listen(0, function() { app.listen(0, function() {
process.env.PORT = this.address().port; process.env.PORT = this.address().port;
done(); done();
}); });
}); });
grunt.registerTask('skip-karma-on-windows', function() { grunt.registerTask('skip-karma', function() {
console.log('*** SKIPPING PHANTOM-JS BASED TESTS ON WINDOWS ***'); console.log(`*** SKIPPING PHANTOM-JS BASED TESTS ON ${process.platform}` +
` ${process.arch} ***`);
}); });
grunt.registerTask('e2e', ['e2e-server', 'karma:e2e']); grunt.registerTask('e2e', ['e2e-server', 'karma:e2e']);
@ -238,8 +239,9 @@ module.exports = function(grunt) {
grunt.registerTask('test', [ grunt.registerTask('test', [
'eslint', 'eslint',
process.env.JENKINS_HOME ? 'mochaTest:unit-xml' : 'mochaTest:unit', process.env.JENKINS_HOME ? 'mochaTest:unit-xml' : 'mochaTest:unit',
process.env.JENKINS_HOME && /^win/.test(process.platform) ? process.env.JENKINS_HOME && (/^win/.test(process.platform) ||
'skip-karma-on-windows' : 'karma:unit-once', /^s390x/.test(process.arch) || /^ppc64/.test(process.arch)) ?
'skip-karma' : 'karma:unit-once',
]); ]);
// alias for sl-ci-run and `npm test` // alias for sl-ci-run and `npm test`

View File

@ -1,4 +1,4 @@
Copyright (c) IBM Corp. 2013,2016. All Rights Reserved. Copyright (c) IBM Corp. 2013,2018. All Rights Reserved.
Node module: loopback Node module: loopback
This project is licensed under the MIT License, full text below. This project is licensed under the MIT License, full text below.

View File

@ -1,6 +1,24 @@
# LoopBack # LoopBack
[![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) [![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)
[![Module LTS Adopted'](https://img.shields.io/badge/Module%20LTS-Adopted-brightgreen.svg?style=flat)](http://github.com/CloudNativeJS/ModuleLTS)
[![IBM Support](https://img.shields.io/badge/IBM%20Support-Frameworks-brightgreen.svg?style=flat)](http://ibm.biz/node-support)
**⚠️ LoopBack 3 has reached end of life. We are no longer accepting pull requests or providing
support for community users. The only exception is fixes for critical bugs and security
vulnerabilities provided as part of support for IBM API Connect customers.
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as soon as possible.
Learn more about
<a href="https://loopback.io/doc/en/contrib/Long-term-support.html">LoopBack's long term support policy.</a>
will be provided or accepted. (See
[Module Long Term Support Policy](#module-long-term-support-policy) below.)**
We urge all LoopBack 3 users to migrate their applications to LoopBack 4 as
soon as possible. Refer to our
[Migration Guide](https://loopback.io/doc/en/lb4/migration-overview.html)
for more information on how to upgrade.
## Overview
LoopBack is a highly-extensible, open-source Node.js framework that enables you to: LoopBack is a highly-extensible, open-source Node.js framework that enables you to:
@ -20,15 +38,22 @@ LoopBack consists of:
LoopBack tools include: LoopBack tools include:
* Command-line tool `loopback-cli` to create applications, models, data sources, and so on. * Command-line tool `loopback-cli` to create applications, models, data sources, and so on.
For more details, see [http://loopback.io/](http://loopback.io/). For more details, see [https://loopback.io/](https://loopback.io/).
## Supported versions
Current|Long Term Support ## Module Long Term Support Policy
:-:|:-:
3.x|2.x
Learn more about our LTS plan in [docs](http://loopback.io/doc/en/contrib/Long-term-support.html). LoopBack 3.x has reached End-of-Life.
This module adopts the [Module Long Term Support (LTS)](http://github.com/CloudNativeJS/ModuleLTS) policy, with the following End Of Life (EOL) dates:
| Version | Status | Published | EOL |
| ---------- | --------------- | --------- | -------------------- |
| LoopBack 4 | Current | Oct 2018 | Apr 2023 _(minimum)_ |
| LoopBack 3 | End-of-Life | Dec 2016 | Dec 2020 |
| LoopBack 2 | End-of-Life | Jul 2014 | Apr 2019 |
Learn more about our LTS plan in [docs](https://loopback.io/doc/en/contrib/Long-term-support.html).
## LoopBack modules ## LoopBack modules
@ -55,7 +80,7 @@ The LoopBack framework is a set of Node.js modules that you can use independentl
### Community Connectors ### Community Connectors
The LoopBack community has created and supports a number of additional connectors. See [Community connectors](http://loopback.io/doc/en/lb2/Community-connectors.html) for details. The 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.
### Components ### Components
* [loopback-component-push](https://github.com/strongloop/loopback-component-push) * [loopback-component-push](https://github.com/strongloop/loopback-component-push)
@ -78,12 +103,12 @@ The LoopBack community has created and supports a number of additional connector
StrongLoop provides a number of example applications that illustrate various key LoopBack features. In some cases, they have accompanying step-by-step instructions (tutorials). StrongLoop provides a number of example applications that illustrate various key LoopBack features. In some cases, they have accompanying step-by-step instructions (tutorials).
See [loopback-example](https://github.com/strongloop/loopback-example) for details. See [examples at loopback.io](https://loopback.io/examples/) for details.
## Resources ## Resources
* [Documentation](http://loopback.io/doc/). * [Documentation](https://loopback.io/doc/).
* [API documentation](http://apidocs.strongloop.com/loopback). * [API documentation](https://apidocs.strongloop.com/loopback).
* [LoopBack Announcements](https://groups.google.com/forum/#!forum/loopbackjs-announcements) * [LoopBack Announcements](https://groups.google.com/forum/#!forum/loopbackjs-announcements)
* [LoopBack Google Group](https://groups.google.com/forum/#!forum/loopbackjs). * [LoopBack Google Group](https://groups.google.com/forum/#!forum/loopbackjs).
* [GitHub issues](https://github.com/strongloop/loopback/issues). * [GitHub issues](https://github.com/strongloop/loopback/issues).
@ -91,10 +116,10 @@ See [loopback-example](https://github.com/strongloop/loopback-example) for detai
## Contributing ## Contributing
Contributions to the LoopBack project are welcome! See [Contributing to LoopBack](http://loopback.io/doc/en/contrib/index.html) for more information. Contributions to the LoopBack project are welcome! See [Contributing to LoopBack](https://loopback.io/doc/en/contrib/index.html) for more information.
## Reporting issues ## Reporting issues
One of the easiest ways to contribute to LoopBack is to report an issue. See [Reporting issues](http://loopback.io/doc/en/contrib/Reporting-issues.html) for more information. One 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.
[![Analytics](https://sl-beacon.appspot.com/UA-37775386-1/github/loopback/readme?pixel)](https://github.com/strongloop/loopback) [![Analytics](https://sl-beacon.appspot.com/UA-37775386-1/github/loopback/readme?pixel)](https://github.com/strongloop/loopback)

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,11 +8,11 @@
*/ */
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var loopback = require('../../lib/loopback'); const loopback = require('../../lib/loopback');
var assert = require('assert'); const assert = require('assert');
var uid = require('uid2'); const uid = require('uid2');
var DEFAULT_TOKEN_LEN = 64; const DEFAULT_TOKEN_LEN = 64;
/** /**
* Token based authentication and access control. * Token based authentication and access control.
@ -79,115 +79,25 @@ module.exports = function(AccessToken) {
}); });
/** /**
* Find a token for the given `ServerRequest`. * Extract the access token id from the HTTP request
* * @param {Request} req HTTP request object
* @param {ServerRequest} req * @options {Object} [options] Each option array is used to add additional keys to find an `accessToken` for a `request`.
* @param {Object} [options] Options for finding the token * @property {Array} [cookies] Array of cookie names.
* @callback {Function} callback * @property {Array} [headers] Array of header names.
* @param {Error} err * @property {Array} [params] Array of param names.
* @param {AccessToken} token * @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request
* @property {Boolean} [bearerTokenBase64Encoded] Defaults to `true`. For `Bearer` token based `Authorization` headers,
* decode the value from `Base64`. If set to `false`, the decoding will be skipped and the token id will be the raw value
* parsed from the header.
* @return {String} The access token
*/ */
AccessToken.getIdForRequest = function(req, options) {
AccessToken.findForRequest = function(req, options, cb) { options = options || {};
if (cb === undefined && typeof options === 'function') { let params = options.params || [];
cb = options; let headers = options.headers || [];
options = {}; let cookies = options.cookies || [];
} let i = 0;
let length, id;
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
e.code = 'INVALID_TOKEN';
cb(e);
}
});
} else {
cb();
}
});
} else {
process.nextTick(function() {
cb();
});
}
};
/**
* Validate the token.
*
* @callback {Function} callback
* @param {Error} err
* @param {Boolean} isValid
*/
AccessToken.prototype.validate = function(cb) {
try {
assert(
this.created && typeof this.created.getTime === 'function',
'token.created must be a valid Date'
);
assert(this.ttl !== 0, 'token.ttl must be not be 0');
assert(this.ttl, 'token.ttl must exist');
assert(this.ttl >= -1, 'token.ttl must be >= -1');
var AccessToken = this.constructor;
var userRelation = AccessToken.relations.user; // may not be set up
var User = userRelation && userRelation.modelTo;
// redefine user model if accessToken's principalType is available
if (this.principalType) {
User = AccessToken.registry.findModel(this.principalType);
if (!User) {
process.nextTick(function() {
return cb(null, false);
});
}
}
var now = Date.now();
var created = this.created.getTime();
var elapsedSeconds = (now - created) / 1000;
var secondsToLive = this.ttl;
var eternalTokensAllowed = !!(User && User.settings.allowEternalTokens);
var isEternalToken = secondsToLive === -1;
var isValid = isEternalToken ?
eternalTokensAllowed :
elapsedSeconds < secondsToLive;
if (isValid) {
process.nextTick(function() {
cb(null, isValid);
});
} else {
this.destroy(function(err) {
cb(err, isValid);
});
}
} catch (e) {
process.nextTick(function() {
cb(e);
});
}
};
function tokenIdForRequest(req, options) {
var params = options.params || [];
var headers = options.headers || [];
var cookies = options.cookies || [];
var i = 0;
var length, id;
// https://github.com/strongloop/loopback/issues/1326 // https://github.com/strongloop/loopback/issues/1326
if (options.searchDefaultTokenKeys !== false) { if (options.searchDefaultTokenKeys !== false) {
@ -197,12 +107,12 @@ module.exports = function(AccessToken) {
} }
for (length = params.length; i < length; i++) { for (length = params.length; i < length; i++) {
var param = params[i]; const param = params[i];
// replacement for deprecated req.param() // replacement for deprecated req.param()
id = req.params && req.params[param] !== undefined ? req.params[param] : id = req.params && req.params[param] !== undefined ? req.params[param] :
req.body && req.body[param] !== undefined ? req.body[param] : req.body && req.body[param] !== undefined ? req.body[param] :
req.query && req.query[param] !== undefined ? req.query[param] : req.query && req.query[param] !== undefined ? req.query[param] :
undefined; undefined;
if (typeof id === 'string') { if (typeof id === 'string') {
return id; return id;
@ -215,11 +125,18 @@ module.exports = function(AccessToken) {
if (typeof id === 'string') { if (typeof id === 'string') {
// Add support for oAuth 2.0 bearer token // Add support for oAuth 2.0 bearer token
// http://tools.ietf.org/html/rfc6750 // http://tools.ietf.org/html/rfc6750
// To prevent Error: Model::findById requires the id argument
// with loopback-datasource-juggler 2.56.0+
if (id === '') continue;
if (id.indexOf('Bearer ') === 0) { if (id.indexOf('Bearer ') === 0) {
id = id.substring(7); id = id.substring(7);
// Decode from base64 if (options.bearerTokenBase64Encoded) {
var buf = new Buffer(id, 'base64'); // Decode from base64
id = buf.toString('utf8'); const buf = new Buffer(id, 'base64');
id = buf.toString('utf8');
}
} else if (/^Basic /i.test(id)) { } else if (/^Basic /i.test(id)) {
id = id.substring(6); id = id.substring(6);
id = (new Buffer(id, 'base64')).toString('utf8'); id = (new Buffer(id, 'base64')).toString('utf8');
@ -230,7 +147,7 @@ module.exports = function(AccessToken) {
// "a2b2c3:" (curl http://a2b2c3@localhost:3000/) // "a2b2c3:" (curl http://a2b2c3@localhost:3000/)
// "token:a2b2c3" (curl http://token:a2b2c3@localhost:3000/) // "token:a2b2c3" (curl http://token:a2b2c3@localhost:3000/)
// ":a2b2c3" // ":a2b2c3"
var parts = /^([^:]*):(.*)$/.exec(id); const parts = /^([^:]*):(.*)$/.exec(id);
if (parts) { if (parts) {
id = parts[2].length > parts[1].length ? parts[2] : parts[1]; id = parts[2].length > parts[1].length ? parts[2] : parts[1];
} }
@ -249,5 +166,116 @@ module.exports = function(AccessToken) {
} }
} }
return null; return null;
} };
/**
* Resolve and validate the access token by id
* @param {String} id Access token
* @callback {Function} cb Callback function
* @param {Error} err Error information
* @param {Object} Resolved access token object
*/
AccessToken.resolve = function(id, cb) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
const e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
e.code = 'INVALID_TOKEN';
cb(e);
}
});
} else {
cb();
}
});
};
/**
* Find a token for the given `ServerRequest`.
*
* @param {ServerRequest} req
* @param {Object} [options] Options for finding the token
* @callback {Function} callback
* @param {Error} err
* @param {AccessToken} token
*/
AccessToken.findForRequest = function(req, options, cb) {
if (cb === undefined && typeof options === 'function') {
cb = options;
options = {};
}
const id = this.getIdForRequest(req, options);
if (id) {
this.resolve(id, cb);
} else {
process.nextTick(cb);
}
};
/**
* Validate the token.
*
* @callback {Function} callback
* @param {Error} err
* @param {Boolean} isValid
*/
AccessToken.prototype.validate = function(cb) {
try {
assert(
this.created && typeof this.created.getTime === 'function',
'token.created must be a valid Date',
);
assert(this.ttl !== 0, 'token.ttl must be not be 0');
assert(this.ttl, 'token.ttl must exist');
assert(this.ttl >= -1, 'token.ttl must be >= -1');
const AccessToken = this.constructor;
const userRelation = AccessToken.relations.user; // may not be set up
let User = userRelation && userRelation.modelTo;
// redefine user model if accessToken's principalType is available
if (this.principalType) {
User = AccessToken.registry.findModel(this.principalType);
if (!User) {
process.nextTick(function() {
return cb(null, false);
});
}
}
const now = Date.now();
const created = this.created.getTime();
const elapsedSeconds = (now - created) / 1000;
const secondsToLive = this.ttl;
const eternalTokensAllowed = !!(User && User.settings.allowEternalTokens);
const isEternalToken = secondsToLive === -1;
const isValid = isEternalToken ?
eternalTokensAllowed :
elapsedSeconds < secondsToLive;
if (isValid) {
process.nextTick(function() {
cb(null, isValid);
});
} else {
this.destroy(function(err) {
cb(err, isValid);
});
}
} catch (e) {
process.nextTick(function() {
cb(e);
});
}
};
}; };

View File

@ -32,12 +32,6 @@
"principalType": "ROLE", "principalType": "ROLE",
"principalId": "$everyone", "principalId": "$everyone",
"permission": "DENY" "permission": "DENY"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"property": "create",
"permission": "ALLOW"
} }
] ]
} }

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -30,20 +30,20 @@
Map to oAuth 2.0 scopes Map to oAuth 2.0 scopes
*/ */
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var loopback = require('../../lib/loopback'); const loopback = require('../../lib/loopback');
var utils = require('../../lib/utils'); const utils = require('../../lib/utils');
var async = require('async'); const async = require('async');
var extend = require('util')._extend; const extend = require('util')._extend;
var assert = require('assert'); const assert = require('assert');
var debug = require('debug')('loopback:security:acl'); const debug = require('debug')('loopback:security:acl');
var ctx = require('../../lib/access-context'); const ctx = require('../../lib/access-context');
var AccessContext = ctx.AccessContext; const AccessContext = ctx.AccessContext;
var Principal = ctx.Principal; const Principal = ctx.Principal;
var AccessRequest = ctx.AccessRequest; const AccessRequest = ctx.AccessRequest;
var Role = loopback.Role; const Role = loopback.Role;
assert(Role, 'Role model must be defined before ACL model'); assert(Role, 'Role model must be defined before ACL model');
/** /**
@ -69,7 +69,7 @@ assert(Role, 'Role model must be defined before ACL model');
* - ALLOW: Explicitly grants access to the resource. * - ALLOW: Explicitly grants access to the resource.
* - AUDIT: Log, in a system-dependent way, the access specified in the permissions component of the ACL entry. * - AUDIT: Log, in a system-dependent way, the access specified in the permissions component of the ACL entry.
* - DENY: Explicitly denies access to the resource. * - DENY: Explicitly denies access to the resource.
* @property {String} principalType Type of the principal; one of: Application, User, Role. * @property {String} principalType Type of the principal; one of: APPLICATION, USER, ROLE.
* @property {String} principalId ID of the principal - such as appId, userId or roleId. * @property {String} principalId ID of the principal - such as appId, userId or roleId.
* @property {Object} settings Extends the `Model.settings` object. * @property {Object} settings Extends the `Model.settings` object.
* @property {String} settings.defaultPermission Default permission setting: ALLOW, DENY, ALARM, or AUDIT. Default is ALLOW. * @property {String} settings.defaultPermission Default permission setting: ALLOW, DENY, ALARM, or AUDIT. Default is ALLOW.
@ -107,18 +107,18 @@ module.exports = function(ACL) {
* @returns {Number} * @returns {Number}
*/ */
ACL.getMatchingScore = function getMatchingScore(rule, req) { ACL.getMatchingScore = function getMatchingScore(rule, req) {
var props = ['model', 'property', 'accessType']; const props = ['model', 'property', 'accessType'];
var score = 0; let score = 0;
for (var i = 0; i < props.length; i++) { for (let i = 0; i < props.length; i++) {
// Shift the score by 4 for each of the properties as the weight // Shift the score by 4 for each of the properties as the weight
score = score * 4; score = score * 4;
var ruleValue = rule[props[i]] || ACL.ALL; const ruleValue = rule[props[i]] || ACL.ALL;
var requestedValue = req[props[i]] || ACL.ALL; const requestedValue = req[props[i]] || ACL.ALL;
var isMatchingMethodName = props[i] === 'property' && const isMatchingMethodName = props[i] === 'property' &&
req.methodNames.indexOf(ruleValue) !== -1; req.methodNames.indexOf(ruleValue) !== -1;
var isMatchingAccessType = ruleValue === requestedValue; let isMatchingAccessType = ruleValue === requestedValue;
if (props[i] === 'accessType' && !isMatchingAccessType) { if (props[i] === 'accessType' && !isMatchingAccessType) {
switch (ruleValue) { switch (ruleValue) {
case ACL.EXECUTE: case ACL.EXECUTE:
@ -219,11 +219,11 @@ module.exports = function(ACL) {
acls = acls.sort(function(rule1, rule2) { acls = acls.sort(function(rule1, rule2) {
return ACL.getMatchingScore(rule2, req) - ACL.getMatchingScore(rule1, req); return ACL.getMatchingScore(rule2, req) - ACL.getMatchingScore(rule1, req);
}); });
var permission = ACL.DEFAULT; let permission = ACL.DEFAULT;
var score = 0; let score = 0;
for (var i = 0; i < acls.length; i++) { for (let i = 0; i < acls.length; i++) {
var candidate = acls[i]; const candidate = acls[i];
score = ACL.getMatchingScore(candidate, req); score = ACL.getMatchingScore(candidate, req);
if (score < 0) { if (score < 0) {
// the highest scored ACL did not match // the highest scored ACL did not match
@ -239,10 +239,11 @@ module.exports = function(ACL) {
break; break;
} }
// For wildcard match, find the strongest permission // For wildcard match, find the strongest permission
var candidateOrder = AccessContext.permissionOrder[candidate.permission]; const candidateOrder = AccessContext.permissionOrder[candidate.permission];
var permissionOrder = AccessContext.permissionOrder[permission]; const permissionOrder = AccessContext.permissionOrder[permission];
if (candidateOrder > permissionOrder) { if (candidateOrder > permissionOrder) {
permission = candidate.permission; permission = candidate.permission;
break;
} }
} }
} }
@ -254,7 +255,7 @@ module.exports = function(ACL) {
debug('with score:', acl.score(req)); debug('with score:', acl.score(req));
}); });
} }
var res = new AccessRequest({ const res = new AccessRequest({
model: req.model, model: req.model,
property: req.property, property: req.property,
accessType: req.accessType, accessType: req.accessType,
@ -275,11 +276,11 @@ module.exports = function(ACL) {
* @return {Object[]} An array of ACLs * @return {Object[]} An array of ACLs
*/ */
ACL.getStaticACLs = function getStaticACLs(model, property) { ACL.getStaticACLs = function getStaticACLs(model, property) {
var modelClass = this.registry.findModel(model); const modelClass = this.registry.findModel(model);
var staticACLs = []; const staticACLs = [];
if (modelClass && modelClass.settings.acls) { if (modelClass && modelClass.settings.acls) {
modelClass.settings.acls.forEach(function(acl) { modelClass.settings.acls.forEach(function(acl) {
var prop = acl.property; let prop = acl.property;
// We support static ACL property with array of string values. // We support static ACL property with array of string values.
if (Array.isArray(prop) && prop.indexOf(property) >= 0) if (Array.isArray(prop) && prop.indexOf(property) >= 0)
prop = property; prop = property;
@ -295,7 +296,7 @@ module.exports = function(ACL) {
} }
}); });
} }
var prop = modelClass && ( const prop = modelClass && (
// regular property // regular property
modelClass.definition.properties[property] || modelClass.definition.properties[property] ||
// relation/scope // relation/scope
@ -331,24 +332,24 @@ module.exports = function(ACL) {
* @param {AccessRequest} result The resolved access request. * @param {AccessRequest} result The resolved access request.
*/ */
ACL.checkPermission = function checkPermission(principalType, principalId, ACL.checkPermission = function checkPermission(principalType, principalId,
model, property, accessType, model, property, accessType,
callback) { callback) {
if (!callback) callback = utils.createPromiseCallback(); if (!callback) callback = utils.createPromiseCallback();
if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) { if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) {
principalId = principalId.toString(); principalId = principalId.toString();
} }
property = property || ACL.ALL; property = property || ACL.ALL;
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]}; const propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]};
accessType = accessType || ACL.ALL; accessType = accessType || ACL.ALL;
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : const accessTypeQuery = (accessType === ACL.ALL) ? undefined :
{inq: [accessType, ACL.ALL, ACL.EXECUTE]}; {inq: [accessType, ACL.ALL, ACL.EXECUTE]};
var req = new AccessRequest({model, property, accessType, registry: this.registry}); const req = new AccessRequest({model, property, accessType, registry: this.registry});
var acls = this.getStaticACLs(model, property); let acls = this.getStaticACLs(model, property);
// resolved is an instance of AccessRequest // resolved is an instance of AccessRequest
var resolved = this.resolvePermission(acls, req); let resolved = this.resolvePermission(acls, req);
if (resolved && resolved.permission === ACL.DENY) { if (resolved && resolved.permission === ACL.DENY) {
debug('Permission denied by statically resolved permission'); debug('Permission denied by statically resolved permission');
@ -359,18 +360,18 @@ module.exports = function(ACL) {
return callback.promise; return callback.promise;
} }
var self = this; const self = this;
this.find({where: {principalType: principalType, principalId: principalId, this.find({where: {principalType: principalType, principalId: principalId,
model: model, property: propertyQuery, accessType: accessTypeQuery}}, model: model, property: propertyQuery, accessType: accessTypeQuery}},
function(err, dynACLs) { function(err, dynACLs) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
acls = acls.concat(dynACLs); acls = acls.concat(dynACLs);
// resolved is an instance of AccessRequest // resolved is an instance of AccessRequest
resolved = self.resolvePermission(acls, req); resolved = self.resolvePermission(acls, req);
return callback(null, resolved); return callback(null, resolved);
}); });
return callback.promise; return callback.promise;
}; };
@ -430,33 +431,33 @@ module.exports = function(ACL) {
*/ */
ACL.checkAccessForContext = function(context, callback) { ACL.checkAccessForContext = function(context, callback) {
if (!callback) callback = utils.createPromiseCallback(); if (!callback) callback = utils.createPromiseCallback();
var self = this; const self = this;
self.resolveRelatedModels(); self.resolveRelatedModels();
var roleModel = self.roleModel; const roleModel = self.roleModel;
if (!(context instanceof AccessContext)) { if (!(context instanceof AccessContext)) {
context.registry = this.registry; context.registry = this.registry;
context = new AccessContext(context); context = new AccessContext(context);
} }
var authorizedRoles = {}; let authorizedRoles = {};
var remotingContext = context.remotingContext; const remotingContext = context.remotingContext;
var model = context.model; const model = context.model;
var modelDefaultPermission = model && model.settings.defaultPermission; const modelDefaultPermission = model && model.settings.defaultPermission;
var property = context.property; const property = context.property;
var accessType = context.accessType; const accessType = context.accessType;
var modelName = context.modelName; const modelName = context.modelName;
var methodNames = context.methodNames; const methodNames = context.methodNames;
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])}; const propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])};
var accessTypeQuery = (accessType === ACL.ALL) ? const accessTypeQuery = (accessType === ACL.ALL) ?
undefined : undefined :
(accessType === ACL.REPLICATE) ? (accessType === ACL.REPLICATE) ?
{inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL]} : {inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL]} :
{inq: [accessType, ACL.ALL]}; {inq: [accessType, ACL.ALL]};
var req = new AccessRequest({ const req = new AccessRequest({
model: modelName, model: modelName,
property, property,
accessType, accessType,
@ -474,22 +475,29 @@ module.exports = function(ACL) {
return callback.promise; return callback.promise;
} }
var effectiveACLs = []; const effectiveACLs = [];
var staticACLs = self.getStaticACLs(model.modelName, property); const staticACLs = self.getStaticACLs(model.modelName, property);
this.find({where: {model: model.modelName, property: propertyQuery, const query = {
accessType: accessTypeQuery}}, function(err, acls) { where: {
model: {inq: [model.modelName, ACL.ALL]},
property: propertyQuery,
accessType: accessTypeQuery,
},
};
this.find(query, function(err, acls) {
if (err) return callback(err); if (err) return callback(err);
var inRoleTasks = []; const inRoleTasks = [];
acls = acls.concat(staticACLs); acls = acls.concat(staticACLs);
acls.forEach(function(acl) { acls.forEach(function(acl) {
// Check exact matches // Check exact matches
for (var i = 0; i < context.principals.length; i++) { for (let i = 0; i < context.principals.length; i++) {
var p = context.principals[i]; const p = context.principals[i];
var typeMatch = p.type === acl.principalType; const typeMatch = p.type === acl.principalType;
var idMatch = String(p.id) === String(acl.principalId); const idMatch = String(p.id) === String(acl.principalId);
if (typeMatch && idMatch) { if (typeMatch && idMatch) {
effectiveACLs.push(acl); effectiveACLs.push(acl);
return; return;
@ -517,7 +525,7 @@ module.exports = function(ACL) {
if (err) return callback(err, null); if (err) return callback(err, null);
// resolved is an instance of AccessRequest // resolved is an instance of AccessRequest
var resolved = self.resolvePermission(effectiveACLs, req); const resolved = self.resolvePermission(effectiveACLs, req);
debug('---Resolved---'); debug('---Resolved---');
resolved.debug(); resolved.debug();
@ -554,7 +562,7 @@ module.exports = function(ACL) {
ACL.checkAccessForToken = function(token, model, modelId, method, callback) { ACL.checkAccessForToken = function(token, model, modelId, method, callback) {
assert(token, 'Access token is required'); assert(token, 'Access token is required');
if (!callback) callback = utils.createPromiseCallback(); if (!callback) callback = utils.createPromiseCallback();
var context = new AccessContext({ const context = new AccessContext({
registry: this.registry, registry: this.registry,
accessToken: token, accessToken: token,
model: model, model: model,
@ -572,7 +580,7 @@ module.exports = function(ACL) {
ACL.resolveRelatedModels = function() { ACL.resolveRelatedModels = function() {
if (!this.roleModel) { if (!this.roleModel) {
var reg = this.registry; const reg = this.registry;
this.roleModel = reg.getModelByType('Role'); this.roleModel = reg.getModelByType('Role');
this.roleMappingModel = reg.getModelByType('RoleMapping'); this.roleMappingModel = reg.getModelByType('RoleMapping');
this.userModel = reg.getModelByType('User'); this.userModel = reg.getModelByType('User');
@ -599,22 +607,25 @@ module.exports = function(ACL) {
break; break;
case ACL.USER: case ACL.USER:
this.userModel.findOne( this.userModel.findOne(
{where: {or: [{username: id}, {email: id}, {id: id}]}}, cb); {where: {or: [{username: id}, {email: id}, {id: id}]}}, cb,
);
break; break;
case ACL.APP: case ACL.APP:
this.applicationModel.findOne( this.applicationModel.findOne(
{where: {or: [{name: id}, {email: id}, {id: id}]}}, cb); {where: {or: [{name: id}, {email: id}, {id: id}]}}, cb,
);
break; break;
default: default:
// try resolving a user model with a name matching the principalType // try resolving a user model with a name matching the principalType
var userModel = this.registry.findModel(type); const userModel = this.registry.findModel(type);
if (userModel) { if (userModel) {
userModel.findOne( userModel.findOne(
{where: {or: [{username: id}, {email: id}, {id: id}]}}, {where: {or: [{username: id}, {email: id}, {id: id}]}},
cb); cb,
);
} else { } else {
process.nextTick(function() { process.nextTick(function() {
var err = new Error(g.f('Invalid principal type: %s', type)); const err = new Error(g.f('Invalid principal type: %s', type));
err.statusCode = 400; err.statusCode = 400;
err.code = 'INVALID_PRINCIPAL_TYPE'; err.code = 'INVALID_PRINCIPAL_TYPE';
cb(err); cb(err);
@ -635,7 +646,7 @@ module.exports = function(ACL) {
*/ */
ACL.isMappedToRole = function(principalType, principalId, role, cb) { ACL.isMappedToRole = function(principalType, principalId, role, cb) {
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
var self = this; const self = this;
this.resolvePrincipal(principalType, principalId, this.resolvePrincipal(principalType, principalId,
function(err, principal) { function(err, principal) {
if (err) return cb(err); if (err) return cb(err);

View File

@ -1,26 +1,26 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
var utils = require('../../lib/utils'); const utils = require('../../lib/utils');
/*! /*!
* Application management functions * Application management functions
*/ */
var crypto = require('crypto'); const crypto = require('crypto');
function generateKey(hmacKey, algorithm, encoding) { function generateKey(hmacKey, algorithm, encoding) {
hmacKey = hmacKey || 'loopback'; hmacKey = hmacKey || 'loopback';
algorithm = algorithm || 'sha1'; algorithm = algorithm || 'sha1';
encoding = encoding || 'hex'; encoding = encoding || 'hex';
var hmac = crypto.createHmac(algorithm, hmacKey); const hmac = crypto.createHmac(algorithm, hmacKey);
var buf = crypto.randomBytes(32); const buf = crypto.randomBytes(32);
hmac.update(buf); hmac.update(buf);
var key = hmac.digest(encoding); const key = hmac.digest(encoding);
return key; return key;
} }
@ -83,7 +83,7 @@ module.exports = function(Application) {
return next(); return next();
} }
var app = ctx.instance; const app = ctx.instance;
app.created = app.modified = new Date(); app.created = app.modified = new Date();
if (!app.id) { if (!app.id) {
app.id = generateKey('id', 'md5'); app.id = generateKey('id', 'md5');
@ -115,8 +115,8 @@ module.exports = function(Application) {
} }
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
var props = {owner: owner, name: name}; const props = {owner: owner, name: name};
for (var p in options) { for (const p in options) {
if (!(p in props)) { if (!(p in props)) {
props[p] = options[p]; props[p] = options[p];
} }
@ -182,9 +182,9 @@ module.exports = function(Application) {
cb(err, null); cb(err, null);
return cb.promise; return cb.promise;
} }
var result = null; let result = null;
var keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey']; const keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey'];
for (var i = 0; i < keyNames.length; i++) { for (let i = 0; i < keyNames.length; i++) {
if (app[keyNames[i]] === key) { if (app[keyNames[i]] === key) {
result = { result = {
application: app, application: app,

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,15 +8,15 @@
*/ */
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var PersistedModel = require('../../lib/loopback').PersistedModel; const PersistedModel = require('../../lib/loopback').PersistedModel;
var loopback = require('../../lib/loopback'); const loopback = require('../../lib/loopback');
var utils = require('../../lib/utils'); const utils = require('../../lib/utils');
var crypto = require('crypto'); const crypto = require('crypto');
var CJSON = {stringify: require('canonical-json')}; const CJSON = {stringify: require('canonical-json')};
var async = require('async'); const async = require('async');
var assert = require('assert'); const assert = require('assert');
var debug = require('debug')('loopback:change'); const debug = require('debug')('loopback:change');
/** /**
* Change list entry. * Change list entry.
@ -58,10 +58,10 @@ module.exports = function(Change) {
Change.setup = function() { Change.setup = function() {
PersistedModel.setup.call(this); PersistedModel.setup.call(this);
var Change = this; const Change = this;
Change.getter.id = function() { Change.getter.id = function() {
var hasModel = this.modelName && this.modelId; const hasModel = this.modelName && this.modelId;
if (!hasModel) return null; if (!hasModel) return null;
return Change.idForModel(this.modelName, this.modelId); return Change.idForModel(this.modelName, this.modelId);
@ -80,12 +80,12 @@ module.exports = function(Change) {
*/ */
Change.rectifyModelChanges = function(modelName, modelIds, callback) { Change.rectifyModelChanges = function(modelName, modelIds, callback) {
var Change = this; const Change = this;
var errors = []; const errors = [];
callback = callback || utils.createPromiseCallback(); callback = callback || utils.createPromiseCallback();
var tasks = modelIds.map(function(id) { const tasks = modelIds.map(function(id) {
return function(cb) { return function(cb) {
Change.findOrCreateChange(modelName, id, function(err, change) { Change.findOrCreateChange(modelName, id, function(err, change) {
if (err) return next(err); if (err) return next(err);
@ -106,13 +106,13 @@ module.exports = function(Change) {
async.parallel(tasks, function(err) { async.parallel(tasks, function(err) {
if (err) return callback(err); if (err) return callback(err);
if (errors.length) { if (errors.length) {
var desc = errors const desc = errors
.map(function(e) { .map(function(e) {
return '#' + e.modelId + ' - ' + e.toString(); return '#' + e.modelId + ' - ' + e.toString();
}) })
.join('\n'); .join('\n');
var msg = g.f('Cannot rectify %s changes:\n%s', modelName, desc); const msg = g.f('Cannot rectify %s changes:\n%s', modelName, desc);
err = new Error(msg); err = new Error(msg);
err.details = {errors: errors}; err.details = {errors: errors};
return callback(err); return callback(err);
@ -148,15 +148,15 @@ module.exports = function(Change) {
Change.findOrCreateChange = function(modelName, modelId, callback) { Change.findOrCreateChange = function(modelName, modelId, callback) {
assert(this.registry.findModel(modelName), modelName + ' does not exist'); assert(this.registry.findModel(modelName), modelName + ' does not exist');
callback = callback || utils.createPromiseCallback(); callback = callback || utils.createPromiseCallback();
var id = this.idForModel(modelName, modelId); const id = this.idForModel(modelName, modelId);
var Change = this; const Change = this;
this.findById(id, function(err, change) { this.findById(id, function(err, change) {
if (err) return callback(err); if (err) return callback(err);
if (change) { if (change) {
callback(null, change); callback(null, change);
} else { } else {
var ch = new Change({ const ch = new Change({
id: id, id: id,
modelName: modelName, modelName: modelName,
modelId: modelId, modelId: modelId,
@ -177,8 +177,8 @@ module.exports = function(Change) {
*/ */
Change.prototype.rectify = function(cb) { Change.prototype.rectify = function(cb) {
var change = this; const change = this;
var currentRev = this.rev; const currentRev = this.rev;
change.debug('rectify change'); change.debug('rectify change');
@ -216,7 +216,7 @@ module.exports = function(Change) {
function(err, checkpoint) { function(err, checkpoint) {
if (err) return cb(err); if (err) return cb(err);
doRectify(checkpoint, rev); doRectify(checkpoint, rev);
} },
); );
} }
@ -274,8 +274,8 @@ module.exports = function(Change) {
Change.prototype.currentRevision = function(cb) { Change.prototype.currentRevision = function(cb) {
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
var model = this.getModelCtor(); const model = this.getModelCtor();
var id = this.getModelId(); const id = this.getModelId();
model.findById(id, function(err, inst) { model.findById(id, function(err, inst) {
if (err) return cb(err); if (err) return cb(err);
if (inst) { if (inst) {
@ -345,8 +345,8 @@ module.exports = function(Change) {
Change.prototype.equals = function(change) { Change.prototype.equals = function(change) {
if (!change) return false; if (!change) return false;
var thisRev = this.rev || null; const thisRev = this.rev || null;
var thatRev = change.rev || null; const thatRev = change.rev || null;
return thisRev === thatRev; return thisRev === thatRev;
}; };
@ -423,8 +423,8 @@ module.exports = function(Change) {
callback(null, {deltas: [], conflicts: []}); callback(null, {deltas: [], conflicts: []});
return callback.promise; return callback.promise;
} }
var remoteChangeIndex = {}; const remoteChangeIndex = {};
var modelIds = []; const modelIds = [];
remoteChanges.forEach(function(ch) { remoteChanges.forEach(function(ch) {
modelIds.push(ch.modelId); modelIds.push(ch.modelId);
remoteChangeIndex[ch.modelId] = new Change(ch); remoteChangeIndex[ch.modelId] = new Change(ch);
@ -439,18 +439,18 @@ module.exports = function(Change) {
}, },
}, function(err, allLocalChanges) { }, function(err, allLocalChanges) {
if (err) return callback(err); if (err) return callback(err);
var deltas = []; const deltas = [];
var conflicts = []; const conflicts = [];
var localModelIds = []; const localModelIds = [];
var localChanges = allLocalChanges.filter(function(c) { const localChanges = allLocalChanges.filter(function(c) {
return c.checkpoint >= since; return c.checkpoint >= since;
}); });
localChanges.forEach(function(localChange) { localChanges.forEach(function(localChange) {
localChange = new Change(localChange); localChange = new Change(localChange);
localModelIds.push(localChange.modelId); localModelIds.push(localChange.modelId);
var remoteChange = remoteChangeIndex[localChange.modelId]; const remoteChange = remoteChangeIndex[localChange.modelId];
if (remoteChange && !localChange.equals(remoteChange)) { if (remoteChange && !localChange.equals(remoteChange)) {
if (remoteChange.conflictsWith(localChange)) { if (remoteChange.conflictsWith(localChange)) {
remoteChange.debug('remote conflict'); remoteChange.debug('remote conflict');
@ -466,8 +466,8 @@ module.exports = function(Change) {
modelIds.forEach(function(id) { modelIds.forEach(function(id) {
if (localModelIds.indexOf(id) !== -1) return; if (localModelIds.indexOf(id) !== -1) return;
var d = remoteChangeIndex[id]; const d = remoteChangeIndex[id];
var oldChange = allLocalChanges.filter(function(c) { const oldChange = allLocalChanges.filter(function(c) {
return c.modelId === id; return c.modelId === id;
})[0]; })[0];
@ -495,14 +495,15 @@ module.exports = function(Change) {
Change.rectifyAll = function(cb) { Change.rectifyAll = function(cb) {
debug('rectify all'); debug('rectify all');
var Change = this; const Change = this;
// this should be optimized // this should be optimized
this.find(function(err, changes) { this.find(function(err, changes) {
if (err) return cb(err); if (err) return cb(err);
async.each( async.each(
changes, changes,
function(c, next) { c.rectify(next); }, function(c, next) { c.rectify(next); },
cb); cb,
);
}); });
}; };
@ -512,7 +513,7 @@ module.exports = function(Change) {
*/ */
Change.getCheckpointModel = function() { Change.getCheckpointModel = function() {
var checkpointModel = this.Checkpoint; let checkpointModel = this.Checkpoint;
if (checkpointModel) return checkpointModel; if (checkpointModel) return checkpointModel;
// FIXME(bajtos) This code creates multiple different models with the same // FIXME(bajtos) This code creates multiple different models with the same
// model name, which is not a valid supported usage of juggler's API. // model name, which is not a valid supported usage of juggler's API.
@ -525,7 +526,7 @@ module.exports = function(Change) {
Change.prototype.debug = function() { Change.prototype.debug = function() {
if (debug.enabled) { if (debug.enabled) {
var args = Array.prototype.slice.call(arguments); const args = Array.prototype.slice.call(arguments);
args[0] = args[0] + ' %s'; args[0] = args[0] + ' %s';
args.push(this.modelName); args.push(this.modelName);
debug.apply(this, args); debug.apply(this, args);
@ -550,16 +551,16 @@ module.exports = function(Change) {
Change.prototype.getModelId = function() { Change.prototype.getModelId = function() {
// TODO(ritch) get rid of the need to create an instance // TODO(ritch) get rid of the need to create an instance
var Model = this.getModelCtor(); const Model = this.getModelCtor();
var id = this.modelId; const id = this.modelId;
var m = new Model(); const m = new Model();
m.setId(id); m.setId(id);
return m.getId(); return m.getId();
}; };
Change.prototype.getModel = function(callback) { Change.prototype.getModel = function(callback) {
var Model = this.constructor.settings.trackModel; const Model = this.constructor.settings.trackModel;
var id = this.getModelId(); const id = this.getModelId();
Model.findById(id, callback); Model.findById(id, callback);
}; };
@ -594,10 +595,10 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.models = function(cb) { Conflict.prototype.models = function(cb) {
var conflict = this; const conflict = this;
var SourceModel = this.SourceModel; const SourceModel = this.SourceModel;
var TargetModel = this.TargetModel; const TargetModel = this.TargetModel;
var source, target; let source, target;
async.parallel([ async.parallel([
getSourceModel, getSourceModel,
@ -636,8 +637,8 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.changes = function(cb) { Conflict.prototype.changes = function(cb) {
var conflict = this; const conflict = this;
var sourceChange, targetChange; let sourceChange, targetChange;
async.parallel([ async.parallel([
getSourceChange, getSourceChange,
@ -645,7 +646,7 @@ module.exports = function(Change) {
], done); ], done);
function getSourceChange(cb) { function getSourceChange(cb) {
var SourceModel = conflict.SourceModel; const SourceModel = conflict.SourceModel;
SourceModel.findLastChange(conflict.modelId, function(err, change) { SourceModel.findLastChange(conflict.modelId, function(err, change) {
if (err) return cb(err); if (err) return cb(err);
sourceChange = change; sourceChange = change;
@ -654,7 +655,7 @@ module.exports = function(Change) {
} }
function getTargetChange(cb) { function getTargetChange(cb) {
var TargetModel = conflict.TargetModel; const TargetModel = conflict.TargetModel;
TargetModel.findLastChange(conflict.modelId, function(err, change) { TargetModel.findLastChange(conflict.modelId, function(err, change) {
if (err) return cb(err); if (err) return cb(err);
targetChange = change; targetChange = change;
@ -683,7 +684,7 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.resolve = function(cb) { Conflict.prototype.resolve = function(cb) {
var conflict = this; const conflict = this;
conflict.TargetModel.findLastChange( conflict.TargetModel.findLastChange(
this.modelId, this.modelId,
function(err, targetChange) { function(err, targetChange) {
@ -691,8 +692,10 @@ module.exports = function(Change) {
conflict.SourceModel.updateLastChange( conflict.SourceModel.updateLastChange(
conflict.modelId, conflict.modelId,
{prev: targetChange.rev}, {prev: targetChange.rev},
cb); cb,
}); );
},
);
}; };
/** /**
@ -715,16 +718,17 @@ module.exports = function(Change) {
* @param {Error} err * @param {Error} err
*/ */
Conflict.prototype.resolveUsingTarget = function(cb) { Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this; const conflict = this;
conflict.models(function(err, source, target) { conflict.models(function(err, source, target) {
if (err) return done(err); if (err) return done(err);
if (target === null) { if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done); return conflict.SourceModel.deleteById(conflict.modelId, done);
} }
var inst = new conflict.SourceModel( const inst = new conflict.SourceModel(
target.toObject(), target.toObject(),
{persisted: true}); {persisted: true},
);
inst.save(done); inst.save(done);
}); });
@ -747,7 +751,7 @@ module.exports = function(Change) {
* @returns {Conflict} A new Conflict instance. * @returns {Conflict} A new Conflict instance.
*/ */
Conflict.prototype.swapParties = function() { Conflict.prototype.swapParties = function() {
var Ctor = this.constructor; const Ctor = this.constructor;
return new Ctor(this.modelId, this.TargetModel, this.SourceModel); return new Ctor(this.modelId, this.TargetModel, this.SourceModel);
}; };
@ -761,14 +765,14 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.resolveManually = function(data, cb) { Conflict.prototype.resolveManually = function(data, cb) {
var conflict = this; const conflict = this;
if (!data) { if (!data) {
return conflict.SourceModel.deleteById(conflict.modelId, done); return conflict.SourceModel.deleteById(conflict.modelId, done);
} }
conflict.models(function(err, source, target) { conflict.models(function(err, source, target) {
if (err) return done(err); if (err) return done(err);
var inst = source || new conflict.SourceModel(target); const inst = source || new conflict.SourceModel(target);
inst.setAttributes(data); inst.setAttributes(data);
inst.save(function(err) { inst.save(function(err) {
if (err) return done(err); if (err) return done(err);
@ -797,11 +801,11 @@ module.exports = function(Change) {
*/ */
Conflict.prototype.type = function(cb) { Conflict.prototype.type = function(cb) {
var conflict = this; const conflict = this;
this.changes(function(err, sourceChange, targetChange) { this.changes(function(err, sourceChange, targetChange) {
if (err) return cb(err); if (err) return cb(err);
var sourceChangeType = sourceChange.type(); const sourceChangeType = sourceChange.type();
var targetChangeType = targetChange.type(); const targetChangeType = targetChange.type();
if (sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) { if (sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) {
return cb(null, Change.UPDATE); return cb(null, Change.UPDATE);
} }

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,7 +8,7 @@
*/ */
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
/** /**
* Checkpoint list entry. * Checkpoint list entry.
@ -35,15 +35,15 @@ module.exports = function(Checkpoint) {
* @param {Number} checkpoint The current checkpoint seq * @param {Number} checkpoint The current checkpoint seq
*/ */
Checkpoint.current = function(cb) { Checkpoint.current = function(cb) {
var Checkpoint = this; const Checkpoint = this;
Checkpoint._getSingleton(function(err, cp) { Checkpoint._getSingleton(function(err, cp) {
cb(err, cp.seq); cb(err, cp.seq);
}); });
}; };
Checkpoint._getSingleton = function(cb) { Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one const query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1}; const initialData = {seq: 1};
this.findOrCreate(query, initialData, cb); this.findOrCreate(query, initialData, cb);
}; };
@ -54,10 +54,10 @@ module.exports = function(Checkpoint) {
* @param {Object} checkpoint The current checkpoint * @param {Object} checkpoint The current checkpoint
*/ */
Checkpoint.bumpLastSeq = function(cb) { Checkpoint.bumpLastSeq = function(cb) {
var Checkpoint = this; const Checkpoint = this;
Checkpoint._getSingleton(function(err, cp) { Checkpoint._getSingleton(function(err, cp) {
if (err) return cb(err); if (err) return cb(err);
var originalSeq = cp.seq; const originalSeq = cp.seq;
cp.seq++; cp.seq++;
// Update the checkpoint but only if it was not changed under our hands // Update the checkpoint but only if it was not changed under our hands
Checkpoint.updateAll({id: cp.id, seq: originalSeq}, {seq: cp.seq}, function(err, info) { Checkpoint.updateAll({id: cp.id, seq: originalSeq}, {seq: cp.seq}, function(err, info) {

View File

@ -1,10 +1,10 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
/** /**
* Email model. Extends LoopBack base [Model](#model-new-model). * Email model. Extends LoopBack base [Model](#model-new-model).

View File

@ -1,10 +1,10 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
/** /**
* Data model for key-value databases. * Data model for key-value databases.
@ -223,19 +223,20 @@ module.exports = function(KeyValueModel) {
function throwNotAttached(modelName, methodName) { function throwNotAttached(modelName, methodName) {
throw new Error(g.f( throw new Error(g.f(
'Cannot call %s.%s(). ' + 'Cannot call %s.%s(). ' +
'The %s method has not been setup. ' + 'The %s method has not been setup. ' +
'The {{KeyValueModel}} has not been correctly attached ' + 'The {{KeyValueModel}} has not been correctly attached ' +
'to a {{DataSource}}!', 'to a {{DataSource}}!',
modelName, methodName, methodName)); modelName, methodName, methodName,
));
} }
function convertNullToNotFoundError(ctx, cb) { function convertNullToNotFoundError(ctx, cb) {
if (ctx.result !== null) return cb(); if (ctx.result !== null) return cb();
var modelName = ctx.method.sharedClass.name; const modelName = ctx.method.sharedClass.name;
var id = ctx.getArgByName('id'); const id = ctx.getArgByName('id');
var msg = g.f('Unknown "%s" {{key}} "%s".', modelName, id); const msg = g.f('Unknown "%s" {{key}} "%s".', modelName, id);
var error = new Error(msg); const error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'KEY_NOT_FOUND'; error.code = 'KEY_NOT_FOUND';
cb(error); cb(error);

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2014,2015. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../../lib/loopback'); const loopback = require('../../lib/loopback');
var utils = require('../../lib/utils'); const utils = require('../../lib/utils');
/** /**
* The `RoleMapping` model extends from the built in `loopback.Model` type. * The `RoleMapping` model extends from the built in `loopback.Model` type.
@ -26,7 +26,7 @@ module.exports = function(RoleMapping) {
RoleMapping.resolveRelatedModels = function() { RoleMapping.resolveRelatedModels = function() {
if (!this.userModel) { if (!this.userModel) {
var reg = this.registry; const reg = this.registry;
this.roleModel = reg.getModelByType('Role'); this.roleModel = reg.getModelByType('Role');
this.userModel = reg.getModelByType('User'); this.userModel = reg.getModelByType('User');
this.applicationModel = reg.getModelByType('Application'); this.applicationModel = reg.getModelByType('Application');
@ -44,7 +44,7 @@ module.exports = function(RoleMapping) {
this.constructor.resolveRelatedModels(); this.constructor.resolveRelatedModels();
if (this.principalType === RoleMapping.APPLICATION) { if (this.principalType === RoleMapping.APPLICATION) {
var applicationModel = this.constructor.applicationModel; const applicationModel = this.constructor.applicationModel;
applicationModel.findById(this.principalId, callback); applicationModel.findById(this.principalId, callback);
} else { } else {
process.nextTick(function() { process.nextTick(function() {
@ -63,7 +63,7 @@ module.exports = function(RoleMapping) {
RoleMapping.prototype.user = function(callback) { RoleMapping.prototype.user = function(callback) {
callback = callback || utils.createPromiseCallback(); callback = callback || utils.createPromiseCallback();
this.constructor.resolveRelatedModels(); this.constructor.resolveRelatedModels();
var userModel; let userModel;
if (this.principalType === RoleMapping.USER) { if (this.principalType === RoleMapping.USER) {
userModel = this.constructor.userModel; userModel = this.constructor.userModel;
@ -94,7 +94,7 @@ module.exports = function(RoleMapping) {
this.constructor.resolveRelatedModels(); this.constructor.resolveRelatedModels();
if (this.principalType === RoleMapping.ROLE) { if (this.principalType === RoleMapping.ROLE) {
var roleModel = this.constructor.roleModel; const roleModel = this.constructor.roleModel;
roleModel.findById(this.principalId, callback); roleModel.findById(this.principalId, callback);
} else { } else {
process.nextTick(function() { process.nextTick(function() {

View File

@ -1,18 +1,18 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../../lib/loopback'); const loopback = require('../../lib/loopback');
var debug = require('debug')('loopback:security:role'); const debug = require('debug')('loopback:security:role');
var assert = require('assert'); const assert = require('assert');
var async = require('async'); const async = require('async');
var utils = require('../../lib/utils'); const utils = require('../../lib/utils');
var ctx = require('../../lib/access-context'); const ctx = require('../../lib/access-context');
var AccessContext = ctx.AccessContext; const AccessContext = ctx.AccessContext;
var Principal = ctx.Principal; const Principal = ctx.Principal;
var RoleMapping = loopback.RoleMapping; const RoleMapping = loopback.RoleMapping;
assert(RoleMapping, 'RoleMapping model must be defined before Role model'); assert(RoleMapping, 'RoleMapping model must be defined before Role model');
@ -24,7 +24,7 @@ assert(RoleMapping, 'RoleMapping model must be defined before Role model');
module.exports = function(Role) { module.exports = function(Role) {
Role.resolveRelatedModels = function() { Role.resolveRelatedModels = function() {
if (!this.userModel) { if (!this.userModel) {
var reg = this.registry; const reg = this.registry;
this.roleMappingModel = reg.getModelByType('RoleMapping'); this.roleMappingModel = reg.getModelByType('RoleMapping');
this.userModel = reg.getModelByType('User'); this.userModel = reg.getModelByType('User');
this.applicationModel = reg.getModelByType('Application'); this.applicationModel = reg.getModelByType('Application');
@ -74,29 +74,29 @@ module.exports = function(Role) {
query.where = query.where || {}; query.where = query.where || {};
roleModel.resolveRelatedModels(); roleModel.resolveRelatedModels();
var relsToModels = { const relsToModels = {
users: roleModel.userModel, users: roleModel.userModel,
applications: roleModel.applicationModel, applications: roleModel.applicationModel,
roles: roleModel, roles: roleModel,
}; };
var ACL = loopback.ACL; const ACL = loopback.ACL;
var relsToTypes = { const relsToTypes = {
users: ACL.USER, users: ACL.USER,
applications: ACL.APP, applications: ACL.APP,
roles: ACL.ROLE, roles: ACL.ROLE,
}; };
var principalModel = relsToModels[rel]; let principalModel = relsToModels[rel];
var principalType = relsToTypes[rel]; let principalType = relsToTypes[rel];
// redefine user model and user type if user principalType is custom (available and not "USER") // redefine user model and user type if user principalType is custom (available and not "USER")
var isCustomUserPrincipalType = rel === 'users' && const isCustomUserPrincipalType = rel === 'users' &&
query.where.principalType && query.where.principalType &&
query.where.principalType !== RoleMapping.USER; query.where.principalType !== RoleMapping.USER;
if (isCustomUserPrincipalType) { if (isCustomUserPrincipalType) {
var registry = this.constructor.registry; const registry = this.constructor.registry;
principalModel = registry.findModel(query.where.principalType); principalModel = registry.findModel(query.where.principalType);
principalType = query.where.principalType; principalType = query.where.principalType;
} }
@ -133,11 +133,10 @@ module.exports = function(Role) {
roleModel.roleMappingModel.find({ roleModel.roleMappingModel.find({
where: {roleId: context.id, principalType: principalType}, where: {roleId: context.id, principalType: principalType},
}, function(err, mappings) { }, function(err, mappings) {
var ids;
if (err) { if (err) {
return callback(err); return callback(err);
} }
ids = mappings.map(function(m) { const ids = mappings.map(function(m) {
return m.principalId; return m.principalId;
}); });
query.where = query.where || {}; query.where = query.where || {};
@ -177,18 +176,18 @@ module.exports = function(Role) {
}); });
return; return;
} }
var modelClass = context.model; const modelClass = context.model;
var modelId = context.modelId; const modelId = context.modelId;
var user = context.getUser(); const user = context.getUser();
var userId = user && user.id; const userId = user && user.id;
var principalType = user && user.principalType; const principalType = user && user.principalType;
var opts = {accessToken: context.accessToken}; const opts = {accessToken: context.accessToken};
Role.isOwner(modelClass, modelId, userId, principalType, opts, callback); Role.isOwner(modelClass, modelId, userId, principalType, opts, callback);
}); });
function isUserClass(modelClass) { function isUserClass(modelClass) {
if (!modelClass) return false; if (!modelClass) return false;
var User = modelClass.modelBuilder.models.User; const User = modelClass.modelBuilder.models.User;
if (!User) return false; if (!User) return false;
return modelClass == User || modelClass.prototype instanceof User; return modelClass == User || modelClass.prototype instanceof User;
} }
@ -222,6 +221,8 @@ module.exports = function(Role) {
* @promise * @promise
*/ */
Role.isOwner = function isOwner(modelClass, modelId, userId, principalType, options, callback) { Role.isOwner = function isOwner(modelClass, modelId, userId, principalType, options, callback) {
const _this = this;
if (!callback && typeof options === 'function') { if (!callback && typeof options === 'function') {
callback = options; callback = options;
options = {}; options = {};
@ -238,8 +239,29 @@ module.exports = function(Role) {
debug('isOwner(): %s %s userId: %s principalType: %s', debug('isOwner(): %s %s userId: %s principalType: %s',
modelClass && modelClass.modelName, modelId, userId, principalType); modelClass && modelClass.modelName, modelId, userId, principalType);
// Return false if userId is missing // Resolve isOwner false if userId is missing
if (!userId) { if (!userId) {
debug('isOwner(): no user id was set, returning false');
process.nextTick(function() {
callback(null, false);
});
return callback.promise;
}
// At this stage, principalType is valid in one of 2 following condition:
// 1. the app has a single user model and principalType is 'USER'
// 2. the app has multiple user models and principalType is not 'USER'
// multiple user models
const isMultipleUsers = _isMultipleUsers();
const isPrincipalTypeValid =
(!isMultipleUsers && principalType === Principal.USER) ||
(isMultipleUsers && principalType !== Principal.USER);
debug('isOwner(): isMultipleUsers?', isMultipleUsers,
'isPrincipalTypeValid?', isPrincipalTypeValid);
// Resolve isOwner false if principalType is invalid
if (!isPrincipalTypeValid) {
process.nextTick(function() { process.nextTick(function() {
callback(null, false); callback(null, false);
}); });
@ -248,14 +270,15 @@ module.exports = function(Role) {
// Is the modelClass User or a subclass of User? // Is the modelClass User or a subclass of User?
if (isUserClass(modelClass)) { if (isUserClass(modelClass)) {
var userModelName = modelClass.modelName; const userModelName = modelClass.modelName;
// matching ids is enough if principalType is USER or matches given user model name // matching ids is enough if principalType is USER or matches given user model name
if (principalType === Principal.USER || principalType === userModelName) { if (principalType === Principal.USER || principalType === userModelName) {
process.nextTick(function() { process.nextTick(function() {
callback(null, matches(modelId, userId)); callback(null, matches(modelId, userId));
}); });
return callback.promise;
} }
return callback.promise; // otherwise continue with the regular owner resolution
} }
modelClass.findById(modelId, options, function(err, inst) { modelClass.findById(modelId, options, function(err, inst) {
@ -265,26 +288,48 @@ module.exports = function(Role) {
} }
debug('Model found: %j', inst); debug('Model found: %j', inst);
// Historically, for principalType USER, we were resolving isOwner() const ownerRelations = modelClass.settings.ownerRelations;
// as true if the model has "userId" or "owner" property matching if (!ownerRelations) {
// id of the current user (principalId), even though there was no return legacyOwnershipCheck(inst);
// belongsTo relation set up. } else {
var ownerId = inst.userId || inst.owner; return checkOwnership(inst);
}
});
return callback.promise;
// NOTE Historically, for principalType USER, we were resolving isOwner()
// as true if the model has "userId" or "owner" property matching
// id of the current user (principalId), even though there was no
// belongsTo relation set up.
// Additionaly, the original implementation did not support the
// possibility for a model to have multiple related users: when
// testing belongsTo relations, the first related user failing the
// ownership check induced the whole isOwner() to resolve as false.
// This behaviour will be pruned at next LoopBack major release.
function legacyOwnershipCheck(inst) {
const ownerId = inst.userId || inst.owner;
if (principalType === Principal.USER && ownerId && 'function' !== typeof ownerId) { if (principalType === Principal.USER && ownerId && 'function' !== typeof ownerId) {
return callback(null, matches(ownerId, userId)); return callback(null, matches(ownerId, userId));
} }
// Try to follow belongsTo // Try to follow belongsTo
for (var r in modelClass.relations) { for (const r in modelClass.relations) {
var rel = modelClass.relations[r]; const rel = modelClass.relations[r];
// relation should be belongsTo and target a User based class // relation should be belongsTo and target a User based class
var belongsToUser = rel.type === 'belongsTo' && isUserClass(rel.modelTo); const belongsToUser = rel.type === 'belongsTo' && isUserClass(rel.modelTo);
if (!belongsToUser) { if (!belongsToUser) {
continue; continue;
} }
// checking related user // checking related user
var userModelName = rel.modelTo.modelName; const relatedUser = rel.modelTo;
if (principalType === Principal.USER || principalType === userModelName) { const userModelName = relatedUser.modelName;
const isMultipleUsers = _isMultipleUsers(relatedUser);
// a relation can be considered for isOwner resolution if:
// 1. the app has a single user model and principalType is 'USER'
// 2. the app has multiple user models and principalType is the related user model name
if ((!isMultipleUsers && principalType === Principal.USER) ||
(isMultipleUsers && principalType === userModelName)) {
debug('Checking relation %s to %s: %j', r, userModelName, rel); debug('Checking relation %s to %s: %j', r, userModelName, rel);
inst[r](processRelatedUser); inst[r](processRelatedUser);
return; return;
@ -295,15 +340,72 @@ module.exports = function(Role) {
callback(null, false); callback(null, false);
function processRelatedUser(err, user) { function processRelatedUser(err, user) {
if (!err && user) { if (err || !user) return callback(err, false);
debug('User found: %j', user.id);
callback(null, matches(user.id, userId)); debug('User found: %j', user.id);
} else { callback(null, matches(user.id, userId));
callback(err, false); }
}
function checkOwnership(inst) {
const ownerRelations = inst.constructor.settings.ownerRelations;
// collecting related users
const relWithUsers = [];
for (const r in modelClass.relations) {
const rel = modelClass.relations[r];
// relation should be belongsTo and target a User based class
if (rel.type !== 'belongsTo' || !isUserClass(rel.modelTo)) {
continue;
}
// checking related user
const relatedUser = rel.modelTo;
const userModelName = relatedUser.modelName;
const isMultipleUsers = _isMultipleUsers(relatedUser);
// a relation can be considered for isOwner resolution if:
// 1. the app has a single user model and principalType is 'USER'
// 2. the app has multiple user models and principalType is the related user model name
// In addition, if an array of relations if provided with the ownerRelations option,
// then the given relation name is further checked against this array
if ((!isMultipleUsers && principalType === Principal.USER) ||
(isMultipleUsers && principalType === userModelName)) {
debug('Checking relation %s to %s: %j', r, userModelName, rel);
if (ownerRelations === true) {
relWithUsers.push(r);
} else if (Array.isArray(ownerRelations) && ownerRelations.indexOf(r) !== -1) {
relWithUsers.push(r);
}
} }
} }
}); if (relWithUsers.length === 0) {
return callback.promise; debug('No matching belongsTo relation found for model %j and user: %j principalType: %j',
modelId, userId, principalType);
return callback(null, false);
}
// check related users: someSeries is used to avoid spamming the db
async.someSeries(relWithUsers, processRelation, callback);
function processRelation(r, cb) {
inst[r](function processRelatedUser(err, user) {
if (err || !user) return cb(err, false);
debug('User found: %j (through %j)', user.id, r);
cb(null, matches(user.id, userId));
});
}
}
// A helper function to check if the app user config is multiple users or
// single user. It can be used with or without a reference user model.
// In case no user model is provided, we use the registry to get any of the
// user model by type. The relation with AccessToken is used to check
// if polymorphism is used, and thus if multiple users.
function _isMultipleUsers(userModel) {
const oneOfUserModels = userModel || _this.registry.getModelByType('User');
const accessTokensRel = oneOfUserModels.relations.accessTokens;
return !!(accessTokensRel && accessTokensRel.polymorphic);
}
}; };
Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) { Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) {
@ -377,15 +479,15 @@ module.exports = function(Role) {
debug('isInRole(): %s', role); debug('isInRole(): %s', role);
context.debug(); context.debug();
var resolver = Role.resolvers[role]; const resolver = Role.resolvers[role];
if (resolver) { if (resolver) {
debug('Custom resolver found for role %s', role); debug('Custom resolver found for role %s', role);
var promise = resolver(role, context, callback); const promise = resolver(role, context, callback);
if (promise && typeof promise.then === 'function') { if (promise && typeof promise.then === 'function') {
promise.then( promise.then(
function(result) { callback(null, result); }, function(result) { callback(null, result); },
callback callback,
); );
} }
return callback.promise; return callback.promise;
@ -399,9 +501,9 @@ module.exports = function(Role) {
return callback.promise; return callback.promise;
} }
var inRole = context.principals.some(function(p) { const inRole = context.principals.some(function(p) {
var principalType = p.type || undefined; const principalType = p.type || undefined;
var principalId = p.id || undefined; const principalId = p.id || undefined;
// Check if it's the same role // Check if it's the same role
return principalType === RoleMapping.ROLE && principalId === role; return principalType === RoleMapping.ROLE && principalId === role;
@ -415,7 +517,7 @@ module.exports = function(Role) {
return callback.promise; return callback.promise;
} }
var roleMappingModel = this.roleMappingModel; const roleMappingModel = this.roleMappingModel;
this.findOne({where: {name: role}}, function(err, result) { this.findOne({where: {name: role}}, function(err, result) {
if (err) { if (err) {
if (callback) callback(err); if (callback) callback(err);
@ -429,10 +531,10 @@ module.exports = function(Role) {
// Iterate through the list of principals // Iterate through the list of principals
async.some(context.principals, function(p, done) { async.some(context.principals, function(p, done) {
var principalType = p.type || undefined; const principalType = p.type || undefined;
var principalId = p.id || undefined; let principalId = p.id || undefined;
var roleId = result.id.toString(); const roleId = result.id.toString();
var principalIdIsString = typeof principalId === 'string'; const principalIdIsString = typeof principalId === 'string';
if (principalId !== null && principalId !== undefined && !principalIdIsString) { if (principalId !== null && principalId !== undefined && !principalIdIsString) {
principalId = principalId.toString(); principalId = principalId.toString();
@ -441,10 +543,10 @@ module.exports = function(Role) {
if (principalType && principalId) { if (principalType && principalId) {
roleMappingModel.findOne({where: {roleId: roleId, roleMappingModel.findOne({where: {roleId: roleId,
principalType: principalType, principalId: principalId}}, principalType: principalType, principalId: principalId}},
function(err, result) { function(err, result) {
debug('Role mapping found: %j', result); debug('Role mapping found: %j', result);
done(!err && result); // The only arg is the result done(!err && result); // The only arg is the result
}); });
} else { } else {
process.nextTick(function() { process.nextTick(function() {
done(false); done(false);
@ -482,18 +584,18 @@ module.exports = function(Role) {
if (!(context instanceof AccessContext)) { if (!(context instanceof AccessContext)) {
context = new AccessContext(context); context = new AccessContext(context);
} }
var roles = []; const roles = [];
this.resolveRelatedModels(); this.resolveRelatedModels();
var addRole = function(role) { const addRole = function(role) {
if (role && roles.indexOf(role) === -1) { if (role && roles.indexOf(role) === -1) {
roles.push(role); roles.push(role);
} }
}; };
var self = this; const self = this;
// Check against the smart roles // Check against the smart roles
var inRoleTasks = []; const inRoleTasks = [];
Object.keys(Role.resolvers).forEach(function(role) { Object.keys(Role.resolvers).forEach(function(role) {
inRoleTasks.push(function(done) { inRoleTasks.push(function(done) {
self.isInRole(role, context, function(err, inRole) { self.isInRole(role, context, function(err, inRole) {
@ -510,11 +612,11 @@ module.exports = function(Role) {
}); });
}); });
var roleMappingModel = this.roleMappingModel; const roleMappingModel = this.roleMappingModel;
context.principals.forEach(function(p) { context.principals.forEach(function(p) {
// Check against the role mappings // Check against the role mappings
var principalType = p.type || undefined; const principalType = p.type || undefined;
var principalId = p.id == null ? undefined : p.id; let principalId = p.id == null ? undefined : p.id;
if (typeof principalId !== 'string' && principalId != null) { if (typeof principalId !== 'string' && principalId != null) {
principalId = principalId.toString(); principalId = principalId.toString();
@ -528,7 +630,7 @@ module.exports = function(Role) {
if (principalType && principalId) { if (principalType && principalId) {
// Please find() treat undefined matches all values // Please find() treat undefined matches all values
inRoleTasks.push(function(done) { inRoleTasks.push(function(done) {
var filter = {where: {principalType: principalType, principalId: principalId}}; const filter = {where: {principalType: principalType, principalId: principalId}};
if (options.returnOnlyRoleNames === true) { if (options.returnOnlyRoleNames === true) {
filter.include = ['role']; filter.include = ['role'];
} }
@ -539,7 +641,7 @@ module.exports = function(Role) {
return; return;
} }
mappings.forEach(function(m) { mappings.forEach(function(m) {
var role; let role;
if (options.returnOnlyRoleNames === true) { if (options.returnOnlyRoleNames === true) {
role = m.toJSON().role.name; role = m.toJSON().role.name;
} else { } else {

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
var loopback = require('../../lib/loopback'); const loopback = require('../../lib/loopback');
/** /**
* Resource owner grants/delegates permissions to client applications * Resource owner grants/delegates permissions to client applications
@ -21,7 +21,7 @@ var loopback = require('../../lib/loopback');
module.exports = function(Scope) { module.exports = function(Scope) {
Scope.resolveRelatedModels = function() { Scope.resolveRelatedModels = function() {
if (!this.aclModel) { if (!this.aclModel) {
var reg = this.registry; const reg = this.registry;
this.aclModel = reg.getModelByType(loopback.ACL); this.aclModel = reg.getModelByType(loopback.ACL);
} }
}; };
@ -38,7 +38,7 @@ module.exports = function(Scope) {
*/ */
Scope.checkPermission = function(scope, model, property, accessType, callback) { Scope.checkPermission = function(scope, model, property, accessType, callback) {
this.resolveRelatedModels(); this.resolveRelatedModels();
var aclModel = this.aclModel; const aclModel = this.aclModel;
assert(aclModel, assert(aclModel,
'ACL model must be defined before Scope.checkPermission is called'); 'ACL model must be defined before Scope.checkPermission is called');
@ -47,7 +47,8 @@ module.exports = function(Scope) {
if (callback) callback(err); if (callback) callback(err);
} else { } else {
aclModel.checkPermission( aclModel.checkPermission(
aclModel.SCOPE, scope.id, model, property, accessType, callback); aclModel.SCOPE, scope.id, model, property, accessType, callback,
);
} }
}); });
}; };

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,16 +8,18 @@
*/ */
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var isEmail = require('isemail'); const isEmail = require('isemail');
var loopback = require('../../lib/loopback'); const loopback = require('../../lib/loopback');
var utils = require('../../lib/utils'); const utils = require('../../lib/utils');
var path = require('path'); const path = require('path');
var qs = require('querystring'); const qs = require('querystring');
var SALT_WORK_FACTOR = 10; const SALT_WORK_FACTOR = 10;
var crypto = require('crypto'); const crypto = require('crypto');
var MAX_PASSWORD_LENGTH = 72; // bcrypt's max length is 72 bytes;
var bcrypt; // See https://github.com/kelektiv/node.bcrypt.js/blob/45f498ef6dc6e8234e58e07834ce06a50ff16352/src/node_blf.h#L59
const MAX_PASSWORD_LENGTH = 72;
let bcrypt;
try { try {
// Try the native module first // Try the native module first
bcrypt = require('bcrypt'); bcrypt = require('bcrypt');
@ -30,12 +32,12 @@ try {
bcrypt = require('bcryptjs'); bcrypt = require('bcryptjs');
} }
var DEFAULT_TTL = 1209600; // 2 weeks in seconds const DEFAULT_TTL = 1209600; // 2 weeks in seconds
var DEFAULT_RESET_PW_TTL = 15 * 60; // 15 mins in seconds const DEFAULT_RESET_PW_TTL = 15 * 60; // 15 mins in seconds
var DEFAULT_MAX_TTL = 31556926; // 1 year in seconds const DEFAULT_MAX_TTL = 31556926; // 1 year in seconds
var assert = require('assert'); const assert = require('assert');
var debug = require('debug')('loopback:user'); const debug = require('debug')('loopback:user');
/** /**
* Built-in User model. * Built-in User model.
@ -121,18 +123,18 @@ module.exports = function(User) {
tokenData = {}; tokenData = {};
} }
var userSettings = this.constructor.settings; const userSettings = this.constructor.settings;
tokenData.ttl = Math.min(tokenData.ttl || userSettings.ttl, userSettings.maxTTL); tokenData.ttl = Math.min(tokenData.ttl || userSettings.ttl, userSettings.maxTTL);
this.accessTokens.create(tokenData, options, cb); this.accessTokens.create(tokenData, options, cb);
return cb.promise; return cb.promise;
}; };
function splitPrincipal(name, realmDelimiter) { function splitPrincipal(name, realmDelimiter) {
var parts = [null, name]; const parts = [null, name];
if (!realmDelimiter) { if (!realmDelimiter) {
return parts; return parts;
} }
var index = name.indexOf(realmDelimiter); const index = name.indexOf(realmDelimiter);
if (index !== -1) { if (index !== -1) {
parts[0] = name.substring(0, index); parts[0] = name.substring(0, index);
parts[1] = name.substring(index + realmDelimiter.length); parts[1] = name.substring(index + realmDelimiter.length);
@ -148,7 +150,7 @@ module.exports = function(User) {
* @returns {Object} The normalized credential object * @returns {Object} The normalized credential object
*/ */
User.normalizeCredentials = function(credentials, realmRequired, realmDelimiter) { User.normalizeCredentials = function(credentials, realmRequired, realmDelimiter) {
var query = {}; const query = {};
credentials = credentials || {}; credentials = credentials || {};
if (!realmRequired) { if (!realmRequired) {
if (credentials.email) { if (credentials.email) {
@ -160,7 +162,7 @@ module.exports = function(User) {
if (credentials.realm) { if (credentials.realm) {
query.realm = credentials.realm; query.realm = credentials.realm;
} }
var parts; let parts;
if (credentials.email) { if (credentials.email) {
parts = splitPrincipal(credentials.email, realmDelimiter); parts = splitPrincipal(credentials.email, realmDelimiter);
query.email = parts[1]; query.email = parts[1];
@ -203,7 +205,7 @@ module.exports = function(User) {
*/ */
User.login = function(credentials, include, fn) { User.login = function(credentials, include, fn) {
var self = this; const self = this;
if (typeof include === 'function') { if (typeof include === 'function') {
fn = include; fn = include;
include = undefined; include = undefined;
@ -220,33 +222,54 @@ module.exports = function(User) {
include = include.toLowerCase(); include = include.toLowerCase();
} }
var realmDelimiter; let realmDelimiter;
// Check if realm is required // Check if realm is required
var realmRequired = !!(self.settings.realmRequired || const realmRequired = !!(self.settings.realmRequired ||
self.settings.realmDelimiter); self.settings.realmDelimiter);
if (realmRequired) { if (realmRequired) {
realmDelimiter = self.settings.realmDelimiter; realmDelimiter = self.settings.realmDelimiter;
} }
var query = self.normalizeCredentials(credentials, realmRequired, const query = self.normalizeCredentials(credentials, realmRequired,
realmDelimiter); realmDelimiter);
if (realmRequired && !query.realm) { if (realmRequired) {
var err1 = new Error(g.f('{{realm}} is required')); if (!query.realm) {
err1.statusCode = 400; const err1 = new Error(g.f('{{realm}} is required'));
err1.code = 'REALM_REQUIRED'; err1.statusCode = 400;
fn(err1); err1.code = 'REALM_REQUIRED';
return fn.promise; fn(err1);
return fn.promise;
} else if (typeof query.realm !== 'string') {
const err5 = new Error(g.f('Invalid realm'));
err5.statusCode = 400;
err5.code = 'INVALID_REALM';
fn(err5);
return fn.promise;
}
} }
if (!query.email && !query.username) { if (!query.email && !query.username) {
var err2 = new Error(g.f('{{username}} or {{email}} is required')); const err2 = new Error(g.f('{{username}} or {{email}} is required'));
err2.statusCode = 400; err2.statusCode = 400;
err2.code = 'USERNAME_EMAIL_REQUIRED'; err2.code = 'USERNAME_EMAIL_REQUIRED';
fn(err2); fn(err2);
return fn.promise; return fn.promise;
} }
if (query.username && typeof query.username !== 'string') {
const err3 = new Error(g.f('Invalid username'));
err3.statusCode = 400;
err3.code = 'INVALID_USERNAME';
fn(err3);
return fn.promise;
} else if (query.email && typeof query.email !== 'string') {
const err4 = new Error(g.f('Invalid email'));
err4.statusCode = 400;
err4.code = 'INVALID_EMAIL';
fn(err4);
return fn.promise;
}
self.findOne({where: query}, function(err, user) { self.findOne({where: query}, function(err, user) {
var defaultError = new Error(g.f('login failed')); const defaultError = new Error(g.f('login failed'));
defaultError.statusCode = 401; defaultError.statusCode = 401;
defaultError.code = 'LOGIN_FAILED'; defaultError.code = 'LOGIN_FAILED';
@ -321,10 +344,10 @@ module.exports = function(User) {
User.logout = function(tokenId, fn) { User.logout = function(tokenId, fn) {
fn = fn || utils.createPromiseCallback(); fn = fn || utils.createPromiseCallback();
var err; let err;
if (!tokenId) { if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout')); err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401; err.statusCode = 401;
process.nextTick(fn, err); process.nextTick(fn, err);
return fn.promise; return fn.promise;
} }
@ -334,7 +357,7 @@ module.exports = function(User) {
fn(err); fn(err);
} else if ('count' in info && info.count === 0) { } else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}')); err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401; err.statusCode = 401;
fn(err); fn(err);
} else { } else {
fn(); fn();
@ -344,12 +367,15 @@ module.exports = function(User) {
}; };
User.observe('before delete', function(ctx, next) { User.observe('before delete', function(ctx, next) {
var AccessToken = ctx.Model.relations.accessTokens.modelTo; // Do nothing when the access control was disabled for this user model.
var pkName = ctx.Model.definition.idName() || 'id'; if (!ctx.Model.relations.accessTokens) return next();
const AccessToken = ctx.Model.relations.accessTokens.modelTo;
const pkName = ctx.Model.definition.idName() || 'id';
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) { ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err); if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; }); const ids = list.map(function(u) { return u[pkName]; });
ctx.where = {}; ctx.where = {};
ctx.where[pkName] = {inq: ids}; ctx.where[pkName] = {inq: ids};
@ -601,11 +627,11 @@ module.exports = function(User) {
*/ */
User.getVerifyOptions = function() { User.getVerifyOptions = function() {
const verifyOptions = { const defaultOptions = {
type: 'email', type: 'email',
from: 'noreply@example.com', from: 'noreply@example.com',
}; };
return this.settings.verifyOptions || verifyOptions; return Object.assign({}, this.settings.verifyOptions || defaultOptions);
}; };
/** /**
@ -694,14 +720,18 @@ module.exports = function(User) {
} }
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
var user = this; const user = this;
var userModel = this.constructor; const userModel = this.constructor;
var registry = userModel.registry; const registry = userModel.registry;
verifyOptions = Object.assign({}, verifyOptions);
// final assertion is performed once all options are assigned // final assertion is performed once all options are assigned
assert(typeof verifyOptions === 'object', assert(typeof verifyOptions === 'object',
'verifyOptions object param required when calling user.verify()'); 'verifyOptions object param required when calling user.verify()');
// Shallow-clone the options object so that we don't override
// the global default options object
verifyOptions = Object.assign({}, verifyOptions);
// Set a default template generation function if none provided // Set a default template generation function if none provided
verifyOptions.templateFn = verifyOptions.templateFn || createVerificationEmailBody; verifyOptions.templateFn = verifyOptions.templateFn || createVerificationEmailBody;
@ -713,39 +743,50 @@ module.exports = function(User) {
verifyOptions.mailer = verifyOptions.mailer || userModel.email || verifyOptions.mailer = verifyOptions.mailer || userModel.email ||
registry.getModelByType(loopback.Email); registry.getModelByType(loopback.Email);
var pkName = userModel.definition.idName() || 'id'; const pkName = userModel.definition.idName() || 'id';
verifyOptions.redirect = verifyOptions.redirect || '/'; verifyOptions.redirect = verifyOptions.redirect || '/';
var defaultTemplate = path.join(__dirname, '..', '..', 'templates', 'verify.ejs'); const defaultTemplate = path.join(__dirname, '..', '..', 'templates', 'verify.ejs');
verifyOptions.template = path.resolve(verifyOptions.template || defaultTemplate); verifyOptions.template = path.resolve(verifyOptions.template || defaultTemplate);
verifyOptions.user = user; verifyOptions.user = user;
verifyOptions.protocol = verifyOptions.protocol || 'http'; verifyOptions.protocol = verifyOptions.protocol || 'http';
var app = userModel.app; const app = userModel.app;
verifyOptions.host = verifyOptions.host || (app && app.get('host')) || 'localhost'; verifyOptions.host = verifyOptions.host || (app && app.get('host')) || 'localhost';
verifyOptions.port = verifyOptions.port || (app && app.get('port')) || 3000; verifyOptions.port = verifyOptions.port || (app && app.get('port')) || 3000;
verifyOptions.restApiRoot = verifyOptions.restApiRoot || (app && app.get('restApiRoot')) || '/api'; verifyOptions.restApiRoot = verifyOptions.restApiRoot || (app && app.get('restApiRoot')) || '/api';
var displayPort = ( const displayPort = (
(verifyOptions.protocol === 'http' && verifyOptions.port == '80') || (verifyOptions.protocol === 'http' && verifyOptions.port == '80') ||
(verifyOptions.protocol === 'https' && verifyOptions.port == '443') (verifyOptions.protocol === 'https' && verifyOptions.port == '443')
) ? '' : ':' + verifyOptions.port; ) ? '' : ':' + verifyOptions.port;
var urlPath = joinUrlPath( if (!verifyOptions.verifyHref) {
verifyOptions.restApiRoot, const confirmMethod = userModel.sharedClass.findMethodByName('confirm');
userModel.http.path, if (!confirmMethod) {
userModel.sharedClass.findMethodByName('confirm').http.path throw new Error(
); 'Cannot build user verification URL, ' +
'the default confirm method is not public. ' +
'Please provide the URL in verifyOptions.verifyHref.',
);
}
verifyOptions.verifyHref = verifyOptions.verifyHref || const urlPath = joinUrlPath(
verifyOptions.protocol + verifyOptions.restApiRoot,
'://' + userModel.http.path,
verifyOptions.host + confirmMethod.http.path,
displayPort + );
urlPath +
'?' + qs.stringify({ verifyOptions.verifyHref =
uid: '' + verifyOptions.user[pkName], verifyOptions.protocol +
redirect: verifyOptions.redirect, '://' +
}); verifyOptions.host +
displayPort +
urlPath +
'?' + qs.stringify({
uid: '' + verifyOptions.user[pkName],
redirect: verifyOptions.redirect,
});
}
verifyOptions.to = verifyOptions.to || user.email; verifyOptions.to = verifyOptions.to || user.email;
verifyOptions.subject = verifyOptions.subject || g.f('Thanks for Registering'); verifyOptions.subject = verifyOptions.subject || g.f('Thanks for Registering');
@ -755,7 +796,7 @@ module.exports = function(User) {
assertVerifyOptions(verifyOptions); assertVerifyOptions(verifyOptions);
// argument "options" is passed depending on verifyOptions.generateVerificationToken function requirements // argument "options" is passed depending on verifyOptions.generateVerificationToken function requirements
var tokenGenerator = verifyOptions.generateVerificationToken; const tokenGenerator = verifyOptions.generateVerificationToken;
if (tokenGenerator.length == 3) { if (tokenGenerator.length == 3) {
tokenGenerator(user, options, addTokenToUserAndSave); tokenGenerator(user, options, addTokenToUserAndSave);
} else { } else {
@ -773,14 +814,17 @@ module.exports = function(User) {
// TODO - support more verification types // TODO - support more verification types
function sendEmail(user) { function sendEmail(user) {
verifyOptions.verifyHref += '&token=' + user.verificationToken; verifyOptions.verifyHref +=
verifyOptions.verifyHref.indexOf('?') === -1 ? '?' : '&';
verifyOptions.verifyHref += 'token=' + user.verificationToken;
verifyOptions.verificationToken = user.verificationToken; verifyOptions.verificationToken = user.verificationToken;
verifyOptions.text = verifyOptions.text || g.f('Please verify your email by opening ' + verifyOptions.text = verifyOptions.text || g.f('Please verify your email by opening ' +
'this link in a web browser:\n\t%s', verifyOptions.verifyHref); 'this link in a web browser:\n\t%s', verifyOptions.verifyHref);
verifyOptions.text = verifyOptions.text.replace(/\{href\}/g, verifyOptions.verifyHref); verifyOptions.text = verifyOptions.text.replace(/\{href\}/g, verifyOptions.verifyHref);
// argument "options" is passed depending on templateFn function requirements // argument "options" is passed depending on templateFn function requirements
var templateFn = verifyOptions.templateFn; const templateFn = verifyOptions.templateFn;
if (templateFn.length == 3) { if (templateFn.length == 3) {
templateFn(verifyOptions, options, setHtmlContentAndSend); templateFn(verifyOptions, options, setHtmlContentAndSend);
} else { } else {
@ -797,7 +841,7 @@ module.exports = function(User) {
delete verifyOptions.template; delete verifyOptions.template;
// argument "options" is passed depending on Email.send function requirements // argument "options" is passed depending on Email.send function requirements
var Email = verifyOptions.mailer; const Email = verifyOptions.mailer;
if (Email.send.length == 3) { if (Email.send.length == 3) {
Email.send(verifyOptions, options, handleAfterSend); Email.send(verifyOptions, options, handleAfterSend);
} else { } else {
@ -829,8 +873,8 @@ module.exports = function(User) {
} }
function createVerificationEmailBody(verifyOptions, options, cb) { function createVerificationEmailBody(verifyOptions, options, cb) {
var template = loopback.template(verifyOptions.template); const template = loopback.template(verifyOptions.template);
var body = template(verifyOptions); const body = template(verifyOptions);
cb(null, body); cb(null, body);
} }
@ -908,11 +952,11 @@ module.exports = function(User) {
User.resetPassword = function(options, cb) { User.resetPassword = function(options, cb) {
cb = cb || utils.createPromiseCallback(); cb = cb || utils.createPromiseCallback();
var UserModel = this; const UserModel = this;
var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL; const ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL;
options = options || {}; options = options || {};
if (typeof options.email !== 'string') { if (typeof options.email !== 'string') {
var err = new Error(g.f('Email is required')); const err = new Error(g.f('Email is required'));
err.statusCode = 400; err.statusCode = 400;
err.code = 'EMAIL_REQUIRED'; err.code = 'EMAIL_REQUIRED';
cb(err); cb(err);
@ -926,7 +970,7 @@ module.exports = function(User) {
} catch (err) { } catch (err) {
return cb(err); return cb(err);
} }
var where = { const where = {
email: options.email, email: options.email,
}; };
if (options.realm) { if (options.realm) {
@ -987,24 +1031,28 @@ module.exports = function(User) {
*/ */
User.hashPassword = function(plain) { User.hashPassword = function(plain) {
this.validatePassword(plain); this.validatePassword(plain);
var salt = bcrypt.genSaltSync(this.settings.saltWorkFactor || SALT_WORK_FACTOR); const salt = bcrypt.genSaltSync(this.settings.saltWorkFactor || SALT_WORK_FACTOR);
return bcrypt.hashSync(plain, salt); return bcrypt.hashSync(plain, salt);
}; };
User.validatePassword = function(plain) { User.validatePassword = function(plain) {
var err; let err;
if (plain && typeof plain === 'string' && plain.length <= MAX_PASSWORD_LENGTH) { if (!plain || typeof plain !== 'string') {
return true; err = new Error(g.f('Invalid password.'));
}
if (plain.length > MAX_PASSWORD_LENGTH) {
err = new Error(g.f('Password too long: %s', plain));
err.code = 'PASSWORD_TOO_LONG';
} else {
err = new Error(g.f('Invalid password: %s', plain));
err.code = 'INVALID_PASSWORD'; err.code = 'INVALID_PASSWORD';
err.statusCode = 422;
throw err;
}
// Bcrypt only supports up to 72 bytes; the rest is silently dropped.
const len = Buffer.byteLength(plain, 'utf8');
if (len > MAX_PASSWORD_LENGTH) {
err = new Error(g.f('The password entered was too long. Max length is %d (entered %d)',
MAX_PASSWORD_LENGTH, len));
err.code = 'PASSWORD_TOO_LONG';
err.statusCode = 422;
throw err;
} }
err.statusCode = 422;
throw err;
}; };
User._invalidateAccessTokensOfUsers = function(userIds, options, cb) { User._invalidateAccessTokensOfUsers = function(userIds, options, cb) {
@ -1016,20 +1064,20 @@ module.exports = function(User) {
if (!Array.isArray(userIds) || !userIds.length) if (!Array.isArray(userIds) || !userIds.length)
return process.nextTick(cb); return process.nextTick(cb);
var accessTokenRelation = this.relations.accessTokens; const accessTokenRelation = this.relations.accessTokens;
if (!accessTokenRelation) if (!accessTokenRelation)
return process.nextTick(cb); return process.nextTick(cb);
var AccessToken = accessTokenRelation.modelTo; const AccessToken = accessTokenRelation.modelTo;
var query = {userId: {inq: userIds}}; const query = {userId: {inq: userIds}};
var tokenPK = AccessToken.definition.idName() || 'id'; const tokenPK = AccessToken.definition.idName() || 'id';
if (options.accessToken && tokenPK in options.accessToken) { if (options.accessToken && tokenPK in options.accessToken) {
query[tokenPK] = {neq: options.accessToken[tokenPK]}; query[tokenPK] = {neq: options.accessToken[tokenPK]};
} }
// add principalType in AccessToken.query if using polymorphic relations // add principalType in AccessToken.query if using polymorphic relations
// between AccessToken and User // between AccessToken and User
var relatedUser = AccessToken.relations.user; const relatedUser = AccessToken.relations.user;
var isRelationPolymorphic = relatedUser && relatedUser.polymorphic && const isRelationPolymorphic = relatedUser && relatedUser.polymorphic &&
!relatedUser.modelTo; !relatedUser.modelTo;
if (isRelationPolymorphic) { if (isRelationPolymorphic) {
query.principalType = this.modelName; query.principalType = this.modelName;
@ -1044,14 +1092,14 @@ module.exports = function(User) {
User.setup = function() { User.setup = function() {
// We need to call the base class's setup method // We need to call the base class's setup method
User.base.setup.call(this); User.base.setup.call(this);
var UserModel = this; const UserModel = this;
// max ttl // max ttl
this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL; this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL;
this.settings.ttl = this.settings.ttl || DEFAULT_TTL; this.settings.ttl = this.settings.ttl || DEFAULT_TTL;
UserModel.setter.email = function(value) { UserModel.setter.email = function(value) {
if (!UserModel.settings.caseSensitiveEmail) { if (!UserModel.settings.caseSensitiveEmail && typeof value === 'string') {
this.$email = value.toLowerCase(); this.$email = value.toLowerCase();
} else { } else {
this.$email = value; this.$email = value;
@ -1062,7 +1110,7 @@ module.exports = function(User) {
if (typeof plain !== 'string') { if (typeof plain !== 'string') {
return; return;
} }
if (plain.indexOf('$2a$') === 0 && plain.length === 60) { if ((plain.indexOf('$2a$') === 0 || plain.indexOf('$2b$') === 0) && plain.length === 60) {
// The password is already hashed. It can be the case // The password is already hashed. It can be the case
// when the instance is loaded from DB // when the instance is loaded from DB
this.$password = plain; this.$password = plain;
@ -1073,7 +1121,7 @@ module.exports = function(User) {
// Make sure emailVerified is not set by creation // Make sure emailVerified is not set by creation
UserModel.beforeRemote('create', function(ctx, user, next) { UserModel.beforeRemote('create', function(ctx, user, next) {
var body = ctx.req.body; const body = ctx.req.body;
if (body && body.emailVerified) { if (body && body.emailVerified) {
body.emailVerified = false; body.emailVerified = false;
} }
@ -1100,7 +1148,7 @@ module.exports = function(User) {
'{{(`include=user`)}}\n\n'), '{{(`include=user`)}}\n\n'),
}, },
http: {verb: 'post'}, http: {verb: 'post'},
} },
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1109,9 +1157,9 @@ module.exports = function(User) {
description: 'Logout a user with access token.', description: 'Logout a user with access token.',
accepts: [ accepts: [
{arg: 'access_token', type: 'string', http: function(ctx) { {arg: 'access_token', type: 'string', http: function(ctx) {
var req = ctx && ctx.req; const req = ctx && ctx.req;
var accessToken = req && req.accessToken; const accessToken = req && req.accessToken;
var tokenID = accessToken ? accessToken.id : undefined; const tokenID = accessToken ? accessToken.id : undefined;
return tokenID; return tokenID;
}, description: 'Do not supply this argument, it is automatically extracted ' + }, description: 'Do not supply this argument, it is automatically extracted ' +
@ -1119,7 +1167,7 @@ module.exports = function(User) {
}, },
], ],
http: {verb: 'all'}, http: {verb: 'all'},
} },
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1131,7 +1179,7 @@ module.exports = function(User) {
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
http: {verb: 'post'}, http: {verb: 'post'},
} },
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1144,7 +1192,7 @@ module.exports = function(User) {
{arg: 'redirect', type: 'string'}, {arg: 'redirect', type: 'string'},
], ],
http: {verb: 'get', path: '/confirm'}, http: {verb: 'get', path: '/confirm'},
} },
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1155,7 +1203,7 @@ module.exports = function(User) {
{arg: 'options', type: 'object', required: true, http: {source: 'body'}}, {arg: 'options', type: 'object', required: true, http: {source: 'body'}},
], ],
http: {verb: 'post', path: '/reset'}, http: {verb: 'post', path: '/reset'},
} },
); );
UserModel.remoteMethod( UserModel.remoteMethod(
@ -1163,15 +1211,13 @@ module.exports = function(User) {
{ {
description: 'Change a user\'s password.', description: 'Change a user\'s password.',
accepts: [ accepts: [
{arg: 'id', type: 'any', {arg: 'id', type: 'any', http: getUserIdFromRequestContext},
http: ctx => ctx.req.accessToken && ctx.req.accessToken.userId,
},
{arg: 'oldPassword', type: 'string', required: true, http: {source: 'form'}}, {arg: 'oldPassword', type: 'string', required: true, http: {source: 'form'}},
{arg: 'newPassword', type: 'string', required: true, http: {source: 'form'}}, {arg: 'newPassword', type: 'string', required: true, http: {source: 'form'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
http: {verb: 'POST', path: '/change-password'}, http: {verb: 'POST', path: '/change-password'},
} },
); );
const setPasswordScopes = UserModel.settings.restrictResetPasswordTokenScope ? const setPasswordScopes = UserModel.settings.restrictResetPasswordTokenScope ?
@ -1182,17 +1228,32 @@ module.exports = function(User) {
{ {
description: 'Reset user\'s password via a password-reset token.', description: 'Reset user\'s password via a password-reset token.',
accepts: [ accepts: [
{arg: 'id', type: 'any', {arg: 'id', type: 'any', http: getUserIdFromRequestContext},
http: ctx => ctx.req.accessToken && ctx.req.accessToken.userId,
},
{arg: 'newPassword', type: 'string', required: true, http: {source: 'form'}}, {arg: 'newPassword', type: 'string', required: true, http: {source: 'form'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
accessScopes: setPasswordScopes, accessScopes: setPasswordScopes,
http: {verb: 'POST', path: '/reset-password'}, http: {verb: 'POST', path: '/reset-password'},
} },
); );
function getUserIdFromRequestContext(ctx) {
const token = ctx.req.accessToken;
if (!token) return;
const hasPrincipalType = 'principalType' in token;
if (hasPrincipalType && token.principalType !== UserModel.modelName) {
// We have multiple user models related to the same access token model
// and the token used to authorize reset-password request was created
// for a different user model.
const err = new Error(g.f('Access Denied'));
err.statusCode = 403;
throw err;
}
return token.userId;
}
UserModel.afterRemote('confirm', function(ctx, inst, next) { UserModel.afterRemote('confirm', function(ctx, inst, next) {
if (ctx.args.redirect !== undefined) { if (ctx.args.redirect !== undefined) {
if (!ctx.res) { if (!ctx.res) {
@ -1276,7 +1337,8 @@ module.exports = function(User) {
// This is a programmer's error, use the default status code 500 // This is a programmer's error, use the default status code 500
return next(new Error( return next(new Error(
'Invalid use of "options.setPassword". Only "password" can be ' + 'Invalid use of "options.setPassword". Only "password" can be ' +
'changed when using this option.')); 'changed when using this option.',
));
} }
return next(); return next();
@ -1288,7 +1350,8 @@ module.exports = function(User) {
const err = new Error( const err = new Error(
'Changing user password via patch/replace API is not allowed. ' + 'Changing user password via patch/replace API is not allowed. ' +
'Use changePassword() or setPassword() instead.'); 'Use changePassword() or setPassword() instead.',
);
err.statusCode = 401; err.statusCode = 401;
err.code = 'PASSWORD_CHANGE_NOT_ALLOWED'; err.code = 'PASSWORD_CHANGE_NOT_ALLOWED';
next(err); next(err);
@ -1298,8 +1361,8 @@ module.exports = function(User) {
if (ctx.isNewInstance) return next(); if (ctx.isNewInstance) return next();
if (!ctx.where && !ctx.instance) return next(); if (!ctx.where && !ctx.instance) return next();
var pkName = ctx.Model.definition.idName() || 'id'; const pkName = ctx.Model.definition.idName() || 'id';
var where = ctx.where; let where = ctx.where;
if (!where) { if (!where) {
where = {}; where = {};
where[pkName] = ctx.instance[pkName]; where[pkName] = ctx.instance[pkName];
@ -1308,15 +1371,22 @@ module.exports = function(User) {
ctx.Model.find({where: where}, ctx.options, function(err, userInstances) { ctx.Model.find({where: where}, ctx.options, function(err, userInstances) {
if (err) return next(err); if (err) return next(err);
ctx.hookState.originalUserData = userInstances.map(function(u) { ctx.hookState.originalUserData = userInstances.map(function(u) {
var user = {}; const user = {};
user[pkName] = u[pkName]; user[pkName] = u[pkName];
user.email = u.email; user.email = u.email;
user.password = u.password; user.password = u.password;
return user; return user;
}); });
var emailChanged; let emailChanged;
if (ctx.instance) { if (ctx.instance) {
emailChanged = ctx.instance.email !== ctx.hookState.originalUserData[0].email; // Check if map does not return an empty array
// Fix server crashes when try to PUT a non existent id
if (ctx.hookState.originalUserData.length > 0) {
emailChanged = ctx.instance.email !== ctx.hookState.originalUserData[0].email;
} else {
emailChanged = true;
}
if (emailChanged && ctx.Model.settings.emailVerificationRequired) { if (emailChanged && ctx.Model.settings.emailVerificationRequired) {
ctx.instance.emailVerified = false; ctx.instance.emailVerified = false;
} }
@ -1337,13 +1407,15 @@ module.exports = function(User) {
if (!ctx.instance && !ctx.data) return next(); if (!ctx.instance && !ctx.data) return next();
if (!ctx.hookState.originalUserData) return next(); if (!ctx.hookState.originalUserData) return next();
var pkName = ctx.Model.definition.idName() || 'id'; const pkName = ctx.Model.definition.idName() || 'id';
var newEmail = (ctx.instance || ctx.data).email; const newEmail = (ctx.instance || ctx.data).email;
var newPassword = (ctx.instance || ctx.data).password; const newPassword = (ctx.instance || ctx.data).password;
if (!newEmail && !newPassword) return next(); if (!newEmail && !newPassword) return next();
var userIdsToExpire = ctx.hookState.originalUserData.filter(function(u) { if (ctx.options.preserveAccessTokens) return next();
const userIdsToExpire = ctx.hookState.originalUserData.filter(function(u) {
return (newEmail && u.email !== newEmail) || return (newEmail && u.email !== newEmail) ||
(newPassword && u.password !== newPassword); (newPassword && u.password !== newPassword);
}).map(function(u) { }).map(function(u) {
@ -1354,7 +1426,7 @@ module.exports = function(User) {
}; };
function emailValidator(err, done) { function emailValidator(err, done) {
var value = this.email; const value = this.email;
if (value == null) if (value == null)
return; return;
if (typeof value !== 'string') if (typeof value !== 'string')
@ -1365,9 +1437,9 @@ function emailValidator(err, done) {
} }
function joinUrlPath(args) { function joinUrlPath(args) {
var result = arguments[0]; let result = arguments[0];
for (var ix = 1; ix < arguments.length; ix++) { for (let ix = 1; ix < arguments.length; ix++) {
var next = arguments[ix]; const next = arguments[ix];
result += result[result.length - 1] === '/' && next[0] === '/' ? result += result[result.length - 1] === '/' && next[0] === '/' ?
next.slice(1) : next; next.slice(1) : next;
} }

View File

@ -1,14 +1,14 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var loopback = require('../../'); const loopback = require('../../');
var client = loopback(); const client = loopback();
var CartItem = require('./models').CartItem; const CartItem = require('./models').CartItem;
var remote = loopback.createDataSource({ const remote = loopback.createDataSource({
connector: loopback.Remote, connector: loopback.Remote,
url: 'http://localhost:3000', url: 'http://localhost:3000',
}); });

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../../'); const loopback = require('../../');
var CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', { const CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', {
tax: {type: Number, default: 0.1}, tax: {type: Number, default: 0.1},
price: Number, price: Number,
item: String, item: String,
@ -17,7 +17,7 @@ var CartItem = exports.CartItem = loopback.PersistedModel.extend('CartItem', {
CartItem.sum = function(cartId, callback) { CartItem.sum = function(cartId, callback) {
this.find({where: {cartId: cartId}}, function(err, items) { this.find({where: {cartId: cartId}}, function(err, items) {
if (err) return callback(err); if (err) return callback(err);
var total = items const total = items
.map(function(item) { .map(function(item) {
return item.total(); return item.total();
}) })
@ -33,8 +33,7 @@ CartItem.remoteMethod('sum',
{ {
accepts: {arg: 'cartId', type: 'number'}, accepts: {arg: 'cartId', type: 'number'},
returns: {arg: 'total', type: 'number'}, returns: {arg: 'total', type: 'number'},
} });
);
CartItem.prototype.total = function() { CartItem.prototype.total = function() {
return this.price * this.qty * (1 + this.tax); return this.price * this.qty * (1 + this.tax);

View File

@ -1,13 +1,13 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../../'); const loopback = require('../../');
var server = module.exports = loopback(); const server = module.exports = loopback();
var CartItem = require('./models').CartItem; const CartItem = require('./models').CartItem;
var memory = loopback.createDataSource({ const memory = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });

View File

@ -1,21 +1,21 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var loopback = require('../../'); const loopback = require('../../');
var app = loopback(); const app = loopback();
app.use(loopback.rest()); app.use(loopback.rest());
var schema = { const schema = {
name: String, name: String,
}; };
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
var Color = app.registry.createModel('color', schema); const Color = app.registry.createModel('color', schema);
app.model(Color, {dataSource: 'db'}); app.model(Color, {dataSource: 'db'});
Color.create({name: 'red'}); Color.create({name: 'red'});

View File

@ -1,23 +1,23 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var models = require('../../lib/models'); const models = require('../../lib/models');
var loopback = require('../../'); const loopback = require('../../');
var app = loopback(); const app = loopback();
app.use(loopback.rest()); app.use(loopback.rest());
var dataSource = loopback.createDataSource('db', {connector: loopback.Memory}); const dataSource = loopback.createDataSource('db', {connector: loopback.Memory});
var Application = models.Application(dataSource); const Application = models.Application(dataSource);
app.model(Application); app.model(Application);
var data = { const data = {
pushSettings: [{ pushSettings: [{
'platform': 'apns', 'platform': 'apns',
'apns': { 'apns': {
@ -43,10 +43,10 @@ Application.create(data, function(err, data) {
}); });
Application.register('rfeng', 'MyApp', {description: g.f('My first mobile application')}, Application.register('rfeng', 'MyApp', {description: g.f('My first mobile application')},
function(err, result) { function(err, result) {
console.log(result.toObject());
result.resetKeys(function(err, result) {
console.log(result.toObject()); console.log(result.toObject());
result.resetKeys(function(err, result) {
console.log(result.toObject());
});
}); });
});

View File

@ -1,22 +1,22 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../../'); const loopback = require('../../');
var app = loopback(); const app = loopback();
var db = app.dataSource('db', {connector: 'memory'}); const db = app.dataSource('db', {connector: 'memory'});
var Color = app.registry.createModel('color', {}, {trackChanges: true}); const Color = app.registry.createModel('color', {}, {trackChanges: true});
app.model(Color, {dataSource: 'db'}); app.model(Color, {dataSource: 'db'});
var Color2 = app.registry.createModel('color2', {}, {trackChanges: true}); const Color2 = app.registry.createModel('color2', {}, {trackChanges: true});
app.model(Color2, {dataSource: 'db'}); app.model(Color2, {dataSource: 'db'});
var target = Color2; const target = Color2;
var source = Color; const source = Color;
var SPEED = process.env.SPEED || 100; const SPEED = process.env.SPEED || 100;
var conflicts; let conflicts;
var steps = [ const steps = [
createSomeInitialSourceData, createSomeInitialSourceData,
@ -137,7 +137,7 @@ function list(model, msg) {
function run(steps) { function run(steps) {
setInterval(function() { setInterval(function() {
var step = steps.shift(); const step = steps.shift();
if (step) { if (step) {
console.log(step.name); console.log(step.name);
step(); step();

View File

@ -1,18 +1,18 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var loopback = require('../../'); const loopback = require('../../');
var app = loopback(); const app = loopback();
app.use(loopback.rest()); app.use(loopback.rest());
var dataSource = app.dataSource('db', {adapter: 'memory'}); const dataSource = app.dataSource('db', {adapter: 'memory'});
var Color = dataSource.define('color', { const Color = dataSource.define('color', {
'name': String, 'name': String,
}); });

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -25,4 +25,5 @@ loopback.Remote = require('loopback-connector-remote');
*/ */
loopback.GeoPoint = require('loopback-datasource-juggler/lib/geo').GeoPoint; loopback.GeoPoint = require('loopback-datasource-juggler/lib/geo').GeoPoint;
loopback.DateString = require('loopback-datasource-juggler/lib/date-string');
loopback.ValidationError = loopback.Model.ValidationError; loopback.ValidationError = loopback.Model.ValidationError;

93
intl/cs/messages.json Normal file
View File

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "Nebyl nalezen žádný záznam změny pro {0} s ID {1}",
"04bd8af876f001ceaf443aad6a9002f9": "Ověření vyžaduje, aby byl definován model {0}.",
"095afbf2f1f0e5be678f5dac5c54e717": "Přístup odepřen",
"0caffe1d763c8cca6a61814abe33b776": "Je vyžadován e-mail.",
"0da38687fed24275c1547e815914a8e3": "Odstraňte související položku podle ID pro {0}.",
"0e21aad369dd09e1965c11949303cefd": "Metadata vzdálené komunikace pro {0}.{1} {{\"isStatic\"}} se neshodují s novým stylem založeném na názvu metody.",
"10e01c895dc0b2fecc385f9f462f1ca6": "Seznam barev je dostupný na adrese {{http://localhost:3000/colors}}",
"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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t PŘEDMĚT:{0}",
"1e85f822b547a75d7d385048030e4ecb": "VytvořenO: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Aktualizujte související položku podle ID pro {0}.",
"275f22ab95671f095640ca99194b7635": "\t OD: {0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} byl odebrán ve verzi 3.0. Další podrobnosti naleznete v části {1}.",
"2d3071e3b18681c80a090dc0efbdb349": "Nelze najít {0} s ID {1}",
"316e5b82c203cf3de31a449ee07d0650": "Očekávaná logická hodnota, obdrženo: {0}",
"320c482401afa1207c04343ab162e803": "Neplatný typ činitele: {0}",
"3438fab56cc7ab92dfd88f0497e523e0": "Vlastnost vztahů konfigurace `{0}` musí být objekt",
"3591f1d3e115b46f9f195df5ca548a6a": "Model nebyl nalezen: model `{0}` rozšiřuje neznámý model `{1}`.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Ověřte svůj e-mail otevřením tohoto odkazu ve webovém prohlížeči:\n\t {0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "Zadané heslo je příliš dlouhé. Maximální délka je {0} (zadáno {1})",
"3aae63bb7e8e046641767571c1591441": "Přihlášení se nezdařilo, protože e-mail nebyl ověřen",
"3aecb24fa8bdd3f79d168761ca8a6729": "Neznámá fáze {{middleware}} {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} používá nastavení modelu {1}, které již není dostupné.",
"3caaa84fc103d6d5612173ae6d43b245": "Neplatný token: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Děkujeme za registraci",
"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.",
"4203ab415ec66a78d3164345439ba76e": "Nelze volat {0}.{1}(). Metoda {2} nebyla nastavena. {{PersistedModel}} nebyl správně připojen ke {{DataSource}}!",
"42a36bac5cf03c4418d664500c81047a": "Volba {{DataSource}} {{\"defaultForType\"}} se již nepodporuje",
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-mail nebyl nalezen",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Odesílání pošty:",
"4b494de07f524703ac0879addbd64b13": "E-mail nebyl ověřen",
"528325f3cbf1b0ab9a08447515daac9a": "Aktualizujte {0} tohoto modelu.",
"543d19bad5e47ee1e9eb8af688e857b4": "Cizí klíč pro {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Nelze vytvořit zdroj dat {0}: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "Musíte připojit model {{Email}} ke konektoru {{Mail}}",
"598ff0255ffd1d1b71e8de55dbe2c034": "Zkontrolujte existenci vztahu {0} k položce podle ID.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Odeberte vztah {0} k položce podle ID.",
"5e81ad3847a290dc650b47618b9cbc7e": "Přihlášení se nezdařilo",
"5fa3afb425819ebde958043e598cb664": "Nelze najít model s {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "Vztah `{0}` neexistuje pro model `{1}`",
"62e8b0a733417978bab22c8dacf5d7e6": "Nelze použít hromadné aktualizace, konektor správně nehlásí počet aktualizovaných záznamů.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Najdete související položku podle ID pro {0}.",
"6bc376432cd9972cf991aad3de371e78": "Chybí data pro změnu: {0}",
"705c2d456a3e204c4af56e671ec3225c": "Nebyl nalezen {{accessToken}}.",
"734a7bebb65e10899935126ba63dd51f": "Vlastnost voleb konfigurace `{0}` musí být objekt",
"779467f467862836e19f494a37d6ab77": "Vlastnost acls konfigurace `{0}` musí být pole objektů",
"7bc7b301ad9c4fc873029d57fb9740fe": "Dotazy {0} z {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Cizí klíč pro {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Moje první mobilní aplikace",
"7e0fca41d098607e1c9aa353c67e0fa1": "Neplatný přístupový token",
"7e287fc885d9fdcf42da3a12f38572c1": "Vyžadována autorizace",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} je vyžadován pro odhlášení",
"80a32e80cbed65eba2103201a7c94710": "Model nebyl nalezen: {0}",
"830cb6c862f8f364e9064cea0026f701": "Načtení vztahu hasOne {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Vlastnost `{0}` nelze překonfigurovat pro `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Neplatné heslo.",
"855ecd4a64885ba272d782435f72a4d4": "Neznámé ID \"{0}\" \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "Přenos nepodporuje přesměrování HTTP.",
"86254879d01a60826a851066987703f2": "Přidejte související položku podle ID pro {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} nebo {{email}} je povinné",
"8ae418c605b6a45f2651be9b1677c180": "Neplatná vzdálená metoda: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "Hromadná aktualizace se nezdařila, konektor upravil neočekávaný počet záznamů: {0}",
"8ecab7f534de38360bd1b1c88e880123": "Podřízené modely `{0}` nebudou dědit nově definované vzdálené metody {1}.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "Konfiguraci `{0}` chybí vlastnost {{`dataSource`}}.\nPoužijte `null` nebo `false` k označení modelů, které nejsou připojeny k žádnému zdroji dat.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Načte vztah belongsTo {0}.",
"a50d10fc6e0959b220e085454c40381e": "Uživatel nebyl nalezen: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Neznámý \"{0}\" {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} je povinný",
"c0057a569ff9d3b509bac61a4b2f605d": "Odstraní všechny {0} tohoto modelu.",
"c2b5d51f007178170ca3952d59640ca4": "Nelze odstranit {0} změn: \n {1}",
"c4ee6d177c974532c3552d2f98eb72ea": "Middleware {0} byl odebrán ve verzi 3.0. Další podrobnosti naleznete v části {1}.",
"c61a5a02ba3801a892308f70f5d55a14": "Metadata vzdálené komunikace {{\"isStatic\"}} jsou zamítnuta. Místo toho zadejte {{\"prototype.name\"}} v názvu metody pro {{isStatic=false}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Je třeba zadat platný e-mail",
"cd0412f2f33a4a2a316acc834f3f21a6": "Je třeba zadat {{id}} nebo {{data}}",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} byl odebrán, místo toho použijte nový modul {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "Vytvoří novou instanci v {0} tohoto modelu.",
"da13d3cdf21330557254670dddd8c5c7": "Vypočte {0} z {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "Ignorování neobjektového nastavení \"methods\" \"{0}\".",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Neznámé \"{0}\" {{id}} \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "Hromadná aktualizace se nezdařila, konektor odstranil neočekávaný počet záznamů: {0}",
"ea63d226b6968e328bdf6876010786b5": "Nelze použít hromadné aktualizace, konektor správně nehlásí počet odstraněných záznamů.",
"ead044e2b4bce74b4357f8a03fb78ec4": "Nelze volat {0}.{1}(). Metoda {2} nebyla nastavena. {{KeyValueModel}} nebyl správně připojen ke {{DataSource}}!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t K:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t PŘENOS:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "výsledek: {0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Konflikt",
"f66ae3cf379b2fce28575a3282defe1a": "Odstraní {0} tohoto modelu.",
"f8e26bcca62a47f579562f1cd2c785ff": "Přepracujte aplikaci tak, aby používala oficiální řešení pro vložení argumentu \"options\" z kontextu požadavku,\nviz {0}"
}

View File

@ -3,11 +3,13 @@
"04bd8af876f001ceaf443aad6a9002f9": "Für die Authentifizierung muss Modell {0} definiert sein.", "04bd8af876f001ceaf443aad6a9002f9": "Für die Authentifizierung muss Modell {0} definiert sein.",
"095afbf2f1f0e5be678f5dac5c54e717": "Zugriff verweigert", "095afbf2f1f0e5be678f5dac5c54e717": "Zugriff verweigert",
"0caffe1d763c8cca6a61814abe33b776": "E-Mail ist erforderlich", "0caffe1d763c8cca6a61814abe33b776": "E-Mail ist erforderlich",
"0da38687fed24275c1547e815914a8e3": "Zugehöriges Element nach ID für {0} löschen.",
"0e21aad369dd09e1965c11949303cefd": "Die Remote-Anbindungs-Metadaten {{\"isStatic\"}} für {0}.{1} entsprechen nicht dem namensbasierten Stil der neuen Methode.", "0e21aad369dd09e1965c11949303cefd": "Die Remote-Anbindungs-Metadaten {{\"isStatic\"}} für {0}.{1} entsprechen nicht dem namensbasierten Stil der neuen Methode.",
"10e01c895dc0b2fecc385f9f462f1ca6": "eine Liste mit Farben ist verfügbar unter {{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "eine Liste mit Farben ist verfügbar unter {{http://localhost:3000/colors}}",
"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", "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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t BETREFF:{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t BETREFF:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Erstellt: {0}", "1e85f822b547a75d7d385048030e4ecb": "Erstellt: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Zugehöriges Element nach ID für {0} aktualisieren.",
"275f22ab95671f095640ca99194b7635": "\t VON:{0}", "275f22ab95671f095640ca99194b7635": "\t VON:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} wurde in Version 3.0 entfernt. Siehe {1} für weitere Details.", "2860bccdf9ef1e350c1a38932ed12173": "{0} wurde in Version 3.0 entfernt. Siehe {1} für weitere Details.",
"2d3071e3b18681c80a090dc0efbdb349": "{0} mit ID {1} konnte nicht gefunden werden", "2d3071e3b18681c80a090dc0efbdb349": "{0} mit ID {1} konnte nicht gefunden werden",
@ -16,8 +18,10 @@
"3438fab56cc7ab92dfd88f0497e523e0": "Die relations-Eigenschaft der Konfiguration '{0}' muss ein Objekt sein", "3438fab56cc7ab92dfd88f0497e523e0": "Die relations-Eigenschaft der Konfiguration '{0}' muss ein Objekt sein",
"3591f1d3e115b46f9f195df5ca548a6a": "Modell nicht gefunden: Modell '{0}' bietet das unbekannte Modell '{1}' an.", "3591f1d3e115b46f9f195df5ca548a6a": "Modell nicht gefunden: Modell '{0}' bietet das unbekannte Modell '{1}' an.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Bestätigen Sie Ihre E-Mail-Adresse, indem Sie diesen Link in einem Web-Browser öffnen:\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "Bestätigen Sie Ihre E-Mail-Adresse, indem Sie diesen Link in einem Web-Browser öffnen:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "Das eingegebene Kennwort war zu lang. Maximale Länge: {0} (eingegeben: {1})",
"3aae63bb7e8e046641767571c1591441": "Anmeldung fehlgeschlagen, da die E-Mail-Adresse nicht bestätigt wurde", "3aae63bb7e8e046641767571c1591441": "Anmeldung fehlgeschlagen, da die E-Mail-Adresse nicht bestätigt wurde",
"3aecb24fa8bdd3f79d168761ca8a6729": "Unbekannte {{middleware}}-Phase {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "Unbekannte {{middleware}}-Phase {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} verwendet Modelleinstellung {1}, die nicht mehr verfügbar ist.",
"3caaa84fc103d6d5612173ae6d43b245": "Ungültiges Token: {0}", "3caaa84fc103d6d5612173ae6d43b245": "Ungültiges Token: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Vielen Dank für die Registrierung", "3d617953470be16d0c2b32f0bcfbb5ee": "Vielen Dank für die Registrierung",
"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.", "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.",
@ -26,51 +30,64 @@
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-Mail nicht gefunden", "44a6c8b1ded4ed653d19ddeaaf89a606": "E-Mail nicht gefunden",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "E-Mail senden:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "E-Mail senden:",
"4b494de07f524703ac0879addbd64b13": "E-Mail-Adresse wurde nicht bestätigt", "4b494de07f524703ac0879addbd64b13": "E-Mail-Adresse wurde nicht bestätigt",
"528325f3cbf1b0ab9a08447515daac9a": "{0} von diesem Modell aktualisieren.",
"543d19bad5e47ee1e9eb8af688e857b4": "Fremdschlüssel für {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Kann Datenquelle {0} nicht erstellen: {1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Kann Datenquelle {0} nicht erstellen: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "Sie müssen das {{Email}}-Modell mit einem {{Mail}}-Konnektor verbinden", "5858e63efaa0e4ad86b61c0459ea32fa": "Sie müssen das {{Email}}-Modell mit einem {{Mail}}-Konnektor verbinden",
"598ff0255ffd1d1b71e8de55dbe2c034": "Vorhandensein von {0}-Beziehung zu einem Element nach ID prüfen.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "{0}-Beziehung zu einem Element nach ID entfernen.",
"5e81ad3847a290dc650b47618b9cbc7e": "Anmeldung fehlgeschlagen", "5e81ad3847a290dc650b47618b9cbc7e": "Anmeldung fehlgeschlagen",
"5fa3afb425819ebde958043e598cb664": "Modell mit {{id}} {0} konnte nicht gefunden werden", "5fa3afb425819ebde958043e598cb664": "Modell mit {{id}} {0} konnte nicht gefunden werden",
"61e5deebaf44d68f4e6a508f30cc31a3": "Beziehung '{0} ist für Modell {1} nicht vorhanden", "61e5deebaf44d68f4e6a508f30cc31a3": "Beziehung '{0} ist für Modell {1} nicht vorhanden",
"62e8b0a733417978bab22c8dacf5d7e6": "Massenaktualisierungen können nicht angewendet werden, der Konnektor meldet die Anzahl aktualisierter Datensätze nicht richtig.", "62e8b0a733417978bab22c8dacf5d7e6": "Massenaktualisierungen können nicht angewendet werden, der Konnektor meldet die Anzahl aktualisierter Datensätze nicht richtig.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Zugehöriges Element nach ID für {0} suchen.",
"6bc376432cd9972cf991aad3de371e78": "Fehlende Daten für Änderung: {0}", "6bc376432cd9972cf991aad3de371e78": "Fehlende Daten für Änderung: {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} konnte nicht gefunden werden",
"734a7bebb65e10899935126ba63dd51f": "Die options-Eigenschaft der Konfiguration '{0}' muss ein Objekt sein", "734a7bebb65e10899935126ba63dd51f": "Die options-Eigenschaft der Konfiguration '{0}' muss ein Objekt sein",
"779467f467862836e19f494a37d6ab77": "Die acls-Eigenschaft der Konfiguration '{0}' muss eine Reihe von Objekten sein", "779467f467862836e19f494a37d6ab77": "Die acls-Eigenschaft der Konfiguration '{0}' muss eine Reihe von Objekten sein",
"7bc7b301ad9c4fc873029d57fb9740fe": "Abfrage von {0} von {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Fremdschlüssel für {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Meine erste mobile Anwendung", "7d5e7ed0efaedf3f55f380caae0df8b8": "Meine erste mobile Anwendung",
"7e0fca41d098607e1c9aa353c67e0fa1": "Ungültiges Zugriffstoken", "7e0fca41d098607e1c9aa353c67e0fa1": "Ungültiges Zugriffstoken",
"7e287fc885d9fdcf42da3a12f38572c1": "Berechtigung erforderlich", "7e287fc885d9fdcf42da3a12f38572c1": "Berechtigung erforderlich",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} muss sich abmelden",
"80a32e80cbed65eba2103201a7c94710": "Modell nicht gefunden: {0}", "80a32e80cbed65eba2103201a7c94710": "Modell nicht gefunden: {0}",
"830cb6c862f8f364e9064cea0026f701": "Ruft hasOne-Beziehung {0} ab.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Eigenschaft '{0}' kann für {1} nicht rekonfiguriert werden", "83cbdc2560ba9f09155ccfc63e08f1a1": "Eigenschaft '{0}' kann für {1} nicht rekonfiguriert werden",
"855eb8db89b4921c42072832d33d2dc2": "Ungültiges Kennwort.",
"855ecd4a64885ba272d782435f72a4d4": "\"{0}\" unbekannt, ID \"{1}\".", "855ecd4a64885ba272d782435f72a4d4": "\"{0}\" unbekannt, ID \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "Die Transportmethode unterstützt keine HTTP-Umleitungen.", "860d1a0b8bd340411fb32baa72867989": "Die Transportmethode unterstützt keine HTTP-Umleitungen.",
"86254879d01a60826a851066987703f2": "Zugehöriges Element nach ID für {0} hinzufügen.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} oder {{email}} ist erforderlich", "895b1f941d026870b3cc8e6af087c197": "{{username}} oder {{email}} ist erforderlich",
"8a17c5ef611e2e7535792316e66b8fca": "Kennwort zu lang: {0}", "8ae418c605b6a45f2651be9b1677c180": "Ungültige Remote-Methode: '{0}'",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Anforderung an Host {0}",
"8ae418c605b6a45f2651be9b1677c180": "Ungültige Remote-Methode: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "Massenaktualisierung fehlgeschlagen, der Konnektor hat eine unerwartete Anzahl an Datensätzen geändert: {0}", "8bab6720ecc58ec6412358c858a53484": "Massenaktualisierung fehlgeschlagen, der Konnektor hat eine unerwartete Anzahl an Datensätzen geändert: {0}",
"8ecab7f534de38360bd1b1c88e880123": "Untergeordnete Modelle von `{0}` übernehmen nicht die neu definierten Remote-Methoden {1}.", "8ecab7f534de38360bd1b1c88e880123": "Untergeordnete Modelle von `{0}` übernehmen nicht die neu definierten Remote-Methoden {1}.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "Der Konfiguration von {0} fehlt die {{`dataSource`}}-Eigenschaft.\nVerwenden Sie 'null' oder 'false', um Modelle zu kennzeichnen, die mit keiner Datenquelle verbunden sind.", "97795efe0c3eb7f35ce8cf8cfe70682b": "Der Konfiguration von {0} fehlt die {{`dataSource`}}-Eigenschaft.\nVerwenden Sie 'null' oder 'false', um Modelle zu kennzeichnen, die mit keiner Datenquelle verbunden sind.",
"a40684f5a9f546115258b76938d1de37": "Eine Liste mit Farben ist verfügbar unter {{http://localhost:3000/colors}}", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Ruft belongsTo-Beziehung {0} ab.",
"a50d10fc6e0959b220e085454c40381e": "Benutzer nicht gefunden: {0}", "a50d10fc6e0959b220e085454c40381e": "Benutzer nicht gefunden: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" unbekannt, {{key}} \"{1}\".", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" unbekannt, {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} ist erforderlich", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} ist erforderlich",
"c0057a569ff9d3b509bac61a4b2f605d": "Löscht alle {0} von diesem Modell.",
"c2b5d51f007178170ca3952d59640ca4": "{0} Änderungen können nicht behoben werden:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "{0} Änderungen können nicht behoben werden:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "{{accessToken}} konnte nicht gefunden werden",
"c4ee6d177c974532c3552d2f98eb72ea": "Middleware {0} wurde in Version 3.0 entfernt. Siehe {1} für weitere Details.", "c4ee6d177c974532c3552d2f98eb72ea": "Middleware {0} wurde in Version 3.0 entfernt. Siehe {1} für weitere Details.",
"c61a5a02ba3801a892308f70f5d55a14": "Metadaten {{\"isStatic\"}} für Remote-Anbindung sind veraltet. Bitte geben Sie {{\"prototype.name\"}} anstelle von {{isStatic=false}} beim Methodennamen an.", "c61a5a02ba3801a892308f70f5d55a14": "Metadaten {{\"isStatic\"}} für Remote-Anbindung sind veraltet. Bitte geben Sie {{\"prototype.name\"}} anstelle von {{isStatic=false}} beim Methodennamen an.",
"c68a93f0a9524fed4ff64372fc90c55f": "Eine gültige E-Mail-Adresse muss angegeben werden", "c68a93f0a9524fed4ff64372fc90c55f": "Eine gültige E-Mail-Adresse muss angegeben werden",
"cd0412f2f33a4a2a316acc834f3f21a6": "muss {{id}} oder {{data}} angeben", "cd0412f2f33a4a2a316acc834f3f21a6": "muss {{id}} oder {{data}} angeben",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} wurde entfernt, verwenden Sie stattdessen das neue Modul {{loopback-boot}}", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} wurde entfernt, verwenden Sie stattdessen das neue Modul {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "Erstellt eine neue Instanz in {0} von diesem Modell.",
"da13d3cdf21330557254670dddd8c5c7": "Zählt {0} von {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "Nicht-Objekt-Einstellung \"{0}\" von \"methods\" wird ignoriert.", "dc568bee32deb0f6eaf63e73b20e8ceb": "Nicht-Objekt-Einstellung \"{0}\" von \"methods\" wird ignoriert.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" unbekannt, {{id}} \"{1}\".", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" unbekannt, {{id}} \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "Massenaktualisierung fehlgeschlagen, der Konnektor hat eine unerwartete Anzahl an Datensätzen gelöscht : {0}", "e92aa25b6b864e3454b65a7c422bd114": "Massenaktualisierung fehlgeschlagen, der Konnektor hat eine unerwartete Anzahl an Datensätzen gelöscht: {0}",
"ea63d226b6968e328bdf6876010786b5": "Massenaktualisierungen können nicht angewendet werden, der Konnektor meldet die Anzahl gelöschter Datensätze nicht richtig.", "ea63d226b6968e328bdf6876010786b5": "Massenaktualisierungen können nicht angewendet werden, der Konnektor meldet die Anzahl gelöschter Datensätze nicht richtig.",
"ead044e2b4bce74b4357f8a03fb78ec4": "{0}.{1}() kann nicht aufgerufen werden. Die Methode {2} wurde nicht konfiguriert. {{KeyValueModel}} wurde nicht ordnungsgemäß an eine {{DataSource}} angehängt!", "ead044e2b4bce74b4357f8a03fb78ec4": "{0}.{1}() kann nicht aufgerufen werden. Die Methode {2} wurde nicht konfiguriert. {{KeyValueModel}} wurde nicht ordnungsgemäß an eine {{DataSource}} angehängt!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t AN:{0}", "ecb06666ef95e5db27a5ac1d6a17923b": "\t AN:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTMETHODE:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTMETHODE:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "Ergebnis:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "Ergebnis:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Konflikt", "f1d4ac54357cc0932f385d56814ba7e4": "Konflikt",
"f58cdc481540cd1f69a4aa4da2e37981": "Ungültiges Kennwort: {0}" "f66ae3cf379b2fce28575a3282defe1a": "Löscht {0} von diesem Modell.",
"f8e26bcca62a47f579562f1cd2c785ff": "Überarbeiten Sie Ihre App, damit sie die offizielle Lösung für eine Injection des Arguments \"options\" aus dem Anforderungskontext verwendet,\nsiehe {0}"
} }

View File

@ -3,11 +3,13 @@
"04bd8af876f001ceaf443aad6a9002f9": "Authentication requires model {0} to be defined.", "04bd8af876f001ceaf443aad6a9002f9": "Authentication requires model {0} to be defined.",
"095afbf2f1f0e5be678f5dac5c54e717": "Access Denied", "095afbf2f1f0e5be678f5dac5c54e717": "Access Denied",
"0caffe1d763c8cca6a61814abe33b776": "Email is required", "0caffe1d763c8cca6a61814abe33b776": "Email is required",
"0da38687fed24275c1547e815914a8e3": "Delete a related item by id for {0}.",
"0e21aad369dd09e1965c11949303cefd": "Remoting metadata for {0}.{1} {{\"isStatic\"}} does not match new method name-based style.", "0e21aad369dd09e1965c11949303cefd": "Remoting metadata for {0}.{1} {{\"isStatic\"}} does not match new method name-based style.",
"10e01c895dc0b2fecc385f9f462f1ca6": "a list of colors is available at {{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "a list of colors is available at {{http://localhost:3000/colors}}",
"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", "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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t SUBJECT:{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t SUBJECT:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Created: {0}", "1e85f822b547a75d7d385048030e4ecb": "Created: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Update a related item by id for {0}.",
"275f22ab95671f095640ca99194b7635": "\t FROM:{0}", "275f22ab95671f095640ca99194b7635": "\t FROM:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} was removed in version 3.0. See {1} for more details.", "2860bccdf9ef1e350c1a38932ed12173": "{0} was removed in version 3.0. See {1} for more details.",
"2d3071e3b18681c80a090dc0efbdb349": "could not find {0} with id {1}", "2d3071e3b18681c80a090dc0efbdb349": "could not find {0} with id {1}",
@ -16,8 +18,10 @@
"3438fab56cc7ab92dfd88f0497e523e0": "The relations property of `{0}` configuration must be an object", "3438fab56cc7ab92dfd88f0497e523e0": "The relations property of `{0}` configuration must be an object",
"3591f1d3e115b46f9f195df5ca548a6a": "Model not found: model `{0}` is extending an unknown model `{1}`.", "3591f1d3e115b46f9f195df5ca548a6a": "Model not found: model `{0}` is extending an unknown model `{1}`.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Please verify your email by opening this link in a web browser:\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "Please verify your email by opening this link in a web browser:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "The password entered was too long. Max length is {0} (entered {1})",
"3aae63bb7e8e046641767571c1591441": "login failed as the email has not been verified", "3aae63bb7e8e046641767571c1591441": "login failed as the email has not been verified",
"3aecb24fa8bdd3f79d168761ca8a6729": "Unknown {{middleware}} phase {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "Unknown {{middleware}} phase {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} is using model setting {1} which is no longer available.",
"3caaa84fc103d6d5612173ae6d43b245": "Invalid token: {0}", "3caaa84fc103d6d5612173ae6d43b245": "Invalid token: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Thanks for Registering", "3d617953470be16d0c2b32f0bcfbb5ee": "Thanks for Registering",
"3d63008ccfb2af1db2142e8cc2716ace": "Warning: No email transport specified for sending email. Setup a transport to send mail messages.", "3d63008ccfb2af1db2142e8cc2716ace": "Warning: No email transport specified for sending email. Setup a transport to send mail messages.",
@ -26,42 +30,54 @@
"44a6c8b1ded4ed653d19ddeaaf89a606": "Email not found", "44a6c8b1ded4ed653d19ddeaaf89a606": "Email not found",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Sending Mail:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "Sending Mail:",
"4b494de07f524703ac0879addbd64b13": "Email has not been verified", "4b494de07f524703ac0879addbd64b13": "Email has not been verified",
"528325f3cbf1b0ab9a08447515daac9a": "Update {0} of this model.",
"543d19bad5e47ee1e9eb8af688e857b4": "Foreign key for {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Cannot create data source {0}: {1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Cannot create data source {0}: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "You must connect the {{Email}} Model to a {{Mail}} connector", "5858e63efaa0e4ad86b61c0459ea32fa": "You must connect the {{Email}} Model to a {{Mail}} connector",
"598ff0255ffd1d1b71e8de55dbe2c034": "Check the existence of {0} relation to an item by id.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Remove the {0} relation to an item by id.",
"5e81ad3847a290dc650b47618b9cbc7e": "login failed", "5e81ad3847a290dc650b47618b9cbc7e": "login failed",
"5fa3afb425819ebde958043e598cb664": "could not find a model with {{id}} {0}", "5fa3afb425819ebde958043e598cb664": "could not find a model with {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "Relation `{0}` does not exist for model `{1}`", "61e5deebaf44d68f4e6a508f30cc31a3": "Relation `{0}` does not exist for model `{1}`",
"62e8b0a733417978bab22c8dacf5d7e6": "Cannot apply bulk updates, the connector does not correctly report the number of updated records.", "62e8b0a733417978bab22c8dacf5d7e6": "Cannot apply bulk updates, the connector does not correctly report the number of updated records.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Find a related item by id for {0}.",
"6bc376432cd9972cf991aad3de371e78": "Missing data for change: {0}", "6bc376432cd9972cf991aad3de371e78": "Missing data for change: {0}",
"705c2d456a3e204c4af56e671ec3225c": "Could not find {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "The options property of `{0}` configuration must be an object", "734a7bebb65e10899935126ba63dd51f": "The options property of `{0}` configuration must be an object",
"779467f467862836e19f494a37d6ab77": "The acls property of `{0}` configuration must be an array of objects", "779467f467862836e19f494a37d6ab77": "The acls property of `{0}` configuration must be an array of objects",
"7bc7b301ad9c4fc873029d57fb9740fe": "Queries {0} of {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Foreign key for {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "My first mobile application", "7d5e7ed0efaedf3f55f380caae0df8b8": "My first mobile application",
"7e0fca41d098607e1c9aa353c67e0fa1": "Invalid Access Token", "7e0fca41d098607e1c9aa353c67e0fa1": "Invalid Access Token",
"7e287fc885d9fdcf42da3a12f38572c1": "Authorization Required", "7e287fc885d9fdcf42da3a12f38572c1": "Authorization Required",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} is required to logout",
"80a32e80cbed65eba2103201a7c94710": "Model not found: {0}", "80a32e80cbed65eba2103201a7c94710": "Model not found: {0}",
"830cb6c862f8f364e9064cea0026f701": "Fetches hasOne relation {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Property `{0}` cannot be reconfigured for `{1}`", "83cbdc2560ba9f09155ccfc63e08f1a1": "Property `{0}` cannot be reconfigured for `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Invalid password.",
"855ecd4a64885ba272d782435f72a4d4": "Unknown \"{0}\" id \"{1}\".", "855ecd4a64885ba272d782435f72a4d4": "Unknown \"{0}\" id \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "The transport does not support HTTP redirects.", "860d1a0b8bd340411fb32baa72867989": "The transport does not support HTTP redirects.",
"86254879d01a60826a851066987703f2": "Add a related item by id for {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} or {{email}} is required", "895b1f941d026870b3cc8e6af087c197": "{{username}} or {{email}} is required",
"8a17c5ef611e2e7535792316e66b8fca": "Password too long: {0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Request to host {0}",
"8ae418c605b6a45f2651be9b1677c180": "Invalid remote method: `{0}`", "8ae418c605b6a45f2651be9b1677c180": "Invalid remote method: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "Bulk update failed, the connector has modified unexpected number of records: {0}", "8bab6720ecc58ec6412358c858a53484": "Bulk update failed, the connector has modified unexpected number of records: {0}",
"8ecab7f534de38360bd1b1c88e880123": "Child models of `{0}` will not inherit newly defined remote methods {1}.", "8ecab7f534de38360bd1b1c88e880123": "Child models of `{0}` will not inherit newly defined remote methods {1}.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "The configuration of `{0}` is missing {{`dataSource`}} property.\nUse `null` or `false` to mark models not attached to any data source.", "97795efe0c3eb7f35ce8cf8cfe70682b": "The configuration of `{0}` is missing {{`dataSource`}} property.\nUse `null` or `false` to mark models not attached to any data source.",
"a40684f5a9f546115258b76938d1de37": "A list of colors is available at {{http://localhost:3000/colors}}", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Fetches belongsTo relation {0}.",
"a50d10fc6e0959b220e085454c40381e": "User not found: {0}", "a50d10fc6e0959b220e085454c40381e": "User not found: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Unknown \"{0}\" {{key}} \"{1}\".", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "Unknown \"{0}\" {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} is required", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} is required",
"c0057a569ff9d3b509bac61a4b2f605d": "Deletes all {0} of this model.",
"c2b5d51f007178170ca3952d59640ca4": "Cannot rectify {0} changes:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "Cannot rectify {0} changes:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "could not find {{accessToken}}",
"c4ee6d177c974532c3552d2f98eb72ea": "{0} middleware was removed in version 3.0. See {1} for more details.", "c4ee6d177c974532c3552d2f98eb72ea": "{0} middleware was removed in version 3.0. See {1} for more details.",
"c61a5a02ba3801a892308f70f5d55a14": "Remoting metadata {{\"isStatic\"}} is deprecated. Please specify {{\"prototype.name\"}} in method name instead for {{isStatic=false}}.", "c61a5a02ba3801a892308f70f5d55a14": "Remoting metadata {{\"isStatic\"}} is deprecated. Please specify {{\"prototype.name\"}} in method name instead for {{isStatic=false}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Must provide a valid email", "c68a93f0a9524fed4ff64372fc90c55f": "Must provide a valid email",
"cd0412f2f33a4a2a316acc834f3f21a6": "must specify an {{id}} or {{data}}", "cd0412f2f33a4a2a316acc834f3f21a6": "must specify an {{id}} or {{data}}",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead",
"d6f43b266533b04d442bdb3955622592": "Creates a new instance in {0} of this model.",
"da13d3cdf21330557254670dddd8c5c7": "Counts {0} of {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "Ignoring non-object \"methods\" setting of \"{0}\".", "dc568bee32deb0f6eaf63e73b20e8ceb": "Ignoring non-object \"methods\" setting of \"{0}\".",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Unknown \"{0}\" {{id}} \"{1}\".", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "Unknown \"{0}\" {{id}} \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "Bulk update failed, the connector has deleted unexpected number of records: {0}", "e92aa25b6b864e3454b65a7c422bd114": "Bulk update failed, the connector has deleted unexpected number of records: {0}",
@ -71,5 +87,6 @@
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "result:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "result:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflict", "f1d4ac54357cc0932f385d56814ba7e4": "Conflict",
"f58cdc481540cd1f69a4aa4da2e37981": "Invalid password: {0}" "f66ae3cf379b2fce28575a3282defe1a": "Deletes {0} of this model.",
"f8e26bcca62a47f579562f1cd2c785ff": "Please rework your app to use the offical solution for injecting \"options\" argument from request context,\nsee {0}"
} }

View File

@ -2,12 +2,14 @@
"03f79fa268fe199de2ce4345515431c1": "No se ha encontrado ningún registro de cambio para {0} con el id {1}", "03f79fa268fe199de2ce4345515431c1": "No se ha encontrado ningún registro de cambio para {0} con el id {1}",
"04bd8af876f001ceaf443aad6a9002f9": "La autenticación requiere la definición del modelo {0}.", "04bd8af876f001ceaf443aad6a9002f9": "La autenticación requiere la definición del modelo {0}.",
"095afbf2f1f0e5be678f5dac5c54e717": "Acceso denegado", "095afbf2f1f0e5be678f5dac5c54e717": "Acceso denegado",
"0caffe1d763c8cca6a61814abe33b776": "Es necesario el correo electrónico", "0caffe1d763c8cca6a61814abe33b776": "El correo electrónico es obligatorio",
"0e21aad369dd09e1965c11949303cefd": "Metadatos de interacción remota para {0}.{1} {{\"isStatic\"}} no coincide con el estilo basado en nombre del nuevo método.", "0da38687fed24275c1547e815914a8e3": "Suprimir un elemento relacionado por id para {0}.",
"0e21aad369dd09e1965c11949303cefd": "Los metadatos de interacción remota para {0}.{1} {{\"isStatic\"}} no coinciden con el estilo basado en nombre del nuevo método.",
"10e01c895dc0b2fecc385f9f462f1ca6": "una lista de colores está disponible en {{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "una lista de colores está disponible en {{http://localhost:3000/colors}}",
"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", "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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t ASUNTO:{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t ASUNTO:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Creado: {0}", "1e85f822b547a75d7d385048030e4ecb": "Creado: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Actualizar un elemento relacionado por id para {0}.",
"275f22ab95671f095640ca99194b7635": "\t DESDE:{0}", "275f22ab95671f095640ca99194b7635": "\t DESDE:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} se ha eliminado en la versión 3.0. Consulte {1} para obtener detalles.", "2860bccdf9ef1e350c1a38932ed12173": "{0} se ha eliminado en la versión 3.0. Consulte {1} para obtener detalles.",
"2d3071e3b18681c80a090dc0efbdb349": "no se ha encontrado {0} con el ID {1}", "2d3071e3b18681c80a090dc0efbdb349": "no se ha encontrado {0} con el ID {1}",
@ -16,8 +18,10 @@
"3438fab56cc7ab92dfd88f0497e523e0": "La configuración de la propiedad relations de `{0}` debe ser un objeto", "3438fab56cc7ab92dfd88f0497e523e0": "La configuración de la propiedad relations de `{0}` debe ser un objeto",
"3591f1d3e115b46f9f195df5ca548a6a": "Modelo no encontrado: el modelo `{0}` está ampliando un modelo desconocido `{1}`.", "3591f1d3e115b46f9f195df5ca548a6a": "Modelo no encontrado: el modelo `{0}` está ampliando un modelo desconocido `{1}`.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Verifique su correo electrónico abriendo este enlace en un navegador:\n\t {0}", "35e5252c62d80f8c54a5290d30f4c7d0": "Verifique su correo electrónico abriendo este enlace en un navegador:\n\t {0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "La contraseña especificada es demasiado larga. La longitud máxima es {0} (se ha especificado {1})",
"3aae63bb7e8e046641767571c1591441": "el inicio de sesión ha fallado porque el correo electrónico no ha sido verificado", "3aae63bb7e8e046641767571c1591441": "el inicio de sesión ha fallado porque el correo electrónico no ha sido verificado",
"3aecb24fa8bdd3f79d168761ca8a6729": "Fase de {{middleware}} desconocida {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "Fase de {{middleware}} desconocida {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} utiliza el valor de modelo {1}, que ya no está disponible.",
"3caaa84fc103d6d5612173ae6d43b245": "La señal no es válida: {0}", "3caaa84fc103d6d5612173ae6d43b245": "La señal no es válida: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Gracias por registrarse", "3d617953470be16d0c2b32f0bcfbb5ee": "Gracias por registrarse",
"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.", "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.",
@ -25,43 +29,55 @@
"42a36bac5cf03c4418d664500c81047a": "La opción de {{DataSource}} {{\"defaultForType\"}} ya no está soportada", "42a36bac5cf03c4418d664500c81047a": "La opción de {{DataSource}} {{\"defaultForType\"}} ya no está soportada",
"44a6c8b1ded4ed653d19ddeaaf89a606": "Correo electrónico no encontrado", "44a6c8b1ded4ed653d19ddeaaf89a606": "Correo electrónico no encontrado",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Enviando correo:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "Enviando correo:",
"4b494de07f524703ac0879addbd64b13": "El correo electrónico no se ha verificado", "4b494de07f524703ac0879addbd64b13": "No se ha verificado el correo electrónico",
"528325f3cbf1b0ab9a08447515daac9a": "Actualizar {0} de este modelo.",
"543d19bad5e47ee1e9eb8af688e857b4": "Clave foránea para {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "No se puede crear el origen de datos {0}: {1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "No se puede crear el origen de datos {0}: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "Debe conectar el modelo de {{Email}} a un conector de {{Mail}}", "5858e63efaa0e4ad86b61c0459ea32fa": "Debe conectar el modelo de {{Email}} a un conector de {{Mail}}",
"598ff0255ffd1d1b71e8de55dbe2c034": "Comprobar la existencia de la relación {0} con un elemento por id.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Eliminar la relación {0} con un elemento por id.",
"5e81ad3847a290dc650b47618b9cbc7e": "el inicio de sesión ha fallado", "5e81ad3847a290dc650b47618b9cbc7e": "el inicio de sesión ha fallado",
"5fa3afb425819ebde958043e598cb664": "no se ha encontrado un modelo con {{id}} {0}", "5fa3afb425819ebde958043e598cb664": "no se ha encontrado un modelo con {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "La relación `{0}` no existe para el modelo `{1}`", "61e5deebaf44d68f4e6a508f30cc31a3": "La relación `{0}` no existe para el modelo `{1}`",
"62e8b0a733417978bab22c8dacf5d7e6": "No pueden aplicarse actualizaciones masivas, el conector no notifica correctamente el número de registros actualizados.", "62e8b0a733417978bab22c8dacf5d7e6": "No pueden aplicarse actualizaciones masivas, el conector no notifica correctamente el número de registros actualizados.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXTO:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t TEXTO:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Buscar un elemento relacionado por id para {0}.",
"6bc376432cd9972cf991aad3de371e78": "Faltan datos para el cambio: {0}", "6bc376432cd9972cf991aad3de371e78": "Faltan datos para el cambio: {0}",
"705c2d456a3e204c4af56e671ec3225c": "No se ha encontrado {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "La configuración de la propiedad de options de `{0}` debe ser un objeto", "734a7bebb65e10899935126ba63dd51f": "La configuración de la propiedad de options de `{0}` debe ser un objeto",
"779467f467862836e19f494a37d6ab77": "La configuración de la propiedad acls de `{0}` debe ser una matriz de objetos", "779467f467862836e19f494a37d6ab77": "La configuración de la propiedad acls de `{0}` debe ser una matriz de objetos",
"7bc7b301ad9c4fc873029d57fb9740fe": "{0} consultas de {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Clave foránea para {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Mi primera aplicación móvil", "7d5e7ed0efaedf3f55f380caae0df8b8": "Mi primera aplicación móvil",
"7e0fca41d098607e1c9aa353c67e0fa1": "Señal de acceso no válida", "7e0fca41d098607e1c9aa353c67e0fa1": "Señal de acceso no válida",
"7e287fc885d9fdcf42da3a12f38572c1": "Autorización necesaria", "7e287fc885d9fdcf42da3a12f38572c1": "Autorización necesaria",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "Es necesario {{accessToken}} para cerrar la sesión",
"80a32e80cbed65eba2103201a7c94710": "No se ha encontrado el modelo: {0}", "80a32e80cbed65eba2103201a7c94710": "No se ha encontrado el modelo: {0}",
"830cb6c862f8f364e9064cea0026f701": "Capta la relación hasOne {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "La propiedad `{0}` no puede reconfigurarse para `{1}`", "83cbdc2560ba9f09155ccfc63e08f1a1": "La propiedad `{0}` no puede reconfigurarse para `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Contraseña no válida.",
"855ecd4a64885ba272d782435f72a4d4": "Id de \"{0}\" desconocido \"{1}\".", "855ecd4a64885ba272d782435f72a4d4": "Id de \"{0}\" desconocido \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "El transporte no admite redirecciones HTTP.", "860d1a0b8bd340411fb32baa72867989": "El transporte no admite redirecciones HTTP.",
"86254879d01a60826a851066987703f2": "Añadir un elemento relacionado por id para {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} o {{email}} es obligatorio", "895b1f941d026870b3cc8e6af087c197": "{{username}} o {{email}} es obligatorio",
"8a17c5ef611e2e7535792316e66b8fca": "Contraseña demasiado larga: {0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Solicitud al host {0}",
"8ae418c605b6a45f2651be9b1677c180": "Método remoto no válido: `{0}`", "8ae418c605b6a45f2651be9b1677c180": "Método remoto no válido: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "La actualización masiva ha fallado, el conector ha modificado un número de registros inesperado: {0}", "8bab6720ecc58ec6412358c858a53484": "La actualización masiva ha fallado, el conector ha modificado un número de registros inesperado: {0}",
"8ecab7f534de38360bd1b1c88e880123": "Los modelos hijo de `{0}` no heredarán los métodos remotos definidos recientemente {1}.", "8ecab7f534de38360bd1b1c88e880123": "Los modelos hijo de `{0}` no heredarán los métodos remotos definidos recientemente {1}.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"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.", "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.",
"a40684f5a9f546115258b76938d1de37": "Una lista de colores está disponible en {{http://localhost:3000/colors}}", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Capta la relación belongsTo {0}.",
"a50d10fc6e0959b220e085454c40381e": "No se ha encontrado el usuario: {0}", "a50d10fc6e0959b220e085454c40381e": "No se ha encontrado el usuario: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "{{key}} de \"{0}\" desconocido \"{1}\".", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "{{key}} de \"{0}\" desconocido \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} es obligatorio", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} es obligatorio",
"c0057a569ff9d3b509bac61a4b2f605d": "Suprime todos los {0} de este modelo.",
"c2b5d51f007178170ca3952d59640ca4": "No se pueden rectificar los cambios de {0}:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "No se pueden rectificar los cambios de {0}:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "no se ha encontrado {{accessToken}}",
"c4ee6d177c974532c3552d2f98eb72ea": "El middleware {0} se ha eliminado en la versión 3.0. Consulte {1} para obtener detalles.", "c4ee6d177c974532c3552d2f98eb72ea": "El middleware {0} se ha eliminado en la versión 3.0. Consulte {1} para obtener detalles.",
"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}}.", "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}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Debe proporcionar un correo electrónico válido", "c68a93f0a9524fed4ff64372fc90c55f": "Debe proporcionar un correo electrónico válido",
"cd0412f2f33a4a2a316acc834f3f21a6": "debe especificar un {{id}} o {{data}}", "cd0412f2f33a4a2a316acc834f3f21a6": "debe especificar un {{id}} o {{data}}",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} se ha eliminado, utilice el nuevo módulo {{loopback-boot}} en su lugar", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} se ha eliminado, utilice el nuevo módulo {{loopback-boot}} en su lugar",
"d6f43b266533b04d442bdb3955622592": "Crea una nueva instancia en {0} de este modelo.",
"da13d3cdf21330557254670dddd8c5c7": "Recuentos {0} de {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "Se ignora el valor \"methods\" no de objeto de \"{0}\".", "dc568bee32deb0f6eaf63e73b20e8ceb": "Se ignora el valor \"methods\" no de objeto de \"{0}\".",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "{{id}} de \"{0}\" desconocido \"{1}\".", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "{{id}} de \"{0}\" desconocido \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "La actualización masiva ha fallado, el conector ha suprimido un número de registros inesperado: {0}", "e92aa25b6b864e3454b65a7c422bd114": "La actualización masiva ha fallado, el conector ha suprimido un número de registros inesperado: {0}",
@ -71,6 +87,7 @@
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTE:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTE:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "resultado:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "resultado:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflicto", "f1d4ac54357cc0932f385d56814ba7e4": "Conflicto",
"f58cdc481540cd1f69a4aa4da2e37981": "Contraseña no válida: {0}" "f66ae3cf379b2fce28575a3282defe1a": "Suprime {0} de este modelo.",
"f8e26bcca62a47f579562f1cd2c785ff": "Reconfigure su aplicación para que utilice la solución oficial para inyectar el argumento \"options\" del contexto de solicitud, \nconsulte {0}"
} }

View File

@ -3,11 +3,13 @@
"04bd8af876f001ceaf443aad6a9002f9": "L'authentification exige que le modèle {0} soit défini.", "04bd8af876f001ceaf443aad6a9002f9": "L'authentification exige que le modèle {0} soit défini.",
"095afbf2f1f0e5be678f5dac5c54e717": "Accès refusé", "095afbf2f1f0e5be678f5dac5c54e717": "Accès refusé",
"0caffe1d763c8cca6a61814abe33b776": "L'adresse électronique est obligatoire", "0caffe1d763c8cca6a61814abe33b776": "L'adresse électronique est obligatoire",
"0e21aad369dd09e1965c11949303cefd": "Métadonnées remoting pour {0}.{1} {{\"isStatic\"}} ne correspond pas le style basé sur le nom de la nouvelle méthode.", "0da38687fed24275c1547e815914a8e3": "Supprimez un élément lié par id pour {0}.",
"0e21aad369dd09e1965c11949303cefd": "Les métadonnées remoting pour {0}.{1} {{\"isStatic\"}} ne correspondent pas au style basé sur le nom de la nouvelle méthode.",
"10e01c895dc0b2fecc385f9f462f1ca6": "une liste de couleurs est disponible sur {{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "une liste de couleurs est disponible sur {{http://localhost:3000/colors}}",
"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", "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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t SUJET :{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t OBJET :{0}",
"1e85f822b547a75d7d385048030e4ecb": "Création de : {0}", "1e85f822b547a75d7d385048030e4ecb": "Création de : {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Mettez à jour un élément lié par id pour {0}.",
"275f22ab95671f095640ca99194b7635": "\t DE :{0}", "275f22ab95671f095640ca99194b7635": "\t DE :{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} a été supprimé dans la version 3.0. Pour plus de détails, voir {1}.", "2860bccdf9ef1e350c1a38932ed12173": "{0} a été supprimé dans la version 3.0. Pour plus de détails, voir {1}.",
"2d3071e3b18681c80a090dc0efbdb349": "impossible de trouver {0} avec l'id {1}", "2d3071e3b18681c80a090dc0efbdb349": "impossible de trouver {0} avec l'id {1}",
@ -16,8 +18,10 @@
"3438fab56cc7ab92dfd88f0497e523e0": "La propriété relations de la configuration `{0}` doit être un objet", "3438fab56cc7ab92dfd88f0497e523e0": "La propriété relations de la configuration `{0}` doit être un objet",
"3591f1d3e115b46f9f195df5ca548a6a": "Modèle introuvable : le modèle `{0}` étend un modèle inconnu `{1}`.", "3591f1d3e115b46f9f195df5ca548a6a": "Modèle introuvable : le modèle `{0}` étend un modèle inconnu `{1}`.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Vérifiez votre courrier électronique en ouvrant ce lien dans un navigateur Web :\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "Vérifiez votre courrier électronique en ouvrant ce lien dans un navigateur Web :\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "Le mot de passe saisi était trop long. La longueur maximale est {0} ({1} caractères saisis)",
"3aae63bb7e8e046641767571c1591441": "la connexion a échoué car l'adresse électronique n'a pas été vérifiée", "3aae63bb7e8e046641767571c1591441": "la connexion a échoué car l'adresse électronique n'a pas été vérifiée",
"3aecb24fa8bdd3f79d168761ca8a6729": "Phase {{middleware}} inconnue {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "Phase {{middleware}} inconnue {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} utilise le paramètre de modèle {1} qui n'est plus disponible.",
"3caaa84fc103d6d5612173ae6d43b245": "Jeton non valide : {0}", "3caaa84fc103d6d5612173ae6d43b245": "Jeton non valide : {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Merci pour votre inscription", "3d617953470be16d0c2b32f0bcfbb5ee": "Merci pour votre inscription",
"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.", "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.",
@ -26,42 +30,54 @@
"44a6c8b1ded4ed653d19ddeaaf89a606": "Adresse électronique introuvable", "44a6c8b1ded4ed653d19ddeaaf89a606": "Adresse électronique introuvable",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Envoi d'un message électronique :", "4a4f04a4e480fc5d4ee73b84d9a4b904": "Envoi d'un message électronique :",
"4b494de07f524703ac0879addbd64b13": "Le courrier électronique n'a pas été vérifié", "4b494de07f524703ac0879addbd64b13": "Le courrier électronique n'a pas été vérifié",
"528325f3cbf1b0ab9a08447515daac9a": "Mettez à jour {0} de ce modèle.",
"543d19bad5e47ee1e9eb8af688e857b4": "Clé externe pour {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Impossible de créer la source de données {0} : {1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Impossible de créer la source de données {0} : {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "Vous devez connecter le modèle {{Email}} à un connecteur {{Mail}}", "5858e63efaa0e4ad86b61c0459ea32fa": "Vous devez connecter le modèle {{Email}} à un connecteur {{Mail}}",
"598ff0255ffd1d1b71e8de55dbe2c034": "Vérifiez l'existence de la relation {0} à un élément par id.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Supprimez la relation {0} à un élément par id.",
"5e81ad3847a290dc650b47618b9cbc7e": "échec de la connexion", "5e81ad3847a290dc650b47618b9cbc7e": "échec de la connexion",
"5fa3afb425819ebde958043e598cb664": "impossible de trouver un modèle avec {{id}} {0}", "5fa3afb425819ebde958043e598cb664": "impossible de trouver un modèle avec {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "La relation `{0}` n'existe pas pour le modèle `{1}`", "61e5deebaf44d68f4e6a508f30cc31a3": "La relation `{0}` n'existe pas pour le modèle `{1}`",
"62e8b0a733417978bab22c8dacf5d7e6": "Impossible d'appliquer des mises à jour en bloc ; le connecteur ne signale pas correctement le nombre d'enregistrements mis à jour.", "62e8b0a733417978bab22c8dacf5d7e6": "Impossible d'appliquer des mises à jour en bloc ; le connecteur ne signale pas correctement le nombre d'enregistrements mis à jour.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXTE :{0}", "63a091ced88001ab6acb58f61ec041c5": "\t TEXTE :{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Recherchez un élément lié par id pour {0}.",
"6bc376432cd9972cf991aad3de371e78": "Données manquantes pour le changement : {0}", "6bc376432cd9972cf991aad3de371e78": "Données manquantes pour le changement : {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} introuvable",
"734a7bebb65e10899935126ba63dd51f": "La propriété options de la configuration `{0}` doit être un objet", "734a7bebb65e10899935126ba63dd51f": "La propriété options de la configuration `{0}` doit être un objet",
"779467f467862836e19f494a37d6ab77": "La propriété acls de la configuration `{0}` doit être un tableau d'objets", "779467f467862836e19f494a37d6ab77": "La propriété acls de la configuration `{0}` doit être un tableau d'objets",
"7bc7b301ad9c4fc873029d57fb9740fe": "Demandes {0} de {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Clé externe pour {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Ma première application mobile", "7d5e7ed0efaedf3f55f380caae0df8b8": "Ma première application mobile",
"7e0fca41d098607e1c9aa353c67e0fa1": "Jeton d'accès non valide", "7e0fca41d098607e1c9aa353c67e0fa1": "Jeton d'accès non valide",
"7e287fc885d9fdcf42da3a12f38572c1": "Autorisation requise", "7e287fc885d9fdcf42da3a12f38572c1": "Autorisation requise",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} est nécessaire pour la déconnexion",
"80a32e80cbed65eba2103201a7c94710": "Modèle introuvable : {0}", "80a32e80cbed65eba2103201a7c94710": "Modèle introuvable : {0}",
"830cb6c862f8f364e9064cea0026f701": "Extrait la relation hasOne {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "La propriété `{0}` ne peut pas être reconfigurée pour `{1}`", "83cbdc2560ba9f09155ccfc63e08f1a1": "La propriété `{0}` ne peut pas être reconfigurée pour `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Mot de passe non valide.",
"855ecd4a64885ba272d782435f72a4d4": "ID \"{0}\" inconnu \"{1}\".", "855ecd4a64885ba272d782435f72a4d4": "ID \"{0}\" inconnu \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "Le transport ne prend pas en charge les réacheminements HTTP.", "860d1a0b8bd340411fb32baa72867989": "Le transport ne prend pas en charge les réacheminements HTTP.",
"86254879d01a60826a851066987703f2": "Ajoutez un élément lié par id pour {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} ou {{email}} est obligatoire", "895b1f941d026870b3cc8e6af087c197": "{{username}} ou {{email}} est obligatoire",
"8a17c5ef611e2e7535792316e66b8fca": "Mot de passe trop long : {0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Demande à l'hôte {0}",
"8ae418c605b6a45f2651be9b1677c180": "Méthode distante non valide : `{0}`", "8ae418c605b6a45f2651be9b1677c180": "Méthode distante non valide : `{0}`",
"8bab6720ecc58ec6412358c858a53484": "La mise à jour en bloc a échoué ; le connecteur a modifié un nombre inattendu d'enregistrements : {0}", "8bab6720ecc58ec6412358c858a53484": "La mise à jour en bloc a échoué ; le connecteur a modifié un nombre inattendu d'enregistrements : {0}",
"8ecab7f534de38360bd1b1c88e880123": "Les modèles enfant de `{0}` n'hériteront pas des méthodes distantes nouvellement définies {1}.", "8ecab7f534de38360bd1b1c88e880123": "Les modèles enfant de `{0}` n'hériteront pas des méthodes distantes nouvellement définies {1}.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML :{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML :{0}",
"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.", "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.",
"a40684f5a9f546115258b76938d1de37": "Une liste de couleurs est disponible sur {{http://localhost:3000/colors}}", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Extrait la relation belongsTo {0}.",
"a50d10fc6e0959b220e085454c40381e": "Utilisateur introuvable : {0}", "a50d10fc6e0959b220e085454c40381e": "Utilisateur introuvable : {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" inconnu.", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" inconnu.",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} est obligatoire", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} est obligatoire",
"c0057a569ff9d3b509bac61a4b2f605d": "Supprime tous les {0} de ce modèle.",
"c2b5d51f007178170ca3952d59640ca4": "Impossible de rectifier les modifications {0} :\n{1}", "c2b5d51f007178170ca3952d59640ca4": "Impossible de rectifier les modifications {0} :\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "impossible de trouver {{accessToken}}",
"c4ee6d177c974532c3552d2f98eb72ea": "Le middleware {0} a été supprimé dans la version 3.0. Pour plus de détails, voir {1}.", "c4ee6d177c974532c3552d2f98eb72ea": "Le middleware {0} a été supprimé dans la version 3.0. Pour plus de détails, voir {1}.",
"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}}.", "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}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Obligation de fournir une adresse électronique valide", "c68a93f0a9524fed4ff64372fc90c55f": "Obligation de fournir une adresse électronique valide",
"cd0412f2f33a4a2a316acc834f3f21a6": "obligation de spécifier {{id}} ou {{data}}", "cd0412f2f33a4a2a316acc834f3f21a6": "obligation de spécifier {{id}} ou {{data}}",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} a été supprimé ; utilisez à la place le nouveau module {{loopback-boot}}", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} a été supprimé ; utilisez à la place le nouveau module {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "Crée une instance dans {0} de ce modèle.",
"da13d3cdf21330557254670dddd8c5c7": "Compte {0} de {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "Le paramètre \"methods\" non objet de \"{0}\" est ignoré.", "dc568bee32deb0f6eaf63e73b20e8ceb": "Le paramètre \"methods\" non objet de \"{0}\" est ignoré.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" inconnu.", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" inconnu.",
"e92aa25b6b864e3454b65a7c422bd114": "La mise à jour en bloc a échoué ; le connecteur a supprimé un nombre inattendu d'enregistrements : {0}", "e92aa25b6b864e3454b65a7c422bd114": "La mise à jour en bloc a échoué ; le connecteur a supprimé un nombre inattendu d'enregistrements : {0}",
@ -71,6 +87,7 @@
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT :{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT :{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "résultat :{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "résultat :{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflit", "f1d4ac54357cc0932f385d56814ba7e4": "Conflit",
"f58cdc481540cd1f69a4aa4da2e37981": "Mot de passe non valide : {0}" "f66ae3cf379b2fce28575a3282defe1a": "Supprime {0} de ce modèle.",
"f8e26bcca62a47f579562f1cd2c785ff": "Transformez votre application pour utiliser la solution officielle d'injection de l'argument \"options\" à partir du contexte de demande. \nVoir {0}"
} }

View File

@ -3,11 +3,13 @@
"04bd8af876f001ceaf443aad6a9002f9": "L'autenticazione richiede che sia definito il modello {0}.", "04bd8af876f001ceaf443aad6a9002f9": "L'autenticazione richiede che sia definito il modello {0}.",
"095afbf2f1f0e5be678f5dac5c54e717": "Accesso negato", "095afbf2f1f0e5be678f5dac5c54e717": "Accesso negato",
"0caffe1d763c8cca6a61814abe33b776": "L'email è obbligatoria", "0caffe1d763c8cca6a61814abe33b776": "L'email è obbligatoria",
"0e21aad369dd09e1965c11949303cefd": "Metadati della comunicazione in remoto per {0}.{1} {{\"isStatic\"}} non corrisponde al nuovo stile basato sul nome del metodo.", "0da38687fed24275c1547e815914a8e3": "Eliminare un elemento correlato in base all'ID per {0}.",
"0e21aad369dd09e1965c11949303cefd": "L'esecuzione della copia in remoto dei metadata per {0}.{1} {{\"isStatic\"}} non corrisponde al nuovo stile basato sul nome del metodo.",
"10e01c895dc0b2fecc385f9f462f1ca6": "un elenco dei colori è disponibile all'indirizzo {{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "un elenco dei colori è disponibile all'indirizzo {{http://localhost:3000/colors}}",
"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", "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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t OGGETTO:{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t OGGETTO:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Creato: {0}", "1e85f822b547a75d7d385048030e4ecb": "Creato: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Aggiornare un elemento correlato in base all'ID per {0}.",
"275f22ab95671f095640ca99194b7635": "\t DA:{0}", "275f22ab95671f095640ca99194b7635": "\t DA:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} è stato rimosso nella versione 3.0. Consultare {1} per ulteriori dettagli.", "2860bccdf9ef1e350c1a38932ed12173": "{0} è stato rimosso nella versione 3.0. Consultare {1} per ulteriori dettagli.",
"2d3071e3b18681c80a090dc0efbdb349": "impossibile trovare {0} con id {1}", "2d3071e3b18681c80a090dc0efbdb349": "impossibile trovare {0} con id {1}",
@ -16,61 +18,76 @@
"3438fab56cc7ab92dfd88f0497e523e0": "La proprietà relations della configurazione `{0}` deve essere un oggetto", "3438fab56cc7ab92dfd88f0497e523e0": "La proprietà relations della configurazione `{0}` deve essere un oggetto",
"3591f1d3e115b46f9f195df5ca548a6a": "Modello non trovato: il modello `{0}` estende un modello sconosciuto `{1}`.", "3591f1d3e115b46f9f195df5ca548a6a": "Modello non trovato: il modello `{0}` estende un modello sconosciuto `{1}`.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Verificare la e-mail aprendo questo link in un browser web:\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "Verificare la e-mail aprendo questo link in un browser web:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "La password immessa è troppo lunga. La lunghezza massima è {0} (immessa {1})",
"3aae63bb7e8e046641767571c1591441": "login non riuscito perché l'email non è stata verificata", "3aae63bb7e8e046641767571c1591441": "login non riuscito perché l'email non è stata verificata",
"3aecb24fa8bdd3f79d168761ca8a6729": "Fase {{middleware}} sconosciuta {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "Fase {{middleware}} sconosciuta {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} utilizza l'impostazione del modello {1} che non è più disponibile.",
"3caaa84fc103d6d5612173ae6d43b245": "Token non valido: {0}", "3caaa84fc103d6d5612173ae6d43b245": "Token non valido: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Grazie per essersi registrati", "3d617953470be16d0c2b32f0bcfbb5ee": "Grazie per essersi registrati",
"3d63008ccfb2af1db2142e8cc2716ace": "Avvertenza: nessun trasporto email specificato per l'invio della email. Configurare un trasporto per inviare messaggi email.", "3d63008ccfb2af1db2142e8cc2716ace": "Avvertenza: nessun trasporto email specificato per l'invio della email. Configurare un trasporto per inviare messaggi email.",
"4203ab415ec66a78d3164345439ba76e": "Impossibile richiamare {0}.{1}(). Il metodo {2} non è stato configurato. {{PersistedModel}} non è stato correttamente collegato ad una {{DataSource}}!", "4203ab415ec66a78d3164345439ba76e": "Impossibile chiamare {0}.{1}(). Il metodo {2} non è stato configurato. {{PersistedModel}} non è stato correttamente collegato ad una {{DataSource}}!",
"42a36bac5cf03c4418d664500c81047a": "L'opzione di {{DataSource}} {{\"defaultForType\"}} non è più supportata", "42a36bac5cf03c4418d664500c81047a": "L'opzione di {{DataSource}} {{\"defaultForType\"}} non è più supportata",
"44a6c8b1ded4ed653d19ddeaaf89a606": "Email non trovata", "44a6c8b1ded4ed653d19ddeaaf89a606": "Email non trovata",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Invio email:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "Invio email:",
"4b494de07f524703ac0879addbd64b13": "La e-mail non è stata verificata", "4b494de07f524703ac0879addbd64b13": "La e-mail non è stata verificata",
"528325f3cbf1b0ab9a08447515daac9a": "Aggiornare {0} di questo modello.",
"543d19bad5e47ee1e9eb8af688e857b4": "Chiave esterna per {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Impossibile creare l'origine dati {0}: {1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Impossibile creare l'origine dati {0}: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "È necessario collegare il modello {{Email}} ad un connettore {{Mail}}", "5858e63efaa0e4ad86b61c0459ea32fa": "È necessario collegare il modello {{Email}} ad un connettore {{Mail}}",
"598ff0255ffd1d1b71e8de55dbe2c034": "Verificare l'esistenza della relazione {0} ad un elemento in base all'ID.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Rimuovere la relazione {0} ad un elemento in base all'ID.",
"5e81ad3847a290dc650b47618b9cbc7e": "login non riuscito", "5e81ad3847a290dc650b47618b9cbc7e": "login non riuscito",
"5fa3afb425819ebde958043e598cb664": "impossibile trovare un modello con {{id}} {0}", "5fa3afb425819ebde958043e598cb664": "impossibile trovare un modello con {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "La relazione `{0}` non esiste per il modello `{1}`", "61e5deebaf44d68f4e6a508f30cc31a3": "La relazione `{0}` non esiste per il modello `{1}`",
"62e8b0a733417978bab22c8dacf5d7e6": "Impossibile applicare gli aggiornamenti in massa, il connettore non indica correttamente il numero di record aggiornati.", "62e8b0a733417978bab22c8dacf5d7e6": "Impossibile applicare gli aggiornamenti in massa, il connettore non indica correttamente il numero di record aggiornati.",
"63a091ced88001ab6acb58f61ec041c5": "\t TESTO:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t TESTO:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Trovare un elemento correlato in base all'ID per {0}.",
"6bc376432cd9972cf991aad3de371e78": "Dati mancanti per la modifica: {0}", "6bc376432cd9972cf991aad3de371e78": "Dati mancanti per la modifica: {0}",
"705c2d456a3e204c4af56e671ec3225c": "Impossibile trovare {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "La proprietà options della configurazione `{0}` deve essere un oggetto", "734a7bebb65e10899935126ba63dd51f": "La proprietà options della configurazione `{0}` deve essere un oggetto",
"779467f467862836e19f494a37d6ab77": "La proprietà acls della configurazione `{0}` deve essere un array di oggetti", "779467f467862836e19f494a37d6ab77": "La proprietà acls della configurazione `{0}` deve essere un array di oggetti",
"7bc7b301ad9c4fc873029d57fb9740fe": "Query {0} di {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Chiave esterna per {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Prima applicazione mobile personale", "7d5e7ed0efaedf3f55f380caae0df8b8": "Prima applicazione mobile personale",
"7e0fca41d098607e1c9aa353c67e0fa1": "Token di accesso non valido", "7e0fca41d098607e1c9aa353c67e0fa1": "Token di accesso non valido",
"7e287fc885d9fdcf42da3a12f38572c1": "Autorizzazione richiesta", "7e287fc885d9fdcf42da3a12f38572c1": "Autorizzazione richiesta",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} deve scollegarsi",
"80a32e80cbed65eba2103201a7c94710": "Modello non trovato: {0}", "80a32e80cbed65eba2103201a7c94710": "Modello non trovato: {0}",
"830cb6c862f8f364e9064cea0026f701": "Recupera la relazione hasOne {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Impossibile riconfigurare la proprietà `{0}` per `{1}`", "83cbdc2560ba9f09155ccfc63e08f1a1": "Impossibile riconfigurare la proprietà `{0}` per `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Password non valida.",
"855ecd4a64885ba272d782435f72a4d4": "ID sconosciuto \"{0}\" \"{1}\".", "855ecd4a64885ba272d782435f72a4d4": "ID sconosciuto \"{0}\" \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "Il trasporto non supporta i reindirizzamenti HTTP.", "860d1a0b8bd340411fb32baa72867989": "Il trasporto non supporta i reindirizzamenti HTTP.",
"86254879d01a60826a851066987703f2": "Aggiungere un elemento correlato in base all'ID per {0}.",
"895b1f941d026870b3cc8e6af087c197": "Sono richiesti {{username}} o {{email}}", "895b1f941d026870b3cc8e6af087c197": "Sono richiesti {{username}} o {{email}}",
"8a17c5ef611e2e7535792316e66b8fca": "Password troppo lunga: {0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Richiesta all'host {0}",
"8ae418c605b6a45f2651be9b1677c180": "Metodo remoto non valido: `{0}`", "8ae418c605b6a45f2651be9b1677c180": "Metodo remoto non valido: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "Aggiornamento in massa non riuscito, il connettore ha modificato un numero non previsto di record: {0}", "8bab6720ecc58ec6412358c858a53484": "Aggiornamento in massa non riuscito, il connettore ha modificato un numero non previsto di record: {0}",
"8ecab7f534de38360bd1b1c88e880123": "I modelli child di `{0}` non erediteranno i metodi remoti definiti di recente {1}.", "8ecab7f534de38360bd1b1c88e880123": "I modelli child di `{0}` non erediteranno i metodi remoti definiti di recente {1}.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "La configurazione di `{0}` non contiene la proprietà {{`dataSource`}}.\nUtilizzare `null` o `false` per contrassegnare i modelli non collegati ad alcuna origine dati.", "97795efe0c3eb7f35ce8cf8cfe70682b": "La configurazione di `{0}` non contiene la proprietà {{`dataSource`}}.\nUtilizzare `null` o `false` per contrassegnare i modelli non collegati ad alcuna origine dati.",
"a40684f5a9f546115258b76938d1de37": "Un elenco dei colori è disponibile all'indirizzo {{http://localhost:3000/colors}}", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Recupera la relazione belongsTo {0}.",
"a50d10fc6e0959b220e085454c40381e": "Utente non trovato: {0}", "a50d10fc6e0959b220e085454c40381e": "Utente non trovato: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "{{key}} \"{0}\" sconosciuto \"{1}\".", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "{{key}} \"{0}\" sconosciuto \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} è obbligatorio", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} è obbligatorio",
"c0057a569ff9d3b509bac61a4b2f605d": "Elimina tutti i {0} di questo modello.",
"c2b5d51f007178170ca3952d59640ca4": "Impossibile correggere {0} modifiche:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "Impossibile correggere {0} modifiche:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "impossibile trovare {{accessToken}}",
"c4ee6d177c974532c3552d2f98eb72ea": "Middleware {0} è stato rimosso nella versione 3.0. Consultare {1} per ulteriori dettagli.", "c4ee6d177c974532c3552d2f98eb72ea": "Middleware {0} è stato rimosso nella versione 3.0. Consultare {1} per ulteriori dettagli.",
"c61a5a02ba3801a892308f70f5d55a14": "L'impostazione dei metadati {{\"isStatic\"}} in remoto è un'operazione obsoleta. Specificare {{\"prototype.name\"}} nel nome del metodo invece di {{isStatic=false}}.", "c61a5a02ba3801a892308f70f5d55a14": "L'impostazione dei metadati {{\"isStatic\"}} in remoto è un'operazione obsoleta. Specificare {{\"prototype.name\"}} nel nome del metodo invece di {{isStatic=false}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "È necessario fornire una email valida", "c68a93f0a9524fed4ff64372fc90c55f": "È necessario fornire una email valida",
"cd0412f2f33a4a2a316acc834f3f21a6": "è necessario specificare {{id}} o {{data}}", "cd0412f2f33a4a2a316acc834f3f21a6": "è necessario specificare {{id}} o {{data}}",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} è stato rimosso, utilizzare il nuovo modulo {{loopback-boot}}", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} è stato rimosso, utilizzare il nuovo modulo {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "Crea una nuova istanza di questo modello in {0}.",
"da13d3cdf21330557254670dddd8c5c7": "{0} di {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "L'impostazione \"methods\" non oggetto di \"{0}\" viene ignorata.", "dc568bee32deb0f6eaf63e73b20e8ceb": "L'impostazione \"methods\" non oggetto di \"{0}\" viene ignorata.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "{{id}} \"{0}\" sconosciuto \"{1}\".", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "{{id}} \"{0}\" sconosciuto \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "Aggiornamento in massa non riuscito, il connettore ha eliminato un numero non previsto di record: {0}", "e92aa25b6b864e3454b65a7c422bd114": "Aggiornamento in massa non riuscito, il connettore ha eliminato un numero non previsto di record: {0}",
"ea63d226b6968e328bdf6876010786b5": "Impossibile applicare gli aggiornamenti in massa, il connettore non indica correttamente il numero di record eliminati.", "ea63d226b6968e328bdf6876010786b5": "Impossibile applicare gli aggiornamenti in massa, il connettore non indica correttamente il numero di record eliminati.",
"ead044e2b4bce74b4357f8a03fb78ec4": "Impossibile richiamare {0}.{1}(). Il metodo {2} non è stato configurato. {{KeyValueModel}} non è stato correttamente collegato ad una {{DataSource}}!", "ead044e2b4bce74b4357f8a03fb78ec4": "Impossibile chiamare {0}.{1}(). Il metodo {2} non è stato configurato. {{KeyValueModel}} non è stato correttamente collegato ad una {{DataSource}}!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t A:{0}", "ecb06666ef95e5db27a5ac1d6a17923b": "\t A:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRASPORTO:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRASPORTO:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "risultato:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "risultato:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflitto", "f1d4ac54357cc0932f385d56814ba7e4": "Conflitto",
"f58cdc481540cd1f69a4aa4da2e37981": "Password non valida: {0}" "f66ae3cf379b2fce28575a3282defe1a": "Elimina {0} di questo modello.",
"f8e26bcca62a47f579562f1cd2c785ff": "Impostare l'app in modo da utilizzare la soluzione ufficiale per inserire l'argomento \"options\" dal contesto della richiesta,\nconsultare {0}"
} }

View File

@ -3,11 +3,13 @@
"04bd8af876f001ceaf443aad6a9002f9": "認証では、モデル {0} を定義する必要があります。", "04bd8af876f001ceaf443aad6a9002f9": "認証では、モデル {0} を定義する必要があります。",
"095afbf2f1f0e5be678f5dac5c54e717": "アクセス拒否", "095afbf2f1f0e5be678f5dac5c54e717": "アクセス拒否",
"0caffe1d763c8cca6a61814abe33b776": "E メールは必須です", "0caffe1d763c8cca6a61814abe33b776": "E メールは必須です",
"0da38687fed24275c1547e815914a8e3": "ID を指定して {0} の関連項目を削除します。",
"0e21aad369dd09e1965c11949303cefd": "{0}.{1} のリモート・メタデータ {{\"isStatic\"}} は、新規メソッドの名前ベースの方式と一致しません。", "0e21aad369dd09e1965c11949303cefd": "{0}.{1} のリモート・メタデータ {{\"isStatic\"}} は、新規メソッドの名前ベースの方式と一致しません。",
"10e01c895dc0b2fecc385f9f462f1ca6": "カラー・リストは {{http://localhost:3000/colors}} で利用できます", "10e01c895dc0b2fecc385f9f462f1ca6": "カラー・リストは {{http://localhost:3000/colors}} で利用できます",
"1b2a6076dccbe91a56f1672eb3b8598c": "応答本文には、ログイン時に作成された {{AccessToken}} のプロパティーが含まれます。\n`include` パラメーターの値によっては、本文に追加のプロパティーが含まれる場合があります:\n\n - `user` - `U+007BUserU+007D` - 現在ログインしているユーザーのデータ。 {{(`include=user`)}}\n\n", "1b2a6076dccbe91a56f1672eb3b8598c": "応答本文には、ログイン時に作成された {{AccessToken}} のプロパティーが含まれます。\n`include` パラメーターの値によっては、本文に追加のプロパティーが含まれる場合があります:\n\n - `user` - `U+007BUserU+007D` - 現在ログインしているユーザーのデータ。 {{(`include=user`)}}\n\n",
"1d7833c3ca2f05fdad8fad7537531c40": "\t 件名:{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t 件名:{0}",
"1e85f822b547a75d7d385048030e4ecb": "作成済み: {0}", "1e85f822b547a75d7d385048030e4ecb": "作成済み: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "ID を指定して {0} の関連項目を更新します。",
"275f22ab95671f095640ca99194b7635": "\t 送信元:{0}", "275f22ab95671f095640ca99194b7635": "\t 送信元:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} は、バージョン 3.0 で削除されました。 詳しくは、{1} を参照してください。", "2860bccdf9ef1e350c1a38932ed12173": "{0} は、バージョン 3.0 で削除されました。 詳しくは、{1} を参照してください。",
"2d3071e3b18681c80a090dc0efbdb349": "ID {1} の {0} が見つかりませんでした", "2d3071e3b18681c80a090dc0efbdb349": "ID {1} の {0} が見つかりませんでした",
@ -16,8 +18,10 @@
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 構成の関係プロパティーはオブジェクトでなければなりません", "3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 構成の関係プロパティーはオブジェクトでなければなりません",
"3591f1d3e115b46f9f195df5ca548a6a": "モデルが見つかりません: モデル `{0}` は不明のモデル `{1}` を拡張しています。", "3591f1d3e115b46f9f195df5ca548a6a": "モデルが見つかりません: モデル `{0}` は不明のモデル `{1}` を拡張しています。",
"35e5252c62d80f8c54a5290d30f4c7d0": "Web ブラウザーで次のリンクを開いて、E メールを検証してください: \n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "Web ブラウザーで次のリンクを開いて、E メールを検証してください: \n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "入力したパスワードが長すぎます。最大長は {0} です ({1} が入力されました)",
"3aae63bb7e8e046641767571c1591441": "E メールが検証されていないため、ログインに失敗しました", "3aae63bb7e8e046641767571c1591441": "E メールが検証されていないため、ログインに失敗しました",
"3aecb24fa8bdd3f79d168761ca8a6729": "不明な {{middleware}} フェーズ {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "不明な {{middleware}} フェーズ {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} は、使用できなくなったモデル設定 {1} を使用しています。",
"3caaa84fc103d6d5612173ae6d43b245": "無効なトークン: {0}", "3caaa84fc103d6d5612173ae6d43b245": "無効なトークン: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "ご登録いただき、ありがとうございます。", "3d617953470be16d0c2b32f0bcfbb5ee": "ご登録いただき、ありがとうございます。",
"3d63008ccfb2af1db2142e8cc2716ace": "警告: E メール送信用の E メール・トランスポートが指定されていません。 メール・メッセージを送信するためのトランスポートをセットアップしてください。", "3d63008ccfb2af1db2142e8cc2716ace": "警告: E メール送信用の E メール・トランスポートが指定されていません。 メール・メッセージを送信するためのトランスポートをセットアップしてください。",
@ -26,42 +30,54 @@
"44a6c8b1ded4ed653d19ddeaaf89a606": "E メールが見つかりません", "44a6c8b1ded4ed653d19ddeaaf89a606": "E メールが見つかりません",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "メールの送信:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "メールの送信:",
"4b494de07f524703ac0879addbd64b13": "E メールが検証されていません", "4b494de07f524703ac0879addbd64b13": "E メールが検証されていません",
"528325f3cbf1b0ab9a08447515daac9a": "このモデルの {0} を更新します。",
"543d19bad5e47ee1e9eb8af688e857b4": "{0} の外部キー。",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "データ・ソース {0}: {1} を作成できません", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "データ・ソース {0}: {1} を作成できません",
"5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} モデルを {{Mail}} コネクターに接続する必要があります", "5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} モデルを {{Mail}} コネクターに接続する必要があります",
"598ff0255ffd1d1b71e8de55dbe2c034": "ID を指定して項目との {0} 関係があることを確認します。",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "ID を指定して項目との {0} 関係を削除します。",
"5e81ad3847a290dc650b47618b9cbc7e": "ログインに失敗しました", "5e81ad3847a290dc650b47618b9cbc7e": "ログインに失敗しました",
"5fa3afb425819ebde958043e598cb664": "{{id}} {0} のモデルが見つかりませんでした", "5fa3afb425819ebde958043e598cb664": "{{id}} {0} のモデルが見つかりませんでした",
"61e5deebaf44d68f4e6a508f30cc31a3": "モデル `{1}` には関係 `{0}` が存在しません", "61e5deebaf44d68f4e6a508f30cc31a3": "モデル `{1}` には関係 `{0}` が存在しません",
"62e8b0a733417978bab22c8dacf5d7e6": "一括更新を適用できません。コネクターは更新されたレコードの数を正しく報告していません。", "62e8b0a733417978bab22c8dacf5d7e6": "一括更新を適用できません。コネクターは更新されたレコードの数を正しく報告していません。",
"63a091ced88001ab6acb58f61ec041c5": "\t テキスト:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t テキスト:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "ID を指定して {0} の関連項目を検索します。",
"6bc376432cd9972cf991aad3de371e78": "変更用のデータがありません: {0}", "6bc376432cd9972cf991aad3de371e78": "変更用のデータがありません: {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} が見つかりませんでした",
"734a7bebb65e10899935126ba63dd51f": "`{0}` 構成のオプション・プロパティーはオブジェクトでなければなりません", "734a7bebb65e10899935126ba63dd51f": "`{0}` 構成のオプション・プロパティーはオブジェクトでなければなりません",
"779467f467862836e19f494a37d6ab77": "`{0}` 構成の ACL プロパティーはオブジェクトの配列でなければなりません", "779467f467862836e19f494a37d6ab77": "`{0}` 構成の ACL プロパティーはオブジェクトの配列でなければなりません",
"7bc7b301ad9c4fc873029d57fb9740fe": "{1} の {0} を照会します。",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0} の外部キー",
"7d5e7ed0efaedf3f55f380caae0df8b8": "最初のモバイル・アプリケーション", "7d5e7ed0efaedf3f55f380caae0df8b8": "最初のモバイル・アプリケーション",
"7e0fca41d098607e1c9aa353c67e0fa1": "無効なアクセス・トークン", "7e0fca41d098607e1c9aa353c67e0fa1": "無効なアクセス・トークン",
"7e287fc885d9fdcf42da3a12f38572c1": "許可が必要です", "7e287fc885d9fdcf42da3a12f38572c1": "許可が必要です",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "ログアウトするには {{accessToken}} が必要です",
"80a32e80cbed65eba2103201a7c94710": "モデルが見つかりません: {0}", "80a32e80cbed65eba2103201a7c94710": "モデルが見つかりません: {0}",
"830cb6c862f8f364e9064cea0026f701": "hasOne 関係 {0} をフェッチします。",
"83cbdc2560ba9f09155ccfc63e08f1a1": "`{1}` のプロパティー `{0}` を再構成できません", "83cbdc2560ba9f09155ccfc63e08f1a1": "`{1}` のプロパティー `{0}` を再構成できません",
"855eb8db89b4921c42072832d33d2dc2": "パスワードが無効です。",
"855ecd4a64885ba272d782435f72a4d4": "\"{0}\" ID \"{1}\" が不明です。", "855ecd4a64885ba272d782435f72a4d4": "\"{0}\" ID \"{1}\" が不明です。",
"860d1a0b8bd340411fb32baa72867989": "トランスポートでは HTTP リダイレクトはサポートされません。", "860d1a0b8bd340411fb32baa72867989": "トランスポートでは HTTP リダイレクトはサポートされません。",
"86254879d01a60826a851066987703f2": "ID を指定して {0} の関連項目を追加します。",
"895b1f941d026870b3cc8e6af087c197": "{{username}} または {{email}} が必要です", "895b1f941d026870b3cc8e6af087c197": "{{username}} または {{email}} が必要です",
"8a17c5ef611e2e7535792316e66b8fca": "パスワードが長すぎます: {0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "ホスト {0} への要求",
"8ae418c605b6a45f2651be9b1677c180": "無効なリモート・メソッド: `{0}`", "8ae418c605b6a45f2651be9b1677c180": "無効なリモート・メソッド: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "一括更新が失敗しました。コネクターは予期しない数のレコードを変更しました: {0}", "8bab6720ecc58ec6412358c858a53484": "一括更新が失敗しました。コネクターは予期しない数のレコードを変更しました: {0}",
"8ecab7f534de38360bd1b1c88e880123": "`{0}` の子モデルは、新しく定義されたリモート・メソッド {1} を継承しません。", "8ecab7f534de38360bd1b1c88e880123": "`{0}` の子モデルは、新しく定義されたリモート・メソッド {1} を継承しません。",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}` の構成は {{`dataSource`}} プロパティーがありません。\nどのデータ・ソースにも付加されていないモデルにマークを付けるには `null` または `false` を使用します。", "97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}` の構成は {{`dataSource`}} プロパティーがありません。\nどのデータ・ソースにも付加されていないモデルにマークを付けるには `null` または `false` を使用します。",
"a40684f5a9f546115258b76938d1de37": "カラー・リストは {{http://localhost:3000/colors}} で利用できます", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "belongsTo 関係 {0} をフェッチします。",
"a50d10fc6e0959b220e085454c40381e": "ユーザーが見つかりません: {0}", "a50d10fc6e0959b220e085454c40381e": "ユーザーが見つかりません: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" が不明です。", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" が不明です。",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} は必須です", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} は必須です",
"c0057a569ff9d3b509bac61a4b2f605d": "このモデルのすべての {0} を削除します。",
"c2b5d51f007178170ca3952d59640ca4": "{0} の変更を修正できません:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "{0} の変更を修正できません:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "{{accessToken}} が見つかりませんでした",
"c4ee6d177c974532c3552d2f98eb72ea": "{0} ミドルウェアは、バージョン 3.0 で削除されました。 詳しくは、{1} を参照してください。", "c4ee6d177c974532c3552d2f98eb72ea": "{0} ミドルウェアは、バージョン 3.0 で削除されました。 詳しくは、{1} を参照してください。",
"c61a5a02ba3801a892308f70f5d55a14": "リモート・メタデータ {{\"isStatic\"}} は非推奨です。 メソッド名では {{isStatic=false}} の代わりに {{\"prototype.name\"}} を指定してください。", "c61a5a02ba3801a892308f70f5d55a14": "リモート・メタデータ {{\"isStatic\"}} は非推奨です。 メソッド名では {{isStatic=false}} の代わりに {{\"prototype.name\"}} を指定してください。",
"c68a93f0a9524fed4ff64372fc90c55f": "有効な E メールを指定する必要があります", "c68a93f0a9524fed4ff64372fc90c55f": "有効な E メールを指定する必要があります",
"cd0412f2f33a4a2a316acc834f3f21a6": "{{id}} または {{data}} を指定する必要があります", "cd0412f2f33a4a2a316acc834f3f21a6": "{{id}} または {{data}} を指定する必要があります",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} は削除されました。代わりに新規モジュール {{loopback-boot}} を使用してください", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} は削除されました。代わりに新規モジュール {{loopback-boot}} を使用してください",
"d6f43b266533b04d442bdb3955622592": "このモデルの {0} に新規インスタンスを作成します。",
"da13d3cdf21330557254670dddd8c5c7": "{1} の {0} をカウントします。",
"dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\" の非オブジェクト「メソッド」設定を無視します。", "dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\" の非オブジェクト「メソッド」設定を無視します。",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" が不明です。", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" が不明です。",
"e92aa25b6b864e3454b65a7c422bd114": "一括更新が失敗しました。コネクターは予期しない数のレコードを削除しました: {0}", "e92aa25b6b864e3454b65a7c422bd114": "一括更新が失敗しました。コネクターは予期しない数のレコードを削除しました: {0}",
@ -71,6 +87,7 @@
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t トランスポート:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t トランスポート:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "結果:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "結果:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "競合", "f1d4ac54357cc0932f385d56814ba7e4": "競合",
"f58cdc481540cd1f69a4aa4da2e37981": "無効なパスワード: {0}" "f66ae3cf379b2fce28575a3282defe1a": "このモデルの {0} を削除します。",
"f8e26bcca62a47f579562f1cd2c785ff": "要求コンテキストからの \"options\" 引数を注入するための公式な解決策を使用するようにアプリケーションを作り直してください。\n{0} を参照してください。"
} }

View File

@ -3,74 +3,91 @@
"04bd8af876f001ceaf443aad6a9002f9": "인증을 위해 {0} 모델이 정의되어야 함", "04bd8af876f001ceaf443aad6a9002f9": "인증을 위해 {0} 모델이 정의되어야 함",
"095afbf2f1f0e5be678f5dac5c54e717": "액세스 거부", "095afbf2f1f0e5be678f5dac5c54e717": "액세스 거부",
"0caffe1d763c8cca6a61814abe33b776": "이메일은 필수입니다.", "0caffe1d763c8cca6a61814abe33b776": "이메일은 필수입니다.",
"0da38687fed24275c1547e815914a8e3": "{0}에 대해 ID로 관련 항목을 삭제하십시오.",
"0e21aad369dd09e1965c11949303cefd": "{0}.{1} {{\"isStatic\"}}의 원격 메타데이터가 새 메소드 이름 기반 스타일과 일치하지 않습니다.", "0e21aad369dd09e1965c11949303cefd": "{0}.{1} {{\"isStatic\"}}의 원격 메타데이터가 새 메소드 이름 기반 스타일과 일치하지 않습니다.",
"10e01c895dc0b2fecc385f9f462f1ca6": "색상 목록은 {{http://localhost:3000/colors}}에 있음", "10e01c895dc0b2fecc385f9f462f1ca6": "색상 목록은 {{http://localhost:3000/colors}}에 있음",
"1b2a6076dccbe91a56f1672eb3b8598c": "응답 본문에 로그인 시 작성한 {{AccessToken}} 특성이 포함됩니다. \n`include` 매개변수 값에 따라 본문에 추가 특성이 포함될 수 있습니다. \n\n - `user` - `U+007BUserU+007D` - 현재 로그인된 사용자의 데이터. {{(`include=user`)}}\n\n", "1b2a6076dccbe91a56f1672eb3b8598c": "응답 본문에 로그인 시 작성한 {{AccessToken}} 특성이 포함됩니다.\n`include` 매개변수 값에 따라 본문에 추가 특성이 포함될 수 있습니다. \n\n - `user` - `U+007BUserU+007D` - 현재 로그인된 사용자의 데이터. {{(`include=user`)}}\n\n",
"1d7833c3ca2f05fdad8fad7537531c40": "\t 제목:{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t 제목:{0}",
"1e85f822b547a75d7d385048030e4ecb": "작성 날짜: {0}", "1e85f822b547a75d7d385048030e4ecb": "작성 날짜: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "{0}에 대해 ID로 관련 항목을 업데이트하십시오.",
"275f22ab95671f095640ca99194b7635": "\t 발신인:{0}", "275f22ab95671f095640ca99194b7635": "\t 발신인:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "버전 3.0에서 {0}이(가) 제거되었습니다. 자세한 정보는 {1}을(를) 참조하십시오. ", "2860bccdf9ef1e350c1a38932ed12173": "버전 3.0에서 {0}이(가) 제거되었습니다. 자세한 정보는 {1}을(를) 참조하십시오.",
"2d3071e3b18681c80a090dc0efbdb349": "ID {1}(으)로 {0}을(를) 찾을 수 없음 ", "2d3071e3b18681c80a090dc0efbdb349": "ID {1}(으)로 {0}을(를) 찾을 수 없음",
"316e5b82c203cf3de31a449ee07d0650": "예상 부울, 실제 {0}", "316e5b82c203cf3de31a449ee07d0650": "예상 부울, 실제 {0}",
"320c482401afa1207c04343ab162e803": "올바르지 않은 프린시펄 유형: {0}", "320c482401afa1207c04343ab162e803": "올바르지 않은 프린시펄 유형: {0}",
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 구성의 관계 특성은 오브젝트여야 함", "3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 구성의 관계 특성은 오브젝트여야 함",
"3591f1d3e115b46f9f195df5ca548a6a": "모델을 찾을 수 없음: 모델 `{0}`은(는) 알 수 없는 모델 `{1}`의 확장입니다. ", "3591f1d3e115b46f9f195df5ca548a6a": "모델을 찾을 수 없음: 모델 `{0}`은(는) 알 수 없는 모델 `{1}`의 확장입니다.",
"35e5252c62d80f8c54a5290d30f4c7d0": "웹 브라우저에서 이 링크를 열어 이메일을 확인하십시오.\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "웹 브라우저에서 이 링크를 열어 이메일을 확인하십시오.\n\t{0}",
"3aae63bb7e8e046641767571c1591441": "이메일이 확인되지 않아서 로그인에 실패했습니다. ", "37bcd4b50dfae98734772e39ffb1ea3d": "입력한 비밀번호가 너무 깁니다. 최대 길이는 {0}입니다({1}자 입력함).",
"3aae63bb7e8e046641767571c1591441": "이메일이 확인되지 않아서 로그인에 실패했습니다.",
"3aecb24fa8bdd3f79d168761ca8a6729": "알 수 없는 {{middleware}} 단계 {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "알 수 없는 {{middleware}} 단계 {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0}에서 더 이상 사용할 수 없는 모델 설정 {1}을(를) 사용하고 있습니다.",
"3caaa84fc103d6d5612173ae6d43b245": "올바르지 않은 토큰: {0}", "3caaa84fc103d6d5612173ae6d43b245": "올바르지 않은 토큰: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "등록해 주셔서 감사합니다.", "3d617953470be16d0c2b32f0bcfbb5ee": "등록해 주셔서 감사합니다.",
"3d63008ccfb2af1db2142e8cc2716ace": "경고: 이메일 발송을 위해 이메일 전송이 지정되지 않았습니다. 메일 메시지를 보내려면 전송을 설정하십시오. ", "3d63008ccfb2af1db2142e8cc2716ace": "경고: 이메일 발송을 위해 이메일 전송이 지정되지 않았습니다. 메일 메시지를 보내려면 전송을 설정하십시오.",
"4203ab415ec66a78d3164345439ba76e": "{0}.{1}()을(를) 호출할 수 없습니다. {2} 메소드가 설정되지 않았습니다. {{PersistedModel}}이(가) {{DataSource}}에 재대로 첨부되지 않았습니다!", "4203ab415ec66a78d3164345439ba76e": "{0}.{1}()을(를) 호출할 수 없습니다. {2} 메소드가 설정되지 않았습니다. {{PersistedModel}}이(가) {{DataSource}}에 재대로 첨부되지 않았습니다!",
"42a36bac5cf03c4418d664500c81047a": "{{DataSource}} 옵션 {{\"defaultForType\"}}이(가) 더 이상 지원되지 않음", "42a36bac5cf03c4418d664500c81047a": "{{DataSource}} 옵션 {{\"defaultForType\"}}이(가) 더 이상 지원되지 않음",
"44a6c8b1ded4ed653d19ddeaaf89a606": "이메일을 찾을 수 없음", "44a6c8b1ded4ed653d19ddeaaf89a606": "이메일을 찾을 수 없음",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "메일 발송 중:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "메일 발송 중:",
"4b494de07f524703ac0879addbd64b13": "이메일이 확인되지 않았습니다.", "4b494de07f524703ac0879addbd64b13": "이메일이 확인되지 않았습니다.",
"528325f3cbf1b0ab9a08447515daac9a": "이 모델의 {0}을(를) 업데이트하십시오.",
"543d19bad5e47ee1e9eb8af688e857b4": "{0}의 외부 키입니다.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "데이터 소스 {0}을(를) 작성할 수 없음: {1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "데이터 소스 {0}을(를) 작성할 수 없음: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} 모델을 {{Mail}} 커넥터에 연결해야 합니다. ", "5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} 모델을 {{Mail}} 커넥터에 연결해야 합니다.",
"598ff0255ffd1d1b71e8de55dbe2c034": "ID로 항목에 대한 {0} 관계의 존재를 확인하십시오.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "ID로 항목에 대한 {0} 관계를 제거하십시오.",
"5e81ad3847a290dc650b47618b9cbc7e": "로그인 실패", "5e81ad3847a290dc650b47618b9cbc7e": "로그인 실패",
"5fa3afb425819ebde958043e598cb664": "{{id}} {0}인 모델을 찾을 수 없음", "5fa3afb425819ebde958043e598cb664": "{{id}} {0}인 모델을 찾을 수 없음",
"61e5deebaf44d68f4e6a508f30cc31a3": "모델 `{1}`에 대해 관계 `{0}`이(가) 없습니다. ", "61e5deebaf44d68f4e6a508f30cc31a3": "모델 `{1}`에 대해 관계 `{0}`이(가) 없습니다.",
"62e8b0a733417978bab22c8dacf5d7e6": "벌크 업데이트를 적용할 수 없습니다. 커넥터가 업데이트된 레코드 수를 제대로 보고하지 않습니다. ", "62e8b0a733417978bab22c8dacf5d7e6": "벌크 업데이트를 적용할 수 없습니다. 커넥터가 업데이트된 레코드 수를 제대로 보고하지 않습니다.",
"63a091ced88001ab6acb58f61ec041c5": "\t 텍스트:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t 텍스트:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "{0}에 대해 ID로 관련 항목을 찾으십시오.",
"6bc376432cd9972cf991aad3de371e78": "변경을 위한 데이터 누락: {0}", "6bc376432cd9972cf991aad3de371e78": "변경을 위한 데이터 누락: {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}}을(를) 찾을 수 없음",
"734a7bebb65e10899935126ba63dd51f": "`{0}` 구성의 옵션 특성은 오브젝트여야 함", "734a7bebb65e10899935126ba63dd51f": "`{0}` 구성의 옵션 특성은 오브젝트여야 함",
"779467f467862836e19f494a37d6ab77": "`{0}` 구성의 acls 특성은 오브젝트 배열이어야 함", "779467f467862836e19f494a37d6ab77": "`{0}` 구성의 acls 특성은 오브젝트 배열이어야 함",
"7bc7b301ad9c4fc873029d57fb9740fe": "{1}의 {0}을(를) 조회합니다.",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0}의 외부 키",
"7d5e7ed0efaedf3f55f380caae0df8b8": "내 첫 번째 모바일 애플리케이션", "7d5e7ed0efaedf3f55f380caae0df8b8": "내 첫 번째 모바일 애플리케이션",
"7e0fca41d098607e1c9aa353c67e0fa1": "올바르지 않은 액세스 토큰", "7e0fca41d098607e1c9aa353c67e0fa1": "올바르지 않은 액세스 토큰",
"7e287fc885d9fdcf42da3a12f38572c1": "권한 필수", "7e287fc885d9fdcf42da3a12f38572c1": "권한 필수",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}}이(가) 로그아웃해야 함",
"80a32e80cbed65eba2103201a7c94710": "모델을 찾을 수 없음: {0}", "80a32e80cbed65eba2103201a7c94710": "모델을 찾을 수 없음: {0}",
"830cb6c862f8f364e9064cea0026f701": "페치에 하나의 관계 {0}이(가) 있습니다.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "`{1}`에 대해 `{0}` 특성을 다시 구성할 수 없음", "83cbdc2560ba9f09155ccfc63e08f1a1": "`{1}`에 대해 `{0}` 특성을 다시 구성할 수 없음",
"855eb8db89b4921c42072832d33d2dc2": "올바르지 않은 비밀번호입니다.",
"855ecd4a64885ba272d782435f72a4d4": "알 수 없는 \"{0}\" ID \"{1}\".", "855ecd4a64885ba272d782435f72a4d4": "알 수 없는 \"{0}\" ID \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "전송에서 HTTP 경로 재지원을 지원하지 않습니다.", "860d1a0b8bd340411fb32baa72867989": "전송에서 HTTP 경로 재지원을 지원하지 않습니다.",
"86254879d01a60826a851066987703f2": "{0}에 대해 ID로 관련 항목을 추가하십시오.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} 또는 {{email}}은(는) 필수입니다.", "895b1f941d026870b3cc8e6af087c197": "{{username}} 또는 {{email}}은(는) 필수입니다.",
"8a17c5ef611e2e7535792316e66b8fca": "비밀번호가 너무 김: {0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "호스트 {0}에 요청",
"8ae418c605b6a45f2651be9b1677c180": "올바르지 않은 원격 메소드: `{0}`", "8ae418c605b6a45f2651be9b1677c180": "올바르지 않은 원격 메소드: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "벌크 업데이트에 실패했습니다. 커넥터가 예상치 못한 수의 레코드를 수정했습니다. {0}", "8bab6720ecc58ec6412358c858a53484": "벌크 업데이트에 실패했습니다. 커넥터가 예상치 못한 수의 레코드를 수정했습니다. {0}",
"8ecab7f534de38360bd1b1c88e880123": "'{0}'의 하위 모델은 새로 정의된 원격 메소드 {1}을(를) 상속하지 않습니다.", "8ecab7f534de38360bd1b1c88e880123": "'{0}'의 하위 모델은 새로 정의된 원격 메소드 {1}을(를) 상속하지 않습니다.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}`의 구성에 {{`dataSource`}} 특성이 누락되었습니다.\n데이터 소스에 첨부되지 않은 모델을 표시하려면 `null` 또는 `false`를 사용하십시오.", "97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}`의 구성에 {{`dataSource`}} 특성이 누락되었습니다.\n데이터 소스에 첨부되지 않은 모델을 표시하려면 `null` 또는 `false`를 사용하십시오.",
"a40684f5a9f546115258b76938d1de37": "색상 목록은 {{http://localhost:3000/colors}}에 있음", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "페치가 관계 {0}에 속합니다.",
"a50d10fc6e0959b220e085454c40381e": "사용자를 찾을 수 없음: {0}", "a50d10fc6e0959b220e085454c40381e": "사용자를 찾을 수 없음: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "알 수 없는 \"{0}\" {{key}} \"{1}\".", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "알 수 없는 \"{0}\" {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}}은(는) 필수입니다.", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}}은(는) 필수입니다.",
"c0057a569ff9d3b509bac61a4b2f605d": "이 모델의 모든 {0}을(를) 삭제합니다.",
"c2b5d51f007178170ca3952d59640ca4": "{0} 변경사항을 교정할 수 없음:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "{0} 변경사항을 교정할 수 없음:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "{{accessToken}}을(를) 찾을 수 없음", "c4ee6d177c974532c3552d2f98eb72ea": "버전 3.0에서 {0} 미들웨어가 제거되었습니다. 자세한 정보는 {1}을(를) 참조하십시오.",
"c4ee6d177c974532c3552d2f98eb72ea": "버전 3.0에서 {0} 미들웨어가 제거되었습니다. 자세한 정보는 {1}을(를) 참조하십시오. ", "c61a5a02ba3801a892308f70f5d55a14": "원격 메타데이터 {{\"isStatic\"}}이(가) 더 이상 사용되지 않습니다. {{isStatic=false}}인 경우 메소드 이름에 대신 {{\"prototype.name\"}}을(를) 지정하십시오.",
"c61a5a02ba3801a892308f70f5d55a14": "원격 메타데이터 {{\"isStatic\"}}이(가) 더 이상 사용되지 않습니다. {{isStatic=false}}인 경우 메소드 이름에 대신 {{\"prototype.name\"}}을(를) 지정하십시오. ",
"c68a93f0a9524fed4ff64372fc90c55f": "올바른 이메일을 제공해야 함", "c68a93f0a9524fed4ff64372fc90c55f": "올바른 이메일을 제공해야 함",
"cd0412f2f33a4a2a316acc834f3f21a6": "{{id}} 또는 {{data}}을(를) 지정해야 함", "cd0412f2f33a4a2a316acc834f3f21a6": "{{id}} 또는 {{data}}을(를) 지정해야 함",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}}이(가) 제거되었습니다. 대신 새 모듈 {{loopback-boot}}을(를) 사용하십시오. ", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}}이(가) 제거되었습니다. 대신 새 모듈 {{loopback-boot}}을(를) 사용하십시오.",
"d6f43b266533b04d442bdb3955622592": "이 모델의 {0}에서 새 인스턴스를 작성합니다.",
"da13d3cdf21330557254670dddd8c5c7": "{1}의 {0}을(를) 계수합니다.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\"의 비오브젝트 \"methods\" 설정 무시", "dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\"의 비오브젝트 \"methods\" 설정 무시",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "알 수 없는 \"{0}\" {{id}} \"{1}\".", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "알 수 없는 \"{0}\" {{id}} \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "벌크 업데이트에 실패했습니다. 커넥터가 예상치 못한 수의 레코드를 삭제했습니다. {0}", "e92aa25b6b864e3454b65a7c422bd114": "벌크 업데이트에 실패했습니다. 커넥터가 예상치 못한 수의 레코드를 삭제했습니다. {0}",
"ea63d226b6968e328bdf6876010786b5": "벌크 업데이트를 적용할 수 없습니다. 커넥터가 삭제된 레코드 수를 제대로 보고하지 않습니다. ", "ea63d226b6968e328bdf6876010786b5": "벌크 업데이트를 적용할 수 없습니다. 커넥터가 삭제된 레코드 수를 제대로 보고하지 않습니다.",
"ead044e2b4bce74b4357f8a03fb78ec4": "{0}.{1}()을(를) 호출할 수 없습니다. {2} 메소드가 설정되지 않았습니다. {{KeyValueModel}}이(가) {{DataSource}}에 재대로 첨부되지 않았습니다!", "ead044e2b4bce74b4357f8a03fb78ec4": "{0}.{1}()을(를) 호출할 수 없습니다. {2} 메소드가 설정되지 않았습니다. {{KeyValueModel}}이(가) {{DataSource}}에 재대로 첨부되지 않았습니다!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t 수신인:{0}", "ecb06666ef95e5db27a5ac1d6a17923b": "\t 수신인:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t 전송:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t 전송:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "결과: {0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "결과: {0}",
"f1d4ac54357cc0932f385d56814ba7e4": "충돌", "f1d4ac54357cc0932f385d56814ba7e4": "충돌",
"f58cdc481540cd1f69a4aa4da2e37981": "올바르지 않은 비밀번호: {0}" "f66ae3cf379b2fce28575a3282defe1a": "이 모델의 {0}을(를) 삭제합니다.",
"f8e26bcca62a47f579562f1cd2c785ff": "요청 컨텍스트의 \"options\" 인수 삽입에 정식 솔루션을 사용하도록 앱을 다시 작업하십시오.\n{0} 참조"
} }

View File

@ -3,11 +3,13 @@
"04bd8af876f001ceaf443aad6a9002f9": "Voor verificatie moet model {0} worden gedefinieerd.", "04bd8af876f001ceaf443aad6a9002f9": "Voor verificatie moet model {0} worden gedefinieerd.",
"095afbf2f1f0e5be678f5dac5c54e717": "Toegang geweigerd", "095afbf2f1f0e5be678f5dac5c54e717": "Toegang geweigerd",
"0caffe1d763c8cca6a61814abe33b776": "E-mail is vereist", "0caffe1d763c8cca6a61814abe33b776": "E-mail is vereist",
"0e21aad369dd09e1965c11949303cefd": "Remoting (externe) metagegevens voor {0}.{1} {{\"isStatic\"}} komen niet overeen met nieuwe naam-gebaseerde stijl van methode.", "0da38687fed24275c1547e815914a8e3": "Gerelateerd item wissen op basis van ID voor {0}.",
"0e21aad369dd09e1965c11949303cefd": "Het niet-lokaal zetten van metagegevens voor {0}.{1} {{\"isStatic\"}} komt niet overeen met nieuwe op naam gebaseerde stijl van de methode.",
"10e01c895dc0b2fecc385f9f462f1ca6": "een lijst van kleuren is beschikbaar op {{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "een lijst van kleuren is beschikbaar op {{http://localhost:3000/colors}}",
"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", "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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t ONDERWERP: {0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t ONDERWERP: {0}",
"1e85f822b547a75d7d385048030e4ecb": "Gemaakt: {0}", "1e85f822b547a75d7d385048030e4ecb": "Gemaakt: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Gerelateerd item bijwerken op basis van ID voor {0}.",
"275f22ab95671f095640ca99194b7635": "\t VAN: {0}", "275f22ab95671f095640ca99194b7635": "\t VAN: {0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} is versie 3.0 verwijderd. Zie {1} voor meer informatie.", "2860bccdf9ef1e350c1a38932ed12173": "{0} is versie 3.0 verwijderd. Zie {1} voor meer informatie.",
"2d3071e3b18681c80a090dc0efbdb349": "kan {0} met ID {1} niet vinden", "2d3071e3b18681c80a090dc0efbdb349": "kan {0} met ID {1} niet vinden",
@ -16,61 +18,76 @@
"3438fab56cc7ab92dfd88f0497e523e0": "De relaties-eigenschap van de '{0}'-configuratie moet een object zijn", "3438fab56cc7ab92dfd88f0497e523e0": "De relaties-eigenschap van de '{0}'-configuratie moet een object zijn",
"3591f1d3e115b46f9f195df5ca548a6a": "Model niet gevonden: model '{0}' is een uitbreiding van onbekend model '{1}'.", "3591f1d3e115b46f9f195df5ca548a6a": "Model niet gevonden: model '{0}' is een uitbreiding van onbekend model '{1}'.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Controleer uw e-mail door deze link te openen in een webbrowser:\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "Controleer uw e-mail door deze link te openen in een webbrowser:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "Het opgegeven wachtwoord is te lang. Max lengte is {0} (opgegeven {1})",
"3aae63bb7e8e046641767571c1591441": "Aanmelding mislukt omdat e-mail niet is gecontroleerd", "3aae63bb7e8e046641767571c1591441": "Aanmelding mislukt omdat e-mail niet is gecontroleerd",
"3aecb24fa8bdd3f79d168761ca8a6729": "Onbekende {{middleware}}-fase {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "Onbekende {{middleware}}-fase {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} werkt met modelinstelling {1}, maar deze is niet meer beschikbaar.",
"3caaa84fc103d6d5612173ae6d43b245": "Ongeldig token: {0}", "3caaa84fc103d6d5612173ae6d43b245": "Ongeldig token: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Hartelijk dank voor uw registratie", "3d617953470be16d0c2b32f0bcfbb5ee": "Hartelijk dank voor uw registratie",
"3d63008ccfb2af1db2142e8cc2716ace": "Waarschuwing: Geen e-mailtransport opgegeven voor verzending van e-mail. Configureer een transport om e-mailberichten te verzenden.", "3d63008ccfb2af1db2142e8cc2716ace": "Waarschuwing: Geen e-mailtransport opgegeven voor verzending van e-mail. Configureer een transport om e-mailberichten te verzenden.",
"4203ab415ec66a78d3164345439ba76e": "{0} kan niet worden aangeroepen. {1}(). De methode {2} is niet geconfigureerd. De {{PersistedModel}} is niet correct gekoppeld aan een {{DataSource}}!", "4203ab415ec66a78d3164345439ba76e": "Kan {0}.{1}() niet aanroepen. De methode {2} is niet geconfigureerd. De {{PersistedModel}} is niet correct gekoppeld aan een {{DataSource}}!",
"42a36bac5cf03c4418d664500c81047a": "{{DataSource}}-optie {{\"defaultForType\"}} wordt niet meer ondersteund", "42a36bac5cf03c4418d664500c81047a": "{{DataSource}}-optie {{\"defaultForType\"}} wordt niet meer ondersteund",
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-mail is niet gevonden", "44a6c8b1ded4ed653d19ddeaaf89a606": "E-mail is niet gevonden",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Mail verzenden:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "Mail verzenden:",
"4b494de07f524703ac0879addbd64b13": "E-mail is niet geverifieerd", "4b494de07f524703ac0879addbd64b13": "E-mail is niet geverifieerd",
"528325f3cbf1b0ab9a08447515daac9a": "{0} van dit model bijwerken.",
"543d19bad5e47ee1e9eb8af688e857b4": "Externe sleutel voor {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Probleem bij maken van gegevensbron {0}: {1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Probleem bij maken van gegevensbron {0}: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "U moet verbinding maken tussen het model {{Email}} en een {{Mail}}-connector", "5858e63efaa0e4ad86b61c0459ea32fa": "U moet verbinding maken tussen het model {{Email}} en een {{Mail}}-connector",
"598ff0255ffd1d1b71e8de55dbe2c034": "Bestaan van {0}-relatie met item controleren op basis van ID.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Verwijder de {0}-relatie met een item op basis van ID.",
"5e81ad3847a290dc650b47618b9cbc7e": "Aanmelden is mislukt", "5e81ad3847a290dc650b47618b9cbc7e": "Aanmelden is mislukt",
"5fa3afb425819ebde958043e598cb664": "geen model gevonden met {{id}} {0}", "5fa3afb425819ebde958043e598cb664": "geen model gevonden met {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "Relatie '{0}' voor model '{1}' bestaat niet", "61e5deebaf44d68f4e6a508f30cc31a3": "Relatie '{0}' voor model '{1}' bestaat niet",
"62e8b0a733417978bab22c8dacf5d7e6": "Bulkupdates kunnen niet worden toegepast, de connector meldt niet het juiste aantal bijgewerkte records.", "62e8b0a733417978bab22c8dacf5d7e6": "Bulkupdates kunnen niet worden toegepast, de connector meldt niet het juiste aantal bijgewerkte records.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEKST: {0}", "63a091ced88001ab6acb58f61ec041c5": "\t TEKST: {0}",
"651f0b3cbba001635152ec3d3d954d0a": "Gerelateerd item zoeken op basis van ID voor {0}.",
"6bc376432cd9972cf991aad3de371e78": "Ontbrekende gegevens voor wijziging: {0}", "6bc376432cd9972cf991aad3de371e78": "Ontbrekende gegevens voor wijziging: {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} is niet gevonden",
"734a7bebb65e10899935126ba63dd51f": "De opties-eigenschap van de '{0}'-configuratie moet een object zijn", "734a7bebb65e10899935126ba63dd51f": "De opties-eigenschap van de '{0}'-configuratie moet een object zijn",
"779467f467862836e19f494a37d6ab77": "De acls-eigenschap van de '{0}'-configuratie moet een array objecten zijn", "779467f467862836e19f494a37d6ab77": "De acls-eigenschap van de '{0}'-configuratie moet een array objecten zijn",
"7bc7b301ad9c4fc873029d57fb9740fe": "Query's {0} van {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Externe sleutel voor {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Mijn eerste mobiele toepassing", "7d5e7ed0efaedf3f55f380caae0df8b8": "Mijn eerste mobiele toepassing",
"7e0fca41d098607e1c9aa353c67e0fa1": "Ongeldig toegangstoken", "7e0fca41d098607e1c9aa353c67e0fa1": "Ongeldig toegangstoken",
"7e287fc885d9fdcf42da3a12f38572c1": "Verplichte verificatie", "7e287fc885d9fdcf42da3a12f38572c1": "Verplichte verificatie",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} is vereist voor afmelding",
"80a32e80cbed65eba2103201a7c94710": "Model is niet gevonden: {0}", "80a32e80cbed65eba2103201a7c94710": "Model is niet gevonden: {0}",
"830cb6c862f8f364e9064cea0026f701": "Haalt hasOne-relatie {0} op.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Eigenschap '{0}' mag niet opnieuw worden geconfigureerd voor '{1}'", "83cbdc2560ba9f09155ccfc63e08f1a1": "Eigenschap '{0}' mag niet opnieuw worden geconfigureerd voor '{1}'",
"855eb8db89b4921c42072832d33d2dc2": "Ongeldig wachtwoord.",
"855ecd4a64885ba272d782435f72a4d4": "Onbekend \"{0}\"-ID \"{1}\".", "855ecd4a64885ba272d782435f72a4d4": "Onbekend \"{0}\"-ID \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "Transport biedt geen ondersteuning voor HTTP-omleidingen.", "860d1a0b8bd340411fb32baa72867989": "Transport biedt geen ondersteuning voor HTTP-omleidingen.",
"86254879d01a60826a851066987703f2": "Gerelateerd item toevoegen op basis van ID voor {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} of {{email}} is verplicht", "895b1f941d026870b3cc8e6af087c197": "{{username}} of {{email}} is verplicht",
"8a17c5ef611e2e7535792316e66b8fca": "Wachtwoord is te lang: {0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Aanvraag voor host {0}",
"8ae418c605b6a45f2651be9b1677c180": "Ongeldige niet-lokale methode: '{0}'", "8ae418c605b6a45f2651be9b1677c180": "Ongeldige niet-lokale methode: '{0}'",
"8bab6720ecc58ec6412358c858a53484": "Bulkupdate is mislukt; connector heeft een onverwacht aantal records gewijzigd: {0}", "8bab6720ecc58ec6412358c858a53484": "Bulkupdate is mislukt; connector heeft een onverwacht aantal records gewijzigd: {0}",
"8ecab7f534de38360bd1b1c88e880123": "Onderliggende modellen van '{0}' nemen de nieuw gedefinieerde niet-lokale methoden {1} niet over.", "8ecab7f534de38360bd1b1c88e880123": "Onderliggende modellen van '{0}' nemen de nieuw gedefinieerde niet-lokale methoden {1} niet over.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML: {0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML: {0}",
"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.", "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.",
"a40684f5a9f546115258b76938d1de37": "Een lijst van kleuren is beschikbaar op {{http://localhost:3000/colors}}", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Haalt belongsTo-relatie {0} op.",
"a50d10fc6e0959b220e085454c40381e": "Gebruiker is niet gevonden: {0}", "a50d10fc6e0959b220e085454c40381e": "Gebruiker is niet gevonden: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Onbekend \"{0}\" {{key}} \"{1}\".", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "Onbekend \"{0}\" {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} is verplicht", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} is verplicht",
"c0057a569ff9d3b509bac61a4b2f605d": "Verwijdert alle {0} van dit model.",
"c2b5d51f007178170ca3952d59640ca4": "Wijzigingen van {0} kunnen niet worden hersteld:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "Wijzigingen van {0} kunnen niet worden hersteld:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "{{accessToken}} is niet gevonden",
"c4ee6d177c974532c3552d2f98eb72ea": "{0} middleware is versie 3.0 verwijderd. Zie {1} voor meer informatie.", "c4ee6d177c974532c3552d2f98eb72ea": "{0} middleware is versie 3.0 verwijderd. Zie {1} voor meer informatie.",
"c61a5a02ba3801a892308f70f5d55a14": "Extern plaatsen (remoting) van metagegevens {{\"isStatic\"}} is gedeprecieerd. Geef {{\"prototype.name\"}} op in naam van methode in plaats van {{isStatic=false}}.", "c61a5a02ba3801a892308f70f5d55a14": "Extern plaatsen (remoting) van metagegevens {{\"isStatic\"}} is gedeprecieerd. Geef {{\"prototype.name\"}} op in naam van methode in plaats van {{isStatic=false}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "U moet een geldig e-mailadres opgeven", "c68a93f0a9524fed4ff64372fc90c55f": "U moet een geldig e-mailadres opgeven",
"cd0412f2f33a4a2a316acc834f3f21a6": "U moet een {{id}} of {{data}} opgeven", "cd0412f2f33a4a2a316acc834f3f21a6": "U moet een {{id}} of {{data}} opgeven",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} is verwijderd; gebruik in plaats daarvan de nieuwe module {{loopback-boot}}", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} is verwijderd; gebruik in plaats daarvan de nieuwe module {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "Maakt een nieuwe instance in {0} van dit model.",
"da13d3cdf21330557254670dddd8c5c7": "Aantal {0} van {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "Niet-object \"methods\"-instelling \"{0}\" wordt genegeerd.", "dc568bee32deb0f6eaf63e73b20e8ceb": "Niet-object \"methods\"-instelling \"{0}\" wordt genegeerd.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Onbekend \"{0}\" {{id}} \"{1}\".", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "Onbekend \"{0}\" {{id}} \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "Bulkupdate is mislukt; connector heeft een onverwacht aantal records gewist: {0}", "e92aa25b6b864e3454b65a7c422bd114": "Bulkupdate is mislukt; connector heeft een onverwacht aantal records gewist: {0}",
"ea63d226b6968e328bdf6876010786b5": "Bulkupdates kunnen niet worden toegepast, de connector meldt niet het juiste aantal gewiste records.", "ea63d226b6968e328bdf6876010786b5": "Bulkupdates kunnen niet worden toegepast, de connector meldt niet het juiste aantal gewiste records.",
"ead044e2b4bce74b4357f8a03fb78ec4": "{0} kan niet worden aangeroepen. {1}(). De methode {2} is niet geconfigureerd. De {{KeyValueModel}} is niet correct gekoppeld aan een {{DataSource}}!", "ead044e2b4bce74b4357f8a03fb78ec4": "Kan {0}.{1}() niet aanroepen. De methode {2} is niet geconfigureerd. De {{KeyValueModel}} is niet correct gekoppeld aan een {{DataSource}}!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t AAN: {0}", "ecb06666ef95e5db27a5ac1d6a17923b": "\t AAN: {0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT: {0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT: {0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "resultaat:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "resultaat:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflict", "f1d4ac54357cc0932f385d56814ba7e4": "Conflict",
"f58cdc481540cd1f69a4aa4da2e37981": "Ongeldige wachtwoord: {0}" "f66ae3cf379b2fce28575a3282defe1a": "Verwijdert {0} van dit model.",
"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}"
} }

93
intl/pl/messages.json Normal file
View File

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "Nie znaleziono rekordu zmiany dla elementu {0} o identyfikatorze {1}",
"04bd8af876f001ceaf443aad6a9002f9": "Uwierzytelnianie wymaga zdefiniowania modelu {0}.",
"095afbf2f1f0e5be678f5dac5c54e717": "Odmowa dostępu",
"0caffe1d763c8cca6a61814abe33b776": "Adres e-mail jest wymagany",
"0da38687fed24275c1547e815914a8e3": "Usuń pokrewny element wg identyfikatora dla {0}.",
"0e21aad369dd09e1965c11949303cefd": "Zdalne metadane dla {0}.{1} {{\"isStatic\"}} nie pasują do opartego na nazwie stylu nowej metody.",
"10e01c895dc0b2fecc385f9f462f1ca6": "lista kolorów jest dostępna pod adresem {{http://localhost:3000/colors}}",
"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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t TEMAT:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Utworzono: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Zaktualizuj pokrewny element wg identyfikatora dla {0}.",
"275f22ab95671f095640ca99194b7635": "\t OD:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "Element {0} został usunięty w wersji 3.0. Więcej informacji na ten temat zawiera sekcja {1}.",
"2d3071e3b18681c80a090dc0efbdb349": "nie można znaleźć elementu {0} o identyfikatorze {1}",
"316e5b82c203cf3de31a449ee07d0650": "Oczekiwano wartości boolowskiej, otrzymano {0}",
"320c482401afa1207c04343ab162e803": "Niepoprawny typ elementu głównego: {0}",
"3438fab56cc7ab92dfd88f0497e523e0": "Właściwość relacji konfiguracji `{0}` musi być obiektem",
"3591f1d3e115b46f9f195df5ca548a6a": "Nie znaleziono modelu: model `{0}` rozszerza nieznany model `{1}`.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Zweryfikuj swój adres e-mail, otwierając ten odsyłacz w przeglądarce WWW:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "Wprowadzone hasło było zbyt długie. Maksymalna liczba znaków: {0} (wprowadzono: {1}).",
"3aae63bb7e8e046641767571c1591441": "logowanie nie powiodło się, ponieważ adres e-mail nie został zweryfikowany",
"3aecb24fa8bdd3f79d168761ca8a6729": "Nieznana faza {{middleware}} {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} używa ustawienia modelu {1}, które nie jest już dostępne.",
"3caaa84fc103d6d5612173ae6d43b245": "Niepoprawny znacznik: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Dziękujemy za zarejestrowanie",
"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.",
"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}}!",
"42a36bac5cf03c4418d664500c81047a": "Opcja {{\"defaultForType\"}} źródła danych {{DataSource}} nie jest już obsługiwana",
"44a6c8b1ded4ed653d19ddeaaf89a606": "Nie znaleziono adresu e-mail",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Wysyłanie poczty:",
"4b494de07f524703ac0879addbd64b13": "Adres e-mail nie został zweryfikowany",
"528325f3cbf1b0ab9a08447515daac9a": "Zaktualizuj element {0} tego modelu.",
"543d19bad5e47ee1e9eb8af688e857b4": "Klucz obcy dla {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Nie można utworzyć źródła danych {0}: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "Należy połączyć model {{Email}} z konektorem {{Mail}}",
"598ff0255ffd1d1b71e8de55dbe2c034": "Sprawdź istnienie relacji {0} z elementem wg identyfikatora.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Usuń relację {0} z elementem wg identyfikatora.",
"5e81ad3847a290dc650b47618b9cbc7e": "logowanie nie powiodło się",
"5fa3afb425819ebde958043e598cb664": "nie można znaleźć modelu o identyfikatorze {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "Relacja `{0}` nie istnieje dla modelu `{1}'",
"62e8b0a733417978bab22c8dacf5d7e6": "Nie można zastosować aktualizacji masowej, ponieważ konektor nie raportuje poprawnie liczby zaktualizowanych rekordów.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEKST:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Znajdź pokrewny element wg identyfikatora dla {0}.",
"6bc376432cd9972cf991aad3de371e78": "Brak danych do zmiany: {0}",
"705c2d456a3e204c4af56e671ec3225c": "Nie można znaleźć obiektu {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "Właściwość options konfiguracji `{0}` musi być obiektem",
"779467f467862836e19f494a37d6ab77": "Właściwość acls konfiguracji `{0}` musi być tablicą obiektów",
"7bc7b301ad9c4fc873029d57fb9740fe": "Odpytuje element {0} w {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Klucz obcy dla {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Moja pierwsza aplikacja dla urządzeń przenośnych",
"7e0fca41d098607e1c9aa353c67e0fa1": "Niepoprawny znacznik dostępu",
"7e287fc885d9fdcf42da3a12f38572c1": "Wymagana autoryzacja",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} jest wymagany do wylogowania się",
"80a32e80cbed65eba2103201a7c94710": "Nie znaleziono modelu: {0}",
"830cb6c862f8f364e9064cea0026f701": "Pobiera relację hasOne {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Nie można zrekonfigurować właściwości `{0}` dla `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Niepoprawne hasło.",
"855ecd4a64885ba272d782435f72a4d4": "Nieznany identyfikator \"{0}\" \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "transport nie obsługuje przekierowań HTTP.",
"86254879d01a60826a851066987703f2": "Dodaj pokrewny element wg identyfikatora dla {0}.",
"895b1f941d026870b3cc8e6af087c197": "Wymagana jest {{username}} lub {{email}}",
"8ae418c605b6a45f2651be9b1677c180": "Niepoprawna metoda zdalna: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "Aktualizacja masowa nie powiodła się, konektor zmodyfikował nieoczekiwaną liczbę rekordów: {0}",
"8ecab7f534de38360bd1b1c88e880123": "Elementy potomne obiektu `{0}` nie odziedziczą nowo zdefiniowanych metod zdalnych {1}.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"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.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Pobiera relację belongsTo {0}.",
"a50d10fc6e0959b220e085454c40381e": "Nie znaleziono użytkownika: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Nieznany klucz {{key}} \"{0}\" \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "Właściwość {{realm}} jest wymagana.",
"c0057a569ff9d3b509bac61a4b2f605d": "Usuwa wszystkie {0} tego modelu.",
"c2b5d51f007178170ca3952d59640ca4": "Nie można skorygować {0} zmian: \n {1}",
"c4ee6d177c974532c3552d2f98eb72ea": "Warstwa pośrednia {0} została usunięta w wersji 3.0. Więcej informacji na ten temat zawiera sekcja {1}.",
"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}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Należy podać poprawny adres e-mail",
"cd0412f2f33a4a2a316acc834f3f21a6": "należy określić {{id}} lub {{data}}",
"d5552322de5605c58b62f47ad26d2716": "Moduł {{`app.boot`}} został usunięty; zamiast niego użyj nowego modułu {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "Tworzy nową instancję w elemencie {0} tego modelu.",
"da13d3cdf21330557254670dddd8c5c7": "Zlicza elementy {0} w {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "Ignorowanie niebędącego obiektem ustawienia \"methods\" elementu \"{0}\".",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Nieznany identyfikator {{id}} \"{0}\" \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "Aktualizacja masowa nie powiodła się, konektor usunął nieoczekiwaną liczbę rekordów: {0}",
"ea63d226b6968e328bdf6876010786b5": "Nie można zastosować aktualizacji masowej, ponieważ konektor nie raportuje poprawnie liczby usuniętych rekordów.",
"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}}!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t DO:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "wynik:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Konflikt",
"f66ae3cf379b2fce28575a3282defe1a": "Usuwa element {0} tego modelu.",
"f8e26bcca62a47f579562f1cd2c785ff": "Zmodyfikuj aplikację w celu użycia oficjalnego rozwiązania do wstrzykiwania argumentu \"options\" z kontekstu żądania,\npatrz sekcja {0}"
}

View File

@ -3,11 +3,13 @@
"04bd8af876f001ceaf443aad6a9002f9": "Autenticação requer que modelo {0} seja definido.", "04bd8af876f001ceaf443aad6a9002f9": "Autenticação requer que modelo {0} seja definido.",
"095afbf2f1f0e5be678f5dac5c54e717": "Acesso Negado", "095afbf2f1f0e5be678f5dac5c54e717": "Acesso Negado",
"0caffe1d763c8cca6a61814abe33b776": "E-mail é necessário", "0caffe1d763c8cca6a61814abe33b776": "E-mail é necessário",
"0e21aad369dd09e1965c11949303cefd": "Metadados remotos para {0}. {1} {{\"isStatic\"}} não corresponde ao novo estilo baseado em nome do método.", "0da38687fed24275c1547e815914a8e3": "Excluir um item relacionado por ID para {0}.",
"0e21aad369dd09e1965c11949303cefd": "Os metadados remotos para {0}.{1} {{\"isStatic\"}} não correspondem ao novo estilo baseado em nome do método. ",
"10e01c895dc0b2fecc385f9f462f1ca6": "uma lista de cores está disponível em {{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "uma lista de cores está disponível em {{http://localhost:3000/colors}}",
"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", "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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t ASSUNTO:{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t ASSUNTO:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Criado: {0}", "1e85f822b547a75d7d385048030e4ecb": "Criado: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Atualizar um item relacionado por ID para {0}.",
"275f22ab95671f095640ca99194b7635": "\t DE:{0}", "275f22ab95671f095640ca99194b7635": "\t DE:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} foi removido na versão 3.0. Consulte {1} para obter mais detalhes.", "2860bccdf9ef1e350c1a38932ed12173": "{0} foi removido na versão 3.0. Consulte {1} para obter mais detalhes.",
"2d3071e3b18681c80a090dc0efbdb349": "não foi possível localizar {0} com ID {1}", "2d3071e3b18681c80a090dc0efbdb349": "não foi possível localizar {0} com ID {1}",
@ -16,8 +18,10 @@
"3438fab56cc7ab92dfd88f0497e523e0": "A propriedade de relações da configuração de `{0}` deve ser um objeto", "3438fab56cc7ab92dfd88f0497e523e0": "A propriedade de relações da configuração de `{0}` deve ser um objeto",
"3591f1d3e115b46f9f195df5ca548a6a": "Modelo não localizado: modelo `{0}` está estendendo um modelo `{1}` desconhecido.", "3591f1d3e115b46f9f195df5ca548a6a": "Modelo não localizado: modelo `{0}` está estendendo um modelo `{1}` desconhecido.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Verifique seu e-mail abrindo este link em um navegador da web:\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "Verifique seu e-mail abrindo este link em um navegador da web:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "A senha inserida é muito longa. O comprimento máximo é de {0} ({1} inserido)",
"3aae63bb7e8e046641767571c1591441": "login com falha pois o e-mail não foi verificado", "3aae63bb7e8e046641767571c1591441": "login com falha pois o e-mail não foi verificado",
"3aecb24fa8bdd3f79d168761ca8a6729": "Fase {0} do {{middleware}} desconhecida", "3aecb24fa8bdd3f79d168761ca8a6729": "Fase {0} do {{middleware}} desconhecida",
"3ca45aa6f705c46a4c598a900716f086": "{0} está usando a configuração do modelo {1} que não está mais disponível.",
"3caaa84fc103d6d5612173ae6d43b245": "Token inválido: {0}", "3caaa84fc103d6d5612173ae6d43b245": "Token inválido: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Obrigado por se Registrar", "3d617953470be16d0c2b32f0bcfbb5ee": "Obrigado por se Registrar",
"3d63008ccfb2af1db2142e8cc2716ace": "Aviso: Nenhum transporte de e-mail especificado para enviar e-mail. Configure um transporte para enviar mensagens de e-mail.", "3d63008ccfb2af1db2142e8cc2716ace": "Aviso: Nenhum transporte de e-mail especificado para enviar e-mail. Configure um transporte para enviar mensagens de e-mail.",
@ -26,42 +30,54 @@
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-mail não encontrado", "44a6c8b1ded4ed653d19ddeaaf89a606": "E-mail não encontrado",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Enviando E-mail:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "Enviando E-mail:",
"4b494de07f524703ac0879addbd64b13": "E-mail não foi verificado", "4b494de07f524703ac0879addbd64b13": "E-mail não foi verificado",
"528325f3cbf1b0ab9a08447515daac9a": "Atualizar {0} deste modelo.",
"543d19bad5e47ee1e9eb8af688e857b4": "Chave estrangeira para {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Não é possível criar origem de dados {0}: {1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Não é possível criar origem de dados {0}: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "Deve-se conectar o Modelo de {{Email}} em um conector de {{Mail}}", "5858e63efaa0e4ad86b61c0459ea32fa": "Deve-se conectar o Modelo de {{Email}} em um conector de {{Mail}}",
"598ff0255ffd1d1b71e8de55dbe2c034": "Verifique a existência da relação de {0} com um item por ID.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Remova a relação de {0} com um item por ID.",
"5e81ad3847a290dc650b47618b9cbc7e": "falha de login", "5e81ad3847a290dc650b47618b9cbc7e": "falha de login",
"5fa3afb425819ebde958043e598cb664": "não foi possível localizar um modelo com {{id}} {0}", "5fa3afb425819ebde958043e598cb664": "não foi possível localizar um modelo com {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "Relação `{0}` não existe para o modelo `{1}`", "61e5deebaf44d68f4e6a508f30cc31a3": "Relação `{0}` não existe para o modelo `{1}`",
"62e8b0a733417978bab22c8dacf5d7e6": "Não é possível aplicar atualizações em massa, o conector não relata o número de registros de atualização corretamente.", "62e8b0a733417978bab22c8dacf5d7e6": "Não é possível aplicar atualizações em massa, o conector não relata o número de registros de atualização corretamente.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXTO:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t TEXTO:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Localize um item relacionado por ID para {0}.",
"6bc376432cd9972cf991aad3de371e78": "Dados ausentes para a mudança: {0}", "6bc376432cd9972cf991aad3de371e78": "Dados ausentes para a mudança: {0}",
"705c2d456a3e204c4af56e671ec3225c": "Não foi possível localizar o {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "A propriedade de opções da configuração de `{0}` deve ser um objeto", "734a7bebb65e10899935126ba63dd51f": "A propriedade de opções da configuração de `{0}` deve ser um objeto",
"779467f467862836e19f494a37d6ab77": "A propriedade acls da configuração de `{0}` deve ser uma matriz de objetos", "779467f467862836e19f494a37d6ab77": "A propriedade acls da configuração de `{0}` deve ser uma matriz de objetos",
"7bc7b301ad9c4fc873029d57fb9740fe": "{0} consultas de {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Chave estrangeira para {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Meu primeiro aplicativo móvel", "7d5e7ed0efaedf3f55f380caae0df8b8": "Meu primeiro aplicativo móvel",
"7e0fca41d098607e1c9aa353c67e0fa1": "Token de Acesso Inválido", "7e0fca41d098607e1c9aa353c67e0fa1": "Token de Acesso Inválido",
"7e287fc885d9fdcf42da3a12f38572c1": "Autorização Necessária", "7e287fc885d9fdcf42da3a12f38572c1": "Autorização Necessária",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} é necessário para efetuar logout",
"80a32e80cbed65eba2103201a7c94710": "Modelo não localizado: {0}", "80a32e80cbed65eba2103201a7c94710": "Modelo não localizado: {0}",
"830cb6c862f8f364e9064cea0026f701": "Busca relação {0} de hasOne.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "A propriedade `{0}` não pode ser reconfigurada para `{1}`", "83cbdc2560ba9f09155ccfc63e08f1a1": "A propriedade `{0}` não pode ser reconfigurada para `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Senha inválida.",
"855ecd4a64885ba272d782435f72a4d4": "ID \"{1}\" de \"{0}\" desconhecido.", "855ecd4a64885ba272d782435f72a4d4": "ID \"{1}\" de \"{0}\" desconhecido.",
"860d1a0b8bd340411fb32baa72867989": "O transporte não suporta redirecionamentos de HTTP.", "860d1a0b8bd340411fb32baa72867989": "O transporte não suporta redirecionamentos de HTTP.",
"86254879d01a60826a851066987703f2": "Inclua um item relacionado por ID para {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} ou {{email}} é necessário", "895b1f941d026870b3cc8e6af087c197": "{{username}} ou {{email}} é necessário",
"8a17c5ef611e2e7535792316e66b8fca": "Senha muito longa: {0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "Solicitação para o host {0}",
"8ae418c605b6a45f2651be9b1677c180": "Método remoto inválido: `{0}`", "8ae418c605b6a45f2651be9b1677c180": "Método remoto inválido: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "Atualização em massa falhou, o conector modificou um número inesperado de registros: {0}", "8bab6720ecc58ec6412358c858a53484": "Atualização em massa falhou, o conector modificou um número inesperado de registros: {0}",
"8ecab7f534de38360bd1b1c88e880123": "Modelos filhos de `{0}` não herdarão métodos remotos recém-definidos {1}.", "8ecab7f534de38360bd1b1c88e880123": "Modelos filhos de `{0}` não herdarão métodos remotos recém-definidos {1}.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"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.", "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.",
"a40684f5a9f546115258b76938d1de37": "Uma lista de cores está disponível em {{http://localhost:3000/colors}}", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Busca relação {0} de belongsTo.",
"a50d10fc6e0959b220e085454c40381e": "Usuário não localizado: {0}", "a50d10fc6e0959b220e085454c40381e": "Usuário não localizado: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" desconhecido.", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" desconhecido.",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} é obrigatório", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} é obrigatório",
"c0057a569ff9d3b509bac61a4b2f605d": "Exclui todos os {0} deste modelo.",
"c2b5d51f007178170ca3952d59640ca4": "Não é possível retificar mudanças de {0}:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "Não é possível retificar mudanças de {0}:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "não foi possível localizar {{accessToken}}",
"c4ee6d177c974532c3552d2f98eb72ea": "{0} middleware foi removido na versão 3.0. Consulte {1} para obter mais detalhes.", "c4ee6d177c974532c3552d2f98eb72ea": "{0} middleware foi removido na versão 3.0. Consulte {1} para obter mais detalhes.",
"c61a5a02ba3801a892308f70f5d55a14": "Metadados {{\"isStatic\"}} remotos estão descontinuados. Especifique {{\"prototype.name\"}} no nome do método em vez de para {{isStatic=false}}.", "c61a5a02ba3801a892308f70f5d55a14": "Metadados {{\"isStatic\"}} remotos estão descontinuados. Especifique {{\"prototype.name\"}} no nome do método em vez de para {{isStatic=false}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Deve-se fornecer um e-mail válido", "c68a93f0a9524fed4ff64372fc90c55f": "Deve-se fornecer um e-mail válido",
"cd0412f2f33a4a2a316acc834f3f21a6": "deve-se especificar um {{id}} ou {{data}}", "cd0412f2f33a4a2a316acc834f3f21a6": "deve-se especificar um {{id}} ou {{data}}",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} foi removido, use o novo módulo {{loopback-boot}} no lugar", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} foi removido, use o novo módulo {{loopback-boot}} no lugar",
"d6f43b266533b04d442bdb3955622592": "Cria uma nova instância no {0} deste modelo.",
"da13d3cdf21330557254670dddd8c5c7": "{0} contagens de {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "Ignorando configuração de \"methods\" de não objeto de \"{0}\".", "dc568bee32deb0f6eaf63e73b20e8ceb": "Ignorando configuração de \"methods\" de não objeto de \"{0}\".",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" desconhecido.", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" desconhecido.",
"e92aa25b6b864e3454b65a7c422bd114": "Atualização em massa falhou, o conector excluiu um número inesperado de registros: {0}", "e92aa25b6b864e3454b65a7c422bd114": "Atualização em massa falhou, o conector excluiu um número inesperado de registros: {0}",
@ -71,6 +87,7 @@
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTE:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTE:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "resultado:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "resultado:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflito", "f1d4ac54357cc0932f385d56814ba7e4": "Conflito",
"f58cdc481540cd1f69a4aa4da2e37981": "Senha inválida: {0}" "f66ae3cf379b2fce28575a3282defe1a": "Exclui {0} deste modelo.",
"f8e26bcca62a47f579562f1cd2c785ff": "Retrabalhe seu aplicativo para usar a solução oficial para injetar o argumento \"options\" do contexto da solicitação,\nconsulte {0}"
} }

93
intl/ru/messages.json Normal file
View File

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "Не удалось найти записи изменений для {0} с ИД {1}",
"04bd8af876f001ceaf443aad6a9002f9": "Для идентификации необходимо определить модель {0}.",
"095afbf2f1f0e5be678f5dac5c54e717": "Доступ запрещен",
"0caffe1d763c8cca6a61814abe33b776": "Необходимо указать адрес электронной почты",
"0da38687fed24275c1547e815914a8e3": "Удалить связанный элемент по ИД для {0}.",
"0e21aad369dd09e1965c11949303cefd": "Метаданные удаленного соединения для {0}.{1} {{\"isStatic\"}} не соответствуют новому стилю на основе имени метода.",
"10e01c895dc0b2fecc385f9f462f1ca6": "Список цветов доступен по адресу {{http://localhost:3000/colors}}",
"1b2a6076dccbe91a56f1672eb3b8598c": "Тело ответа содержит свойства маркера {{AccessToken}}, созданного при входе в систему.\nВ зависимости от значения параметра `include`, тело может содержать дополнительные свойства:\n\n - `user` - `U+007BUserU+007D` - данные пользователя, вошедшего в систему. {{(`include=user`)}}\n\n",
"1d7833c3ca2f05fdad8fad7537531c40": "\t ТЕМА:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Кем создано: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Изменить связанный элемент по ИД для {0}.",
"275f22ab95671f095640ca99194b7635": "\t От кого:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} удален в версии 3.0. Дополнительные сведения приведены в {1}.",
"2d3071e3b18681c80a090dc0efbdb349": "не удалось найти {0} с ИД {1}",
"316e5b82c203cf3de31a449ee07d0650": "Ожидался тип boolean, получен тип {0}",
"320c482401afa1207c04343ab162e803": "Недопустимый тип субъекта: {0}",
"3438fab56cc7ab92dfd88f0497e523e0": "Свойство relations конфигурации `{0}` должно быть объектом",
"3591f1d3e115b46f9f195df5ca548a6a": "Модель не найдена: модель `{0}` расширяет неизвестную модель `{1}`.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Проверьте адрес электронной почты. Для этого откройте ссылку в веб-браузере:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "Введен слишком длинный пароль. Максимальная длина составляет {0} символов (введено {1} символов)",
"3aae63bb7e8e046641767571c1591441": "Вход в систему не выполнен, так как не проверен адрес электронной почты",
"3aecb24fa8bdd3f79d168761ca8a6729": "Неизвестный этап {{middleware}} {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} использует параметр модели {1}, который больше не доступен.",
"3caaa84fc103d6d5612173ae6d43b245": "Недопустимый маркер: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Спасибо за регистрацию",
"3d63008ccfb2af1db2142e8cc2716ace": "Предупреждение: не указан транспортный протокол для отправки электронной почты. Настройте транспортный протокол для отправки сообщений электронной почты.",
"4203ab415ec66a78d3164345439ba76e": "Не удалось вызвать {0}. {1} (). Метод {2} не настроен. Модель {{PersistedModel}} неправильно подключена к источнику данных {{DataSource}}!",
"42a36bac5cf03c4418d664500c81047a": "Опция {{DataSource}} {{\"defaultForType\"}} больше не поддерживается",
"44a6c8b1ded4ed653d19ddeaaf89a606": "Не найден адрес электронной почты",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Отправка почты:",
"4b494de07f524703ac0879addbd64b13": "Не проверен адрес электронной почты",
"528325f3cbf1b0ab9a08447515daac9a": "Обновить {0} этой модели.",
"543d19bad5e47ee1e9eb8af688e857b4": "Внешний ключ для {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Не удалось создать источник данных {0}: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "Необходимо подключить модель {{Email}} к коннектору {{Mail}}",
"598ff0255ffd1d1b71e8de55dbe2c034": "Проверьте существование связи {0} с элементом по ИД.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Удалить связь {0} с элементом по ИД.",
"5e81ad3847a290dc650b47618b9cbc7e": "Вход в систему не выполнен",
"5fa3afb425819ebde958043e598cb664": "Не удалось найти модель с {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "Связь `{0}` не существует для модели `{1}`",
"62e8b0a733417978bab22c8dacf5d7e6": "Не удалось применить обновления большого объема данных, коннектор неправильно сообщает о числе обновленных записей.",
"63a091ced88001ab6acb58f61ec041c5": "\t ТЕКСТ: {0}",
"651f0b3cbba001635152ec3d3d954d0a": "Найти связанный элемент по ИД для {0}.",
"6bc376432cd9972cf991aad3de371e78": "Отсутствуют данные для изменения: {0}",
"705c2d456a3e204c4af56e671ec3225c": "Не удалось найти {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "Свойство options конфигурации `{0}` должно быть объектом",
"779467f467862836e19f494a37d6ab77": "Свойство acls конфигурации `{0}` должно быть массивом объектов",
"7bc7b301ad9c4fc873029d57fb9740fe": "{0} запросов из {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Внешний ключ для {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Мое первое мобильное приложение",
"7e0fca41d098607e1c9aa353c67e0fa1": "Недопустимый маркер доступа",
"7e287fc885d9fdcf42da3a12f38572c1": "Требуется авторизация",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} требуется для завершения сеанса",
"80a32e80cbed65eba2103201a7c94710": "Модель не найдена: {0}",
"830cb6c862f8f364e9064cea0026f701": "Получает связь hasOne {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Не удается повторно выполнить конфигурацию свойства `{0}` для `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Недопустимый пароль.",
"855ecd4a64885ba272d782435f72a4d4": "Неизвестный ИД \"{0}\" \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "Транспортный протокол не поддерживает перенаправление HTTP.",
"86254879d01a60826a851066987703f2": "Добавить связанный элемент по ИД для {0}.",
"895b1f941d026870b3cc8e6af087c197": "Необходимо указать {{username}} или {{email}}",
"8ae418c605b6a45f2651be9b1677c180": "Недопустимый удаленный метод: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "Обновление большого объема данных не выполнено, коннектор изменил непредвиденное число записей: {0}",
"8ecab7f534de38360bd1b1c88e880123": "Дочерние модели ` {0} ' не будут наследовать только что определенные удаленные методы {1}.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "В конфигурации `{0}` отсутствует свойство {{`dataSource`}}.\nДля пометки моделей, которые не подключены в источникам данных, используйте значение `null` или `false`.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Получает связь belongsTo {0}.",
"a50d10fc6e0959b220e085454c40381e": "Пользователь не найден: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Неизвестный {{key}} \"{0}\" \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "Необходимо указать {{realm}}",
"c0057a569ff9d3b509bac61a4b2f605d": "Удалить все {0} этой модели.",
"c2b5d51f007178170ca3952d59640ca4": "Не удалось исправить изменения {0}:\n{1}",
"c4ee6d177c974532c3552d2f98eb72ea": "Промежуточное ПО {0} удалено в версии 3.0. Дополнительные сведения приведены в {1}.",
"c61a5a02ba3801a892308f70f5d55a14": "Метаданные удаленного соединения {{\"isStatic\"}} устарели. В имени метода вместо {{isStatic=false}} укажите {{\"prototype.name\"}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Необходимо указать допустимый адрес электронной почты",
"cd0412f2f33a4a2a316acc834f3f21a6": "Необходимо указать {{id}} или {{data}}",
"d5552322de5605c58b62f47ad26d2716": "Модуль {{`app.boot`}} был удален, используйте вместо него новый модуль {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "Создает новый экземпляр в {0} этой модели.",
"da13d3cdf21330557254670dddd8c5c7": "Считает {0} из {1}.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "Не относящийся к объекту параметр \"methods\" для \"{0}\" игнорируется.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Неизвестный {{id}} \"{0}\" \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "Обновление большого объема данных не выполнено, коннектор удалил непредвиденное число записей: {0}",
"ea63d226b6968e328bdf6876010786b5": "Не удалось применить обновления большого объема данных, коннектор неправильно сообщает о числе удаленных записей.",
"ead044e2b4bce74b4357f8a03fb78ec4": "Не удалось вызвать {0}.{1}(). Метод {2} не настроен. Модель {{KeyValueModel}} неправильно подключена к источнику данных {{DataSource}}!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t Кому:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t Транспортный протокол:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "результат:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Конфликт",
"f66ae3cf379b2fce28575a3282defe1a": "Удалить {0} этой модели.",
"f8e26bcca62a47f579562f1cd2c785ff": "Доработайте приложение с целью использования официального решения для добавления аргумента \"options\" из контекста запроса,\nсм. {0}"
}

View File

@ -3,11 +3,13 @@
"04bd8af876f001ceaf443aad6a9002f9": "Kimlik doğrulaması {0} modelinin tanımlanmasını gerektiriyor.", "04bd8af876f001ceaf443aad6a9002f9": "Kimlik doğrulaması {0} modelinin tanımlanmasını gerektiriyor.",
"095afbf2f1f0e5be678f5dac5c54e717": "Erişim Verilmedi", "095afbf2f1f0e5be678f5dac5c54e717": "Erişim Verilmedi",
"0caffe1d763c8cca6a61814abe33b776": "E-posta zorunludur", "0caffe1d763c8cca6a61814abe33b776": "E-posta zorunludur",
"0da38687fed24275c1547e815914a8e3": "{0} için ilgili bir öğeyi tanıtıcı temelinde siler.",
"0e21aad369dd09e1965c11949303cefd": "{0}.{1} {{\"isStatic\"}} ile ilgili uzaktan iletişim meta verisi, yöntem adına dayalı yeni stille eşleşmiyor.", "0e21aad369dd09e1965c11949303cefd": "{0}.{1} {{\"isStatic\"}} ile ilgili uzaktan iletişim meta verisi, yöntem adına dayalı yeni stille eşleşmiyor.",
"10e01c895dc0b2fecc385f9f462f1ca6": "renklerin listesine şu adresle erişebilirsiniz: {{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "renklerin listesine şu adresle erişebilirsiniz: {{http://localhost:3000/colors}}",
"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", "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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t KONU:{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t KONU:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Yaratıldığı tarih: {0}", "1e85f822b547a75d7d385048030e4ecb": "Yaratıldığı tarih: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "{0} için ilgili bir öğeyi tanıtıcı temelinde günceller.",
"275f22ab95671f095640ca99194b7635": "\t KİMDEN:{0}", "275f22ab95671f095640ca99194b7635": "\t KİMDEN:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0}, 3.0 sürümünde kaldırıldı. Daha fazla ayrıntı için bkz. {1}.", "2860bccdf9ef1e350c1a38932ed12173": "{0}, 3.0 sürümünde kaldırıldı. Daha fazla ayrıntı için bkz. {1}.",
"2d3071e3b18681c80a090dc0efbdb349": "{1} tanıtıcılı {0} bulunamadı", "2d3071e3b18681c80a090dc0efbdb349": "{1} tanıtıcılı {0} bulunamadı",
@ -16,61 +18,76 @@
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` yapılandırmasının ilişkiler (relations) özelliği bir nesne olmalıdır", "3438fab56cc7ab92dfd88f0497e523e0": "`{0}` yapılandırmasının ilişkiler (relations) özelliği bir nesne olmalıdır",
"3591f1d3e115b46f9f195df5ca548a6a": "Model bulunamadı: `{0}` modeli, bilinmeyen `{1}` modelini genişletiyor.", "3591f1d3e115b46f9f195df5ca548a6a": "Model bulunamadı: `{0}` modeli, bilinmeyen `{1}` modelini genişletiyor.",
"35e5252c62d80f8c54a5290d30f4c7d0": "Lütfen bu bağlantıyı bir web tarayıcısında açarak e-postanızı doğrulayın:\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "Lütfen bu bağlantıyı bir web tarayıcısında açarak e-postanızı doğrulayın:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "Girilen parola çok uzundu. Uzunluk üst sınırı: {0} (girilen: {1})",
"3aae63bb7e8e046641767571c1591441": "e-posta doğrulanmadığından oturum açma başarısız oldu", "3aae63bb7e8e046641767571c1591441": "e-posta doğrulanmadığından oturum açma başarısız oldu",
"3aecb24fa8bdd3f79d168761ca8a6729": "Bilinmeyen {{middleware}} aşaması {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "Bilinmeyen {{middleware}} aşaması {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0}, artık kullanılabilir olmayan {1} model ayarını kullanıyor.",
"3caaa84fc103d6d5612173ae6d43b245": "Geçersiz belirteç: {0}", "3caaa84fc103d6d5612173ae6d43b245": "Geçersiz belirteç: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Kaydolduğunuz için teşekkürler", "3d617953470be16d0c2b32f0bcfbb5ee": "Kaydolduğunuz için teşekkürler",
"3d63008ccfb2af1db2142e8cc2716ace": "Uyarı: E-posta göndermek için e-posta aktarımı belirtilmedi. Posta iletileri göndermek için aktarım ayarlayın.", "3d63008ccfb2af1db2142e8cc2716ace": "Uyarı: E-posta göndermek için e-posta aktarımı belirtilmedi. Posta iletileri göndermek için aktarım ayarlayın.",
"4203ab415ec66a78d3164345439ba76e": "Çağrılamıyor: {0}.{1}(). {2} yöntemi ayarlanmamış. {{PersistedModel}}, bir veri kaynağına ({{DataSource}}) doğru olarak eklenmedi!", "4203ab415ec66a78d3164345439ba76e": "{0}.{1}() çağrılamıyor. {2} yöntemi ayarlanmamış. {{PersistedModel}}, bir veri kaynağına ({{DataSource}}) doğru olarak eklenmedi!",
"42a36bac5cf03c4418d664500c81047a": "{{DataSource}} seçeneği {{\"defaultForType\"}} artık desteklenmiyor.", "42a36bac5cf03c4418d664500c81047a": "{{DataSource}} seçeneği {{\"defaultForType\"}} artık desteklenmiyor.",
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-posta bulunamadı", "44a6c8b1ded4ed653d19ddeaaf89a606": "E-posta bulunamadı",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Posta Gönderiliyor:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "Posta Gönderiliyor:",
"4b494de07f524703ac0879addbd64b13": "E-posta doğrulanmadı", "4b494de07f524703ac0879addbd64b13": "E-posta doğrulanmadı",
"528325f3cbf1b0ab9a08447515daac9a": "Bu modele ilişkin {0} güncellemesi.",
"543d19bad5e47ee1e9eb8af688e857b4": "{0} için dış anahtar.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Veri kaynağı {0} yaratılamıyor: {1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Veri kaynağı {0} yaratılamıyor: {1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} modelini bir {{Mail}} bağlayıcısına bağlamalısınız", "5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} modelini bir {{Mail}} bağlayıcısına bağlamalısınız",
"598ff0255ffd1d1b71e8de55dbe2c034": "Bir öğeye yönelik {0} ilişkisinin var olup olmadığını tanıtıcı temelinde denetler.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "Bir öğeye yönelik {0} ilişkisini kaldırır.",
"5e81ad3847a290dc650b47618b9cbc7e": "oturum açma başarısız oldu", "5e81ad3847a290dc650b47618b9cbc7e": "oturum açma başarısız oldu",
"5fa3afb425819ebde958043e598cb664": "{{id}} {0} tanıtıcılı bir model bulunamadı", "5fa3afb425819ebde958043e598cb664": "{{id}} {0} tanıtıcılı bir model bulunamadı",
"61e5deebaf44d68f4e6a508f30cc31a3": "`{1}` modeli için `{0}` ilişkisi yok", "61e5deebaf44d68f4e6a508f30cc31a3": "`{1}` modeli için `{0}` ilişkisi yok",
"62e8b0a733417978bab22c8dacf5d7e6": "Toplu güncelleme uygulanamaz; bağlayıcı, güncellenen kayıtların sayısını doğru olarak bildirmiyor.", "62e8b0a733417978bab22c8dacf5d7e6": "Toplu güncelleme uygulanamaz; bağlayıcı, güncellenen kayıtların sayısını doğru olarak bildirmiyor.",
"63a091ced88001ab6acb58f61ec041c5": "\t METİN:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t METİN:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "{0} için ilgili bir öğeyi tanıtıcı temelinde bulur.",
"6bc376432cd9972cf991aad3de371e78": "Değişiklik için veri eksik: {0}", "6bc376432cd9972cf991aad3de371e78": "Değişiklik için veri eksik: {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} bulunamadı",
"734a7bebb65e10899935126ba63dd51f": "`{0}` yapılandırmasının seçenekler (options) özelliği bir nesne olmalıdır.", "734a7bebb65e10899935126ba63dd51f": "`{0}` yapılandırmasının seçenekler (options) özelliği bir nesne olmalıdır.",
"779467f467862836e19f494a37d6ab77": "`{0}` yapılandırmasının erişim denetim listeleri (acls) özelliği bir nesne dizisi olmalıdır.", "779467f467862836e19f494a37d6ab77": "`{0}` yapılandırmasının erişim denetim listeleri (acls) özelliği bir nesne dizisi olmalıdır.",
"7bc7b301ad9c4fc873029d57fb9740fe": "{1} ile ilişkili {0} öğesini sorgular.",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0} için dış anahtar",
"7d5e7ed0efaedf3f55f380caae0df8b8": "İlk mobil uygulamam", "7d5e7ed0efaedf3f55f380caae0df8b8": "İlk mobil uygulamam",
"7e0fca41d098607e1c9aa353c67e0fa1": "Geçersiz Erişim Belirteci", "7e0fca41d098607e1c9aa353c67e0fa1": "Geçersiz Erişim Belirteci",
"7e287fc885d9fdcf42da3a12f38572c1": "Yetkilendirme Gerekli", "7e287fc885d9fdcf42da3a12f38572c1": "Yetkilendirme Gerekli",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "Oturumu kapatmak için {{accessToken}} zorunludur",
"80a32e80cbed65eba2103201a7c94710": "Model bulunamadı: {0}", "80a32e80cbed65eba2103201a7c94710": "Model bulunamadı: {0}",
"830cb6c862f8f364e9064cea0026f701": "{0} hasOne ilişkisini alır.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "`{0}` özelliği `{1}` için yeniden yapılandırılamıyor", "83cbdc2560ba9f09155ccfc63e08f1a1": "`{0}` özelliği `{1}` için yeniden yapılandırılamıyor",
"855eb8db89b4921c42072832d33d2dc2": "Geçersiz parola.",
"855ecd4a64885ba272d782435f72a4d4": "Bilinmeyen \"{0}\" tanıtıcısı \"{1}\".", "855ecd4a64885ba272d782435f72a4d4": "Bilinmeyen \"{0}\" tanıtıcısı \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "Aktarım HTTP yeniden yönlendirmelerini desteklemiyor.", "860d1a0b8bd340411fb32baa72867989": "Aktarım HTTP yeniden yönlendirmelerini desteklemiyor.",
"86254879d01a60826a851066987703f2": "{0} için ilgili bir öğeyi tanıtıcı temelinde ekler.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} ya da {{email}} zorunludur", "895b1f941d026870b3cc8e6af087c197": "{{username}} ya da {{email}} zorunludur",
"8a17c5ef611e2e7535792316e66b8fca": "Parola çok uzun: {0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "{0} ana makinesine yönelik istek",
"8ae418c605b6a45f2651be9b1677c180": "Uzak yöntem geçersiz: `{0}`", "8ae418c605b6a45f2651be9b1677c180": "Uzak yöntem geçersiz: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "Toplu güncelleme başarısız oldu, bağlayıcı beklenmeyen sayıda kaydı değiştirdi: {0}", "8bab6720ecc58ec6412358c858a53484": "Toplu güncelleme başarısız oldu, bağlayıcı beklenmeyen sayıda kaydı değiştirdi: {0}",
"8ecab7f534de38360bd1b1c88e880123": "`{0}` alt modelleri, yeni tanımlanan uzak yöntemleri ({1}) devralmayacaktır.", "8ecab7f534de38360bd1b1c88e880123": "`{0}` alt modelleri, yeni tanımlanan uzak yöntemleri ({1}) devralmayacaktır.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"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.", "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.",
"a40684f5a9f546115258b76938d1de37": "Renklerin listesine şu adresle erişebilirsiniz: {{http://localhost:3000/colors}}", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "{0} belongsTo ilişkisini alır.",
"a50d10fc6e0959b220e085454c40381e": "Kullanıcı bulunamadı: {0}", "a50d10fc6e0959b220e085454c40381e": "Kullanıcı bulunamadı: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Bilinmeyen \"{0}\" {{key}} \"{1}\".", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "Bilinmeyen \"{0}\" {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} zorunludur", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} zorunludur",
"c0057a569ff9d3b509bac61a4b2f605d": "Bu modele ilişkin tüm {0} öğelerini siler.",
"c2b5d51f007178170ca3952d59640ca4": "{0} değişiklik düzeltilemiyor:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "{0} değişiklik düzeltilemiyor:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "{{accessToken}} bulunamadı",
"c4ee6d177c974532c3552d2f98eb72ea": "{0} ara katman yazılımı, 3.0 sürümünde kaldırıldı. Daha fazla ayrıntı için bkz. {1}.", "c4ee6d177c974532c3552d2f98eb72ea": "{0} ara katman yazılımı, 3.0 sürümünde kaldırıldı. Daha fazla ayrıntı için bkz. {1}.",
"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.", "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.",
"c68a93f0a9524fed4ff64372fc90c55f": "Geçerli bir e-posta belirtilmeli", "c68a93f0a9524fed4ff64372fc90c55f": "Geçerli bir e-posta belirtilmeli",
"cd0412f2f33a4a2a316acc834f3f21a6": "bir {{id}} ya da {{data}} belirtmelidir", "cd0412f2f33a4a2a316acc834f3f21a6": "bir {{id}} ya da {{data}} belirtmelidir",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} kaldırıldı, onun yerine yeni {{loopback-boot}} modülünü kullanın", "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} kaldırıldı, onun yerine yeni {{loopback-boot}} modülünü kullanın",
"d6f43b266533b04d442bdb3955622592": "Bu modele ilişkin {0} içinde yeni eşgörünüm yaratır.",
"da13d3cdf21330557254670dddd8c5c7": "{1} ile ilişkili {0} öğesini sayar.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\" öğesinin nesne olmayan \"methods\" atarı yoksayılıyor.", "dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\" öğesinin nesne olmayan \"methods\" atarı yoksayılıyor.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Bilinmeyen \"{0}\" {{id}} \"{1}\".", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "Bilinmeyen \"{0}\" {{id}} \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "Toplu güncelleme başarısız oldu, bağlayıcı beklenmeyen sayıda kaydı sildi: {0}", "e92aa25b6b864e3454b65a7c422bd114": "Toplu güncelleme başarısız oldu, bağlayıcı beklenmeyen sayıda kaydı sildi: {0}",
"ea63d226b6968e328bdf6876010786b5": "Toplu güncelleme uygulanamaz; bağlayıcı, silinen kayıtların sayısını doğru olarak bildirmiyor.", "ea63d226b6968e328bdf6876010786b5": "Toplu güncelleme uygulanamaz; bağlayıcı, silinen kayıtların sayısını doğru olarak bildirmiyor.",
"ead044e2b4bce74b4357f8a03fb78ec4": "Çağrılamıyor: {0}.{1}(). {2} yöntemi ayarlanmamış. {{KeyValueModel}}, bir veri kaynağına ({{DataSource}}) doğru olarak eklenmedi!", "ead044e2b4bce74b4357f8a03fb78ec4": "{0}.{1}() çağrılamıyor. {2} yöntemi ayarlanmamış. {{KeyValueModel}}, bir veri kaynağına ({{DataSource}}) doğru olarak eklenmedi!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t KİME:{0}", "ecb06666ef95e5db27a5ac1d6a17923b": "\t KİME:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t AKTARIM:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t AKTARIM:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "sonuç:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "sonuç:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Çakışma", "f1d4ac54357cc0932f385d56814ba7e4": "Çakışma",
"f58cdc481540cd1f69a4aa4da2e37981": "Geçersiz parola: {0}" "f66ae3cf379b2fce28575a3282defe1a": "Bu modele ilişkin {0} öğesini siler.",
"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}"
} }

View File

@ -3,21 +3,25 @@
"04bd8af876f001ceaf443aad6a9002f9": "认证需要定义模型 {0}。", "04bd8af876f001ceaf443aad6a9002f9": "认证需要定义模型 {0}。",
"095afbf2f1f0e5be678f5dac5c54e717": "拒绝访问", "095afbf2f1f0e5be678f5dac5c54e717": "拒绝访问",
"0caffe1d763c8cca6a61814abe33b776": "电子邮件是必需的", "0caffe1d763c8cca6a61814abe33b776": "电子邮件是必需的",
"0da38687fed24275c1547e815914a8e3": "按标识删除 {0} 的相关项。",
"0e21aad369dd09e1965c11949303cefd": "{0}.{1} {{\"isStatic\"}} 的远程处理元数据不匹配新的基于方法名称的样式。", "0e21aad369dd09e1965c11949303cefd": "{0}.{1} {{\"isStatic\"}} 的远程处理元数据不匹配新的基于方法名称的样式。",
"10e01c895dc0b2fecc385f9f462f1ca6": "颜色列表位于:{{http://localhost:3000/colors}}", "10e01c895dc0b2fecc385f9f462f1ca6": "颜色列表位于:{{http://localhost:3000/colors}}",
"1b2a6076dccbe91a56f1672eb3b8598c": "响应主体包含在登录时创建的 {{AccessToken}} 的属性。\n根据“include”参数的值主体可包含其他属性\n\n - `user` - `U+007BUserU+007D` - 当前已登录用户的数据。 {{(`include=user`)}}\n\n", "1b2a6076dccbe91a56f1672eb3b8598c": "响应主体包含在登录时创建的 {{AccessToken}} 的属性。\n根据“include”参数的值主体可包含其他属性\n\n - `user` - `U+007BUserU+007D` - 当前已登录用户的数据。 {{(`include=user`)}}\n\n",
"1d7833c3ca2f05fdad8fad7537531c40": "\t主题{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t主题{0}",
"1e85f822b547a75d7d385048030e4ecb": "创建时间:{0}", "1e85f822b547a75d7d385048030e4ecb": "创建时间:{0}",
"22fe62fa8d595b72c62208beddaa2a56": "按标识更新 {0} 的相关项。",
"275f22ab95671f095640ca99194b7635": "\t发件人{0}", "275f22ab95671f095640ca99194b7635": "\t发件人{0}",
"2860bccdf9ef1e350c1a38932ed12173": "V3.0 中了 {0}。请参阅 {1} 以获取更多详细信息。", "2860bccdf9ef1e350c1a38932ed12173": "V3.0 中除了 {0}。请参阅 {1} 以获取更多详细信息。",
"2d3071e3b18681c80a090dc0efbdb349": "无法找到标识为 {1} 的 {0}", "2d3071e3b18681c80a090dc0efbdb349": "无法找到标识为 {1} 的 {0}",
"316e5b82c203cf3de31a449ee07d0650": "期望布尔值,获取 {0}", "316e5b82c203cf3de31a449ee07d0650": "期望布尔值,获取 {0}",
"320c482401afa1207c04343ab162e803": "无效的主体类型:{0}", "320c482401afa1207c04343ab162e803": "无效的主体类型:{0}",
"3438fab56cc7ab92dfd88f0497e523e0": "“{0}”配置的关系属性必须是对象。", "3438fab56cc7ab92dfd88f0497e523e0": "“{0}”配置的关系属性必须是对象。",
"3591f1d3e115b46f9f195df5ca548a6a": "找不到模型:模型“{0}”正在扩展未知的模型“{1}”。", "3591f1d3e115b46f9f195df5ca548a6a": "找不到模型:模型“{0}”正在扩展未知的模型“{1}”。",
"35e5252c62d80f8c54a5290d30f4c7d0": "请通过在 Web 浏览器中打开此链接来验证您的电子邮件:\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "请通过在 Web 浏览器中打开此链接来验证您的电子邮件:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "输入的密码过长。最大长度为 {0}(输入的长度为 {1}",
"3aae63bb7e8e046641767571c1591441": "因为尚未验证电子邮件,登录失败", "3aae63bb7e8e046641767571c1591441": "因为尚未验证电子邮件,登录失败",
"3aecb24fa8bdd3f79d168761ca8a6729": "未知的 {{middleware}} 阶段 {0}", "3aecb24fa8bdd3f79d168761ca8a6729": "未知的 {{middleware}} 阶段 {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} 正在使用目前不再可用的模型设置 {1}。",
"3caaa84fc103d6d5612173ae6d43b245": "无效的令牌:{0}", "3caaa84fc103d6d5612173ae6d43b245": "无效的令牌:{0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "感谢您注册", "3d617953470be16d0c2b32f0bcfbb5ee": "感谢您注册",
"3d63008ccfb2af1db2142e8cc2716ace": "警告:未指定用于发送电子邮件的电子邮件传输。设置传输以发送电子邮件消息。", "3d63008ccfb2af1db2142e8cc2716ace": "警告:未指定用于发送电子邮件的电子邮件传输。设置传输以发送电子邮件消息。",
@ -26,42 +30,54 @@
"44a6c8b1ded4ed653d19ddeaaf89a606": "找不到电子邮件", "44a6c8b1ded4ed653d19ddeaaf89a606": "找不到电子邮件",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "正在发送电子邮件:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "正在发送电子邮件:",
"4b494de07f524703ac0879addbd64b13": "尚未验证电子邮件", "4b494de07f524703ac0879addbd64b13": "尚未验证电子邮件",
"528325f3cbf1b0ab9a08447515daac9a": "更新此模型的 {0}。",
"543d19bad5e47ee1e9eb8af688e857b4": "{0} 的外键。",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "无法创建数据源 {0}{1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "无法创建数据源 {0}{1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "您必须将 {{Email}} 模型连接到 {{Mail}} 连接器", "5858e63efaa0e4ad86b61c0459ea32fa": "您必须将 {{Email}} 模型连接到 {{Mail}} 连接器",
"598ff0255ffd1d1b71e8de55dbe2c034": "按标识检查项的 {0} 关系是否存在。",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "按标识除去项的 {0} 关系。",
"5e81ad3847a290dc650b47618b9cbc7e": "登录失败", "5e81ad3847a290dc650b47618b9cbc7e": "登录失败",
"5fa3afb425819ebde958043e598cb664": "找不到具有 {{id}} {0} 的模型", "5fa3afb425819ebde958043e598cb664": "找不到具有 {{id}} {0} 的模型",
"61e5deebaf44d68f4e6a508f30cc31a3": "对于模型“{1}”,关系“{0}”不存在", "61e5deebaf44d68f4e6a508f30cc31a3": "对于模型“{1}”,关系“{0}”不存在",
"62e8b0a733417978bab22c8dacf5d7e6": "无法应用批量更新,连接器未正确报告更新的记录数。", "62e8b0a733417978bab22c8dacf5d7e6": "无法应用批量更新,连接器未正确报告更新的记录数。",
"63a091ced88001ab6acb58f61ec041c5": "\t 文本:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t 文本:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "按标识查找 {0} 的相关项。",
"6bc376432cd9972cf991aad3de371e78": "缺少更改的数据:{0}", "6bc376432cd9972cf991aad3de371e78": "缺少更改的数据:{0}",
"705c2d456a3e204c4af56e671ec3225c": "无法找到 {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "“{0}”配置的选项属性必须是对象。", "734a7bebb65e10899935126ba63dd51f": "“{0}”配置的选项属性必须是对象。",
"779467f467862836e19f494a37d6ab77": "“{0}”配置的 acls 属性必须是对象数组。", "779467f467862836e19f494a37d6ab77": "“{0}”配置的 acls 属性必须是对象数组。",
"7bc7b301ad9c4fc873029d57fb9740fe": "查询 {1} 的 {0}。",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0} 的外键",
"7d5e7ed0efaedf3f55f380caae0df8b8": "我的第一个移动应用程序", "7d5e7ed0efaedf3f55f380caae0df8b8": "我的第一个移动应用程序",
"7e0fca41d098607e1c9aa353c67e0fa1": "无效的访问令牌", "7e0fca41d098607e1c9aa353c67e0fa1": "无效的访问令牌",
"7e287fc885d9fdcf42da3a12f38572c1": "需要授权", "7e287fc885d9fdcf42da3a12f38572c1": "需要授权",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} 需要注销",
"80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}", "80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}",
"830cb6c862f8f364e9064cea0026f701": "访存 hasOne 关系 {0}。",
"83cbdc2560ba9f09155ccfc63e08f1a1": "无法针对“{1}”重新配置属性“{0}”。", "83cbdc2560ba9f09155ccfc63e08f1a1": "无法针对“{1}”重新配置属性“{0}”。",
"855eb8db89b4921c42072832d33d2dc2": "密码无效。",
"855ecd4a64885ba272d782435f72a4d4": "未知的“{0}”标识“{1}”。", "855ecd4a64885ba272d782435f72a4d4": "未知的“{0}”标识“{1}”。",
"860d1a0b8bd340411fb32baa72867989": "传输不支持 HTTP 重定向。", "860d1a0b8bd340411fb32baa72867989": "传输不支持 HTTP 重定向。",
"86254879d01a60826a851066987703f2": "按标识添加 {0} 的相关项。",
"895b1f941d026870b3cc8e6af087c197": "{{username}} 或 {{email}} 是必需的", "895b1f941d026870b3cc8e6af087c197": "{{username}} 或 {{email}} 是必需的",
"8a17c5ef611e2e7535792316e66b8fca": "密码过长:{0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "到主机 {0} 的请求",
"8ae418c605b6a45f2651be9b1677c180": "无效的远程方法:“{0}”", "8ae418c605b6a45f2651be9b1677c180": "无效的远程方法:“{0}”",
"8bab6720ecc58ec6412358c858a53484": "批量更新失败,连接器已修改意外数量的记录:{0}", "8bab6720ecc58ec6412358c858a53484": "批量更新失败,连接器已修改意外数量的记录:{0}",
"8ecab7f534de38360bd1b1c88e880123": "“{0}”的子模型不会继承最新定义的远程方法 {1}。", "8ecab7f534de38360bd1b1c88e880123": "“{0}”的子模型不会继承最新定义的远程方法 {1}。",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "“{0}”的配置缺少 {{`dataSource`}} 属性。\n使用“null”或“false”来标记未附加到任何数据源的模型。", "97795efe0c3eb7f35ce8cf8cfe70682b": "“{0}”的配置缺少 {{`dataSource`}} 属性。\n使用“null”或“false”来标记未附加到任何数据源的模型。",
"a40684f5a9f546115258b76938d1de37": "颜色列表位于:{{http://localhost:3000/colors}}", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "访存 belongsTo 关系 {0}。",
"a50d10fc6e0959b220e085454c40381e": "找不到用户:{0}", "a50d10fc6e0959b220e085454c40381e": "找不到用户:{0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "未知的“{0}”{{key}}“{1}”。", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "未知的“{0}”{{key}}“{1}”。",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} 是必需的", "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} 是必需的",
"c0057a569ff9d3b509bac61a4b2f605d": "删除此模型的所有 {0}。",
"c2b5d51f007178170ca3952d59640ca4": "无法纠正 {0} 更改:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "无法纠正 {0} 更改:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "找不到 {{accessToken}}", "c4ee6d177c974532c3552d2f98eb72ea": "V3.0 中移除了 {0} 中间件。请参阅 {1} 以获取更多详细信息。",
"c4ee6d177c974532c3552d2f98eb72ea": "V3.0 中除去了 {0} 中间件。请参阅 {1} 以获取更多详细信息。",
"c61a5a02ba3801a892308f70f5d55a14": "不推荐使用远程处理元数据 {{\"isStatic\"}}。而是针对 {{isStatic=false}} 在方法名称中指定 {{\"prototype.name\"}}。", "c61a5a02ba3801a892308f70f5d55a14": "不推荐使用远程处理元数据 {{\"isStatic\"}}。而是针对 {{isStatic=false}} 在方法名称中指定 {{\"prototype.name\"}}。",
"c68a93f0a9524fed4ff64372fc90c55f": "必须提供有效电子邮件", "c68a93f0a9524fed4ff64372fc90c55f": "必须提供有效电子邮件",
"cd0412f2f33a4a2a316acc834f3f21a6": "必须指定 {{id}} 或 {{data}}", "cd0412f2f33a4a2a316acc834f3f21a6": "必须指定 {{id}} 或 {{data}}",
"d5552322de5605c58b62f47ad26d2716": "已除去 {{`app.boot`}},请改用新模块 {{loopback-boot}}", "d5552322de5605c58b62f47ad26d2716": "已移除 {{`app.boot`}},请改用新模块 {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "在此模型的 {0} 中创建新实例。",
"da13d3cdf21330557254670dddd8c5c7": "计算 {0} 的数量({1})。",
"dc568bee32deb0f6eaf63e73b20e8ceb": "忽略“{0}”的非对象“方法”设置。", "dc568bee32deb0f6eaf63e73b20e8ceb": "忽略“{0}”的非对象“方法”设置。",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "未知的“{0}”{{id}}“{1}”。", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "未知的“{0}”{{id}}“{1}”。",
"e92aa25b6b864e3454b65a7c422bd114": "批量更新失败,连接器已删除意外数量的记录:{0}", "e92aa25b6b864e3454b65a7c422bd114": "批量更新失败,连接器已删除意外数量的记录:{0}",
@ -71,6 +87,7 @@
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t 传输:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t 传输:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "结果:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "结果:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "冲突", "f1d4ac54357cc0932f385d56814ba7e4": "冲突",
"f58cdc481540cd1f69a4aa4da2e37981": "无效的密码:{0}" "f66ae3cf379b2fce28575a3282defe1a": "删除此模型的 {0}。",
"f8e26bcca62a47f579562f1cd2c785ff": "请重新设计您的应用程序以使用正式解决方案插入来自请求上下文的“options”自变量\n请参阅 {0}"
} }

View File

@ -3,11 +3,13 @@
"04bd8af876f001ceaf443aad6a9002f9": "需要定義模型 {0} 才能鑑別。", "04bd8af876f001ceaf443aad6a9002f9": "需要定義模型 {0} 才能鑑別。",
"095afbf2f1f0e5be678f5dac5c54e717": "拒絕存取", "095afbf2f1f0e5be678f5dac5c54e717": "拒絕存取",
"0caffe1d763c8cca6a61814abe33b776": "需要電子郵件", "0caffe1d763c8cca6a61814abe33b776": "需要電子郵件",
"0e21aad369dd09e1965c11949303cefd": "{0}.{1} 的遠端 meta 資料 {{\"isStatic\"}} 不符合新的方法名稱型樣式。", "0da38687fed24275c1547e815914a8e3": "依 id 刪除 {0} 的相關項目。",
"0e21aad369dd09e1965c11949303cefd": "{0} 的遠端 meta 資料。{1} {{\"isStatic\"}} 不符合新的方法名稱型樣式。",
"10e01c895dc0b2fecc385f9f462f1ca6": "{{http://localhost:3000/colors}} 提供顏色清單", "10e01c895dc0b2fecc385f9f462f1ca6": "{{http://localhost:3000/colors}} 提供顏色清單",
"1b2a6076dccbe91a56f1672eb3b8598c": "回應內文包含登入時建立的 {{AccessToken}} 的內容。\n根據 `include` 參數的值而定,內文可能包含其他內容:\n\n - `user` - `U+007BUserU+007D` - 目前登入的使用者的資料。 {{(`include=user`)}}\n\n", "1b2a6076dccbe91a56f1672eb3b8598c": "回應內文包含登入時建立的 {{AccessToken}} 的內容。\n根據 `include` 參數的值而定,內文可能包含其他內容:\n\n - `user` - `U+007BUserU+007D` - 目前登入的使用者的資料。 {{(`include=user`)}}\n\n",
"1d7833c3ca2f05fdad8fad7537531c40": "\t 主旨:{0}", "1d7833c3ca2f05fdad8fad7537531c40": "\t 主旨:{0}",
"1e85f822b547a75d7d385048030e4ecb": "已建立:{0}", "1e85f822b547a75d7d385048030e4ecb": "已建立:{0}",
"22fe62fa8d595b72c62208beddaa2a56": "依 id 更新 {0} 的相關項目。",
"275f22ab95671f095640ca99194b7635": "\t 寄件者:{0}", "275f22ab95671f095640ca99194b7635": "\t 寄件者:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "3.0 版中已移除 {0}。如需詳細資料,請參閱 {1}。", "2860bccdf9ef1e350c1a38932ed12173": "3.0 版中已移除 {0}。如需詳細資料,請參閱 {1}。",
"2d3071e3b18681c80a090dc0efbdb349": "找不到 id 為 {1} 的 {0}", "2d3071e3b18681c80a090dc0efbdb349": "找不到 id 為 {1} 的 {0}",
@ -16,61 +18,76 @@
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 配置的 relations 內容必須是物件", "3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 配置的 relations 內容必須是物件",
"3591f1d3e115b46f9f195df5ca548a6a": "找不到模型:模型 `{0}` 正在延伸不明模型 `{1}`。", "3591f1d3e115b46f9f195df5ca548a6a": "找不到模型:模型 `{0}` 正在延伸不明模型 `{1}`。",
"35e5252c62d80f8c54a5290d30f4c7d0": "請在 Web 瀏覽器中開啟此鏈結來驗證電子郵件:\n\t{0}", "35e5252c62d80f8c54a5290d30f4c7d0": "請在 Web 瀏覽器中開啟此鏈結來驗證電子郵件:\n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "輸入的密碼太長。長度上限為 {0}(輸入了 {1}",
"3aae63bb7e8e046641767571c1591441": "因為尚未驗證電子郵件,所以登入失敗", "3aae63bb7e8e046641767571c1591441": "因為尚未驗證電子郵件,所以登入失敗",
"3aecb24fa8bdd3f79d168761ca8a6729": "{{middleware}} 階段 {0} 不明", "3aecb24fa8bdd3f79d168761ca8a6729": "{{middleware}} 階段 {0} 不明",
"3ca45aa6f705c46a4c598a900716f086": "{0} 正在使用已不再可用的模型設定 {1}。",
"3caaa84fc103d6d5612173ae6d43b245": "無效記號:{0}", "3caaa84fc103d6d5612173ae6d43b245": "無效記號:{0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "感謝您登錄", "3d617953470be16d0c2b32f0bcfbb5ee": "感謝您登錄",
"3d63008ccfb2af1db2142e8cc2716ace": "警告:未指定用於傳送電子郵件的電子郵件傳輸。請設定傳輸來傳送郵件訊息。", "3d63008ccfb2af1db2142e8cc2716ace": "警告:未指定用於傳送電子郵件的電子郵件傳輸。請設定傳輸來傳送郵件訊息。",
"4203ab415ec66a78d3164345439ba76e": "無法呼叫 {0}.{1}()。尚未設定 {2} 方法。{{PersistedModel}} 未正確連接至 {{DataSource}}", "4203ab415ec66a78d3164345439ba76e": "無法呼叫 {0}{1}()。尚未設定 {2} 方法。{{PersistedModel}} 未正確連接至 {{DataSource}}",
"42a36bac5cf03c4418d664500c81047a": "不再支援 {{DataSource}} 選項 {{\"defaultForType\"}}", "42a36bac5cf03c4418d664500c81047a": "不再支援 {{DataSource}} 選項 {{\"defaultForType\"}}",
"44a6c8b1ded4ed653d19ddeaaf89a606": "找不到電子郵件", "44a6c8b1ded4ed653d19ddeaaf89a606": "找不到電子郵件",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "正在傳送郵件:", "4a4f04a4e480fc5d4ee73b84d9a4b904": "正在傳送郵件:",
"4b494de07f524703ac0879addbd64b13": "尚未驗證電子郵件", "4b494de07f524703ac0879addbd64b13": "尚未驗證電子郵件",
"528325f3cbf1b0ab9a08447515daac9a": "更新這個模型的 {0}。",
"543d19bad5e47ee1e9eb8af688e857b4": "{0} 的外部索引鍵。",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "無法建立資料來源 {0}{1}", "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "無法建立資料來源 {0}{1}",
"5858e63efaa0e4ad86b61c0459ea32fa": "您必須將 {{Email}} 模型連接至 {{Mail}} 連接器", "5858e63efaa0e4ad86b61c0459ea32fa": "您必須將 {{Email}} 模型連接至 {{Mail}} 連接器",
"598ff0255ffd1d1b71e8de55dbe2c034": "依 id 檢查項目的 {0} 關係是否存在。",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "依 id 移除項目的 {0} 關係。",
"5e81ad3847a290dc650b47618b9cbc7e": "登入失敗", "5e81ad3847a290dc650b47618b9cbc7e": "登入失敗",
"5fa3afb425819ebde958043e598cb664": "找不到 {{id}} 為 {0} 的模型", "5fa3afb425819ebde958043e598cb664": "找不到 {{id}} 為 {0} 的模型",
"61e5deebaf44d68f4e6a508f30cc31a3": "模型 `{1}` 的關係 `{0}` 不存在", "61e5deebaf44d68f4e6a508f30cc31a3": "模型 `{1}` 的關係 `{0}` 不存在",
"62e8b0a733417978bab22c8dacf5d7e6": "無法套用大量更新,連接器未正確報告已更新的記錄數。", "62e8b0a733417978bab22c8dacf5d7e6": "無法套用大量更新,連接器未正確報告已更新的記錄數。",
"63a091ced88001ab6acb58f61ec041c5": "\t 文字:{0}", "63a091ced88001ab6acb58f61ec041c5": "\t 文字:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "依 id 尋找 {0} 的相關項目。",
"6bc376432cd9972cf991aad3de371e78": "遺漏變更的資料:{0}", "6bc376432cd9972cf991aad3de371e78": "遺漏變更的資料:{0}",
"705c2d456a3e204c4af56e671ec3225c": "找不到 {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "`{0}` 配置的 options 內容必須是物件", "734a7bebb65e10899935126ba63dd51f": "`{0}` 配置的 options 內容必須是物件",
"779467f467862836e19f494a37d6ab77": "`{0}` 配置的 acls 內容必須是物件陣列", "779467f467862836e19f494a37d6ab77": "`{0}` 配置的 acls 內容必須是物件陣列",
"7bc7b301ad9c4fc873029d57fb9740fe": "查詢 {0} 個(共 {1} 個)。",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0} 的外部索引鍵",
"7d5e7ed0efaedf3f55f380caae0df8b8": "我的第一個行動式應用程式", "7d5e7ed0efaedf3f55f380caae0df8b8": "我的第一個行動式應用程式",
"7e0fca41d098607e1c9aa353c67e0fa1": "存取記號無效", "7e0fca41d098607e1c9aa353c67e0fa1": "存取記號無效",
"7e287fc885d9fdcf42da3a12f38572c1": "需要授權", "7e287fc885d9fdcf42da3a12f38572c1": "需要授權",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "需要 {{accessToken}} 才能登出",
"80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}", "80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}",
"830cb6c862f8f364e9064cea0026f701": "提取 hasOne 關係 {0}。",
"83cbdc2560ba9f09155ccfc63e08f1a1": "無法為 `{1}` 重新配置內容 `{0}`", "83cbdc2560ba9f09155ccfc63e08f1a1": "無法為 `{1}` 重新配置內容 `{0}`",
"855eb8db89b4921c42072832d33d2dc2": "密碼無效。",
"855ecd4a64885ba272d782435f72a4d4": "\"{0}\" ID \"{1}\" 不明。", "855ecd4a64885ba272d782435f72a4d4": "\"{0}\" ID \"{1}\" 不明。",
"860d1a0b8bd340411fb32baa72867989": "傳輸不支援 HTTP 重新導向。", "860d1a0b8bd340411fb32baa72867989": "傳輸不支援 HTTP 重新導向。",
"86254879d01a60826a851066987703f2": "依 id 新增 {0} 的相關項目。",
"895b1f941d026870b3cc8e6af087c197": "需要 {{username}} 或 {{email}}", "895b1f941d026870b3cc8e6af087c197": "需要 {{username}} 或 {{email}}",
"8a17c5ef611e2e7535792316e66b8fca": "密碼太長:{0}",
"8a27e0c9ce3ebf0e0c3978efb456e13e": "向主機 {0} 要求",
"8ae418c605b6a45f2651be9b1677c180": "無效的遠端方法:`{0}`", "8ae418c605b6a45f2651be9b1677c180": "無效的遠端方法:`{0}`",
"8bab6720ecc58ec6412358c858a53484": "大量更新失敗,連接器已修改超乎預期的記錄數:{0}", "8bab6720ecc58ec6412358c858a53484": "大量更新失敗,連接器已修改超乎預期的記錄數:{0}",
"8ecab7f534de38360bd1b1c88e880123": "`{0}` 的子項模型將不會繼承新定義的遠端方法 {1}。", "8ecab7f534de38360bd1b1c88e880123": "`{0}` 的子項模型將不會繼承新定義的遠端方法 {1}。",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML{0}", "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}` 的配置遺漏 {{`dataSource`}} 內容。\n請使用 `null` 或 `false` 來標示未連接至任何資料來源的模型。", "97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}` 的配置遺漏 {{`dataSource`}} 內容。\n請使用 `null` 或 `false` 來標示未連接至任何資料來源的模型。",
"a40684f5a9f546115258b76938d1de37": "{{http://localhost:3000/colors}} 提供顏色清單", "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "提取 belongsTo 關係 {0}。",
"a50d10fc6e0959b220e085454c40381e": "找不到使用者:{0}", "a50d10fc6e0959b220e085454c40381e": "找不到使用者:{0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" 不明。", "b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" 不明。",
"ba96498b10c179f9cd75f75c8def4f70": "需要 {{realm}}", "ba96498b10c179f9cd75f75c8def4f70": "需要 {{realm}}",
"c0057a569ff9d3b509bac61a4b2f605d": "刪除這個模型的所有 {0}。",
"c2b5d51f007178170ca3952d59640ca4": "無法更正 {0} 個變更:\n{1}", "c2b5d51f007178170ca3952d59640ca4": "無法更正 {0} 個變更:\n{1}",
"c34fa20eea0091747fcc9eda204b8d37": "找不到 {{accessToken}}",
"c4ee6d177c974532c3552d2f98eb72ea": "3.0 版中已移除 {0} 中介軟體。如需詳細資料,請參閱 {1}。", "c4ee6d177c974532c3552d2f98eb72ea": "3.0 版中已移除 {0} 中介軟體。如需詳細資料,請參閱 {1}。",
"c61a5a02ba3801a892308f70f5d55a14": "遠端 meta 資料 {{\"isStatic\"}} 已淘汰。請在方法名稱中指定 {{\"prototype.name\"}} 來代替 {{isStatic=false}}。", "c61a5a02ba3801a892308f70f5d55a14": "遠端 meta 資料 {{\"isStatic\"}} 已淘汰。請在方法名稱中指定 {{\"prototype.name\"}} 來代替 {{isStatic=false}}。",
"c68a93f0a9524fed4ff64372fc90c55f": "必須提供有效的電子郵件", "c68a93f0a9524fed4ff64372fc90c55f": "必須提供有效的電子郵件",
"cd0412f2f33a4a2a316acc834f3f21a6": "必須指定 {{id}} 或 {{data}}", "cd0412f2f33a4a2a316acc834f3f21a6": "必須指定 {{id}} 或 {{data}}",
"d5552322de5605c58b62f47ad26d2716": "已移除 {{`app.boot`}},請改用新的模組 {{loopback-boot}}", "d5552322de5605c58b62f47ad26d2716": "已移除 {{`app.boot`}},請改用新的模組 {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "在這個模型的 {0} 中建立新實例。",
"da13d3cdf21330557254670dddd8c5c7": "計算 {0} 個(共 {1} 個)。",
"dc568bee32deb0f6eaf63e73b20e8ceb": "忽略 \"{0}\" 的非物件 \"methods\" 設定。", "dc568bee32deb0f6eaf63e73b20e8ceb": "忽略 \"{0}\" 的非物件 \"methods\" 設定。",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" 不明。", "e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" 不明。",
"e92aa25b6b864e3454b65a7c422bd114": "大量更新失敗,連接器已刪除非預期的記錄數:{0}", "e92aa25b6b864e3454b65a7c422bd114": "大量更新失敗,連接器已刪除非預期的記錄數:{0}",
"ea63d226b6968e328bdf6876010786b5": "無法套用大量更新,連接器未正確報告已刪除的記錄數。", "ea63d226b6968e328bdf6876010786b5": "無法套用大量更新,連接器未正確報告已刪除的記錄數。",
"ead044e2b4bce74b4357f8a03fb78ec4": "無法呼叫 {0}.{1}()。尚未設定 {2} 方法。{{KeyValueModel}} 未正確連接至 {{DataSource}}", "ead044e2b4bce74b4357f8a03fb78ec4": "無法呼叫 {0}{1}()。尚未設定 {2} 方法。{{KeyValueModel}} 未正確連接至 {{DataSource}}",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t 收件者:{0}", "ecb06666ef95e5db27a5ac1d6a17923b": "\t 收件者:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t 傳輸:{0}", "f0aed00a3d3d0b97d6594e4b70e0c201": "\t 傳輸:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "結果:{0}", "f0bd73df8714cefb925e3b8da2f4c5f6": "結果:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "衝突", "f1d4ac54357cc0932f385d56814ba7e4": "衝突",
"f58cdc481540cd1f69a4aa4da2e37981": "無效密碼:{0}" "f66ae3cf379b2fce28575a3282defe1a": "刪除這個模型的 {0}。",
"f8e26bcca62a47f579562f1cd2c785ff": "請重做您的應用程式以使用正式解決方案,從要求環境定義注入 \"options\" 引數,\n請參閱 {0}"
} }

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
var loopback = require('./loopback'); const loopback = require('./loopback');
var debug = require('debug')('loopback:security:access-context'); const debug = require('debug')('loopback:security:access-context');
const DEFAULT_SCOPES = ['DEFAULT']; const DEFAULT_SCOPES = ['DEFAULT'];
@ -50,7 +50,7 @@ function AccessContext(context) {
'Application registry is mandatory in AccessContext but missing in provided context'); 'Application registry is mandatory in AccessContext but missing in provided context');
this.registry = context.registry; this.registry = context.registry;
this.principals = context.principals || []; this.principals = context.principals || [];
var model = context.model; let model = context.model;
model = ('string' === typeof model) ? this.registry.getModel(model) : model; model = ('string' === typeof model) ? this.registry.getModel(model) : model;
this.model = model; this.model = model;
this.modelName = model && model.modelName; this.modelName = model && model.modelName;
@ -77,20 +77,19 @@ function AccessContext(context) {
'AccessToken model must be defined before AccessContext model'); 'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS; this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
var principalType = context.principalType || Principal.USER; const principalType = context.principalType || Principal.USER;
var principalId = context.principalId || undefined; const principalId = context.principalId || undefined;
var principalName = context.principalName || undefined; const principalName = context.principalName || undefined;
if (principalId != null) {
if (principalId) {
this.addPrincipal(principalType, principalId, principalName); this.addPrincipal(principalType, principalId, principalName);
} }
var token = this.accessToken || {}; const token = this.accessToken;
if (token.userId) { if (token.userId != null) {
this.addPrincipal(Principal.USER, token.userId); this.addPrincipal(token.principalType || Principal.USER, token.userId);
} }
if (token.appId) { if (token.appId != null) {
this.addPrincipal(Principal.APPLICATION, token.appId); this.addPrincipal(Principal.APPLICATION, token.appId);
} }
this.remotingContext = context.remotingContext; this.remotingContext = context.remotingContext;
@ -127,9 +126,9 @@ AccessContext.permissionOrder = {
* @returns {boolean} * @returns {boolean}
*/ */
AccessContext.prototype.addPrincipal = function(principalType, principalId, principalName) { AccessContext.prototype.addPrincipal = function(principalType, principalId, principalName) {
var principal = new Principal(principalType, principalId, principalName); const principal = new Principal(principalType, principalId, principalName);
for (var i = 0; i < this.principals.length; i++) { for (let i = 0; i < this.principals.length; i++) {
var p = this.principals[i]; const p = this.principals[i];
if (p.equals(principal)) { if (p.equals(principal)) {
return false; return false;
} }
@ -143,7 +142,7 @@ AccessContext.prototype.addPrincipal = function(principalType, principalId, prin
* @returns {*} * @returns {*}
*/ */
AccessContext.prototype.getUserId = function() { AccessContext.prototype.getUserId = function() {
var user = this.getUser(); const user = this.getUser();
return user && user.id; return user && user.id;
}; };
@ -152,10 +151,10 @@ AccessContext.prototype.getUserId = function() {
* @returns {*} * @returns {*}
*/ */
AccessContext.prototype.getUser = function() { AccessContext.prototype.getUser = function() {
var BaseUser = this.registry.getModel('User'); const BaseUser = this.registry.getModel('User');
for (var i = 0; i < this.principals.length; i++) { for (let i = 0; i < this.principals.length; i++) {
var p = this.principals[i]; const p = this.principals[i];
var isBuiltinPrincipal = p.type === Principal.APP || const isBuiltinPrincipal = p.type === Principal.APP ||
p.type === Principal.ROLE || p.type === Principal.ROLE ||
p.type == Principal.SCOPE; p.type == Principal.SCOPE;
if (isBuiltinPrincipal) continue; if (isBuiltinPrincipal) continue;
@ -166,7 +165,7 @@ AccessContext.prototype.getUser = function() {
} }
// or permit to resolve a valid user model // or permit to resolve a valid user model
var userModel = this.registry.findModel(p.type); const userModel = this.registry.findModel(p.type);
if (!userModel) continue; if (!userModel) continue;
if (userModel.prototype instanceof BaseUser) { if (userModel.prototype instanceof BaseUser) {
return {id: p.id, principalType: p.type}; return {id: p.id, principalType: p.type};
@ -179,8 +178,8 @@ AccessContext.prototype.getUser = function() {
* @returns {*} * @returns {*}
*/ */
AccessContext.prototype.getAppId = function() { AccessContext.prototype.getAppId = function() {
for (var i = 0; i < this.principals.length; i++) { for (let i = 0; i < this.principals.length; i++) {
var p = this.principals[i]; const p = this.principals[i];
if (p.type === Principal.APPLICATION) { if (p.type === Principal.APPLICATION) {
return p.id; return p.id;
} }
@ -193,7 +192,7 @@ AccessContext.prototype.getAppId = function() {
* @returns {boolean} * @returns {boolean}
*/ */
AccessContext.prototype.isAuthenticated = function() { AccessContext.prototype.isAuthenticated = function() {
return !!(this.getUserId() || this.getAppId()); return this.getUserId() != null || this.getAppId() != null;
}; };
/** /**
@ -326,7 +325,7 @@ function AccessRequest(model, property, accessType, permission, methodNames, reg
} }
if (arguments.length === 1 && typeof model === 'object') { if (arguments.length === 1 && typeof model === 'object') {
// The argument is an object that contains all required properties // The argument is an object that contains all required properties
var obj = model || {}; const obj = model || {};
this.model = obj.model || AccessContext.ALL; this.model = obj.model || AccessContext.ALL;
this.property = obj.property || AccessContext.ALL; this.property = obj.property || AccessContext.ALL;
this.accessType = obj.accessType || AccessContext.ALL; this.accessType = obj.accessType || AccessContext.ALL;
@ -364,10 +363,10 @@ AccessRequest.prototype.isWildcard = function() {
*/ */
AccessRequest.prototype.exactlyMatches = function(acl) { AccessRequest.prototype.exactlyMatches = function(acl) {
var matchesModel = acl.model === this.model; const matchesModel = acl.model === this.model;
var matchesProperty = acl.property === this.property; const matchesProperty = acl.property === this.property;
var matchesMethodName = this.methodNames.indexOf(acl.property) !== -1; const matchesMethodName = this.methodNames.indexOf(acl.property) !== -1;
var matchesAccessType = acl.accessType === this.accessType; const matchesAccessType = acl.accessType === this.accessType;
if (matchesModel && matchesAccessType) { if (matchesModel && matchesAccessType) {
return matchesProperty || matchesMethodName; return matchesProperty || matchesMethodName;
@ -389,9 +388,9 @@ AccessRequest.prototype.settleDefaultPermission = function(defaultPermission) {
if (this.permission !== 'DEFAULT') if (this.permission !== 'DEFAULT')
return; return;
var modelName = this.model; const modelName = this.model;
if (!defaultPermission) { if (!defaultPermission) {
var modelClass = this.registry.findModel(modelName); const modelClass = this.registry.findModel(modelName);
defaultPermission = modelClass && modelClass.settings.defaultPermission; defaultPermission = modelClass && modelClass.settings.defaultPermission;
} }

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,17 +8,17 @@
*/ */
'use strict'; 'use strict';
var g = require('./globalize'); const g = require('./globalize');
var DataSource = require('loopback-datasource-juggler').DataSource; const DataSource = require('loopback-datasource-juggler').DataSource;
var Registry = require('./registry'); const Registry = require('./registry');
var assert = require('assert'); const assert = require('assert');
var fs = require('fs'); const fs = require('fs');
var extend = require('util')._extend; const extend = require('util')._extend;
var RemoteObjects = require('strong-remoting'); const RemoteObjects = require('strong-remoting');
var classify = require('underscore.string/classify'); const classify = require('underscore.string/classify');
var camelize = require('underscore.string/camelize'); const camelize = require('underscore.string/camelize');
var path = require('path'); const path = require('path');
var util = require('util'); const util = require('util');
/** /**
* The `App` object represents a Loopback application. * The `App` object represents a Loopback application.
@ -49,7 +49,7 @@ function App() {
* Export the app prototype. * Export the app prototype.
*/ */
var app = module.exports = {}; const app = module.exports = {};
/** /**
* Lazily load a set of [remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions). * Lazily load a set of [remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).
@ -62,7 +62,7 @@ app.remotes = function() {
if (this._remotes) { if (this._remotes) {
return this._remotes; return this._remotes;
} else { } else {
var options = {}; let options = {};
if (this.get) { if (this.get) {
options = this.get('remoting'); options = this.get('remoting');
@ -78,7 +78,7 @@ app.remotes = function() {
app.disuse = function(route) { app.disuse = function(route) {
if (this.stack) { if (this.stack) {
for (var i = 0; i < this.stack.length; i++) { for (let i = 0; i < this.stack.length; i++) {
if (this.stack[i].route === route) { if (this.stack[i].route === route) {
this.stack.splice(i, 1); this.stack.splice(i, 1);
} }
@ -111,11 +111,11 @@ app.disuse = function(route) {
*/ */
app.model = function(Model, config) { app.model = function(Model, config) {
var isPublic = true; let isPublic = true;
var registry = this.registry; const registry = this.registry;
if (typeof Model === 'string') { if (typeof Model === 'string') {
var msg = 'app.model(modelName, settings) is no longer supported. ' + const msg = 'app.model(modelName, settings) is no longer supported. ' +
'Use app.registry.createModel(modelName, definition) and ' + 'Use app.registry.createModel(modelName, definition) and ' +
'app.model(ModelCtor, config) instead.'; 'app.model(ModelCtor, config) instead.';
throw new Error(msg); throw new Error(msg);
@ -130,7 +130,7 @@ app.model = function(Model, config) {
Model.modelName + ' must be a descendant of loopback.Model'); Model.modelName + ' must be a descendant of loopback.Model');
} }
var modelName = Model.modelName; const modelName = Model.modelName;
this.models[modelName] = this.models[modelName] =
this.models[classify(modelName)] = this.models[classify(modelName)] =
this.models[camelize(modelName)] = Model; this.models[camelize(modelName)] = Model;
@ -149,11 +149,13 @@ app.model = function(Model, config) {
this.emit('modelRemoted', Model.sharedClass); this.emit('modelRemoted', Model.sharedClass);
} }
var self = this; const self = this;
Model.on('remoteMethodDisabled', function(model, methodName) { Model.on('remoteMethodDisabled', function(model, methodName) {
clearHandlerCache(self);
self.emit('remoteMethodDisabled', model, methodName); self.emit('remoteMethodDisabled', model, methodName);
}); });
Model.on('remoteMethodAdded', function(model) { Model.on('remoteMethodAdded', function(model) {
clearHandlerCache(self);
self.emit('remoteMethodAdded', model); self.emit('remoteMethodAdded', model);
}); });
@ -163,6 +165,44 @@ app.model = function(Model, config) {
return Model; return Model;
}; };
/**
* Remove all references to a previously registered Model.
*
* The method emits "modelDeleted" event as a counter-part to "modelRemoted"
* event.
*
* @param {String} modelName The name of the model to remove.
*/
app.deleteModelByName = function(modelName) {
const ModelCtor = this.models[modelName];
delete this.models[modelName];
delete this.models[classify(modelName)];
delete this.models[camelize(modelName)];
if (ModelCtor) {
ModelCtor.removeAllListeners();
const ix = this._models.indexOf(ModelCtor);
if (ix > -1) {
this._models.splice(ix, 1);
}
}
const remotes = this.remotes();
remotes.deleteClassByName(modelName);
remotes.deleteTypeByName(modelName);
if (ModelCtor && ModelCtor.dataSource) {
ModelCtor.dataSource.deleteModelByName(modelName);
} else {
this.registry.modelBuilder.deleteModelByName(modelName);
}
clearHandlerCache(this);
this.emit('modelDeleted', ModelCtor || modelName);
};
/** /**
* Get the models exported by the app. Returns only models defined using `app.model()` * Get the models exported by the app. Returns only models defined using `app.model()`
* *
@ -226,7 +266,7 @@ app.models = function() {
*/ */
app.dataSource = function(name, config) { app.dataSource = function(name, config) {
try { try {
var ds = dataSourcesFromConfig(name, config, this.connectors, this.registry); const ds = dataSourcesFromConfig(name, config, this.connectors, this.registry);
this.dataSources[name] = this.dataSources[name] =
this.dataSources[classify(name)] = this.dataSources[classify(name)] =
this.dataSources[camelize(name)] = ds; this.dataSources[camelize(name)] = ds;
@ -267,7 +307,7 @@ app.connector = function(name, connector) {
*/ */
app.remoteObjects = function() { app.remoteObjects = function() {
var result = {}; const result = {};
this.remotes().classes().forEach(function(sharedClass) { this.remotes().classes().forEach(function(sharedClass) {
result[sharedClass.name] = sharedClass.ctor; result[sharedClass.name] = sharedClass.ctor;
@ -282,13 +322,13 @@ app.remoteObjects = function() {
*/ */
app.handler = function(type, options) { app.handler = function(type, options) {
var handlers = this._handlers || (this._handlers = {}); const handlers = this._handlers || (this._handlers = {});
if (handlers[type]) { if (handlers[type]) {
return handlers[type]; return handlers[type];
} }
var remotes = this.remotes(); const remotes = this.remotes();
var handler = this._handlers[type] = remotes.handler(type, options); const handler = this._handlers[type] = remotes.handler(type, options);
remotes.classes().forEach(function(sharedClass) { remotes.classes().forEach(function(sharedClass) {
sharedClass.ctor.emit('mounted', app, sharedClass, remotes); sharedClass.ctor.emit('mounted', app, sharedClass, remotes);
@ -308,28 +348,29 @@ app.dataSources = app.datasources = {};
*/ */
app.enableAuth = function(options) { app.enableAuth = function(options) {
var AUTH_MODELS = ['User', 'AccessToken', 'ACL', 'Role', 'RoleMapping']; const AUTH_MODELS = ['User', 'AccessToken', 'ACL', 'Role', 'RoleMapping'];
var remotes = this.remotes(); const remotes = this.remotes();
var app = this; const app = this;
if (options && options.dataSource) { if (options && options.dataSource) {
var appModels = app.registry.modelBuilder.models; const appModels = app.registry.modelBuilder.models;
AUTH_MODELS.forEach(function(m) { AUTH_MODELS.forEach(function(m) {
var Model = app.registry.findModel(m); const Model = app.registry.findModel(m);
if (!Model) { if (!Model) {
throw new Error( throw new Error(
g.f('Authentication requires model %s to be defined.', m)); g.f('Authentication requires model %s to be defined.', m),
);
} }
if (Model.dataSource || Model.app) return; if (Model.dataSource || Model.app) return;
// Find descendants of Model that are attached, // Find descendants of Model that are attached,
// for example "Customer" extending "User" model // for example "Customer" extending "User" model
for (var name in appModels) { for (const name in appModels) {
var candidate = appModels[name]; const candidate = appModels[name];
var isSubclass = candidate.prototype instanceof Model; const isSubclass = candidate.prototype instanceof Model;
var isAttached = !!candidate.dataSource || !!candidate.app; const isAttached = !!candidate.dataSource || !!candidate.app;
if (isSubclass && isAttached) return; if (isSubclass && isAttached) return;
} }
@ -341,22 +382,22 @@ app.enableAuth = function(options) {
} }
remotes.authorization = function(ctx, next) { remotes.authorization = function(ctx, next) {
var method = ctx.method; const method = ctx.method;
var req = ctx.req; const req = ctx.req;
var Model = method.ctor; const Model = method.ctor;
var modelInstance = ctx.instance; const modelInstance = ctx.instance;
var modelId = modelInstance && modelInstance.id || const modelId = modelInstance && modelInstance.id ||
// replacement for deprecated req.param() // replacement for deprecated req.param()
(req.params && req.params.id !== undefined ? req.params.id : (req.params && req.params.id !== undefined ? req.params.id :
req.body && req.body.id !== undefined ? req.body.id : req.body && req.body.id !== undefined ? req.body.id :
req.query && req.query.id !== undefined ? req.query.id : req.query && req.query.id !== undefined ? req.query.id :
undefined); undefined);
var modelName = Model.modelName; const modelName = Model.modelName;
var modelSettings = Model.settings || {}; const modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401; let errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) { if (!req.accessToken) {
errStatusCode = 401; errStatusCode = 401;
} }
@ -374,7 +415,7 @@ app.enableAuth = function(options) {
} else if (allowed) { } else if (allowed) {
next(); next();
} else { } else {
var messages = { const messages = {
403: { 403: {
message: g.f('Access Denied'), message: g.f('Access Denied'),
code: 'ACCESS_DENIED', code: 'ACCESS_DENIED',
@ -389,12 +430,12 @@ app.enableAuth = function(options) {
}, },
}; };
var e = new Error(messages[errStatusCode].message || messages[403].message); const e = new Error(messages[errStatusCode].message || messages[403].message);
e.statusCode = errStatusCode; e.statusCode = errStatusCode;
e.code = messages[errStatusCode].code || messages[403].code; e.code = messages[errStatusCode].code || messages[403].code;
next(e); next(e);
} }
} },
); );
} else { } else {
next(); next();
@ -445,7 +486,8 @@ app._verifyAuthModelRelations = function() {
'custom User subclass, but does not fix AccessToken relations ' + 'custom User subclass, but does not fix AccessToken relations ' +
'to use this new model.\n' + 'to use this new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth', 'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, userName, userName); Model.modelName, userName, userName,
);
return; return;
} }
@ -453,12 +495,25 @@ app._verifyAuthModelRelations = function() {
'The model %j does not have "belongsTo User-like model" relation ' + 'The model %j does not have "belongsTo User-like model" relation ' +
'configured.\n' + 'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth', 'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName); Model.modelName,
);
} }
function verifyUserRelations(Model) { function verifyUserRelations(Model) {
const hasManyTokens = Model.relations && Model.relations.accessTokens; const hasManyTokens = Model.relations && Model.relations.accessTokens;
if (hasManyTokens) return;
if (hasManyTokens) {
// display a temp warning message for users using multiple users config
if (hasManyTokens.polymorphic) {
console.warn(
'The app configuration follows the multiple user models setup ' +
'as described in http://ibm.biz/setup-loopback-auth',
'The built-in role resolver $owner is not currently compatible ' +
'with this configuration and should not be used in production.',
);
}
return;
}
const relationsConfig = Model.settings.relations || {}; const relationsConfig = Model.settings.relations || {};
const accessTokenName = (relationsConfig.accessTokens || {}).model; const accessTokenName = (relationsConfig.accessTokens || {}).model;
@ -471,7 +526,8 @@ app._verifyAuthModelRelations = function() {
'AccessToken subclass, but does not fix User relations to use this ' + 'AccessToken subclass, but does not fix User relations to use this ' +
'new model.\n' + 'new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth', 'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, accessTokenName, accessTokenName); Model.modelName, accessTokenName, accessTokenName,
);
return; return;
} }
@ -479,44 +535,46 @@ app._verifyAuthModelRelations = function() {
'The model %j does not have "hasMany AccessToken-like models" relation ' + 'The model %j does not have "hasMany AccessToken-like models" relation ' +
'configured.\n' + 'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth', 'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName); Model.modelName,
);
} }
}; };
app.boot = function(options) { app.boot = function(options) {
throw new Error( throw new Error(
g.f('{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead')); g.f('{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead'),
);
}; };
function dataSourcesFromConfig(name, config, connectorRegistry, registry) { function dataSourcesFromConfig(name, config, connectorRegistry, registry) {
var connectorPath; let connectorPath;
assert(typeof config === 'object', assert(typeof config === 'object',
'can not create data source without config object'); 'can not create data source without config object');
if (typeof config.connector === 'string') { if (typeof config.connector === 'string') {
name = config.connector; const connectorName = config.connector;
if (connectorRegistry[name]) { if (connectorRegistry[connectorName]) {
config.connector = connectorRegistry[name]; config.connector = connectorRegistry[connectorName];
} else { } else {
connectorPath = path.join(__dirname, 'connectors', name + '.js'); connectorPath = path.join(__dirname, 'connectors', connectorName + '.js');
if (fs.existsSync(connectorPath)) { if (fs.existsSync(connectorPath)) {
config.connector = require(connectorPath); config.connector = require(connectorPath);
} }
} }
if (config.connector && typeof config.connector === 'object' && !config.connector.name) if (config.connector && typeof config.connector === 'object' && !config.connector.name)
config.connector.name = name; config.connector.name = connectorName;
} }
return registry.createDataSource(config); return registry.createDataSource(name, config);
} }
function configureModel(ModelCtor, config, app) { function configureModel(ModelCtor, config, app) {
assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'), assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'),
ModelCtor.modelName + ' must be a descendant of loopback.Model'); ModelCtor.modelName + ' must be a descendant of loopback.Model');
var dataSource = config.dataSource; let dataSource = config.dataSource;
if (dataSource) { if (dataSource) {
if (typeof dataSource === 'string') { if (typeof dataSource === 'string') {
@ -526,61 +584,16 @@ function configureModel(ModelCtor, config, app) {
assert( assert(
dataSource instanceof DataSource, dataSource instanceof DataSource,
ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' + ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' +
config.dataSource + '"' config.dataSource + '"',
); );
} }
config = extend({}, config); config = extend({}, config);
config.dataSource = dataSource; config.dataSource = dataSource;
setSharedMethodSharedProperties(ModelCtor, app, config);
app.registry.configureModel(ModelCtor, config); app.registry.configureModel(ModelCtor, config);
} }
function setSharedMethodSharedProperties(model, app, modelConfigs) {
var settings = {};
// apply config.json settings
var config = app.get('remoting');
var configHasSharedMethodsSettings = config &&
config.sharedMethods &&
typeof config.sharedMethods === 'object';
if (configHasSharedMethodsSettings)
util._extend(settings, config.sharedMethods);
// apply model-config.json settings
var modelConfig = modelConfigs.options;
var modelConfigHasSharedMethodsSettings = modelConfig &&
modelConfig.remoting &&
modelConfig.remoting.sharedMethods &&
typeof modelConfig.remoting.sharedMethods === 'object';
if (modelConfigHasSharedMethodsSettings)
util._extend(settings, modelConfig.remoting.sharedMethods);
// validate setting values
Object.keys(settings).forEach(function(setting) {
var settingValue = settings[setting];
var settingValueType = typeof settingValue;
if (settingValueType !== 'boolean')
throw new TypeError(g.f('Expected boolean, got %s', settingValueType));
});
// set sharedMethod.shared using the merged settings
var sharedMethods = model.sharedClass.methods({includeDisabled: true});
sharedMethods.forEach(function(sharedMethod) {
// use the specific setting if it exists
var hasSpecificSetting = settings.hasOwnProperty(sharedMethod.name);
if (hasSpecificSetting) {
sharedMethod.shared = settings[sharedMethod.name];
} else { // otherwise, use the default setting if it exists
var hasDefaultSetting = settings.hasOwnProperty('*');
if (hasDefaultSetting)
sharedMethod.shared = settings['*'];
}
});
}
function clearHandlerCache(app) { function clearHandlerCache(app) {
app._handlers = undefined; app._handlers = undefined;
} }
@ -614,15 +627,15 @@ function clearHandlerCache(app) {
* as the request handler. * as the request handler.
*/ */
app.listen = function(cb) { app.listen = function(cb) {
var self = this; const self = this;
var server = require('http').createServer(this); const server = require('http').createServer(this);
server.on('listening', function() { server.on('listening', function() {
self.set('port', this.address().port); self.set('port', this.address().port);
var listeningOnAll = false; let listeningOnAll = false;
var host = self.get('host'); let host = self.get('host');
if (!host) { if (!host) {
listeningOnAll = true; listeningOnAll = true;
host = this.address().address; host = this.address().address;
@ -632,23 +645,22 @@ app.listen = function(cb) {
} }
if (!self.get('url')) { if (!self.get('url')) {
if (process.platform === 'win32' && listeningOnAll) { if (listeningOnAll) {
// Windows browsers don't support `0.0.0.0` host in the URL
// We are replacing it with localhost to build a URL // We are replacing it with localhost to build a URL
// that can be copied and pasted into the browser. // that can be copied and pasted into the browser.
host = 'localhost'; host = 'localhost';
} }
var url = 'http://' + host + ':' + self.get('port') + '/'; const url = 'http://' + host + ':' + self.get('port') + '/';
self.set('url', url); self.set('url', url);
} }
}); });
var useAppConfig = const useAppConfig =
arguments.length === 0 || arguments.length === 0 ||
(arguments.length == 1 && typeof arguments[0] == 'function'); (arguments.length == 1 && typeof arguments[0] == 'function');
if (useAppConfig) { if (useAppConfig) {
var port = this.get('port'); let port = this.get('port');
// NOTE(bajtos) port:undefined no longer works on node@6, // NOTE(bajtos) port:undefined no longer works on node@6,
// we must pass port:0 explicitly // we must pass port:0 explicitly
if (port === undefined) port = 0; if (port === undefined) port = 0;

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2014. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter;
var util = require('util'); const util = require('util');
module.exports = browserExpress; module.exports = browserExpress;

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -12,47 +12,58 @@ module.exports = function(registry) {
registry.KeyValueModel = createModel( registry.KeyValueModel = createModel(
require('../common/models/key-value-model.json'), require('../common/models/key-value-model.json'),
require('../common/models/key-value-model.js')); require('../common/models/key-value-model.js'),
);
registry.Email = createModel( registry.Email = createModel(
require('../common/models/email.json'), require('../common/models/email.json'),
require('../common/models/email.js')); require('../common/models/email.js'),
);
registry.Application = createModel( registry.Application = createModel(
require('../common/models/application.json'), require('../common/models/application.json'),
require('../common/models/application.js')); require('../common/models/application.js'),
);
registry.AccessToken = createModel( registry.AccessToken = createModel(
require('../common/models/access-token.json'), require('../common/models/access-token.json'),
require('../common/models/access-token.js')); require('../common/models/access-token.js'),
);
registry.User = createModel( registry.User = createModel(
require('../common/models/user.json'), require('../common/models/user.json'),
require('../common/models/user.js')); require('../common/models/user.js'),
);
registry.RoleMapping = createModel( registry.RoleMapping = createModel(
require('../common/models/role-mapping.json'), require('../common/models/role-mapping.json'),
require('../common/models/role-mapping.js')); require('../common/models/role-mapping.js'),
);
registry.Role = createModel( registry.Role = createModel(
require('../common/models/role.json'), require('../common/models/role.json'),
require('../common/models/role.js')); require('../common/models/role.js'),
);
registry.ACL = createModel( registry.ACL = createModel(
require('../common/models/acl.json'), require('../common/models/acl.json'),
require('../common/models/acl.js')); require('../common/models/acl.js'),
);
registry.Scope = createModel( registry.Scope = createModel(
require('../common/models/scope.json'), require('../common/models/scope.json'),
require('../common/models/scope.js')); require('../common/models/scope.js'),
);
registry.Change = createModel( registry.Change = createModel(
require('../common/models/change.json'), require('../common/models/change.json'),
require('../common/models/change.js')); require('../common/models/change.js'),
);
registry.Checkpoint = createModel( registry.Checkpoint = createModel(
require('../common/models/checkpoint.json'), require('../common/models/checkpoint.json'),
require('../common/models/checkpoint.js')); require('../common/models/checkpoint.js'),
);
function createModel(definitionJson, customizeFn) { function createModel(definitionJson, customizeFn) {
// Clone the JSON definition to allow applications // Clone the JSON definition to allow applications
@ -63,7 +74,7 @@ module.exports = function(registry) {
// object instance it loaded during the first call. // object instance it loaded during the first call.
definitionJson = cloneDeepJson(definitionJson); definitionJson = cloneDeepJson(definitionJson);
var Model = registry.createModel(definitionJson); const Model = registry.createModel(definitionJson);
customizeFn(Model); customizeFn(Model);
return Model; return Model;
} }

View File

@ -0,0 +1,80 @@
// Copyright IBM Corp. 2017,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
const util = require('util');
const extend = require('util')._extend;
const g = require('./globalize');
module.exports = function(modelCtor, remotingConfig, modelConfig) {
const settings = {};
// apply config.json settings
const configHasSharedMethodsSettings = remotingConfig &&
remotingConfig.sharedMethods &&
typeof remotingConfig.sharedMethods === 'object';
if (configHasSharedMethodsSettings)
util._extend(settings, remotingConfig.sharedMethods);
// apply model-config.json settings
const options = modelConfig.options;
const modelConfigHasSharedMethodsSettings = options &&
options.remoting &&
options.remoting.sharedMethods &&
typeof options.remoting.sharedMethods === 'object';
if (modelConfigHasSharedMethodsSettings)
util._extend(settings, options.remoting.sharedMethods);
// validate setting values
Object.keys(settings).forEach(function(setting) {
const settingValue = settings[setting];
const settingValueType = typeof settingValue;
if (settingValueType !== 'boolean')
throw new TypeError(g.f('Expected boolean, got %s', settingValueType));
});
// set sharedMethod.shared using the merged settings
const sharedMethods = modelCtor.sharedClass.methods({includeDisabled: true});
// re-map glob style values to regular expressions
const tests = Object
.keys(settings)
.filter(function(setting) {
return settings.hasOwnProperty(setting) && setting.indexOf('*') >= 0;
})
.map(function(setting) {
// Turn * into an testable regexp string
const glob = escapeRegExp(setting).replace(/\*/g, '(.)*');
return {regex: new RegExp(glob), setting: settings[setting]};
}) || [];
sharedMethods.forEach(function(sharedMethod) {
// use the specific setting if it exists
const methodName = sharedMethod.isStatic ? sharedMethod.name : 'prototype.' + sharedMethod.name;
const hasSpecificSetting = settings.hasOwnProperty(methodName);
if (hasSpecificSetting) {
if (settings[methodName] === false) {
sharedMethod.sharedClass.disableMethodByName(methodName);
} else {
sharedMethod.shared = true;
}
} else {
tests.forEach(function(glob) {
if (glob.regex.test(methodName)) {
if (glob.setting === false) {
sharedMethod.sharedClass.disableMethodByName(methodName);
} else {
sharedMethod.shared = true;
}
}
});
}
});
};
// Sanitize all RegExp reserved characters except * for pattern gobbing
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&');
}

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2014. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -14,11 +14,11 @@ module.exports = Connector;
* Module dependencies. * Module dependencies.
*/ */
var EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter;
var debug = require('debug')('connector'); const debug = require('debug')('connector');
var util = require('util'); const util = require('util');
var inherits = util.inherits; const inherits = util.inherits;
var assert = require('assert'); const assert = require('assert');
/** /**
* Create a new `Connector` with the given `options`. * Create a new `Connector` with the given `options`.
@ -45,7 +45,7 @@ inherits(Connector, EventEmitter);
*/ */
Connector._createJDBAdapter = function(jdbModule) { Connector._createJDBAdapter = function(jdbModule) {
var fauxSchema = {}; const fauxSchema = {};
jdbModule.initialize(fauxSchema, function() { jdbModule.initialize(fauxSchema, function() {
// connected // connected
}); });

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,11 +8,11 @@
*/ */
'use strict'; 'use strict';
var g = require('../globalize'); const g = require('../globalize');
var mailer = require('nodemailer'); const mailer = require('nodemailer');
var assert = require('assert'); const assert = require('assert');
var debug = require('debug')('loopback:connector:mail'); const debug = require('debug')('loopback:connector:mail');
var loopback = require('../loopback'); const loopback = require('../loopback');
/** /**
* Export the MailConnector class. * Export the MailConnector class.
@ -27,7 +27,7 @@ module.exports = MailConnector;
function MailConnector(settings) { function MailConnector(settings) {
assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object'); assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object');
var transports = settings.transports; let transports = settings.transports;
// if transports is not in settings object AND settings.transport exists // if transports is not in settings object AND settings.transport exists
if (!transports && settings.transport) { if (!transports && settings.transport) {
@ -74,19 +74,17 @@ MailConnector.prototype.DataAccessObject = Mailer;
*/ */
MailConnector.prototype.setupTransport = function(setting) { MailConnector.prototype.setupTransport = function(setting) {
var connector = this; const connector = this;
connector.transports = connector.transports || []; connector.transports = connector.transports || [];
connector.transportsIndex = connector.transportsIndex || {}; connector.transportsIndex = connector.transportsIndex || {};
var transport; let transport;
var transportType = (setting.type || 'STUB').toLowerCase(); const transportType = (setting.type || 'STUB').toLowerCase();
if (transportType === 'direct') { if (transportType === 'smtp') {
transport = mailer.createTransport();
} else if (transportType === 'smtp') {
transport = mailer.createTransport(setting); transport = mailer.createTransport(setting);
} else { } else {
var transportModuleName = 'nodemailer-' + transportType + '-transport'; const transportModuleName = 'nodemailer-' + transportType + '-transport';
var transportModule = require(transportModuleName); const transportModule = require(transportModuleName);
transport = mailer.createTransport(transportModule(setting)); transport = mailer.createTransport(transportModule(setting));
} }
@ -140,12 +138,12 @@ MailConnector.prototype.defaultTransport = function() {
*/ */
Mailer.send = function(options, fn) { Mailer.send = function(options, fn) {
var dataSource = this.dataSource; const dataSource = this.dataSource;
var settings = dataSource && dataSource.settings; const settings = dataSource && dataSource.settings;
var connector = dataSource.connector; const connector = dataSource.connector;
assert(connector, 'Cannot send mail without a connector!'); assert(connector, 'Cannot send mail without a connector!');
var transport = connector.transportForName(options.transport); let transport = connector.transportForName(options.transport);
if (!transport) { if (!transport) {
transport = connector.defaultTransport(); transport = connector.defaultTransport();

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2014. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -14,12 +14,12 @@ module.exports = Memory;
* Module dependencies. * Module dependencies.
*/ */
var Connector = require('./base-connector'); const Connector = require('./base-connector');
var debug = require('debug')('memory'); const debug = require('debug')('memory');
var util = require('util'); const util = require('util');
var inherits = util.inherits; const inherits = util.inherits;
var assert = require('assert'); const assert = require('assert');
var JdbMemory = require('loopback-datasource-juggler/lib/connectors/memory'); const JdbMemory = require('loopback-datasource-juggler/lib/connectors/memory');
/** /**
* Create a new `Memory` connector with the given `options`. * Create a new `Memory` connector with the given `options`.

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015,2016. All Rights Reserved. // Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('./globalize'); const g = require('./globalize');
var juggler = require('loopback-datasource-juggler'); const juggler = require('loopback-datasource-juggler');
var remoting = require('strong-remoting'); const remoting = require('strong-remoting');
module.exports = function(loopback) { module.exports = function(loopback) {
juggler.getCurrentContext = juggler.getCurrentContext =
@ -15,20 +15,23 @@ module.exports = function(loopback) {
throw new Error(g.f( throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.', '%s was removed in version 3.0. See %s for more details.',
'loopback.getCurrentContext()', 'loopback.getCurrentContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html')); 'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
}; };
loopback.runInContext = function(fn) { loopback.runInContext = function(fn) {
throw new Error(g.f( throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.', '%s was removed in version 3.0. See %s for more details.',
'loopback.runInContext()', 'loopback.runInContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html')); 'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
}; };
loopback.createContext = function(scopeName) { loopback.createContext = function(scopeName) {
throw new Error(g.f( throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.', '%s was removed in version 3.0. See %s for more details.',
'loopback.createContext()', 'loopback.createContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html')); 'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
}; };
}; };

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2016. All Rights Reserved. // Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var path = require('path'); const path = require('path');
var SG = require('strong-globalize'); const SG = require('strong-globalize');
SG.SetRootDir(path.join(__dirname, '..'), {autonomousMsgLoading: 'all'}); SG.SetRootDir(path.join(__dirname, '..'), {autonomousMsgLoading: 'all'});
module.exports = SG(); module.exports = SG();

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,16 +8,17 @@
*/ */
'use strict'; 'use strict';
var express = require('express'); const express = require('express');
var loopbackExpress = require('./server-app'); const loopbackExpress = require('./server-app');
var proto = require('./application'); const proto = require('./application');
var fs = require('fs'); const fs = require('fs');
var ejs = require('ejs'); const ejs = require('ejs');
var path = require('path'); const path = require('path');
var merge = require('util')._extend; const merge = require('util')._extend;
var assert = require('assert'); const assert = require('assert');
var Registry = require('./registry'); const Registry = require('./registry');
var juggler = require('loopback-datasource-juggler'); const juggler = require('loopback-datasource-juggler');
const configureSharedMethods = require('./configure-shared-methods');
/** /**
* LoopBack core module. It provides static properties and * LoopBack core module. It provides static properties and
@ -39,7 +40,7 @@ var juggler = require('loopback-datasource-juggler');
* @header loopback * @header loopback
*/ */
var loopback = module.exports = createApplication; const loopback = module.exports = createApplication;
/*! /*!
* Framework version. * Framework version.
@ -72,12 +73,19 @@ Object.defineProperties(loopback, {
*/ */
function createApplication(options) { function createApplication(options) {
var app = loopbackExpress(); const app = loopbackExpress();
merge(app, proto); merge(app, proto);
app.loopback = loopback; app.loopback = loopback;
app.on('modelRemoted', function() {
app.models().forEach(function(Model) {
if (!Model.config) return;
configureSharedMethods(Model, app.get('remoting'), Model.config);
});
});
// Create a new instance of models registry per each app instance // Create a new instance of models registry per each app instance
app.models = function() { app.models = function() {
return proto.models.apply(this, arguments); return proto.models.apply(this, arguments);
@ -99,7 +107,7 @@ function createApplication(options) {
if (loopback.localRegistry || options && options.localRegistry === true) { if (loopback.localRegistry || options && options.localRegistry === true) {
// setup the app registry // setup the app registry
var registry = app.registry = new Registry(); const registry = app.registry = new Registry();
if (options && options.loadBuiltinModels === true) { if (options && options.loadBuiltinModels === true) {
require('./builtin-models')(registry); require('./builtin-models')(registry);
} }
@ -111,8 +119,8 @@ function createApplication(options) {
} }
function mixin(source) { function mixin(source) {
for (var key in source) { for (const key in source) {
var desc = Object.getOwnPropertyDescriptor(source, key); const desc = Object.getOwnPropertyDescriptor(source, key);
// Fix for legacy (pre-ES5) browsers like PhantomJS // Fix for legacy (pre-ES5) browsers like PhantomJS
if (!desc) continue; if (!desc) continue;
@ -191,13 +199,13 @@ loopback.remoteMethod = function(fn, options) {
* var render = loopback.template('foo.ejs'); * var render = loopback.template('foo.ejs');
* var html = render({foo: 'bar'}); * var html = render({foo: 'bar'});
* *
* @param {String} path Path to the template file. * @param {String} file Path to the template file.
* @returns {Function} * @returns {Function}
*/ */
loopback.template = function(file) { loopback.template = function(file) {
var templates = this._templates || (this._templates = {}); const templates = this._templates || (this._templates = {});
var str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8')); const str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8'));
return ejs.compile(str, { return ejs.compile(str, {
filename: file, filename: file,
}); });

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -7,15 +7,14 @@
* Module Dependencies. * Module Dependencies.
*/ */
'use strict'; 'use strict';
var g = require('./globalize'); const g = require('./globalize');
var assert = require('assert'); const assert = require('assert');
var debug = require('debug')('loopback:model'); const debug = require('debug')('loopback:model');
var RemoteObjects = require('strong-remoting'); const RemoteObjects = require('strong-remoting');
var SharedClass = require('strong-remoting').SharedClass; const SharedClass = require('strong-remoting').SharedClass;
var extend = require('util')._extend; const extend = require('util')._extend;
var format = require('util').format;
var deprecated = require('depd')('loopback'); const deprecated = require('depd')('loopback');
module.exports = function(registry) { module.exports = function(registry) {
/** /**
@ -102,10 +101,10 @@ module.exports = function(registry) {
* @property {SharedClass} Model.sharedMethod The `strong-remoting` [SharedClass](http://apidocs.strongloop.com/strong-remoting/#sharedclass) that contains remoting (and http) metadata. Static property. * @property {SharedClass} Model.sharedMethod The `strong-remoting` [SharedClass](http://apidocs.strongloop.com/strong-remoting/#sharedclass) that contains remoting (and http) metadata. Static property.
* @property {Object} settings Contains additional model settings. * @property {Object} settings Contains additional model settings.
* @property {string} settings.http.path Base URL of the model HTTP route. * @property {string} settings.http.path Base URL of the model HTTP route.
* @property [{string}] settings.acls Array of ACLs for the model. * @property {Array.<Object>} settings.acls Array of ACLs for the model.
* @class * @class
*/ */
var Model = registry.modelBuilder.define('Model'); const Model = registry.modelBuilder.define('Model');
Model.registry = registry; Model.registry = registry;
@ -116,23 +115,23 @@ module.exports = function(registry) {
*/ */
Model.setup = function() { Model.setup = function() {
var ModelCtor = this; const ModelCtor = this;
var Parent = this.super_; const Parent = this.super_;
if (!ModelCtor.registry && Parent && Parent.registry) { if (!ModelCtor.registry && Parent && Parent.registry) {
ModelCtor.registry = Parent.registry; ModelCtor.registry = Parent.registry;
} }
var options = this.settings; const options = this.settings;
var typeName = this.modelName; const typeName = this.modelName;
// support remoting prototype methods // support remoting prototype methods
// it's important to setup this function *before* calling `new SharedClass` // it's important to setup this function *before* calling `new SharedClass`
// otherwise remoting metadata from our base model is picked up // otherwise remoting metadata from our base model is picked up
ModelCtor.sharedCtor = function(data, id, options, fn) { ModelCtor.sharedCtor = function(data, id, options, fn) {
var ModelCtor = this; const ModelCtor = this;
var isRemoteInvocationWithOptions = typeof data !== 'object' && const isRemoteInvocationWithOptions = typeof data !== 'object' &&
typeof id === 'object' && typeof id === 'object' &&
typeof options === 'function'; typeof options === 'function';
if (isRemoteInvocationWithOptions) { if (isRemoteInvocationWithOptions) {
@ -161,14 +160,14 @@ module.exports = function(registry) {
} }
} }
if (id && data) { if (id != null && data) {
var model = new ModelCtor(data); const model = new ModelCtor(data);
model.id = id; model.id = id;
fn(null, model); fn(null, model);
} else if (data) { } else if (data) {
fn(null, new ModelCtor(data)); fn(null, new ModelCtor(data));
} else if (id) { } else if (id != null) {
var filter = {}; const filter = {};
ModelCtor.findById(id, filter, options, function(err, model) { ModelCtor.findById(id, filter, options, function(err, model) {
if (err) { if (err) {
fn(err); fn(err);
@ -186,7 +185,7 @@ module.exports = function(registry) {
} }
}; };
var idDesc = ModelCtor.modelName + ' id'; const idDesc = ModelCtor.modelName + ' id';
ModelCtor.sharedCtor.accepts = [ ModelCtor.sharedCtor.accepts = [
{arg: 'id', type: 'any', required: true, http: {source: 'path'}, {arg: 'id', type: 'any', required: true, http: {source: 'path'},
description: idDesc}, description: idDesc},
@ -200,21 +199,21 @@ module.exports = function(registry) {
ModelCtor.sharedCtor.returns = {root: true}; ModelCtor.sharedCtor.returns = {root: true};
var remotingOptions = {}; const remotingOptions = {};
extend(remotingOptions, options.remoting || {}); extend(remotingOptions, options.remoting || {});
// create a sharedClass // create a sharedClass
var sharedClass = ModelCtor.sharedClass = new SharedClass( const sharedClass = ModelCtor.sharedClass = new SharedClass(
ModelCtor.modelName, ModelCtor.modelName,
ModelCtor, ModelCtor,
remotingOptions remotingOptions,
); );
// before remote hook // before remote hook
ModelCtor.beforeRemote = function(name, fn) { ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName; const className = this.modelName;
this._runWhenAttachedToApp(function(app) { this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes(); const remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) { remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next); return fn(ctx, ctx.result, next);
}); });
@ -223,9 +222,9 @@ module.exports = function(registry) {
// after remote hook // after remote hook
ModelCtor.afterRemote = function(name, fn) { ModelCtor.afterRemote = function(name, fn) {
var className = this.modelName; const className = this.modelName;
this._runWhenAttachedToApp(function(app) { this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes(); const remotes = app.remotes();
remotes.after(className + '.' + name, function(ctx, next) { remotes.after(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next); return fn(ctx, ctx.result, next);
}); });
@ -233,16 +232,16 @@ module.exports = function(registry) {
}; };
ModelCtor.afterRemoteError = function(name, fn) { ModelCtor.afterRemoteError = function(name, fn) {
var className = this.modelName; const className = this.modelName;
this._runWhenAttachedToApp(function(app) { this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes(); const remotes = app.remotes();
remotes.afterError(className + '.' + name, fn); remotes.afterError(className + '.' + name, fn);
}); });
}; };
ModelCtor._runWhenAttachedToApp = function(fn) { ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app); if (this.app) return fn(this.app);
var self = this; const self = this;
self.once('attached', function() { self.once('attached', function() {
fn(self.app); fn(self.app);
}); });
@ -251,17 +250,19 @@ module.exports = function(registry) {
if ('injectOptionsFromRemoteContext' in options) { if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f( console.warn(g.f(
'%s is using model setting %s which is no longer available.', '%s is using model setting %s which is no longer available.',
typeName, 'injectOptionsFromRemoteContext')); typeName, 'injectOptionsFromRemoteContext',
));
console.warn(g.f( console.warn(g.f(
'Please rework your app to use the offical solution for injecting ' + 'Please rework your app to use the offical solution for injecting ' +
'"options" argument from request context,\nsee %s', '"options" argument from request context,\nsee %s',
'http://loopback.io/doc/en/lb3/Using-current-context.html')); 'http://loopback.io/doc/en/lb3/Using-current-context.html',
));
} }
// resolve relation functions // resolve relation functions
sharedClass.resolve(function resolver(define) { sharedClass.resolve(function resolver(define) {
var relations = ModelCtor.relations || {}; const relations = ModelCtor.relations || {};
var defineRaw = define; const defineRaw = define;
define = function(name, options, fn) { define = function(name, options, fn) {
if (options.accepts) { if (options.accepts) {
options = extend({}, options); options = extend({}, options);
@ -271,8 +272,8 @@ module.exports = function(registry) {
}; };
// get the relations // get the relations
for (var relationName in relations) { for (const relationName in relations) {
var relation = relations[relationName]; const relation = relations[relationName];
if (relation.type === 'belongsTo') { if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define); ModelCtor.belongsToRemoting(relationName, relation, define);
} else if ( } else if (
@ -286,11 +287,16 @@ module.exports = function(registry) {
relation.type === 'referencesMany') { relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define); ModelCtor.hasManyRemoting(relationName, relation, define);
} }
// Automatically enable nestRemoting if the flag is set to true in the
// relation options
if (relation.options && relation.options.nestRemoting) {
ModelCtor.nestRemoting(relationName);
}
} }
// handle scopes // handle scopes
var scopes = ModelCtor.scopes || {}; const scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) { for (const scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define); ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
} }
}); });
@ -301,9 +307,9 @@ module.exports = function(registry) {
/*! /*!
* Get the reference to ACL in a lazy fashion to avoid race condition in require * Get the reference to ACL in a lazy fashion to avoid race condition in require
*/ */
var _aclModel = null; let _aclModel = null;
Model._ACL = function getACL(ACL) { Model._ACL = function getACL(ACL) {
var registry = this.registry; const registry = this.registry;
if (ACL !== undefined) { if (ACL !== undefined) {
// The function is used as a setter // The function is used as a setter
_aclModel = ACL; _aclModel = ACL;
@ -311,7 +317,7 @@ module.exports = function(registry) {
if (_aclModel) { if (_aclModel) {
return _aclModel; return _aclModel;
} }
var aclModel = registry.getModel('ACL'); const aclModel = registry.getModel('ACL');
_aclModel = registry.getModelByType(aclModel); _aclModel = registry.getModelByType(aclModel);
return _aclModel; return _aclModel;
}; };
@ -329,9 +335,9 @@ module.exports = function(registry) {
*/ */
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) { Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; const ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS; token = token || ANONYMOUS;
var aclModel = Model._ACL(); const aclModel = Model._ACL();
ctx = ctx || {}; ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) { if (typeof ctx === 'function' && callback === undefined) {
@ -367,10 +373,10 @@ module.exports = function(registry) {
} }
assert( assert(
typeof method === 'object', typeof method === 'object',
'method is a required argument and must be a RemoteMethod object' 'method is a required argument and must be a RemoteMethod object',
); );
var ACL = Model._ACL(); const ACL = Model._ACL();
// Check the explicit setting of accessType // Check the explicit setting of accessType
if (method.accessType) { if (method.accessType) {
@ -384,7 +390,7 @@ module.exports = function(registry) {
} }
// Default GET requests to READ // Default GET requests to READ
var verb = method.http && method.http.verb; let verb = method.http && method.http.verb;
if (typeof verb === 'string') { if (typeof verb === 'string') {
verb = verb.toUpperCase(); verb = verb.toUpperCase();
} }
@ -432,7 +438,7 @@ module.exports = function(registry) {
*/ */
Model.getApp = function(callback) { Model.getApp = function(callback) {
var self = this; const self = this;
self._runWhenAttachedToApp(function(app) { self._runWhenAttachedToApp(function(app) {
assert(self.app); assert(self.app);
assert.equal(app, self.app); assert.equal(app, self.app);
@ -457,21 +463,21 @@ module.exports = function(registry) {
Model.remoteMethod = function(name, options) { Model.remoteMethod = function(name, options) {
if (options.isStatic === undefined) { if (options.isStatic === undefined) {
var m = name.match(/^prototype\.(.*)$/); const m = name.match(/^prototype\.(.*)$/);
options.isStatic = !m; options.isStatic = !m;
name = options.isStatic ? name : m[1]; name = options.isStatic ? name : m[1];
} }
if (options.accepts) { if (options.accepts) {
options = extend({}, options); options = extend({}, options);
options.accepts = setupOptionsArgs(options.accepts); options.accepts = setupOptionsArgs(options.accepts, this);
} }
this.sharedClass.defineMethod(name, options); this.sharedClass.defineMethod(name, options);
this.emit('remoteMethodAdded', this.sharedClass); this.emit('remoteMethodAdded', this.sharedClass);
}; };
function setupOptionsArgs(accepts) { function setupOptionsArgs(accepts, modelClass) {
if (!Array.isArray(accepts)) if (!Array.isArray(accepts))
accepts = [accepts]; accepts = [accepts];
@ -479,21 +485,33 @@ module.exports = function(registry) {
if (arg.http && arg.http === 'optionsFromRequest') { if (arg.http && arg.http === 'optionsFromRequest') {
// clone to preserve the input value // clone to preserve the input value
arg = extend({}, arg); arg = extend({}, arg);
arg.http = createOptionsViaModelMethod; arg.http = createOptionsViaModelMethod.bind(modelClass);
} }
return arg; return arg;
}); });
} }
function createOptionsViaModelMethod(ctx) { function createOptionsViaModelMethod(ctx) {
var EMPTY_OPTIONS = {}; const ModelCtor = (ctx.method && ctx.method.ctor) || this;
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor) /**
return EMPTY_OPTIONS; * Configure default values for juggler settings to protect user-supplied
* input from attacking juggler
*/
const DEFAULT_OPTIONS = {
// Default to `true` so that hidden properties cannot be used in query
prohibitHiddenPropertiesInQuery: ModelCtor._getProhibitHiddenPropertiesInQuery({}, true),
// Default to `12` for the max depth of a query object
maxDepthOfQuery: ModelCtor._getMaxDepthOfQuery({}, 12),
// Default to `32` for the max depth of a data object
maxDepthOfData: ModelCtor._getMaxDepthOfData({}, 32),
};
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function') if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS; return DEFAULT_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName); debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx); return Object.assign(DEFAULT_OPTIONS,
ModelCtor.createOptionsFromRemotingContext(ctx));
} }
/** /**
@ -508,7 +526,7 @@ module.exports = function(registry) {
Model.disableRemoteMethod = function(name, isStatic) { Model.disableRemoteMethod = function(name, isStatic) {
deprecated('Model.disableRemoteMethod is deprecated. ' + deprecated('Model.disableRemoteMethod is deprecated. ' +
'Use Model.disableRemoteMethodByName instead.'); 'Use Model.disableRemoteMethodByName instead.');
var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); const key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic);
this.sharedClass.disableMethodByName(key); this.sharedClass.disableMethodByName(key);
this.emit('remoteMethodDisabled', this.sharedClass, key); this.emit('remoteMethodDisabled', this.sharedClass, key);
}; };
@ -526,10 +544,10 @@ module.exports = function(registry) {
}; };
Model.belongsToRemoting = function(relationName, relation, define) { Model.belongsToRemoting = function(relationName, relation, define) {
var modelName = relation.modelTo && relation.modelTo.modelName; let modelName = relation.modelTo && relation.modelTo.modelName;
modelName = modelName || 'PersistedModel'; modelName = modelName || 'PersistedModel';
var fn = this.prototype[relationName]; const fn = this.prototype[relationName];
var pathName = (relation.options.http && relation.options.http.path) || relationName; const pathName = (relation.options.http && relation.options.http.path) || relationName;
define('__get__' + relationName, { define('__get__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'get', path: '/' + pathName}, http: {verb: 'get', path: '/' + pathName},
@ -538,7 +556,7 @@ module.exports = function(registry) {
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
accessType: 'READ', accessType: 'READ',
description: format('Fetches belongsTo relation %s.', relationName), description: g.f('Fetches belongsTo relation %s.', relationName),
returns: {arg: relationName, type: modelName, root: true}, returns: {arg: relationName, type: modelName, root: true},
}, fn); }, fn);
}; };
@ -546,17 +564,19 @@ module.exports = function(registry) {
function convertNullToNotFoundError(toModelName, ctx, cb) { function convertNullToNotFoundError(toModelName, ctx, cb) {
if (ctx.result !== null) return cb(); if (ctx.result !== null) return cb();
var fk = ctx.getArgByName('fk'); const fk = ctx.getArgByName('fk');
var msg = g.f('Unknown "%s" id "%s".', toModelName, fk); const msg = fk ?
var error = new Error(msg); g.f('Unknown "%s" id "%s".', toModelName, fk) :
g.f('No "%s" instance(s) found', toModelName);
const error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND'; error.code = 'MODEL_NOT_FOUND';
cb(error); cb(error);
} }
Model.hasOneRemoting = function(relationName, relation, define) { Model.hasOneRemoting = function(relationName, relation, define) {
var pathName = (relation.options.http && relation.options.http.path) || relationName; const pathName = (relation.options.http && relation.options.http.path) || relationName;
var toModelName = relation.modelTo.modelName; const toModelName = relation.modelTo.modelName;
define('__get__' + relationName, { define('__get__' + relationName, {
isStatic: false, isStatic: false,
@ -565,7 +585,7 @@ module.exports = function(registry) {
{arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'refresh', type: 'boolean', http: {source: 'query'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Fetches hasOne relation %s.', relationName), description: g.f('Fetches hasOne relation %s.', relationName),
accessType: 'READ', accessType: 'READ',
returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, returns: {arg: relationName, type: relation.modelTo.modelName, root: true},
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)},
@ -581,7 +601,7 @@ module.exports = function(registry) {
}, },
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Creates a new instance in %s of this model.', relationName), description: g.f('Creates a new instance in %s of this model.', relationName),
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: 'data', type: toModelName, root: true}, returns: {arg: 'data', type: toModelName, root: true},
}); });
@ -596,7 +616,7 @@ module.exports = function(registry) {
}, },
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Update %s of this model.', relationName), description: g.f('Update %s of this model.', relationName),
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: 'data', type: toModelName, root: true}, returns: {arg: 'data', type: toModelName, root: true},
}); });
@ -607,73 +627,73 @@ module.exports = function(registry) {
accepts: [ accepts: [
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Deletes %s of this model.', relationName), description: g.f('Deletes %s of this model.', relationName),
accessType: 'WRITE', accessType: 'WRITE',
}); });
}; };
Model.hasManyRemoting = function(relationName, relation, define) { Model.hasManyRemoting = function(relationName, relation, define) {
var pathName = (relation.options.http && relation.options.http.path) || relationName; const pathName = (relation.options.http && relation.options.http.path) || relationName;
var toModelName = relation.modelTo.modelName; const toModelName = relation.modelTo.modelName;
var findByIdFunc = this.prototype['__findById__' + relationName]; const findByIdFunc = this.prototype['__findById__' + relationName];
define('__findById__' + relationName, { define('__findById__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'get', path: '/' + pathName + '/:fk'}, http: {verb: 'get', path: '/' + pathName + '/:fk'},
accepts: [ accepts: [
{ {
arg: 'fk', type: 'any', arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName), description: g.f('Foreign key for %s', relationName),
required: true, required: true,
http: {source: 'path'}, http: {source: 'path'},
}, },
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Find a related item by id for %s.', relationName), description: g.f('Find a related item by id for %s.', relationName),
accessType: 'READ', accessType: 'READ',
returns: {arg: 'result', type: toModelName, root: true}, returns: {arg: 'result', type: toModelName, root: true},
rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)},
}, findByIdFunc); }, findByIdFunc);
var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; const destroyByIdFunc = this.prototype['__destroyById__' + relationName];
define('__destroyById__' + relationName, { define('__destroyById__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'delete', path: '/' + pathName + '/:fk'}, http: {verb: 'delete', path: '/' + pathName + '/:fk'},
accepts: [ accepts: [
{ {
arg: 'fk', type: 'any', arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName), description: g.f('Foreign key for %s', relationName),
required: true, required: true,
http: {source: 'path'}, http: {source: 'path'},
}, },
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Delete a related item by id for %s.', relationName), description: g.f('Delete a related item by id for %s.', relationName),
accessType: 'WRITE', accessType: 'WRITE',
returns: [], returns: [],
}, destroyByIdFunc); }, destroyByIdFunc);
var updateByIdFunc = this.prototype['__updateById__' + relationName]; const updateByIdFunc = this.prototype['__updateById__' + relationName];
define('__updateById__' + relationName, { define('__updateById__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'put', path: '/' + pathName + '/:fk'}, http: {verb: 'put', path: '/' + pathName + '/:fk'},
accepts: [ accepts: [
{arg: 'fk', type: 'any', {arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName), description: g.f('Foreign key for %s', relationName),
required: true, required: true,
http: {source: 'path'}}, http: {source: 'path'}},
{arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Update a related item by id for %s.', relationName), description: g.f('Update a related item by id for %s.', relationName),
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: 'result', type: toModelName, root: true}, returns: {arg: 'result', type: toModelName, root: true},
}, updateByIdFunc); }, updateByIdFunc);
if (relation.modelThrough || relation.type === 'referencesMany') { if (relation.modelThrough || relation.type === 'referencesMany') {
var modelThrough = relation.modelThrough || relation.modelTo; const modelThrough = relation.modelThrough || relation.modelTo;
var accepts = []; const accepts = [];
if (relation.type === 'hasMany' && relation.modelThrough) { if (relation.type === 'hasMany' && relation.modelThrough) {
// Restrict: only hasManyThrough relation can have additional properties // Restrict: only hasManyThrough relation can have additional properties
accepts.push({ accepts.push({
@ -682,66 +702,66 @@ module.exports = function(registry) {
}); });
} }
var addFunc = this.prototype['__link__' + relationName]; const addFunc = this.prototype['__link__' + relationName];
define('__link__' + relationName, { define('__link__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'},
accepts: [{arg: 'fk', type: 'any', accepts: [{arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName), description: g.f('Foreign key for %s', relationName),
required: true, required: true,
http: {source: 'path'}}, http: {source: 'path'}},
].concat(accepts).concat([ ].concat(accepts).concat([
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
]), ]),
description: format('Add a related item by id for %s.', relationName), description: g.f('Add a related item by id for %s.', relationName),
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: relationName, type: modelThrough.modelName, root: true}, returns: {arg: relationName, type: modelThrough.modelName, root: true},
}, addFunc); }, addFunc);
var removeFunc = this.prototype['__unlink__' + relationName]; const removeFunc = this.prototype['__unlink__' + relationName];
define('__unlink__' + relationName, { define('__unlink__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'},
accepts: [ accepts: [
{ {
arg: 'fk', type: 'any', arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName), description: g.f('Foreign key for %s', relationName),
required: true, required: true,
http: {source: 'path'}, http: {source: 'path'},
}, },
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Remove the %s relation to an item by id.', relationName), description: g.f('Remove the %s relation to an item by id.', relationName),
accessType: 'WRITE', accessType: 'WRITE',
returns: [], returns: [],
}, removeFunc); }, removeFunc);
// FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD?
// true --> 200 and false --> 404? // true --> 200 and false --> 404?
var existsFunc = this.prototype['__exists__' + relationName]; const existsFunc = this.prototype['__exists__' + relationName];
define('__exists__' + relationName, { define('__exists__' + relationName, {
isStatic: false, isStatic: false,
http: {verb: 'head', path: '/' + pathName + '/rel/:fk'}, http: {verb: 'head', path: '/' + pathName + '/rel/:fk'},
accepts: [ accepts: [
{ {
arg: 'fk', type: 'any', arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName), description: g.f('Foreign key for %s', relationName),
required: true, required: true,
http: {source: 'path'}, http: {source: 'path'},
}, },
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Check the existence of %s relation to an item by id.', relationName), description: g.f('Check the existence of %s relation to an item by id.', relationName),
accessType: 'READ', accessType: 'READ',
returns: {arg: 'exists', type: 'boolean', root: true}, returns: {arg: 'exists', type: 'boolean', root: true},
rest: { rest: {
// After hook to map exists to 200/404 for HEAD // After hook to map exists to 200/404 for HEAD
after: function(ctx, cb) { after: function(ctx, cb) {
if (ctx.result === false) { if (ctx.result === false) {
var modelName = ctx.method.sharedClass.name; const modelName = ctx.method.sharedClass.name;
var id = ctx.getArgByName('id'); const id = ctx.getArgByName('id');
var msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id); const msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id);
var error = new Error(msg); const error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND'; error.code = 'MODEL_NOT_FOUND';
cb(error); cb(error);
@ -755,21 +775,31 @@ module.exports = function(registry) {
}; };
Model.scopeRemoting = function(scopeName, scope, define) { Model.scopeRemoting = function(scopeName, scope, define) {
var pathName = const pathName =
(scope.options && scope.options.http && scope.options.http.path) || scopeName; (scope.options && scope.options.http && scope.options.http.path) || scopeName;
var isStatic = scope.isStatic; let modelTo = scope.modelTo;
var toModelName = scope.modelTo.modelName;
const isStatic = scope.isStatic;
let toModelName = scope.modelTo.modelName;
// https://github.com/strongloop/loopback/issues/811 // https://github.com/strongloop/loopback/issues/811
// Check if the scope is for a hasMany relation // Check if the scope is for a hasMany relation
var relation = this.relations[scopeName]; const relation = this.relations[scopeName];
if (relation && relation.modelTo) { if (relation && relation.modelTo) {
// For a relation with through model, the toModelName should be the one // For a relation with through model, the toModelName should be the one
// from the target model // from the target model
toModelName = relation.modelTo.modelName; toModelName = relation.modelTo.modelName;
modelTo = relation.modelTo;
} }
// if there is atleast one updateOnly property, then we set
// createOnlyInstance flag in __create__ to indicate loopback-swagger
// code to create a separate model instance for create operation only
const updateOnlyProps = modelTo.getUpdateOnlyProperties ?
modelTo.getUpdateOnlyProperties() : false;
const hasUpdateOnlyProps = updateOnlyProps && updateOnlyProps.length > 0;
define('__get__' + scopeName, { define('__get__' + scopeName, {
isStatic: isStatic, isStatic: isStatic,
http: {verb: 'get', path: '/' + pathName}, http: {verb: 'get', path: '/' + pathName},
@ -777,7 +807,7 @@ module.exports = function(registry) {
{arg: 'filter', type: 'object'}, {arg: 'filter', type: 'object'},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Queries %s of %s.', scopeName, this.modelName), description: g.f('Queries %s of %s.', scopeName, this.modelName),
accessType: 'READ', accessType: 'READ',
returns: {arg: scopeName, type: [toModelName], root: true}, returns: {arg: scopeName, type: [toModelName], root: true},
}); });
@ -791,11 +821,12 @@ module.exports = function(registry) {
type: 'object', type: 'object',
allowArray: true, allowArray: true,
model: toModelName, model: toModelName,
createOnlyInstance: hasUpdateOnlyProps,
http: {source: 'body'}, http: {source: 'body'},
}, },
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Creates a new instance in %s of this model.', scopeName), description: g.f('Creates a new instance in %s of this model.', scopeName),
accessType: 'WRITE', accessType: 'WRITE',
returns: {arg: 'data', type: toModelName, root: true}, returns: {arg: 'data', type: toModelName, root: true},
}); });
@ -813,7 +844,7 @@ module.exports = function(registry) {
}, },
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Deletes all %s of this model.', scopeName), description: g.f('Deletes all %s of this model.', scopeName),
accessType: 'WRITE', accessType: 'WRITE',
}); });
@ -827,9 +858,9 @@ module.exports = function(registry) {
}, },
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
description: format('Counts %s of %s.', scopeName, this.modelName), description: g.f('Counts %s of %s.', scopeName, this.modelName),
accessType: 'READ', accessType: 'READ',
returns: {arg: 'count', type: 'number'}, returns: {arg: 'count', type: 'number'},
}); });
}; };
@ -855,26 +886,29 @@ module.exports = function(registry) {
} }
options = options || {}; options = options || {};
var regExp = /^__([^_]+)__([^_]+)$/; const regExp = /^__([^_]+)__([^_]+)$/;
var relation = this.relations[relationName]; const relation = this.relations[relationName];
if (relation && relation.modelTo && relation.modelTo.sharedClass) { if (relation && relation._nestRemotingProcessed) {
var self = this; return; // Prevent unwanted circular traversals!
var sharedClass = this.sharedClass; } else if (relation && relation.modelTo && relation.modelTo.sharedClass) {
var sharedToClass = relation.modelTo.sharedClass; relation._nestRemotingProcessed = true;
var toModelName = relation.modelTo.modelName; const self = this;
const sharedClass = this.sharedClass;
const sharedToClass = relation.modelTo.sharedClass;
const toModelName = relation.modelTo.modelName;
var pathName = options.pathName || relation.options.path || relationName; const pathName = options.pathName || relation.options.path || relationName;
var paramName = options.paramName || 'nk'; const paramName = options.paramName || 'nk';
var http = [].concat(sharedToClass.http || [])[0]; const http = [].concat(sharedToClass.http || [])[0];
var httpPath, acceptArgs; let httpPath, acceptArgs;
if (relation.multiple) { if (relation.multiple) {
httpPath = pathName + '/:' + paramName; httpPath = pathName + '/:' + paramName;
acceptArgs = [ acceptArgs = [
{ {
arg: paramName, type: 'any', http: {source: 'path'}, arg: paramName, type: 'any', http: {source: 'path'},
description: format('Foreign key for %s.', relation.name), description: g.f('Foreign key for %s.', relation.name),
required: true, required: true,
}, },
]; ];
@ -889,30 +923,30 @@ module.exports = function(registry) {
// A method should return the method name to use, if it is to be // A method should return the method name to use, if it is to be
// included as a nested method - a falsy return value will skip. // included as a nested method - a falsy return value will skip.
var filter = filterCallback || options.filterMethod || function(method, relation) { const filter = filterCallback || options.filterMethod || function(method, relation) {
var matches = method.name.match(regExp); const matches = method.name.match(regExp);
if (matches) { if (matches) {
return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; return '__' + matches[1] + '__' + relation.name + '__' + matches[2];
} }
}; };
sharedToClass.methods().forEach(function(method) { sharedToClass.methods().forEach(function(method) {
var methodName; let methodName;
if (!method.isStatic && (methodName = filter(method, relation))) { if (!method.isStatic && (methodName = filter(method, relation))) {
var prefix = relation.multiple ? '__findById__' : '__get__'; const prefix = relation.multiple ? '__findById__' : '__get__';
var getterName = options.getterName || (prefix + relationName); const getterName = options.getterName || (prefix + relationName);
var getterFn = relation.modelFrom.prototype[getterName]; const getterFn = relation.modelFrom.prototype[getterName];
if (typeof getterFn !== 'function') { if (typeof getterFn !== 'function') {
throw new Error(g.f('Invalid remote method: `%s`', getterName)); throw new Error(g.f('Invalid remote method: `%s`', getterName));
} }
var nestedFn = relation.modelTo.prototype[method.name]; const nestedFn = relation.modelTo.prototype[method.name];
if (typeof nestedFn !== 'function') { if (typeof nestedFn !== 'function') {
throw new Error(g.f('Invalid remote method: `%s`', method.name)); throw new Error(g.f('Invalid remote method: `%s`', method.name));
} }
var opts = {}; const opts = {};
opts.accepts = acceptArgs.concat(method.accepts || []); opts.accepts = acceptArgs.concat(method.accepts || []);
opts.returns = [].concat(method.returns || []); opts.returns = [].concat(method.returns || []);
@ -922,47 +956,54 @@ module.exports = function(registry) {
opts.rest.delegateTo = method; opts.rest.delegateTo = method;
opts.http = []; opts.http = [];
var routes = [].concat(method.http || []); const routes = [].concat(method.http || []);
routes.forEach(function(route) { routes.forEach(function(route) {
if (route.path) { if (route.path) {
var copy = extend({}, route); const copy = extend({}, route);
copy.path = httpPath + route.path; copy.path = httpPath + route.path;
opts.http.push(copy); opts.http.push(copy);
} }
}); });
const lastArg = opts.accepts[opts.accepts.length - 1] || {};
const hasOptionsFromContext =
(lastArg.arg || lastArg.name) === 'options' &&
lastArg.type === 'object' && lastArg.http;
if (relation.multiple) { if (relation.multiple) {
sharedClass.defineMethod(methodName, opts, function(fkId) { sharedClass.defineMethod(methodName, opts, function(fkId) {
var args = Array.prototype.slice.call(arguments, 1); const args = Array.prototype.slice.call(arguments, 1);
var last = args[args.length - 1]; const cb = args[args.length - 1];
var cb = typeof last === 'function' ? last : null; const contextOptions =
this[getterName](fkId, function(err, inst) { hasOptionsFromContext && args[args.length - 2] || {};
if (err && cb) return cb(err); this[getterName](fkId, contextOptions, function(err, inst) {
if (err) return cb(err);
if (inst instanceof relation.modelTo) { if (inst instanceof relation.modelTo) {
try { try {
nestedFn.apply(inst, args); nestedFn.apply(inst, args);
} catch (err) { } catch (err) {
if (cb) return cb(err); return cb(err);
} }
} else if (cb) { } else {
cb(err, null); cb(err, null);
} }
}); });
}, method.isStatic); }, method.isStatic);
} else { } else {
sharedClass.defineMethod(methodName, opts, function() { sharedClass.defineMethod(methodName, opts, function() {
var args = Array.prototype.slice.call(arguments); const args = Array.prototype.slice.call(arguments);
var last = args[args.length - 1]; const cb = args[args.length - 1];
var cb = typeof last === 'function' ? last : null; const contextOptions =
this[getterName](function(err, inst) { hasOptionsFromContext && args[args.length - 2] || {};
if (err && cb) return cb(err); this[getterName](contextOptions, function(err, inst) {
if (err) return cb(err);
if (inst instanceof relation.modelTo) { if (inst instanceof relation.modelTo) {
try { try {
nestedFn.apply(inst, args); nestedFn.apply(inst, args);
} catch (err) { } catch (err) {
if (cb) return cb(err); return cb(err);
} }
} else if (cb) { } else {
cb(err, null); cb(err, null);
} }
}); });
@ -976,19 +1017,19 @@ module.exports = function(registry) {
if (options.hooks === false) return; // don't inherit before/after hooks if (options.hooks === false) return; // don't inherit before/after hooks
self.once('mounted', function(app, sc, remotes) { self.once('mounted', function(app, sc, remotes) {
var listenerTree = extend({}, remotes.listenerTree || {}); const listenerTree = extend({}, remotes.listenerTree || {});
listenerTree.before = listenerTree.before || {}; listenerTree.before = listenerTree.before || {};
listenerTree.after = listenerTree.after || {}; listenerTree.after = listenerTree.after || {};
var beforeListeners = listenerTree.before[toModelName] || {}; const beforeListeners = listenerTree.before[toModelName] || {};
var afterListeners = listenerTree.after[toModelName] || {}; const afterListeners = listenerTree.after[toModelName] || {};
sharedClass.methods().forEach(function(method) { sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo; const delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) { if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype']; const before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype']; const after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name; const m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) { if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) { self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next); before[delegateTo.name]._listeners.call(null, ctx, next);
@ -1003,7 +1044,7 @@ module.exports = function(registry) {
}); });
}); });
} else { } else {
var msg = g.f('Relation `%s` does not exist for model `%s`', relationName, this.modelName); const msg = g.f('Relation `%s` does not exist for model `%s`', relationName, this.modelName);
throw new Error(msg); throw new Error(msg);
} }
}; };
@ -1046,7 +1087,7 @@ module.exports = function(registry) {
*/ */
Model.createOptionsFromRemotingContext = function(ctx) { Model.createOptionsFromRemotingContext = function(ctx) {
return { return {
accessToken: ctx.req.accessToken, accessToken: ctx.req ? ctx.req.accessToken : null,
}; };
}; };

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -7,18 +7,20 @@
* Module Dependencies. * Module Dependencies.
*/ */
'use strict'; 'use strict';
var g = require('./globalize'); const g = require('./globalize');
var runtime = require('./runtime'); const runtime = require('./runtime');
var assert = require('assert'); const assert = require('assert');
var async = require('async'); const async = require('async');
var deprecated = require('depd')('loopback'); const deprecated = require('depd')('loopback');
var debug = require('debug')('loopback:persisted-model'); const debug = require('debug')('loopback:persisted-model');
var PassThrough = require('stream').PassThrough; const PassThrough = require('stream').PassThrough;
var utils = require('./utils'); const utils = require('./utils');
var REPLICATION_CHUNK_SIZE = -1; const filterNodes = require('loopback-filters');
const REPLICATION_CHUNK_SIZE = -1;
module.exports = function(registry) { module.exports = function(registry) {
var Model = registry.getModel('Model'); const Model = registry.getModel('Model');
/** /**
* Extends Model with basic query and CRUD support. * Extends Model with basic query and CRUD support.
@ -36,7 +38,7 @@ module.exports = function(registry) {
* @class PersistedModel * @class PersistedModel
*/ */
var PersistedModel = Model.extend('PersistedModel'); const PersistedModel = Model.extend('PersistedModel');
/*! /*!
* Setup the `PersistedModel` constructor. * Setup the `PersistedModel` constructor.
@ -46,7 +48,7 @@ module.exports = function(registry) {
// call Model.setup first // call Model.setup first
Model.setup.call(this); Model.setup.call(this);
var PersistedModel = this; const PersistedModel = this;
// enable change tracking (usually for replication) // enable change tracking (usually for replication)
if (this.settings.trackChanges) { if (this.settings.trackChanges) {
@ -70,7 +72,7 @@ module.exports = function(registry) {
g.f('Cannot call %s.%s().' + g.f('Cannot call %s.%s().' +
' The %s method has not been setup.' + ' The %s method has not been setup.' +
' The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!', ' The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!',
modelName, methodName, methodName) modelName, methodName, methodName),
); );
} }
@ -83,10 +85,10 @@ module.exports = function(registry) {
function convertNullToNotFoundError(ctx, cb) { function convertNullToNotFoundError(ctx, cb) {
if (ctx.result !== null) return cb(); if (ctx.result !== null) return cb();
var modelName = ctx.method.sharedClass.name; const modelName = ctx.method.sharedClass.name;
var id = ctx.getArgByName('id'); const id = ctx.getArgByName('id');
var msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id); const msg = g.f('Unknown "%s" {{id}} "%s".', modelName, id);
var error = new Error(msg); const error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND'; error.code = 'MODEL_NOT_FOUND';
cb(error); cb(error);
@ -401,7 +403,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.prototype.save = function(options, callback) { PersistedModel.prototype.save = function(options, callback) {
var Model = this.constructor; const Model = this.constructor;
if (typeof options == 'function') { if (typeof options == 'function') {
callback = options; callback = options;
@ -419,9 +421,9 @@ module.exports = function(registry) {
options.throws = false; options.throws = false;
} }
var inst = this; const inst = this;
var data = inst.toObject(true); const data = inst.toObject(true);
var id = this.getId(); const id = this.getId();
if (!id) { if (!id) {
return Model.create(this, callback); return Model.create(this, callback);
@ -436,7 +438,7 @@ module.exports = function(registry) {
if (valid) { if (valid) {
save(); save();
} else { } else {
var err = new Model.ValidationError(inst); const err = new Model.ValidationError(inst);
// throws option is dangerous for async usage // throws option is dangerous for async usage
if (options.throws) { if (options.throws) {
throw err; throw err;
@ -579,7 +581,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.prototype.setId = function(val) { PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource(); const ds = this.getDataSource();
this[this.getIdName()] = val; this[this.getIdName()] = val;
}; };
@ -590,7 +592,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.prototype.getId = function() { PersistedModel.prototype.getId = function() {
var data = this.toObject(); const data = this.toObject();
if (!data) return; if (!data) return;
return data[this.getIdName()]; return data[this.getIdName()];
}; };
@ -612,8 +614,8 @@ module.exports = function(registry) {
*/ */
PersistedModel.getIdName = function() { PersistedModel.getIdName = function() {
var Model = this; const Model = this;
var ds = Model.getDataSource(); const ds = Model.getDataSource();
if (ds.idName) { if (ds.idName) {
return ds.idName(Model.modelName); return ds.idName(Model.modelName);
@ -623,15 +625,22 @@ module.exports = function(registry) {
}; };
PersistedModel.setupRemoting = function() { PersistedModel.setupRemoting = function() {
var PersistedModel = this; const PersistedModel = this;
var typeName = PersistedModel.modelName; const typeName = PersistedModel.modelName;
var options = PersistedModel.settings; const options = PersistedModel.settings;
// if there is atleast one updateOnly property, then we set
// createOnlyInstance flag in __create__ to indicate loopback-swagger
// code to create a separate model instance for create operation only
const updateOnlyProps = this.getUpdateOnlyProperties ?
this.getUpdateOnlyProperties() : false;
const hasUpdateOnlyProps = updateOnlyProps && updateOnlyProps.length > 0;
// This is just for LB 3.x // This is just for LB 3.x
options.replaceOnPUT = options.replaceOnPUT !== false; options.replaceOnPUT = options.replaceOnPUT !== false;
function setRemoting(scope, name, options) { function setRemoting(scope, name, options) {
var fn = scope[name]; const fn = scope[name];
fn._delegate = true; fn._delegate = true;
options.isStatic = scope === PersistedModel; options.isStatic = scope === PersistedModel;
PersistedModel.remoteMethod(name, options); PersistedModel.remoteMethod(name, options);
@ -643,6 +652,7 @@ module.exports = function(registry) {
accepts: [ accepts: [
{ {
arg: 'data', type: 'object', model: typeName, allowArray: true, arg: 'data', type: 'object', model: typeName, allowArray: true,
createOnlyInstance: hasUpdateOnlyProps,
description: 'Model instance data', description: 'Model instance data',
http: {source: 'body'}, http: {source: 'body'},
}, },
@ -652,7 +662,7 @@ module.exports = function(registry) {
http: {verb: 'post', path: '/'}, http: {verb: 'post', path: '/'},
}); });
var upsertOptions = { const upsertOptions = {
aliases: ['upsert', 'updateOrCreate'], aliases: ['upsert', 'updateOrCreate'],
description: 'Patch an existing model instance or insert a new one ' + description: 'Patch an existing model instance or insert a new one ' +
'into the data source.', 'into the data source.',
@ -673,7 +683,7 @@ module.exports = function(registry) {
} }
setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); setRemoting(PersistedModel, 'patchOrCreate', upsertOptions);
var replaceOrCreateOptions = { const replaceOrCreateOptions = {
description: 'Replace an existing model instance or insert a new one into the data source.', description: 'Replace an existing model instance or insert a new one into the data source.',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
@ -714,7 +724,8 @@ module.exports = function(registry) {
description: 'Check whether a model instance exists in the data source.', description: 'Check whether a model instance exists in the data source.',
accessType: 'READ', accessType: 'READ',
accepts: [ accepts: [
{arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'id', type: 'any', description: 'Model id', required: true,
http: {source: 'path'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
returns: {arg: 'exists', type: 'boolean'}, returns: {arg: 'exists', type: 'boolean'},
@ -730,10 +741,10 @@ module.exports = function(registry) {
return cb(); return cb();
} }
if (!ctx.result.exists) { if (!ctx.result.exists) {
var modelName = ctx.method.sharedClass.name; const modelName = ctx.method.sharedClass.name;
var id = ctx.getArgByName('id'); const id = ctx.getArgByName('id');
var msg = 'Unknown "' + modelName + '" id "' + id + '".'; const msg = 'Unknown "' + modelName + '" id "' + id + '".';
var error = new Error(msg); const error = new Error(msg);
error.statusCode = error.status = 404; error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND'; error.code = 'MODEL_NOT_FOUND';
cb(error); cb(error);
@ -761,7 +772,7 @@ module.exports = function(registry) {
rest: {after: convertNullToNotFoundError}, rest: {after: convertNullToNotFoundError},
}); });
var replaceByIdOptions = { const replaceByIdOptions = {
description: 'Replace attributes for a model instance and persist it into the data source.', description: 'Replace attributes for a model instance and persist it into the data source.',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
@ -787,7 +798,9 @@ module.exports = function(registry) {
accepts: [ accepts: [
{arg: 'filter', type: 'object', description: {arg: 'filter', type: 'object', description:
'Filter defining fields, where, include, order, offset, and limit - must be a ' + 'Filter defining fields, where, include, order, offset, and limit - must be a ' +
'JSON-encoded string ({"something":"value"})'}, 'JSON-encoded string (`{"where":{"something":"value"}}`). ' +
'See https://loopback.io/doc/en/lb3/Querying-data.html#using-stringified-json-in-rest-queries ' +
'for more details.'},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
returns: {arg: 'data', type: [typeName], root: true}, returns: {arg: 'data', type: [typeName], root: true},
@ -800,7 +813,9 @@ module.exports = function(registry) {
accepts: [ accepts: [
{arg: 'filter', type: 'object', description: {arg: 'filter', type: 'object', description:
'Filter defining fields, where, include, order, offset, and limit - must be a ' + 'Filter defining fields, where, include, order, offset, and limit - must be a ' +
'JSON-encoded string ({"something":"value"})'}, 'JSON-encoded string (`{"where":{"something":"value"}}`). ' +
'See https://loopback.io/doc/en/lb3/Querying-data.html#using-stringified-json-in-rest-queries ' +
'for more details.'},
{arg: 'options', type: 'object', http: 'optionsFromRequest'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'},
], ],
returns: {arg: 'data', type: typeName, root: true}, returns: {arg: 'data', type: typeName, root: true},
@ -874,7 +889,7 @@ module.exports = function(registry) {
http: {verb: 'get', path: '/count'}, http: {verb: 'get', path: '/count'},
}); });
var updateAttributesOptions = { const updateAttributesOptions = {
aliases: ['updateAttributes'], aliases: ['updateAttributes'],
description: 'Patch attributes for a model instance and persist it into ' + description: 'Patch attributes for a model instance and persist it into ' +
'the data source.', 'the data source.',
@ -1023,7 +1038,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.diff = function(since, remoteChanges, callback) { PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel(); const Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback); Change.diff(this.modelName, since, remoteChanges, callback);
}; };
@ -1049,9 +1064,9 @@ module.exports = function(registry) {
filter = {}; filter = {};
} }
var idName = this.dataSource.idName(this.modelName); const idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel(); const Change = this.getChangeModel();
var model = this; const model = this;
const changeFilter = this.createChangeFilter(since, filter); const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {}; filter = filter || {};
@ -1063,13 +1078,13 @@ module.exports = function(registry) {
Change.find(changeFilter, function(err, changes) { Change.find(changeFilter, function(err, changes) {
if (err) return callback(err); if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) { const ids = changes.map(function(change) {
return change.getModelId(); return change.getModelId();
}); });
filter.where[idName] = {inq: ids}; filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) { model.find(filter, function(err, models) {
if (err) return callback(err); if (err) return callback(err);
var modelIds = models.map(function(m) { const modelIds = models.map(function(m) {
return m[idName].toString(); return m[idName].toString();
}); });
callback(null, changes.filter(function(ch) { callback(null, changes.filter(function(ch) {
@ -1087,7 +1102,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.checkpoint = function(cb) { PersistedModel.checkpoint = function(cb) {
var Checkpoint = this.getChangeModel().getCheckpointModel(); const Checkpoint = this.getChangeModel().getCheckpointModel();
Checkpoint.bumpLastSeq(cb); Checkpoint.bumpLastSeq(cb);
}; };
@ -1100,7 +1115,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.currentCheckpoint = function(cb) { PersistedModel.currentCheckpoint = function(cb) {
var Checkpoint = this.getChangeModel().getCheckpointModel(); const Checkpoint = this.getChangeModel().getCheckpointModel();
Checkpoint.current(cb); Checkpoint.current(cb);
}; };
@ -1121,7 +1136,7 @@ module.exports = function(registry) {
*/ */
PersistedModel.replicate = function(since, targetModel, options, callback) { PersistedModel.replicate = function(since, targetModel, options, callback) {
var lastArg = arguments[arguments.length - 1]; const lastArg = arguments[arguments.length - 1];
if (typeof lastArg === 'function' && arguments.length > 1) { if (typeof lastArg === 'function' && arguments.length > 1) {
callback = lastArg; callback = lastArg;
@ -1142,7 +1157,7 @@ module.exports = function(registry) {
options = options || {}; options = options || {};
var sourceModel = this; const sourceModel = this;
callback = callback || utils.createPromiseCallback(); callback = callback || utils.createPromiseCallback();
debug('replicating %s since %s to %s since %s', debug('replicating %s since %s to %s since %s',
@ -1166,7 +1181,7 @@ module.exports = function(registry) {
// until no changes were replicated, but at most MAX_ATTEMPTS times // until no changes were replicated, but at most MAX_ATTEMPTS times
// to prevent starvation. In most cases, the second run will find no changes // to prevent starvation. In most cases, the second run will find no changes
// to replicate and we are done. // to replicate and we are done.
var MAX_ATTEMPTS = 3; const MAX_ATTEMPTS = 3;
run(1, since); run(1, since);
return callback.promise; return callback.promise;
@ -1176,7 +1191,7 @@ module.exports = function(registry) {
tryReplicate(sourceModel, targetModel, since, options, next); tryReplicate(sourceModel, targetModel, since, options, next);
function next(err, conflicts, cps, updates) { function next(err, conflicts, cps, updates) {
var finished = err || conflicts.length || const finished = err || conflicts.length ||
!updates || updates.length === 0 || !updates || updates.length === 0 ||
attempt >= MAX_ATTEMPTS; attempt >= MAX_ATTEMPTS;
@ -1188,10 +1203,10 @@ module.exports = function(registry) {
}; };
function tryReplicate(sourceModel, targetModel, since, options, callback) { function tryReplicate(sourceModel, targetModel, since, options, callback) {
var Change = sourceModel.getChangeModel(); const Change = sourceModel.getChangeModel();
var TargetChange = targetModel.getChangeModel(); const TargetChange = targetModel.getChangeModel();
var changeTrackingEnabled = Change && TargetChange; const changeTrackingEnabled = Change && TargetChange;
var replicationChunkSize = REPLICATION_CHUNK_SIZE; let replicationChunkSize = REPLICATION_CHUNK_SIZE;
if (sourceModel.settings && sourceModel.settings.replicationChunkSize) { if (sourceModel.settings && sourceModel.settings.replicationChunkSize) {
replicationChunkSize = sourceModel.settings.replicationChunkSize; replicationChunkSize = sourceModel.settings.replicationChunkSize;
@ -1199,12 +1214,12 @@ module.exports = function(registry) {
assert( assert(
changeTrackingEnabled, changeTrackingEnabled,
'You must enable change tracking before replicating' 'You must enable change tracking before replicating',
); );
var diff, updates, newSourceCp, newTargetCp; let diff, updates, newSourceCp, newTargetCp;
var tasks = [ const tasks = [
checkpoints, checkpoints,
getSourceChanges, getSourceChanges,
getDiffFromTarget, getDiffFromTarget,
@ -1221,7 +1236,8 @@ module.exports = function(registry) {
function(filter, pagingCallback) { function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback); sourceModel.changes(since.source, filter, pagingCallback);
}, },
debug.enabled ? log : cb); debug.enabled ? log : cb,
);
function log(err, result) { function log(err, result) {
if (err) return cb(err); if (err) return cb(err);
@ -1238,7 +1254,8 @@ module.exports = function(registry) {
function(smallArray, chunkCallback) { function(smallArray, chunkCallback) {
return targetModel.diff(since.target, smallArray, chunkCallback); return targetModel.diff(since.target, smallArray, chunkCallback);
}, },
debug.enabled ? log : cb); debug.enabled ? log : cb,
);
function log(err, result) { function log(err, result) {
if (err) return cb(err); if (err) return cb(err);
@ -1266,7 +1283,8 @@ module.exports = function(registry) {
function(smallArray, chunkCallback) { function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback); return sourceModel.createUpdates(smallArray, chunkCallback);
}, },
cb); cb,
);
} else { } else {
// nothing to replicate // nothing to replicate
done(); done();
@ -1286,7 +1304,7 @@ module.exports = function(registry) {
}); });
}, },
function(notUsed, err) { function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts; const conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) { if (conflicts && err.statusCode == 409) {
diff.conflicts = conflicts; diff.conflicts = conflicts;
// filter out updates that were not applied // filter out updates that were not applied
@ -1298,11 +1316,12 @@ module.exports = function(registry) {
return cb(); return cb();
} }
cb(err); cb(err);
}); },
);
} }
function checkpoints() { function checkpoints() {
var cb = arguments[arguments.length - 1]; const cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) { sourceModel.checkpoint(function(err, source) {
if (err) return cb(err); if (err) return cb(err);
newSourceCp = source.seq; newSourceCp = source.seq;
@ -1326,9 +1345,9 @@ module.exports = function(registry) {
debug('\t\tnew checkpoints: { source: %j, target: %j }', debug('\t\tnew checkpoints: { source: %j, target: %j }',
newSourceCp, newTargetCp); newSourceCp, newTargetCp);
var conflicts = diff.conflicts.map(function(change) { const conflicts = diff.conflicts.map(function(change) {
return new Change.Conflict( return new Change.Conflict(
change.modelId, sourceModel, targetModel change.modelId, sourceModel, targetModel,
); );
}); });
@ -1337,7 +1356,7 @@ module.exports = function(registry) {
} }
if (callback) { if (callback) {
var newCheckpoints = {source: newSourceCp, target: newTargetCp}; const newCheckpoints = {source: newSourceCp, target: newTargetCp};
callback(null, conflicts, newCheckpoints, updates); callback(null, conflicts, newCheckpoints, updates);
} }
} }
@ -1352,15 +1371,15 @@ module.exports = function(registry) {
*/ */
PersistedModel.createUpdates = function(deltas, cb) { PersistedModel.createUpdates = function(deltas, cb) {
var Change = this.getChangeModel(); const Change = this.getChangeModel();
var updates = []; const updates = [];
var Model = this; const Model = this;
var tasks = []; const tasks = [];
deltas.forEach(function(change) { deltas.forEach(function(change) {
change = new Change(change); change = new Change(change);
var type = change.type(); const type = change.type();
var update = {type: type, change: change}; const update = {type: type, change: change};
switch (type) { switch (type) {
case Change.CREATE: case Change.CREATE:
case Change.UPDATE: case Change.UPDATE:
@ -1404,12 +1423,12 @@ module.exports = function(registry) {
*/ */
PersistedModel.bulkUpdate = function(updates, options, callback) { PersistedModel.bulkUpdate = function(updates, options, callback) {
var tasks = []; const tasks = [];
var Model = this; const Model = this;
var Change = this.getChangeModel(); const Change = this.getChangeModel();
var conflicts = []; const conflicts = [];
var lastArg = arguments[arguments.length - 1]; const lastArg = arguments[arguments.length - 1];
if (typeof lastArg === 'function' && arguments.length > 1) { if (typeof lastArg === 'function' && arguments.length > 1) {
callback = lastArg; callback = lastArg;
@ -1425,8 +1444,8 @@ module.exports = function(registry) {
if (err) return callback(err); if (err) return callback(err);
updates.forEach(function(update) { updates.forEach(function(update) {
var id = update.change.modelId; const id = update.change.modelId;
var current = currentMap[id]; const current = currentMap[id];
switch (update.type) { switch (update.type) {
case Change.UPDATE: case Change.UPDATE:
tasks.push(function(cb) { tasks.push(function(cb) {
@ -1461,13 +1480,13 @@ module.exports = function(registry) {
}; };
function buildLookupOfAffectedModelData(Model, updates, callback) { function buildLookupOfAffectedModelData(Model, updates, callback) {
var idName = Model.dataSource.idName(Model.modelName); const idName = Model.dataSource.idName(Model.modelName);
var affectedIds = updates.map(function(u) { return u.change.modelId; }); const affectedIds = updates.map(function(u) { return u.change.modelId; });
var whereAffected = {}; const whereAffected = {};
whereAffected[idName] = {inq: affectedIds}; whereAffected[idName] = {inq: affectedIds};
Model.find({where: whereAffected}, function(err, affectedList) { Model.find({where: whereAffected}, function(err, affectedList) {
if (err) return callback(err); if (err) return callback(err);
var dataLookup = {}; const dataLookup = {};
affectedList.forEach(function(it) { affectedList.forEach(function(it) {
dataLookup[it[idName]] = it; dataLookup[it[idName]] = it;
}); });
@ -1476,8 +1495,8 @@ module.exports = function(registry) {
} }
function applyUpdate(Model, id, current, data, change, conflicts, options, cb) { function applyUpdate(Model, id, current, data, change, conflicts, options, cb) {
var Change = Model.getChangeModel(); const Change = Model.getChangeModel();
var rev = current ? Change.revisionForInst(current) : null; const rev = current ? Change.revisionForInst(current) : null;
if (rev !== change.prev) { if (rev !== change.prev) {
debug('Detected non-rectified change of %s %j', debug('Detected non-rectified change of %s %j',
@ -1496,7 +1515,7 @@ module.exports = function(registry) {
Model.updateAll(current.toObject(), data, options, function(err, result) { Model.updateAll(current.toObject(), data, options, function(err, result) {
if (err) return cb(err); if (err) return cb(err);
var count = result && result.count; const count = result && result.count;
switch (count) { switch (count) {
case 1: case 1:
// The happy path, exactly one record was updated // The happy path, exactly one record was updated
@ -1516,14 +1535,16 @@ module.exports = function(registry) {
return cb(new Error( return cb(new Error(
g.f('Cannot apply bulk updates, ' + g.f('Cannot apply bulk updates, ' +
'the connector does not correctly report ' + 'the connector does not correctly report ' +
'the number of updated records.'))); 'the number of updated records.'),
));
default: default:
debug('%s.updateAll modified unexpected number of instances: %j', debug('%s.updateAll modified unexpected number of instances: %j',
Model.modelName, count); Model.modelName, count);
return cb(new Error( return cb(new Error(
g.f('Bulk update failed, the connector has modified unexpected ' + g.f('Bulk update failed, the connector has modified unexpected ' +
'number of records: %s', JSON.stringify(count)))); 'number of records: %s', JSON.stringify(count)),
));
} }
}); });
} }
@ -1552,7 +1573,7 @@ module.exports = function(registry) {
Model.modelName, id); Model.modelName, id);
conflicts.push(change); conflicts.push(change);
var Change = Model.getChangeModel(); const Change = Model.getChangeModel();
return Change.rectifyModelChanges(Model.modelName, [id], cb); return Change.rectifyModelChanges(Model.modelName, [id], cb);
} }
} }
@ -1564,8 +1585,8 @@ module.exports = function(registry) {
return cb(); return cb();
} }
var Change = Model.getChangeModel(); const Change = Model.getChangeModel();
var rev = Change.revisionForInst(current); const rev = Change.revisionForInst(current);
if (rev !== change.prev) { if (rev !== change.prev) {
debug('Detected non-rectified change of %s %j', debug('Detected non-rectified change of %s %j',
Model.modelName, id); Model.modelName, id);
@ -1578,7 +1599,7 @@ module.exports = function(registry) {
Model.deleteAll(current.toObject(), options, function(err, result) { Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err); if (err) return cb(err);
var count = result && result.count; const count = result && result.count;
switch (count) { switch (count) {
case 1: case 1:
// The happy path, exactly one record was updated // The happy path, exactly one record was updated
@ -1598,14 +1619,16 @@ module.exports = function(registry) {
return cb(new Error( return cb(new Error(
g.f('Cannot apply bulk updates, ' + g.f('Cannot apply bulk updates, ' +
'the connector does not correctly report ' + 'the connector does not correctly report ' +
'the number of deleted records.'))); 'the number of deleted records.'),
));
default: default:
debug('%s.deleteAll modified unexpected number of instances: %j', debug('%s.deleteAll modified unexpected number of instances: %j',
Model.modelName, count); Model.modelName, count);
return cb(new Error( return cb(new Error(
g.f('Bulk update failed, the connector has deleted unexpected ' + g.f('Bulk update failed, the connector has deleted unexpected ' +
'number of records: %s', JSON.stringify(count)))); 'number of records: %s', JSON.stringify(count)),
));
} }
}); });
} }
@ -1617,8 +1640,8 @@ module.exports = function(registry) {
*/ */
PersistedModel.getChangeModel = function() { PersistedModel.getChangeModel = function() {
var changeModel = this.Change; const changeModel = this.Change;
var isSetup = changeModel && changeModel.dataSource; const isSetup = changeModel && changeModel.dataSource;
assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName);
@ -1634,15 +1657,15 @@ module.exports = function(registry) {
*/ */
PersistedModel.getSourceId = function(cb) { PersistedModel.getSourceId = function(cb) {
var dataSource = this.dataSource; const dataSource = this.dataSource;
if (!dataSource) { if (!dataSource) {
this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); this.once('dataSourceAttached', this.getSourceId.bind(this, cb));
} }
assert( assert(
dataSource.connector.name, dataSource.connector.name,
'Model.getSourceId: cannot get id without dataSource.connector.name' 'Model.getSourceId: cannot get id without dataSource.connector.name',
); );
var id = [dataSource.connector.name, this.modelName].join('-'); const id = [dataSource.connector.name, this.modelName].join('-');
cb(null, id); cb(null, id);
}; };
@ -1651,17 +1674,17 @@ module.exports = function(registry) {
*/ */
PersistedModel.enableChangeTracking = function() { PersistedModel.enableChangeTracking = function() {
var Model = this; const Model = this;
var Change = this.Change || this._defineChangeModel(); const Change = this.Change || this._defineChangeModel();
var cleanupInterval = Model.settings.changeCleanupInterval || 30000; const cleanupInterval = Model.settings.changeCleanupInterval || 30000;
assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName +
' is not attached to a dataSource'); ' is not attached to a dataSource');
var idName = this.getIdName(); const idName = this.getIdName();
var idProp = this.definition.properties[idName]; const idProp = this.definition.properties[idName];
var idType = idProp && idProp.type; const idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn; const idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' + deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.'); 'which requires a string id with GUID/UUID default value.');
@ -1691,8 +1714,8 @@ module.exports = function(registry) {
}; };
function rectifyOnSave(ctx, next) { function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance; const instance = ctx.instance || ctx.currentInstance;
var id = instance ? instance.getId() : const id = instance ? instance.getId() :
getIdFromWhereByModelId(ctx.Model, ctx.where); getIdFromWhereByModelId(ctx.Model, ctx.where);
if (debug.enabled) { if (debug.enabled) {
@ -1702,7 +1725,7 @@ module.exports = function(registry) {
ctx.instance, ctx.currentInstance, ctx.where, ctx.data); ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
} }
if (id) { if (id != null) {
ctx.Model.rectifyChange(id, reportErrorAndNext); ctx.Model.rectifyChange(id, reportErrorAndNext);
} else { } else {
ctx.Model.rectifyAllChanges(reportErrorAndNext); ctx.Model.rectifyAllChanges(reportErrorAndNext);
@ -1717,7 +1740,7 @@ module.exports = function(registry) {
} }
function rectifyOnDelete(ctx, next) { function rectifyOnDelete(ctx, next) {
var id = ctx.instance ? ctx.instance.getId() : const id = ctx.instance ? ctx.instance.getId() :
getIdFromWhereByModelId(ctx.Model, ctx.where); getIdFromWhereByModelId(ctx.Model, ctx.where);
if (debug.enabled) { if (debug.enabled) {
@ -1726,7 +1749,7 @@ module.exports = function(registry) {
debug('context instance:%j where:%j', ctx.instance, ctx.where); debug('context instance:%j where:%j', ctx.instance, ctx.where);
} }
if (id) { if (id != null) {
ctx.Model.rectifyChange(id, reportErrorAndNext); ctx.Model.rectifyChange(id, reportErrorAndNext);
} else { } else {
ctx.Model.rectifyAllChanges(reportErrorAndNext); ctx.Model.rectifyAllChanges(reportErrorAndNext);
@ -1741,10 +1764,10 @@ module.exports = function(registry) {
} }
function getIdFromWhereByModelId(Model, where) { function getIdFromWhereByModelId(Model, where) {
var idName = Model.getIdName(); const idName = Model.getIdName();
if (!(idName in where)) return undefined; if (!(idName in where)) return undefined;
var id = where[idName]; const id = where[idName];
// TODO(bajtos) support object values that are not LB conditions // TODO(bajtos) support object values that are not LB conditions
if (typeof id === 'string' || typeof id === 'number') { if (typeof id === 'string' || typeof id === 'number') {
return id; return id;
@ -1753,16 +1776,17 @@ module.exports = function(registry) {
} }
PersistedModel._defineChangeModel = function() { PersistedModel._defineChangeModel = function() {
var BaseChangeModel = this.registry.getModel('Change'); const BaseChangeModel = this.registry.getModel('Change');
assert(BaseChangeModel, assert(BaseChangeModel,
'Change model must be defined before enabling change replication'); 'Change model must be defined before enabling change replication');
const additionalChangeModelProperties = const additionalChangeModelProperties =
this.settings.additionalChangeModelProperties || {}; this.settings.additionalChangeModelProperties || {};
this.Change = BaseChangeModel.extend(this.modelName + '-change', this.Change = BaseChangeModel.extend(
this.modelName + '-change',
additionalChangeModelProperties, additionalChangeModelProperties,
{trackModel: this} {trackModel: this},
); );
if (this.dataSource) { if (this.dataSource) {
@ -1770,7 +1794,7 @@ module.exports = function(registry) {
} }
// Re-attach related models whenever our datasource is changed. // Re-attach related models whenever our datasource is changed.
var self = this; const self = this;
this.on('dataSourceAttached', function() { this.on('dataSourceAttached', function() {
attachRelatedModels(self); attachRelatedModels(self);
}); });
@ -1808,17 +1832,17 @@ module.exports = function(registry) {
*/ */
PersistedModel.rectifyChange = function(id, callback) { PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel(); const Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback); Change.rectifyModelChanges(this.modelName, [id], callback);
}; };
PersistedModel.findLastChange = function(id, cb) { PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel(); const Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb); Change.findOne({where: {modelId: id}}, cb);
}; };
PersistedModel.updateLastChange = function(id, data, cb) { PersistedModel.updateLastChange = function(id, data, cb) {
var self = this; const self = this;
this.findLastChange(id, function(err, inst) { this.findLastChange(id, function(err, inst) {
if (err) return cb(err); if (err) return cb(err);
if (!inst) { if (!inst) {
@ -1847,83 +1871,104 @@ module.exports = function(registry) {
cb = options; cb = options;
options = undefined; options = undefined;
} }
cb = cb || utils.createPromiseCallback();
var idName = this.getIdName(); const idName = this.getIdName();
var Model = this; const Model = this;
var changes = new PassThrough({objectMode: true}); const changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() { changes._destroy = function() {
changes.removeAllListeners('error'); changes.end();
changes.removeAllListeners('end'); changes.emit('end');
writeable = false; changes.emit('close');
changes = null;
}; };
changes.on('error', function() { changes.destroy = changes.destroy || changes._destroy; // node 8 compability
writeable = false;
}); changes.on('error', removeHandlers);
changes.on('end', function() { changes.on('close', removeHandlers);
writeable = false; changes.on('finish', removeHandlers);
}); changes.on('end', removeHandlers);
process.nextTick(function() { process.nextTick(function() {
cb(null, changes); cb(null, changes);
}); });
Model.observe('after save', createChangeHandler('save')); Model.observe('after save', changeHandler);
Model.observe('after delete', createChangeHandler('delete')); Model.observe('after delete', deleteHandler);
function createChangeHandler(type) { return cb.promise;
return function(ctx, next) {
// since it might have set to null via destroy function changeHandler(ctx, next) {
if (!changes) { const change = createChangeObject(ctx, 'save');
return next(); if (change) {
changes.write(change);
}
next();
}
function deleteHandler(ctx, next) {
const change = createChangeObject(ctx, 'delete');
if (change) {
changes.write(change);
}
next();
}
function createChangeObject(ctx, type) {
const where = ctx.where;
let data = ctx.instance || ctx.data;
const whereId = where && where[idName];
// the data includes the id
// or the where includes the id
let target;
if (data && (data[idName] || data[idName] === 0)) {
target = data[idName];
} else if (where && (where[idName] || where[idName] === 0)) {
target = where[idName];
}
const hasTarget = target === 0 || !!target;
// apply filtering if options is set
if (options) {
const filtered = filterNodes([data], options);
if (filtered.length !== 1) {
return null;
} }
data = filtered[0];
}
var where = ctx.where; const change = {
var data = ctx.instance || ctx.data; target: target,
var whereId = where && where[idName]; where: where,
data: data,
// the data includes the id
// or the where includes the id
var target;
if (data && (data[idName] || data[idName] === 0)) {
target = data[idName];
} else if (where && (where[idName] || where[idName] === 0)) {
target = where[idName];
}
var hasTarget = target === 0 || !!target;
var change = {
target: target,
where: where,
data: data,
};
switch (type) {
case 'save':
if (ctx.isNewInstance === undefined) {
change.type = hasTarget ? 'update' : 'create';
} else {
change.type = ctx.isNewInstance ? 'create' : 'update';
}
break;
case 'delete':
change.type = 'remove';
break;
}
// TODO(ritch) this is ugly... maybe a ReadableStream would be better
if (writeable) {
changes.write(change);
}
next();
}; };
switch (type) {
case 'save':
if (ctx.isNewInstance === undefined) {
change.type = hasTarget ? 'update' : 'create';
} else {
change.type = ctx.isNewInstance ? 'create' : 'update';
}
break;
case 'delete':
change.type = 'remove';
break;
}
return change;
}
function removeHandlers() {
Model.removeObserver('after save', changeHandler);
Model.removeObserver('after delete', deleteHandler);
} }
}; };

View File

@ -1,17 +1,17 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('./globalize'); const g = require('./globalize');
var assert = require('assert'); const assert = require('assert');
var extend = require('util')._extend; const extend = require('util')._extend;
var juggler = require('loopback-datasource-juggler'); const juggler = require('loopback-datasource-juggler');
var debug = require('debug')('loopback:registry'); const debug = require('debug')('loopback:registry');
var DataSource = juggler.DataSource; const DataSource = juggler.DataSource;
var ModelBuilder = juggler.ModelBuilder; const ModelBuilder = juggler.ModelBuilder;
var deprecated = require('depd')('strong-remoting'); const deprecated = require('depd')('strong-remoting');
module.exports = Registry; module.exports = Registry;
@ -97,7 +97,7 @@ function Registry() {
Registry.prototype.createModel = function(name, properties, options) { Registry.prototype.createModel = function(name, properties, options) {
if (arguments.length === 1 && typeof name === 'object') { if (arguments.length === 1 && typeof name === 'object') {
var config = name; const config = name;
name = config.name; name = config.name;
properties = config.properties; properties = config.properties;
options = buildModelOptionsFromConfig(config); options = buildModelOptionsFromConfig(config);
@ -107,10 +107,10 @@ Registry.prototype.createModel = function(name, properties, options) {
} }
options = options || {}; options = options || {};
var BaseModel = options.base || options.super; let BaseModel = options.base || options.super;
if (typeof BaseModel === 'string') { if (typeof BaseModel === 'string') {
var baseName = BaseModel; const baseName = BaseModel;
BaseModel = this.findModel(BaseModel); BaseModel = this.findModel(BaseModel);
if (!BaseModel) { if (!BaseModel) {
throw new Error(g.f('Model not found: model `%s` is extending an unknown model `%s`.', throw new Error(g.f('Model not found: model `%s` is extending an unknown model `%s`.',
@ -119,7 +119,7 @@ Registry.prototype.createModel = function(name, properties, options) {
} }
BaseModel = BaseModel || this.getModel('PersistedModel'); BaseModel = BaseModel || this.getModel('PersistedModel');
var model = BaseModel.extend(name, properties, options); const model = BaseModel.extend(name, properties, options);
model.registry = this; model.registry = this;
this._defineRemoteMethods(model, model.settings.methods); this._defineRemoteMethods(model, model.settings.methods);
@ -128,8 +128,8 @@ Registry.prototype.createModel = function(name, properties, options) {
}; };
function buildModelOptionsFromConfig(config) { function buildModelOptionsFromConfig(config) {
var options = extend({}, config.options); const options = extend({}, config.options);
for (var key in config) { for (const key in config) {
if (['name', 'properties', 'options'].indexOf(key) !== -1) { if (['name', 'properties', 'options'].indexOf(key) !== -1) {
// Skip items which have special meaning // Skip items which have special meaning
continue; continue;
@ -152,7 +152,7 @@ function buildModelOptionsFromConfig(config) {
* @param {Object} acl * @param {Object} acl
*/ */
function addACL(acls, acl) { function addACL(acls, acl) {
for (var i = 0, n = acls.length; i < n; i++) { for (let i = 0, n = acls.length; i < n; i++) {
// Check if there is a matching acl to be overriden // Check if there is a matching acl to be overriden
if (acls[i].property === acl.property && if (acls[i].property === acl.property &&
acls[i].accessType === acl.accessType && acls[i].accessType === acl.accessType &&
@ -176,12 +176,14 @@ function addACL(acls, acl) {
*/ */
Registry.prototype.configureModel = function(ModelCtor, config) { Registry.prototype.configureModel = function(ModelCtor, config) {
var settings = ModelCtor.settings; const settings = ModelCtor.settings;
var modelName = ModelCtor.modelName; const modelName = ModelCtor.modelName;
ModelCtor.config = config;
// Relations // Relations
if (typeof config.relations === 'object' && config.relations !== null) { if (typeof config.relations === 'object' && config.relations !== null) {
var relations = settings.relations = settings.relations || {}; const relations = settings.relations = settings.relations || {};
Object.keys(config.relations).forEach(function(key) { Object.keys(config.relations).forEach(function(key) {
// FIXME: [rfeng] We probably should check if the relation exists // FIXME: [rfeng] We probably should check if the relation exists
relations[key] = extend(relations[key] || {}, config.relations[key]); relations[key] = extend(relations[key] || {}, config.relations[key]);
@ -193,7 +195,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
// ACLs // ACLs
if (Array.isArray(config.acls)) { if (Array.isArray(config.acls)) {
var acls = settings.acls = settings.acls || []; const acls = settings.acls = settings.acls || [];
config.acls.forEach(function(acl) { config.acls.forEach(function(acl) {
addACL(acls, acl); addACL(acls, acl);
}); });
@ -203,7 +205,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
} }
// Settings // Settings
var excludedProperties = { const excludedProperties = {
base: true, base: true,
'super': true, 'super': true,
relations: true, relations: true,
@ -211,7 +213,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
dataSource: true, dataSource: true,
}; };
if (typeof config.options === 'object' && config.options !== null) { if (typeof config.options === 'object' && config.options !== null) {
for (var p in config.options) { for (const p in config.options) {
if (!(p in excludedProperties)) { if (!(p in excludedProperties)) {
settings[p] = config.options[p]; settings[p] = config.options[p];
} else { } else {
@ -228,7 +230,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
// configuration, so that the datasource picks up updated relations // configuration, so that the datasource picks up updated relations
if (config.dataSource) { if (config.dataSource) {
assert(config.dataSource instanceof DataSource, assert(config.dataSource instanceof DataSource,
'Cannot configure ' + ModelCtor.modelName + 'Cannot configure ' + ModelCtor.modelName +
': config.dataSource must be an instance of DataSource'); ': config.dataSource must be an instance of DataSource');
ModelCtor.attachTo(config.dataSource); ModelCtor.attachTo(config.dataSource);
debug('Attached model `%s` to dataSource `%s`', debug('Attached model `%s` to dataSource `%s`',
@ -242,16 +244,18 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
g.warn( g.warn(
'The configuration of `%s` is missing {{`dataSource`}} property.\n' + 'The configuration of `%s` is missing {{`dataSource`}} property.\n' +
'Use `null` or `false` to mark models not attached to any data source.', 'Use `null` or `false` to mark models not attached to any data source.',
modelName); modelName,
);
} }
var newMethodNames = config.methods && Object.keys(config.methods); const newMethodNames = config.methods && Object.keys(config.methods);
var hasNewMethods = newMethodNames && newMethodNames.length; const hasNewMethods = newMethodNames && newMethodNames.length;
var hasDescendants = this.getModelByType(ModelCtor) !== ModelCtor; const hasDescendants = this.getModelByType(ModelCtor) !== ModelCtor;
if (hasNewMethods && hasDescendants) { if (hasNewMethods && hasDescendants) {
g.warn( g.warn(
'Child models of `%s` will not inherit newly defined remote methods %s.', 'Child models of `%s` will not inherit newly defined remote methods %s.',
modelName, newMethodNames); modelName, newMethodNames,
);
} }
// Remote methods // Remote methods
@ -267,13 +271,13 @@ Registry.prototype._defineRemoteMethods = function(ModelCtor, methods) {
} }
Object.keys(methods).forEach(function(key) { Object.keys(methods).forEach(function(key) {
var meta = methods[key]; let meta = methods[key];
var m = key.match(/^prototype\.(.*)$/); const m = key.match(/^prototype\.(.*)$/);
var isStatic = !m; const isStatic = !m;
if (typeof meta.isStatic !== 'boolean') { if (typeof meta.isStatic !== 'boolean') {
key = isStatic ? key : m[1]; key = isStatic ? key : m[1];
meta.isStatic = isStatic; meta = Object.assign({}, meta, {isStatic});
} else if (meta.isStatic && m) { } else if (meta.isStatic && m) {
throw new Error(g.f('Remoting metadata for %s.%s {{"isStatic"}} does ' + throw new Error(g.f('Remoting metadata for %s.%s {{"isStatic"}} does ' +
'not match new method name-based style.', ModelCtor.modelName, key)); 'not match new method name-based style.', ModelCtor.modelName, key));
@ -309,7 +313,7 @@ Registry.prototype.findModel = function(modelName) {
* @header loopback.getModel(modelName) * @header loopback.getModel(modelName)
*/ */
Registry.prototype.getModel = function(modelName) { Registry.prototype.getModel = function(modelName) {
var model = this.findModel(modelName); const model = this.findModel(modelName);
if (model) return model; if (model) return model;
throw new Error(g.f('Model not found: %s', modelName)); throw new Error(g.f('Model not found: %s', modelName));
@ -325,8 +329,8 @@ Registry.prototype.getModel = function(modelName) {
* @header loopback.getModelByType(modelType) * @header loopback.getModelByType(modelType)
*/ */
Registry.prototype.getModelByType = function(modelType) { Registry.prototype.getModelByType = function(modelType) {
var type = typeof modelType; const type = typeof modelType;
var accepted = ['function', 'string']; const accepted = ['function', 'string'];
assert(accepted.indexOf(type) > -1, assert(accepted.indexOf(type) > -1,
'The model type must be a constructor or model name'); 'The model type must be a constructor or model name');
@ -335,8 +339,8 @@ Registry.prototype.getModelByType = function(modelType) {
modelType = this.getModel(modelType); modelType = this.getModel(modelType);
} }
var models = this.modelBuilder.models; const models = this.modelBuilder.models;
for (var m in models) { for (const m in models) {
if (models[m].prototype instanceof modelType) { if (models[m].prototype instanceof modelType) {
return models[m]; return models[m];
} }
@ -355,15 +359,15 @@ Registry.prototype.getModelByType = function(modelType) {
*/ */
Registry.prototype.createDataSource = function(name, options) { Registry.prototype.createDataSource = function(name, options) {
var self = this; const self = this;
var ds = new DataSource(name, options, self.modelBuilder); const ds = new DataSource(name, options, self.modelBuilder);
ds.createModel = function(name, properties, settings) { ds.createModel = function(name, properties, settings) {
settings = settings || {}; settings = settings || {};
var BaseModel = settings.base || settings.super; let BaseModel = settings.base || settings.super;
if (!BaseModel) { if (!BaseModel) {
// Check the connector types // Check the connector types
var connectorTypes = ds.getTypes(); const connectorTypes = ds.getTypes();
if (Array.isArray(connectorTypes) && connectorTypes.indexOf('db') !== -1) { if (Array.isArray(connectorTypes) && connectorTypes.indexOf('db') !== -1) {
// Only set up the base model to PersistedModel if the connector is DB // Only set up the base model to PersistedModel if the connector is DB
BaseModel = self.PersistedModel; BaseModel = self.PersistedModel;
@ -372,13 +376,13 @@ Registry.prototype.createDataSource = function(name, options) {
} }
settings.base = BaseModel; settings.base = BaseModel;
} }
var ModelCtor = self.createModel(name, properties, settings); const ModelCtor = self.createModel(name, properties, settings);
ModelCtor.attachTo(ds); ModelCtor.attachTo(ds);
return ModelCtor; return ModelCtor;
}; };
if (ds.settings && ds.settings.defaultForType) { if (ds.settings && ds.settings.defaultForType) {
var msg = g.f('{{DataSource}} option {{"defaultForType"}} is no longer supported'); const msg = g.f('{{DataSource}} option {{"defaultForType"}} is no longer supported');
throw new Error(msg); throw new Error(msg);
} }
@ -394,9 +398,9 @@ Registry.prototype.createDataSource = function(name, options) {
Registry.prototype.memory = function(name) { Registry.prototype.memory = function(name) {
name = name || 'default'; name = name || 'default';
var memory = ( let memory = (
this._memoryDataSources || (this._memoryDataSources = {}) this._memoryDataSources || (this._memoryDataSources = {})
)[name]; )[name];
if (!memory) { if (!memory) {
memory = this._memoryDataSources[name] = this.createDataSource({ memory = this._memoryDataSources[name] = this.createDataSource({

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -10,7 +10,7 @@
*/ */
'use strict'; 'use strict';
var runtime = exports; const runtime = exports;
/** /**
* True if running in a browser environment; false otherwise. * True if running in a browser environment; false otherwise.

View File

@ -1,23 +1,23 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('./globalize'); const g = require('./globalize');
var assert = require('assert'); const assert = require('assert');
var express = require('express'); const express = require('express');
var merge = require('util')._extend; const merge = require('util')._extend;
var mergePhaseNameLists = require('loopback-phase').mergePhaseNameLists; const mergePhaseNameLists = require('loopback-phase').mergePhaseNameLists;
var debug = require('debug')('loopback:app'); const debug = require('debug')('loopback:app');
var stableSortInPlace = require('stable').inplace; const stableSortInPlace = require('stable').inplace;
var BUILTIN_MIDDLEWARE = {builtin: true}; const BUILTIN_MIDDLEWARE = {builtin: true};
var proto = {}; const proto = {};
module.exports = function loopbackExpress() { module.exports = function loopbackExpress() {
var app = express(); const app = express();
app.__expressLazyRouter = app.lazyrouter; app.__expressLazyRouter = app.lazyrouter;
merge(app, proto); merge(app, proto);
return app; return app;
@ -64,23 +64,23 @@ proto.middlewareFromConfig = function(factory, config) {
if (config.enabled === false) if (config.enabled === false)
return; return;
var params = config.params; let params = config.params;
if (params === undefined) { if (params === undefined) {
params = []; params = [];
} else if (!Array.isArray(params)) { } else if (!Array.isArray(params)) {
params = [params]; params = [params];
} }
var handler = factory.apply(null, params); let handler = factory.apply(null, params);
// Check if methods/verbs filter exists // Check if methods/verbs filter exists
var verbs = config.methods || config.verbs; let verbs = config.methods || config.verbs;
if (Array.isArray(verbs)) { if (Array.isArray(verbs)) {
verbs = verbs.map(function(verb) { verbs = verbs.map(function(verb) {
return verb && verb.toUpperCase(); return verb && verb.toUpperCase();
}); });
if (verbs.indexOf('ALL') === -1) { if (verbs.indexOf('ALL') === -1) {
var originalHandler = handler; const originalHandler = handler;
if (handler.length <= 3) { if (handler.length <= 3) {
// Regular handler // Regular handler
handler = function(req, res, next) { handler = function(req, res, next) {
@ -145,7 +145,7 @@ proto.defineMiddlewarePhases = function(nameOrArray) {
mergePhaseNameLists(this._requestHandlingPhases, nameOrArray); mergePhaseNameLists(this._requestHandlingPhases, nameOrArray);
} else { } else {
// add the new phase before 'routes' // add the new phase before 'routes'
var routesIx = this._requestHandlingPhases.indexOf('routes'); const routesIx = this._requestHandlingPhases.indexOf('routes');
this._requestHandlingPhases.splice(routesIx - 1, 0, nameOrArray); this._requestHandlingPhases.splice(routesIx - 1, 0, nameOrArray);
} }
@ -181,10 +181,10 @@ proto.middleware = function(name, paths, handler) {
paths = '/'; paths = '/';
} }
var fullPhaseName = name; const fullPhaseName = name;
var handlerName = handler.name || '<anonymous>'; const handlerName = handler.name || '<anonymous>';
var m = name.match(/^(.+):(before|after)$/); const m = name.match(/^(.+):(before|after)$/);
if (m) { if (m) {
name = m[1]; name = m[1];
} }
@ -197,7 +197,7 @@ proto.middleware = function(name, paths, handler) {
this._skipLayerSorting = true; this._skipLayerSorting = true;
this.use(paths, handler); this.use(paths, handler);
var layer = this._findLayerByHandler(handler); const layer = this._findLayerByHandler(handler);
if (layer) { if (layer) {
// Set the phase name for sorting // Set the phase name for sorting
layer.phase = fullPhaseName; layer.phase = fullPhaseName;
@ -221,17 +221,20 @@ proto.middleware = function(name, paths, handler) {
*/ */
proto._findLayerByHandler = function(handler) { proto._findLayerByHandler = function(handler) {
// Other handlers can be added to the stack, for example, // Other handlers can be added to the stack, for example,
// NewRelic adds sentinel handler. We need to search the stack // NewRelic adds sentinel handler, and AppDynamics adds
for (var k = this._router.stack.length - 1; k >= 0; k--) { // some additional proxy info. We need to search the stack
if (this._router.stack[k].handle === handler || for (let k = this._router.stack.length - 1; k >= 0; k--) {
// NewRelic replaces the handle and keeps it as __NR_original const isOriginal = this._router.stack[k].handle === handler;
this._router.stack[k].handle['__NR_original'] === handler const isNewRelic = this._router.stack[k].handle['__NR_original'] === handler;
) { const isAppDynamics = this._router.stack[k].handle['__appdynamicsProxyInfo__'] &&
this._router.stack[k].handle['__appdynamicsProxyInfo__']['orig'] === handler;
if (isOriginal || isNewRelic || isAppDynamics) {
return this._router.stack[k]; return this._router.stack[k];
} else { } else {
// Aggressively check if the original handler has been wrapped // Aggressively check if the original handler has been wrapped
// into a new function with a property pointing to the original handler // into a new function with a property pointing to the original handler
for (var p in this._router.stack[k].handle) { for (const p in this._router.stack[k].handle) {
if (this._router.stack[k].handle[p] === handler) { if (this._router.stack[k].handle[p] === handler) {
return this._router.stack[k]; return this._router.stack[k];
} }
@ -243,12 +246,12 @@ proto._findLayerByHandler = function(handler) {
// Install our custom PhaseList-based handler into the app // Install our custom PhaseList-based handler into the app
proto.lazyrouter = function() { proto.lazyrouter = function() {
var self = this; const self = this;
if (self._router) return; if (self._router) return;
self.__expressLazyRouter(); self.__expressLazyRouter();
var router = self._router; const router = self._router;
// Mark all middleware added by Router ctor as builtin // Mark all middleware added by Router ctor as builtin
// The sorting algo will keep them at beginning of the list // The sorting algo will keep them at beginning of the list
@ -258,14 +261,14 @@ proto.lazyrouter = function() {
router.__expressUse = router.use; router.__expressUse = router.use;
router.use = function useAndSort() { router.use = function useAndSort() {
var retval = this.__expressUse.apply(this, arguments); const retval = this.__expressUse.apply(this, arguments);
self._sortLayersByPhase(); self._sortLayersByPhase();
return retval; return retval;
}; };
router.__expressRoute = router.route; router.__expressRoute = router.route;
router.route = function routeAndSort() { router.route = function routeAndSort() {
var retval = this.__expressRoute.apply(this, arguments); const retval = this.__expressRoute.apply(this, arguments);
self._sortLayersByPhase(); self._sortLayersByPhase();
return retval; return retval;
}; };
@ -279,19 +282,19 @@ proto.lazyrouter = function() {
proto._sortLayersByPhase = function() { proto._sortLayersByPhase = function() {
if (this._skipLayerSorting) return; if (this._skipLayerSorting) return;
var phaseOrder = {}; const phaseOrder = {};
this._requestHandlingPhases.forEach(function(name, ix) { this._requestHandlingPhases.forEach(function(name, ix) {
phaseOrder[name + ':before'] = ix * 3; phaseOrder[name + ':before'] = ix * 3;
phaseOrder[name] = ix * 3 + 1; phaseOrder[name] = ix * 3 + 1;
phaseOrder[name + ':after'] = ix * 3 + 2; phaseOrder[name + ':after'] = ix * 3 + 2;
}); });
var router = this._router; const router = this._router;
stableSortInPlace(router.stack, compareLayers); stableSortInPlace(router.stack, compareLayers);
function compareLayers(left, right) { function compareLayers(left, right) {
var leftPhase = left.phase; const leftPhase = left.phase;
var rightPhase = right.phase; const rightPhase = right.phase;
if (leftPhase === rightPhase) return 0; if (leftPhase === rightPhase) return 0;

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -10,12 +10,12 @@ exports.uploadInChunks = uploadInChunks;
exports.downloadInChunks = downloadInChunks; exports.downloadInChunks = downloadInChunks;
exports.concatResults = concatResults; exports.concatResults = concatResults;
var Promise = require('bluebird'); const Promise = require('bluebird');
var async = require('async'); const async = require('async');
function createPromiseCallback() { function createPromiseCallback() {
var cb; let cb;
var promise = new Promise(function(resolve, reject) { const promise = new Promise(function(resolve, reject) {
cb = function(err, data) { cb = function(err, data) {
if (err) return reject(err); if (err) return reject(err);
return resolve(data); return resolve(data);
@ -28,7 +28,8 @@ function createPromiseCallback() {
function throwPromiseNotDefined() { function throwPromiseNotDefined() {
throw new Error( throw new Error(
'Your Node runtime does support ES6 Promises. ' + 'Your Node runtime does support ES6 Promises. ' +
'Set "global.Promise" to your preferred implementation of promises.'); 'Set "global.Promise" to your preferred implementation of promises.',
);
} }
/** /**
@ -39,23 +40,23 @@ function throwPromiseNotDefined() {
* @param {Function} cb - the callback * @param {Function} cb - the callback
*/ */
function uploadInChunks(largeArray, chunkSize, processFunction, cb) { function uploadInChunks(largeArray, chunkSize, processFunction, cb) {
var chunkArrays = []; const chunkArrays = [];
if (!chunkSize || chunkSize < 1 || largeArray.length <= chunkSize) { if (!chunkSize || chunkSize < 1 || largeArray.length <= chunkSize) {
// if chunking not required // if chunking not required
processFunction(largeArray, cb); processFunction(largeArray, cb);
} else { } else {
// copying so that the largeArray object does not get affected during splice // copying so that the largeArray object does not get affected during splice
var copyOfLargeArray = [].concat(largeArray); const copyOfLargeArray = [].concat(largeArray);
// chunking to smaller arrays // chunking to smaller arrays
while (copyOfLargeArray.length > 0) { while (copyOfLargeArray.length > 0) {
chunkArrays.push(copyOfLargeArray.splice(0, chunkSize)); chunkArrays.push(copyOfLargeArray.splice(0, chunkSize));
} }
var tasks = chunkArrays.map(function(chunkArray) { const tasks = chunkArrays.map(function(chunkArray) {
return function(previousResults, chunkCallback) { return function(previousResults, chunkCallback) {
var lastArg = arguments[arguments.length - 1]; const lastArg = arguments[arguments.length - 1];
if (typeof lastArg === 'function') { if (typeof lastArg === 'function') {
chunkCallback = lastArg; chunkCallback = lastArg;
@ -91,7 +92,7 @@ function uploadInChunks(largeArray, chunkSize, processFunction, cb) {
* @param {Function} cb - the callback * @param {Function} cb - the callback
*/ */
function downloadInChunks(filter, chunkSize, processFunction, cb) { function downloadInChunks(filter, chunkSize, processFunction, cb) {
var results = []; let results = [];
filter = filter ? JSON.parse(JSON.stringify(filter)) : {}; filter = filter ? JSON.parse(JSON.stringify(filter)) : {};
if (!chunkSize || chunkSize < 1) { if (!chunkSize || chunkSize < 1) {

View File

@ -1,6 +1,6 @@
{ {
"name": "loopback", "name": "loopback",
"version": "3.7.0", "version": "3.28.0",
"description": "LoopBack: Open Source Framework for Node.js", "description": "LoopBack: Open Source Framework for Node.js",
"homepage": "http://loopback.io", "homepage": "http://loopback.io",
"keywords": [ "keywords": [
@ -34,7 +34,7 @@
"test": "nyc grunt mocha-and-karma" "test": "nyc grunt mocha-and-karma"
}, },
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=8"
}, },
"dependencies": { "dependencies": {
"async": "^2.0.1", "async": "^2.0.1",
@ -47,57 +47,58 @@
"ejs": "^2.3.1", "ejs": "^2.3.1",
"express": "^4.14.0", "express": "^4.14.0",
"inflection": "^1.6.0", "inflection": "^1.6.0",
"isemail": "^2.2.1", "isemail": "^3.2.0",
"loopback-connector-remote": "^3.0.0", "loopback-connector-remote": "^3.0.0",
"loopback-datasource-juggler": "^3.0.0", "loopback-datasource-juggler": "^3.28.0",
"loopback-filters": "^1.0.0",
"loopback-phase": "^3.0.0", "loopback-phase": "^3.0.0",
"nodemailer": "^2.5.0", "nodemailer": "^6.4.16",
"nodemailer-stub-transport": "^1.0.0", "nodemailer-direct-transport": "^3.3.2",
"nodemailer-stub-transport": "^1.1.0",
"serve-favicon": "^2.2.0", "serve-favicon": "^2.2.0",
"stable": "^0.1.5", "stable": "^0.1.5",
"strong-globalize": "^2.7.0", "strong-globalize": "^4.1.1",
"strong-remoting": "^3.0.0", "strong-remoting": "^3.11.0",
"uid2": "0.0.3", "uid2": "0.0.3",
"underscore.string": "^3.0.3" "underscore.string": "^3.3.5"
}, },
"devDependencies": { "devDependencies": {
"babel-preset-es2015": "^6.22.0", "browserify": "^16.5.0",
"babelify": "^7.3.0", "chai": "^4.2.0",
"browserify": "^13.1.0",
"chai": "^3.5.0",
"cookie-parser": "^1.3.4", "cookie-parser": "^1.3.4",
"coveralls": "^2.11.15", "coveralls": "^3.0.2",
"dirty-chai": "^1.2.2", "dirty-chai": "^2.0.1",
"eslint-config-loopback": "^8.0.0", "eslint": "^6.5.1",
"eslint-config-loopback": "^13.1.0",
"express-session": "^1.14.0", "express-session": "^1.14.0",
"grunt": "^1.0.1", "grunt": "^1.0.1",
"grunt-browserify": "^5.0.0", "grunt-browserify": "^5.0.0",
"grunt-cli": "^1.2.0", "grunt-cli": "^1.2.0",
"grunt-contrib-uglify": "^2.0.0", "grunt-contrib-uglify": "^4.0.1",
"grunt-contrib-watch": "^1.0.0", "grunt-contrib-watch": "^1.0.0",
"grunt-eslint": "^19.0.0", "grunt-eslint": "^22.0.0",
"grunt-karma": "^2.0.0", "grunt-karma": "^3.0.2",
"grunt-mocha-test": "^0.12.7", "grunt-mocha-test": "^0.13.3",
"karma": "^1.1.2", "is-docker": "^2.0.0",
"karma-browserify": "^5.1.1", "karma": "^4.1.0",
"karma-chrome-launcher": "^1.0.1", "karma-browserify": "^6.0.0",
"karma-chrome-launcher": "^3.1.0",
"karma-es6-shim": "^1.0.0", "karma-es6-shim": "^1.0.0",
"karma-firefox-launcher": "^1.0.0", "karma-firefox-launcher": "^1.0.0",
"karma-html2js-preprocessor": "^1.0.0", "karma-html2js-preprocessor": "^1.0.0",
"karma-junit-reporter": "~1.0.0", "karma-junit-reporter": "^1.2.0",
"karma-mocha": "^1.1.1", "karma-mocha": "^1.1.1",
"karma-phantomjs-launcher": "^1.0.0",
"karma-script-launcher": "^1.0.0", "karma-script-launcher": "^1.0.0",
"loopback-boot": "^2.7.0", "loopback-boot": "^2.7.0",
"loopback-context": "^1.0.0", "loopback-context": "^1.0.0",
"mocha": "^3.0.0", "mocha": "^6.2.1",
"nyc": "^10.1.2", "nyc": "^14.1.1",
"phantomjs-prebuilt": "^2.1.7", "sinon": "^7.5.0",
"sinon": "^1.13.0", "sinon-chai": "^3.2.0",
"sinon-chai": "^2.8.0", "strong-error-handler": "^3.0.0",
"strong-error-handler": "^1.0.1", "strong-task-emitter": "^0.0.8",
"strong-task-emitter": "^0.0.6", "supertest": "^4.0.2",
"supertest": "^3.0.0" "which": "^2.0.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -117,5 +118,14 @@
"debug": "*,-mocha:*,-eslint:*" "debug": "*,-mocha:*,-eslint:*"
} }
}, },
"license": "MIT" "copyright.owner": "IBM Corp.",
"license": "MIT",
"author": "IBM Corp.",
"ci": {
"downstreamIgnoreList": [
"bluemix-service-broker",
"gateway-director-bluemix",
"plan-manager"
]
}
} }

View File

@ -1,14 +1,15 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
module.exports = function() { module.exports = function() {
throw new Error(g.f( throw new Error(g.f(
'%s middleware was removed in version 3.0. See %s for more details.', '%s middleware was removed in version 3.0. See %s for more details.',
'loopback#context', 'loopback#context',
'http://loopback.io/doc/en/lb2/Using-current-context.html')); 'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
}; };

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2014. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var favicon = require('serve-favicon'); const favicon = require('serve-favicon');
var path = require('path'); const path = require('path');
/** /**
* Serve the LoopBack favicon. * Serve the LoopBack favicon.

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2015. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,9 +8,9 @@
*/ */
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var loopback = require('../../lib/loopback'); const loopback = require('../../lib/loopback');
var async = require('async'); const async = require('async');
/*! /*!
* Export the middleware. * Export the middleware.
@ -30,26 +30,27 @@ module.exports = rest;
*/ */
function rest() { function rest() {
var handlers; // Cached handlers let handlers; // Cached handlers
return function restApiHandler(req, res, next) { return function restApiHandler(req, res, next) {
var app = req.app; const app = req.app;
var registry = app.registry; const registry = app.registry;
if (!handlers) { if (!handlers) {
handlers = []; handlers = [];
var remotingOptions = app.get('remoting') || {}; const remotingOptions = app.get('remoting') || {};
var contextOptions = remotingOptions.context; const contextOptions = remotingOptions.context;
if (contextOptions !== undefined && contextOptions !== false) { if (contextOptions !== undefined && contextOptions !== false) {
throw new Error(g.f( throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.', '%s was removed in version 3.0. See %s for more details.',
'remoting.context option', 'remoting.context option',
'http://loopback.io/doc/en/lb2/Using-current-context.html')); 'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
} }
if (app.isAuthEnabled) { if (app.isAuthEnabled) {
var AccessToken = registry.getModelByType('AccessToken'); const AccessToken = registry.getModelByType('AccessToken');
handlers.push(loopback.token({model: AccessToken, app: app})); handlers.push(loopback.token({model: AccessToken, app: app}));
} }

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014. All Rights Reserved. // Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -24,7 +24,7 @@ module.exports = status;
* @header loopback.status() * @header loopback.status()
*/ */
function status() { function status() {
var started = new Date(); const started = new Date();
return function(req, res) { return function(req, res) {
res.send({ res.send({

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -8,10 +8,10 @@
*/ */
'use strict'; 'use strict';
var g = require('../../lib/globalize'); const g = require('../../lib/globalize');
var loopback = require('../../lib/loopback'); const loopback = require('../../lib/loopback');
var assert = require('assert'); const assert = require('assert');
var debug = require('debug')('loopback:middleware:token'); const debug = require('debug')('loopback:middleware:token');
/*! /*!
* Export the middleware. * Export the middleware.
@ -28,9 +28,9 @@ function rewriteUserLiteral(req, currentUserLiteral, next) {
if (req.accessToken && req.accessToken.userId) { if (req.accessToken && req.accessToken.userId) {
// Replace /me/ with /current-user-id/ // Replace /me/ with /current-user-id/
var urlBeforeRewrite = req.url; const urlBeforeRewrite = req.url;
req.url = req.url.replace(literalRegExp, req.url = req.url.replace(literalRegExp,
'/' + req.accessToken.userId + '$1'); '/' + req.accessToken.userId + '$1');
if (req.url !== urlBeforeRewrite) { if (req.url !== urlBeforeRewrite) {
debug('req.url has been rewritten from %s to %s', urlBeforeRewrite, debug('req.url has been rewritten from %s to %s', urlBeforeRewrite,
@ -40,9 +40,10 @@ function rewriteUserLiteral(req, currentUserLiteral, next) {
debug( debug(
'URL %s matches current-user literal %s,' + 'URL %s matches current-user literal %s,' +
' but no (valid) access token was provided.', ' but no (valid) access token was provided.',
req.url, currentUserLiteral); req.url, currentUserLiteral,
);
var e = new Error(g.f('Authorization Required')); const e = new Error(g.f('Authorization Required'));
e.status = e.statusCode = 401; e.status = e.statusCode = 401;
e.code = 'AUTHORIZATION_REQUIRED'; e.code = 'AUTHORIZATION_REQUIRED';
return next(e); return next(e);
@ -88,14 +89,17 @@ function escapeRegExp(str) {
* @property {Boolean} [overwriteExistingToken] only has effect in combination with `enableDoublecheck`. If truthy, will allow to overwrite an existing accessToken. * @property {Boolean} [overwriteExistingToken] only has effect in combination with `enableDoublecheck`. If truthy, will allow to overwrite an existing accessToken.
* @property {Function|String} [model] AccessToken model name or class to use. * @property {Function|String} [model] AccessToken model name or class to use.
* @property {String} [currentUserLiteral] String literal for the current user. * @property {String} [currentUserLiteral] String literal for the current user.
* @property {Boolean} [bearerTokenBase64Encoded] Defaults to `true`. For `Bearer` token based `Authorization` headers,
* decode the value from `Base64`. If set to `false`, the decoding will be skipped and the token id will be the raw value
* parsed from the header.
* @header loopback.token([options]) * @header loopback.token([options])
*/ */
function token(options) { function token(options) {
options = options || {}; options = options || {};
var TokenModel; let TokenModel;
var currentUserLiteral = options.currentUserLiteral; let currentUserLiteral = options.currentUserLiteral;
if (currentUserLiteral && (typeof currentUserLiteral !== 'string')) { if (currentUserLiteral && (typeof currentUserLiteral !== 'string')) {
debug('Set currentUserLiteral to \'me\' as the value is not a string.'); debug('Set currentUserLiteral to \'me\' as the value is not a string.');
currentUserLiteral = 'me'; currentUserLiteral = 'me';
@ -104,12 +108,15 @@ function token(options) {
currentUserLiteral = escapeRegExp(currentUserLiteral); currentUserLiteral = escapeRegExp(currentUserLiteral);
} }
var enableDoublecheck = !!options.enableDoublecheck; if (options.bearerTokenBase64Encoded === undefined) {
var overwriteExistingToken = !!options.overwriteExistingToken; options.bearerTokenBase64Encoded = true;
}
const enableDoublecheck = !!options.enableDoublecheck;
const overwriteExistingToken = !!options.overwriteExistingToken;
return function(req, res, next) { return function(req, res, next) {
var app = req.app; const app = req.app;
var registry = app.registry; const registry = app.registry;
if (!TokenModel) { if (!TokenModel) {
TokenModel = registry.getModel(options.model || 'AccessToken'); TokenModel = registry.getModel(options.model || 'AccessToken');
} }
@ -134,7 +141,7 @@ function token(options) {
TokenModel.findForRequest(req, options, function(err, token) { TokenModel.findForRequest(req, options, function(err, token) {
req.accessToken = token || null; req.accessToken = token || null;
var ctx = req.loopbackContext; const ctx = req.loopbackContext;
if (ctx && ctx.active) ctx.set('accessToken', token); if (ctx && ctx.active) ctx.set('accessToken', token);
if (err) return next(err); if (err) return next(err);

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2014. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
@ -18,7 +18,7 @@ module.exports = urlNotFound;
*/ */
function urlNotFound() { function urlNotFound() {
return function raiseUrlNotFoundError(req, res, next) { return function raiseUrlNotFoundError(req, res, next) {
var error = new Error('Cannot ' + req.method + ' ' + req.url); const error = new Error('Cannot ' + req.method + ' ' + req.url);
error.status = 404; error.status = 404;
next(error); next(error);
}; };

View File

@ -1,18 +1,18 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../'); const loopback = require('../');
var lt = require('./helpers/loopback-testing-helper'); const lt = require('./helpers/loopback-testing-helper');
var path = require('path'); const path = require('path');
var ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control'); const ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control');
var app = require(path.join(ACCESS_CONTROL_APP, 'server/server.js')); const app = require(path.join(ACCESS_CONTROL_APP, 'server/server.js'));
var assert = require('assert'); const assert = require('assert');
var USER = {email: 'test@test.test', password: 'test'}; const USER = {email: 'test@test.test', password: 'test'};
var CURRENT_USER = {email: 'current@test.test', password: 'test'}; const CURRENT_USER = {email: 'current@test.test', password: 'test'};
var debug = require('debug')('loopback:test:access-control.integration'); const debug = require('debug')('loopback:test:access-control.integration');
describe('access control - integration', function() { describe('access control - integration', function() {
lt.beforeEach.withApp(app); lt.beforeEach.withApp(app);
@ -75,10 +75,12 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForUser); lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForUser);
lt.it.shouldBeAllowedWhenCalledAnonymously( lt.it.shouldBeAllowedWhenCalledAnonymously(
'POST', '/api/users', newUserData()); 'POST', '/api/users', newUserData(),
);
lt.it.shouldBeAllowedWhenCalledByUser( lt.it.shouldBeAllowedWhenCalledByUser(
CURRENT_USER, 'POST', '/api/users', newUserData()); CURRENT_USER, 'POST', '/api/users', newUserData(),
);
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/logout'); lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/logout');
@ -108,7 +110,7 @@ describe('access control - integration', function() {
this.res.statusCode, this.res.statusCode,
this.res.headers, this.res.headers,
this.res.text); this.res.text);
var user = this.res.body; const user = this.res.body;
assert.equal(user.password, undefined); assert.equal(user.password, undefined);
}); });
}); });
@ -135,7 +137,7 @@ describe('access control - integration', function() {
return '/api/users/' + this.randomUser.id; return '/api/users/' + this.randomUser.id;
} }
var userCounter; var userCounter; // eslint-disable-line no-var
function newUserData() { function newUserData() {
userCounter = userCounter ? ++userCounter : 1; userCounter = userCounter ? ++userCounter : 1;
@ -147,14 +149,14 @@ describe('access control - integration', function() {
}); });
describe('/banks', function() { describe('/banks', function() {
var SPECIAL_USER = {email: 'special@test.test', password: 'test'}; const SPECIAL_USER = {email: 'special@test.test', password: 'test'};
// define dynamic role that would only grant access when the authenticated user's email is equal to // define dynamic role that would only grant access when the authenticated user's email is equal to
// SPECIAL_USER's email // SPECIAL_USER's email
before(function() { before(function() {
var roleModel = app.registry.getModel('Role'); const roleModel = app.registry.getModel('Role');
var userModel = app.registry.getModel('user'); const userModel = app.registry.getModel('user');
roleModel.registerResolver('$dynamic-role', function(role, context, callback) { roleModel.registerResolver('$dynamic-role', function(role, context, callback) {
if (!(context && context.accessToken && context.accessToken.userId)) { if (!(context && context.accessToken && context.accessToken.userId)) {
@ -162,7 +164,7 @@ describe('access control - integration', function() {
if (callback) callback(null, false); if (callback) callback(null, false);
}); });
} }
var accessToken = context.accessToken; const accessToken = context.accessToken;
userModel.findById(accessToken.userId, function(err, user) { userModel.findById(accessToken.userId, function(err, user) {
if (err) { if (err) {
return callback(err, false); return callback(err, false);
@ -208,9 +210,9 @@ describe('access control - integration', function() {
}); });
describe('/accounts with replaceOnPUT true', function() { describe('/accounts with replaceOnPUT true', function() {
var count = 0; let count = 0;
before(function() { before(function() {
var roleModel = loopback.getModelByType(loopback.Role); const roleModel = loopback.getModelByType(loopback.Role);
roleModel.registerResolver('$dummy', function(role, context, callback) { roleModel.registerResolver('$dummy', function(role, context, callback) {
process.nextTick(function() { process.nextTick(function() {
if (context.remotingContext) { if (context.remotingContext) {
@ -248,9 +250,9 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount); lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() { lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
var actId; let actId;
beforeEach(function(done) { beforeEach(function(done) {
var self = this; const self = this;
// Create an account under the given user // Create an account under the given user
app.models.accountWithReplaceOnPUTtrue.create({ app.models.accountWithReplaceOnPUTtrue.create({
userId: self.user.id, userId: self.user.id,
@ -312,9 +314,9 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount); lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() { lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
var actId; let actId;
beforeEach(function(done) { beforeEach(function(done) {
var self = this; const self = this;
// Create an account under the given user // Create an account under the given user
app.models.accountWithReplaceOnPUTfalse.create({ app.models.accountWithReplaceOnPUTfalse.create({
userId: self.user.id, userId: self.user.id,

View File

@ -1,23 +1,23 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
var expect = require('./helpers/expect'); const expect = require('./helpers/expect');
var cookieParser = require('cookie-parser'); const cookieParser = require('cookie-parser');
var LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
var contextMiddleware = require('loopback-context').perRequest; const contextMiddleware = require('loopback-context').perRequest;
var loopback = require('../'); const loopback = require('../');
var extend = require('util')._extend; const extend = require('util')._extend;
var session = require('express-session'); const session = require('express-session');
var request = require('supertest'); const request = require('supertest');
var Token, ACL, User, TestModel; let Token, ACL, User, TestModel;
describe('loopback.token(options)', function() { describe('loopback.token(options)', function() {
var app; let app;
beforeEach(function(done) { beforeEach(function(done) {
app = loopback({localRegistry: true, loadBuiltinModels: true}); app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
@ -52,7 +52,7 @@ describe('loopback.token(options)', function() {
}); });
it('defaults to built-in AccessToken model', function() { it('defaults to built-in AccessToken model', function() {
var BuiltInToken = app.registry.getModel('AccessToken'); const BuiltInToken = app.registry.getModel('AccessToken');
app.model(BuiltInToken, {dataSource: 'db'}); app.model(BuiltInToken, {dataSource: 'db'});
app.enableAuth({dataSource: 'db'}); app.enableAuth({dataSource: 'db'});
@ -115,14 +115,14 @@ describe('loopback.token(options)', function() {
}); });
}); });
it('should populate req.token from the query string', function(done) { it('populates req.token from the query string', function(done) {
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/?access_token=' + this.token.id) .get('/?access_token=' + this.token.id)
.expect(200) .expect(200)
.end(done); .end(done);
}); });
it('should populate req.token from an authorization header', function(done) { it('populates req.token from an authorization header', function(done) {
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
.set('authorization', this.token.id) .set('authorization', this.token.id)
@ -130,7 +130,7 @@ describe('loopback.token(options)', function() {
.end(done); .end(done);
}); });
it('should populate req.token from an X-Access-Token header', function(done) { it('populates req.token from an X-Access-Token header', function(done) {
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
.set('X-Access-Token', this.token.id) .set('X-Access-Token', this.token.id)
@ -138,34 +138,46 @@ describe('loopback.token(options)', function() {
.end(done); .end(done);
}); });
it('should not search default keys when searchDefaultTokenKeys is false', it('does not search default keys when searchDefaultTokenKeys is false',
function(done) { function(done) {
var tokenId = this.token.id; const tokenId = this.token.id;
var app = createTestApp( const app = createTestApp(
this.token, this.token,
{token: {searchDefaultTokenKeys: false}}, {token: {searchDefaultTokenKeys: false}},
done); done,
var agent = request.agent(app); );
const agent = request.agent(app);
// Set the token cookie // Set the token cookie
agent.get('/token').expect(200).end(function(err, res) { agent.get('/token').expect(200).end(function(err, res) {
if (err) return done(err); if (err) return done(err);
// Make a request that sets the token in all places searched by default // Make a request that sets the token in all places searched by default
agent.get('/check-access?access_token=' + tokenId) agent.get('/check-access?access_token=' + tokenId)
.set('X-Access-Token', tokenId) .set('X-Access-Token', tokenId)
.set('authorization', tokenId) .set('authorization', tokenId)
// Expect 401 because there is no (non-default) place configured where // Expect 401 because there is no (non-default) place configured where
// the middleware should load the token from // the middleware should load the token from
.expect(401) .expect(401)
.end(done);
});
});
it('populates req.token from an authorization header with bearer token with base64',
function(done) {
let token = this.token.id;
token = 'Bearer ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done)
.get('/')
.set('authorization', token)
.expect(200)
.end(done); .end(done);
}); });
});
it('should populate req.token from an authorization header with bearer token', function(done) { it('populates req.token from an authorization header with bearer token', function(done) {
var token = this.token.id; let token = this.token.id;
token = 'Bearer ' + new Buffer(token).toString('base64'); token = 'Bearer ' + token;
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, {token: {bearerTokenBase64Encoded: false}}, done)
.get('/') .get('/')
.set('authorization', token) .set('authorization', token)
.expect(200) .expect(200)
@ -174,7 +186,7 @@ describe('loopback.token(options)', function() {
describe('populating req.token from HTTP Basic Auth formatted authorization header', function() { describe('populating req.token from HTTP Basic Auth formatted authorization header', function() {
it('parses "standalone-token"', function(done) { it('parses "standalone-token"', function(done) {
var token = this.token.id; let token = this.token.id;
token = 'Basic ' + new Buffer(token).toString('base64'); token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
@ -184,7 +196,7 @@ describe('loopback.token(options)', function() {
}); });
it('parses "token-and-empty-password:"', function(done) { it('parses "token-and-empty-password:"', function(done) {
var token = this.token.id + ':'; let token = this.token.id + ':';
token = 'Basic ' + new Buffer(token).toString('base64'); token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
@ -194,7 +206,7 @@ describe('loopback.token(options)', function() {
}); });
it('parses "ignored-user:token-is-password"', function(done) { it('parses "ignored-user:token-is-password"', function(done) {
var token = 'username:' + this.token.id; let token = 'username:' + this.token.id;
token = 'Basic ' + new Buffer(token).toString('base64'); token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
@ -204,7 +216,7 @@ describe('loopback.token(options)', function() {
}); });
it('parses "token-is-username:ignored-password"', function(done) { it('parses "token-is-username:ignored-password"', function(done) {
var token = this.token.id + ':password'; let token = this.token.id + ':password';
token = 'Basic ' + new Buffer(token).toString('base64'); token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done) createTestAppAndRequest(this.token, done)
.get('/') .get('/')
@ -214,8 +226,8 @@ describe('loopback.token(options)', function() {
}); });
}); });
it('should populate req.token from a secure cookie', function(done) { it('populates req.token from a secure cookie', function(done) {
var app = createTestApp(this.token, done); const app = createTestApp(this.token, done);
request(app) request(app)
.get('/token') .get('/token')
@ -227,9 +239,9 @@ describe('loopback.token(options)', function() {
}); });
}); });
it('should populate req.token from a header or a secure cookie', function(done) { it('populates req.token from a header or a secure cookie', function(done) {
var app = createTestApp(this.token, done); const app = createTestApp(this.token, done);
var id = this.token.id; const id = this.token.id;
request(app) request(app)
.get('/token') .get('/token')
.end(function(err, res) { .end(function(err, res) {
@ -241,11 +253,11 @@ describe('loopback.token(options)', function() {
}); });
}); });
it('should rewrite url for the current user literal at the end without query', it('rewrites url for the current user literal at the end without query',
function(done) { function(done) {
var app = createTestApp(this.token, done); const app = createTestApp(this.token, done);
var id = this.token.id; const id = this.token.id;
var userId = this.token.userId; const userId = this.token.userId;
request(app) request(app)
.get('/users/me') .get('/users/me')
.set('authorization', id) .set('authorization', id)
@ -257,11 +269,11 @@ describe('loopback.token(options)', function() {
}); });
}); });
it('should rewrite url for the current user literal at the end with query', it('rewrites url for the current user literal at the end with query',
function(done) { function(done) {
var app = createTestApp(this.token, done); const app = createTestApp(this.token, done);
var id = this.token.id; const id = this.token.id;
var userId = this.token.userId; const userId = this.token.userId;
request(app) request(app)
.get('/users/me?state=1') .get('/users/me?state=1')
.set('authorization', id) .set('authorization', id)
@ -273,11 +285,11 @@ describe('loopback.token(options)', function() {
}); });
}); });
it('should rewrite url for the current user literal in the middle', it('rewrites url for the current user literal in the middle',
function(done) { function(done) {
var app = createTestApp(this.token, done); const app = createTestApp(this.token, done);
var id = this.token.id; const id = this.token.id;
var userId = this.token.userId; const userId = this.token.userId;
request(app) request(app)
.get('/users/me/1') .get('/users/me/1')
.set('authorization', id) .set('authorization', id)
@ -289,9 +301,9 @@ describe('loopback.token(options)', function() {
}); });
}); });
it('should generate a 401 on a current user literal route without an authToken', it('generates a 401 on a current user literal route without an authToken',
function(done) { function(done) {
var app = createTestApp(null, done); const app = createTestApp(null, done);
request(app) request(app)
.get('/users/me') .get('/users/me')
.set('authorization', null) .set('authorization', null)
@ -299,9 +311,19 @@ describe('loopback.token(options)', function() {
.end(done); .end(done);
}); });
it('should generate a 401 on a current user literal route with invalid authToken', it('generates a 401 on a current user literal route with empty authToken',
function(done) { function(done) {
var app = createTestApp(this.token, done); const app = createTestApp(null, done);
request(app)
.get('/users/me')
.set('authorization', '')
.expect(401)
.end(done);
});
it('generates a 401 on a current user literal route with invalid authToken',
function(done) {
const app = createTestApp(this.token, done);
request(app) request(app)
.get('/users/me') .get('/users/me')
.set('Authorization', 'invald-token-id') .set('Authorization', 'invald-token-id')
@ -309,8 +331,8 @@ describe('loopback.token(options)', function() {
.end(done); .end(done);
}); });
it('should skip when req.token is already present', function(done) { it('skips when req.token is already present', function(done) {
var tokenStub = {id: 'stub id'}; const tokenStub = {id: 'stub id'};
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = tokenStub; req.accessToken = tokenStub;
@ -334,35 +356,35 @@ describe('loopback.token(options)', function() {
}); });
describe('loading multiple instances of token middleware', function() { describe('loading multiple instances of token middleware', function() {
it('should skip when req.token is already present and no further options are set', it('skips when req.token is already present and no further options are set',
function(done) { function(done) {
var tokenStub = {id: 'stub id'}; const tokenStub = {id: 'stub id'};
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = tokenStub; req.accessToken = tokenStub;
next(); next();
}); });
app.use(loopback.token({model: Token})); app.use(loopback.token({model: Token}));
app.get('/', function(req, res, next) { app.get('/', function(req, res, next) {
res.send(req.accessToken); res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', this.token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql(tokenStub);
done();
}); });
});
it('should not overwrite valid existing token (has "id" property) ' + request(app).get('/')
.set('Authorization', this.token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql(tokenStub);
done();
});
});
it('does not overwrite valid existing token (has "id" property) ' +
' when overwriteExistingToken is falsy', ' when overwriteExistingToken is falsy',
function(done) { function(done) {
var tokenStub = {id: 'stub id'}; const tokenStub = {id: 'stub id'};
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = tokenStub; req.accessToken = tokenStub;
@ -388,10 +410,10 @@ describe('loopback.token(options)', function() {
}); });
}); });
it('should overwrite invalid existing token (is !== undefined and has no "id" property) ' + it('overwrites invalid existing token (is !== undefined and has no "id" property) ' +
' when enableDoublecheck is true', ' when enableDoublecheck is true',
function(done) { function(done) {
var token = this.token; const token = this.token;
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = null; req.accessToken = null;
next(); next();
@ -421,11 +443,11 @@ describe('loopback.token(options)', function() {
}); });
}); });
it('should overwrite existing token when enableDoublecheck ' + it('overwrites existing token when enableDoublecheck ' +
'and overwriteExistingToken options are truthy', 'and overwriteExistingToken options are truthy',
function(done) { function(done) {
var token = this.token; const token = this.token;
var tokenStub = {id: 'stub id'}; const tokenStub = {id: 'stub id'};
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.accessToken = tokenStub; req.accessToken = tokenStub;
@ -462,12 +484,20 @@ describe('loopback.token(options)', function() {
describe('AccessToken', function() { describe('AccessToken', function() {
beforeEach(createTestingToken); beforeEach(createTestingToken);
it('should auto-generate id', function() { it('has getIdForRequest method', function() {
expect(typeof Token.getIdForRequest).to.eql('function');
});
it('has resolve method', function() {
expect(typeof Token.resolve).to.eql('function');
});
it('generates id automatically', function() {
assert(this.token.id); assert(this.token.id);
assert.equal(this.token.id.length, 64); assert.equal(this.token.id.length, 64);
}); });
it('should auto-generate created date', function() { it('generates created date automatically', function() {
assert(this.token.created); assert(this.token.created);
assert(Object.prototype.toString.call(this.token.created), '[object Date]'); assert(Object.prototype.toString.call(this.token.created), '[object Date]');
}); });
@ -490,29 +520,29 @@ describe('AccessToken', function() {
}); });
it('allows eternal tokens when enabled by User.allowEternalTokens', it('allows eternal tokens when enabled by User.allowEternalTokens',
function(done) { function(done) {
var Token = givenLocalTokenModel(); const Token = givenLocalTokenModel();
// Overwrite User settings - enable eternal tokens // Overwrite User settings - enable eternal tokens
Token.app.models.User.settings.allowEternalTokens = true; Token.app.models.User.settings.allowEternalTokens = true;
Token.create({userId: '123', ttl: -1}, function(err, token) { Token.create({userId: '123', ttl: -1}, function(err, token) {
if (err) return done(err);
token.validate(function(err, isValid) {
if (err) return done(err); if (err) return done(err);
expect(isValid, 'isValid').to.equal(true); token.validate(function(err, isValid) {
done(); if (err) return done(err);
expect(isValid, 'isValid').to.equal(true);
done();
});
}); });
}); });
});
}); });
describe('.findForRequest()', function() { describe('.findForRequest()', function() {
beforeEach(createTestingToken); beforeEach(createTestingToken);
it('supports two-arg variant with no options', function(done) { it('supports two-arg variant with no options', function(done) {
var expectedTokenId = this.token.id; const expectedTokenId = this.token.id;
var req = mockRequest({ const req = mockRequest({
headers: {'authorization': expectedTokenId}, headers: {'authorization': expectedTokenId},
}); });
@ -525,6 +555,54 @@ describe('AccessToken', function() {
}); });
}); });
it('allows getIdForRequest() to be overridden', function(done) {
const expectedTokenId = this.token.id;
const current = Token.getIdForRequest;
let called = false;
Token.getIdForRequest = function(req, options) {
called = true;
return expectedTokenId;
};
const req = mockRequest({
headers: {'authorization': 'dummy'},
});
Token.findForRequest(req, function(err, token) {
Token.getIdForRequest = current;
if (err) return done(err);
expect(token.id).to.eql(expectedTokenId);
expect(called).to.be.true();
done();
});
});
it('allows resolve() to be overridden', function(done) {
const expectedTokenId = this.token.id;
const current = Token.resolve;
let called = false;
Token.resolve = function(id, cb) {
called = true;
process.nextTick(function() {
cb(null, {id: expectedTokenId});
});
};
const req = mockRequest({
headers: {'authorization': expectedTokenId},
});
Token.findForRequest(req, function(err, token) {
Token.validate = current;
if (err) return done(err);
expect(token.id).to.eql(expectedTokenId);
expect(called).to.be.true();
done();
});
});
function mockRequest(opts) { function mockRequest(opts) {
return extend( return extend(
{ {
@ -537,13 +615,14 @@ describe('AccessToken', function() {
param: function(name) { return this._params[name]; }, param: function(name) { return this._params[name]; },
header: function(name) { return this.headers[name]; }, header: function(name) { return this.headers[name]; },
}, },
opts); opts,
);
} }
}); });
}); });
describe('app.enableAuth()', function() { describe('app.enableAuth()', function() {
var app; let app;
beforeEach(function setupAuthWithModels() { beforeEach(function setupAuthWithModels() {
app = loopback({localRegistry: true, loadBuiltinModels: true}); app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
@ -574,7 +653,7 @@ describe('app.enableAuth()', function() {
return done(err); return done(err);
} }
var errorResponse = res.body.error; const errorResponse = res.body.error;
assert(errorResponse); assert(errorResponse);
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED'); assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
@ -582,7 +661,7 @@ describe('app.enableAuth()', function() {
}); });
}); });
it('prevent remote call with app setting status on denied ACL', function(done) { it('denies remote call with app setting status 403', function(done) {
createTestAppAndRequest(this.token, {app: {aclErrorStatus: 403}}, done) createTestAppAndRequest(this.token, {app: {aclErrorStatus: 403}}, done)
.del('/tests/123') .del('/tests/123')
.expect(403) .expect(403)
@ -592,7 +671,7 @@ describe('app.enableAuth()', function() {
return done(err); return done(err);
} }
var errorResponse = res.body.error; const errorResponse = res.body.error;
assert(errorResponse); assert(errorResponse);
assert.equal(errorResponse.code, 'ACCESS_DENIED'); assert.equal(errorResponse.code, 'ACCESS_DENIED');
@ -600,7 +679,7 @@ describe('app.enableAuth()', function() {
}); });
}); });
it('prevent remote call with app setting status on denied ACL', function(done) { it('denies remote call with app setting status 404', function(done) {
createTestAppAndRequest(this.token, {model: {aclErrorStatus: 404}}, done) createTestAppAndRequest(this.token, {model: {aclErrorStatus: 404}}, done)
.del('/tests/123') .del('/tests/123')
.expect(404) .expect(404)
@ -610,7 +689,7 @@ describe('app.enableAuth()', function() {
return done(err); return done(err);
} }
var errorResponse = res.body.error; const errorResponse = res.body.error;
assert(errorResponse); assert(errorResponse);
assert.equal(errorResponse.code, 'MODEL_NOT_FOUND'); assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');
@ -618,7 +697,7 @@ describe('app.enableAuth()', function() {
}); });
}); });
it('prevent remote call if the accessToken is missing and required', function(done) { it('prevents remote call if the accessToken is missing and required', function(done) {
createTestAppAndRequest(null, done) createTestAppAndRequest(null, done)
.del('/tests/123') .del('/tests/123')
.expect(401) .expect(401)
@ -628,7 +707,7 @@ describe('app.enableAuth()', function() {
return done(err); return done(err);
} }
var errorResponse = res.body.error; const errorResponse = res.body.error;
assert(errorResponse); assert(errorResponse);
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED'); assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
@ -637,9 +716,9 @@ describe('app.enableAuth()', function() {
}); });
it('stores token in the context', function(done) { it('stores token in the context', function(done) {
var TestModel = app.registry.createModel('TestModel', {base: 'Model'}); const TestModel = app.registry.createModel('TestModel', {base: 'Model'});
TestModel.getToken = function(cb) { TestModel.getToken = function(cb) {
var ctx = LoopBackContext.getCurrentContext(); const ctx = LoopBackContext.getCurrentContext();
cb(null, ctx && ctx.get('accessToken') || null); cb(null, ctx && ctx.get('accessToken') || null);
}; };
TestModel.remoteMethod('getToken', { TestModel.remoteMethod('getToken', {
@ -654,7 +733,7 @@ describe('app.enableAuth()', function() {
app.use(loopback.token({model: Token})); app.use(loopback.token({model: Token}));
app.use(loopback.rest()); app.use(loopback.rest());
var token = this.token; const token = this.token;
request(app) request(app)
.get('/TestModels/token?_format=json') .get('/TestModels/token?_format=json')
.set('authorization', token.id) .set('authorization', token.id)
@ -693,7 +772,7 @@ describe('app.enableAuth()', function() {
}); });
function createTestingToken(done) { function createTestingToken(done) {
var test = this; const test = this;
Token.create({userId: '123'}, function(err, token) { Token.create({userId: '123'}, function(err, token) {
if (err) return done(err); if (err) return done(err);
@ -704,7 +783,7 @@ function createTestingToken(done) {
} }
function createTestAppAndRequest(testToken, settings, done) { function createTestAppAndRequest(testToken, settings, done) {
var app = createTestApp(testToken, settings, done); const app = createTestApp(testToken, settings, done);
return request(app); return request(app);
} }
@ -714,14 +793,14 @@ function createTestApp(testToken, settings, done) {
settings = {}; settings = {};
} }
var appSettings = settings.app || {}; const appSettings = settings.app || {};
var modelSettings = settings.model || {}; const modelSettings = settings.model || {};
var tokenSettings = extend({ const tokenSettings = extend({
model: Token, model: Token,
currentUserLiteral: 'me', currentUserLiteral: 'me',
}, settings.token); }, settings.token);
var app = loopback({localRegistry: true, loadBuiltinModels: true}); const app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
app.use(cookieParser('secret')); app.use(cookieParser('secret'));
@ -745,7 +824,7 @@ function createTestApp(testToken, settings, done) {
res.status(req.accessToken ? 200 : 401).end(); res.status(req.accessToken ? 200 : 401).end();
}); });
app.use('/users/:uid', function(req, res) { app.use('/users/:uid', function(req, res) {
var result = {userId: req.params.uid}; const result = {userId: req.params.uid};
if (req.query.state) { if (req.query.state) {
result.state = req.query.state; result.state = req.query.state;
} else if (req.url !== '/') { } else if (req.url !== '/') {
@ -760,7 +839,7 @@ function createTestApp(testToken, settings, done) {
app.set(key, appSettings[key]); app.set(key, appSettings[key]);
}); });
var modelOptions = { const modelOptions = {
acls: [ acls: [
{ {
principalType: 'ROLE', principalType: 'ROLE',
@ -776,20 +855,20 @@ function createTestApp(testToken, settings, done) {
modelOptions[key] = modelSettings[key]; modelOptions[key] = modelSettings[key];
}); });
var TestModel = app.registry.createModel('test', {}, modelOptions); const TestModel = app.registry.createModel('test', {}, modelOptions);
app.model(TestModel, {dataSource: 'db'}); app.model(TestModel, {dataSource: 'db'});
return app; return app;
} }
function givenLocalTokenModel() { function givenLocalTokenModel() {
var app = loopback({localRegistry: true, loadBuiltinModels: true}); const app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
var User = app.registry.getModel('User'); const User = app.registry.getModel('User');
app.model(User, {dataSource: 'db'}); app.model(User, {dataSource: 'db'});
var Token = app.registry.getModel('AccessToken'); const Token = app.registry.getModel('AccessToken');
app.model(Token, {dataSource: 'db'}); app.model(Token, {dataSource: 'db'});
return Token; return Token;

View File

@ -1,34 +1,27 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
var expect = require('./helpers/expect'); const expect = require('./helpers/expect');
var loopback = require('../index'); const loopback = require('../index');
var Scope = loopback.Scope; const Scope = loopback.Scope;
var ACL = loopback.ACL; const ACL = loopback.ACL;
var request = require('supertest'); const request = require('supertest');
var Promise = require('bluebird'); const Promise = require('bluebird');
var supertest = require('supertest'); const supertest = require('supertest');
var Role = loopback.Role; const Role = loopback.Role;
var RoleMapping = loopback.RoleMapping; const RoleMapping = loopback.RoleMapping;
var User = loopback.User; const User = loopback.User;
var testModel; const async = require('async');
// Speed up the password hashing algorithm for tests // Speed up the password hashing algorithm for tests
User.settings.saltWorkFactor = 4; User.settings.saltWorkFactor = 4;
function checkResult(err, result) { let ds = null;
// console.log(err, result); let testModel;
assert(!err);
}
var ds = null;
before(function() {
ds = loopback.createDataSource({connector: loopback.Memory});
});
describe('ACL model', function() { describe('ACL model', function() {
it('provides DEFAULT_SCOPE constant', () => { it('provides DEFAULT_SCOPE constant', () => {
@ -37,68 +30,64 @@ describe('ACL model', function() {
}); });
describe('security scopes', function() { describe('security scopes', function() {
beforeEach(function() { beforeEach(setupTestModels);
var ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
testModel = loopback.PersistedModel.extend('testModel');
ACL.attachTo(ds);
Role.attachTo(ds);
RoleMapping.attachTo(ds);
User.attachTo(ds);
Scope.attachTo(ds);
testModel.attachTo(ds);
});
it('should allow access to models for the given scope by wildcard', function() { it('should allow access to models for the given scope by wildcard', function(done) {
Scope.create({name: 'userScope', description: 'access user information'}, Scope.create({name: 'userScope', description: 'access user information'},
function(err, scope) { function(err, scope) {
ACL.create({ ACL.create({
principalType: ACL.SCOPE, principalId: scope.id, principalType: ACL.SCOPE, principalId: scope.id,
model: 'User', property: ACL.ALL, model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW, accessType: ACL.ALL, permission: ACL.ALLOW,
}, function(err, resource) {
Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, checkResult);
Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, checkResult);
Scope.checkPermission('userScope', 'User', 'name', ACL.READ, checkResult);
});
});
});
it('should allow access to models for the given scope', function() {
Scope.create({name: 'testModelScope', description: 'access testModel information'},
function(err, scope) {
ACL.create({
principalType: ACL.SCOPE, principalId: scope.id,
model: 'testModel', property: 'name',
accessType: ACL.READ, permission: ACL.ALLOW,
}, function(err, resource) {
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
model: 'testModel', property: 'name',
accessType: ACL.WRITE, permission: ACL.DENY,
}, function(err, resource) { }, function(err, resource) {
// console.log(resource); async.parallel([
Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, cb => Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, cb),
function(err, perm) { cb => Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, cb),
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY cb => Scope.checkPermission('userScope', 'User', 'name', ACL.READ, cb),
}); ], (err) => {
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, assert.ifError(err);
function(err, perm) { done();
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY });
}); });
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, });
function(err, perm) { });
assert(perm.permission === ACL.ALLOW);
}); it('should allow access to models for the given scope', function(done) {
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, Scope.create({name: 'testModelScope', description: 'access testModel information'},
function(err, perm) { function(err, scope) {
assert(perm.permission === ACL.DENY); ACL.create({
}); principalType: ACL.SCOPE, principalId: scope.id,
model: 'testModel', property: 'name',
accessType: ACL.READ, permission: ACL.ALLOW,
}, function(err, resource) {
ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
model: 'testModel', property: 'name',
accessType: ACL.WRITE, permission: ACL.DENY,
}, function(err, resource) {
async.parallel([
cb => Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, cb),
cb => Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, cb),
cb => Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, cb),
cb => Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, cb),
], (err, perms) => {
if (err) return done(err);
assert.deepEqual(perms.map(p => p.permission), [
ACL.DENY,
ACL.DENY,
ACL.ALLOW,
ACL.DENY,
]);
done();
});
});
}); });
}); });
});
}); });
}); });
describe('security ACLs', function() { describe('security ACLs', function() {
beforeEach(setupTestModels);
it('supports checkPermission() returning a promise', function() { it('supports checkPermission() returning a promise', function() {
return ACL.create({ return ACL.create({
principalType: ACL.USER, principalType: ACL.USER,
@ -108,16 +97,54 @@ describe('security ACLs', function() {
accessType: ACL.ALL, accessType: ACL.ALL,
permission: ACL.ALLOW, permission: ACL.ALLOW,
}) })
.then(function() { .then(function() {
return ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL); return ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL);
}) })
.then(function(access) { .then(function(access) {
assert(access.permission === ACL.ALLOW); assert(access.permission === ACL.ALLOW);
}); });
});
it('supports ACL rules with a wildcard for models', function() {
const A_USER_ID = 'a-test-user';
// By default, access is allowed to all users
return assertPermission(ACL.ALLOW, 'initial state')
// An ACL rule applying to all models denies access to everybody
.then(() => ACL.create({
model: '*',
property: '*',
accessType: '*',
principalType: 'ROLE',
principalId: '$everyone',
permission: 'DENY',
}))
.then(() => assertPermission(ACL.DENY, 'all denied'))
// A rule for a specific model overrides the rule matching all models
.then(() => ACL.create({
model: testModel.modelName,
property: '*',
accessType: '*',
principalType: ACL.USER,
principalId: A_USER_ID,
permission: ACL.ALLOW,
}))
.then(() => assertPermission(ACL.ALLOW, 'only a single model allowed'));
function assertPermission(expectedPermission, msg) {
return ACL.checkAccessForContext({
principals: [{type: ACL.USER, id: A_USER_ID}],
model: testModel.modelName,
accessType: ACL.ALL,
}).then(accessContext => {
const actual = accessContext.isAllowed() ? ACL.ALLOW : ACL.DENY;
expect(actual, msg).to.equal(expectedPermission);
});
}
}); });
it('supports checkAccessForContext() returning a promise', function() { it('supports checkAccessForContext() returning a promise', function() {
var testModel = ds.createModel('testModel', { const testModel = ds.createModel('testModel', {
acls: [ acls: [
{principalType: ACL.USER, principalId: 'u001', {principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW}, accessType: ACL.ALL, permission: ACL.ALLOW},
@ -129,13 +156,13 @@ describe('security ACLs', function() {
model: 'testModel', model: 'testModel',
accessType: ACL.ALL, accessType: ACL.ALL,
}) })
.then(function(access) { .then(function(access) {
assert(access.permission === ACL.ALLOW); assert(access.permission === ACL.ALLOW);
}); });
}); });
it('should order ACL entries based on the matching score', function() { it('should order ACL entries based on the matching score', function() {
var acls = [ let acls = [
{ {
'model': 'account', 'model': 'account',
'accessType': '*', 'accessType': '*',
@ -157,7 +184,7 @@ describe('security ACLs', function() {
'principalType': 'ROLE', 'principalType': 'ROLE',
'principalId': '$everyone', 'principalId': '$everyone',
}]; }];
var req = { const req = {
model: 'account', model: 'account',
property: 'find', property: 'find',
accessType: 'WRITE', accessType: 'WRITE',
@ -165,7 +192,7 @@ describe('security ACLs', function() {
acls = acls.map(function(a) { return new ACL(a); }); acls = acls.map(function(a) { return new ACL(a); });
var perm = ACL.resolvePermission(acls, req); const perm = ACL.resolvePermission(acls, req);
// remove the registry from AccessRequest instance to ease asserting // remove the registry from AccessRequest instance to ease asserting
delete perm.registry; delete perm.registry;
assert.deepEqual(perm, {model: 'account', assert.deepEqual(perm, {model: 'account',
@ -186,7 +213,42 @@ describe('security ACLs', function() {
// }); // });
}); });
it('should allow access to models for the given principal by wildcard', function() { it('should order ACL entries based on the matching score even with wildcard req', function() {
let acls = [
{
'model': 'account',
'accessType': '*',
'permission': 'DENY',
'principalType': 'ROLE',
'principalId': '$everyone',
},
{
'model': 'account',
'accessType': '*',
'permission': 'ALLOW',
'principalType': 'ROLE',
'principalId': '$owner',
}];
const req = {
model: 'account',
property: '*',
accessType: 'WRITE',
};
acls = acls.map(function(a) { return new ACL(a); });
const perm = ACL.resolvePermission(acls, req);
// remove the registry from AccessRequest instance to ease asserting.
// Check the above test case for more info.
delete perm.registry;
assert.deepEqual(perm, {model: 'account',
property: '*',
accessType: 'WRITE',
permission: 'ALLOW',
methodNames: []});
});
it('should allow access to models for the given principal by wildcard', function(done) {
// jscs:disable validateIndentation // jscs:disable validateIndentation
ACL.create({ ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL, principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
@ -196,18 +258,22 @@ describe('security ACLs', function() {
principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL, principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY, accessType: ACL.READ, permission: ACL.DENY,
}, function(err, acl) { }, function(err, acl) {
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function(err, perm) { async.parallel([
assert(perm.permission === ACL.DENY); cb => ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, cb),
}); cb => ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, cb),
], (err, perms) => {
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function(err, perm) { if (err) return done(err);
assert(perm.permission === ACL.DENY); assert.deepEqual(perms.map(p => p.permission), [
ACL.DENY,
ACL.DENY,
]);
done();
}); });
}); });
}); });
}); });
it('should allow access to models by exception', function() { it('should allow access to models by exception', function(done) {
ACL.create({ ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL, principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.DENY, accessType: ACL.ALL, permission: ACL.DENY,
@ -220,42 +286,32 @@ describe('security ACLs', function() {
principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL, principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL,
accessType: ACL.EXECUTE, permission: ACL.ALLOW, accessType: ACL.EXECUTE, permission: ACL.ALLOW,
}, function(err, acl) { }, function(err, acl) {
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, async.parallel([
function(err, perm) { cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, cb),
assert(perm.permission === ACL.ALLOW); cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, cb),
}); cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, cb),
cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, cb),
ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, cb => ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.WRITE, cb),
function(err, perm) { cb => ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.READ, cb),
assert(perm.permission === ACL.ALLOW); ], (err, perms) => {
}); if (err) return done(err);
assert.deepEqual(perms.map(p => p.permission), [
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, ACL.ALLOW,
function(err, perm) { ACL.ALLOW,
assert(perm.permission === ACL.DENY); ACL.DENY,
}); ACL.DENY,
ACL.ALLOW,
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, ACL.ALLOW,
function(err, perm) { ]);
assert(perm.permission === ACL.DENY); done();
});
ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.READ,
function(err, perm) {
assert(perm.permission === ACL.ALLOW);
}); });
}); });
}); });
}); });
}); });
it('should honor defaultPermission from the model', function() { it('should honor defaultPermission from the model', function(done) {
var Customer = ds.createModel('Customer', { const Customer = ds.createModel('Customer', {
name: { name: {
type: String, type: String,
acls: [ acls: [
@ -275,22 +331,23 @@ describe('security ACLs', function() {
// ACL default permission is to DENY for model Customer // ACL default permission is to DENY for model Customer
Customer.settings.defaultPermission = ACL.DENY; Customer.settings.defaultPermission = ACL.DENY;
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, async.parallel([
function(err, perm) { cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, cb),
assert(perm.permission === ACL.DENY); cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, cb),
}); cb => ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, cb),
], (err, perms) => {
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function(err, perm) { if (err) return done(err);
assert(perm.permission === ACL.ALLOW); assert.deepEqual(perms.map(p => p.permission), [
}); ACL.DENY,
ACL.ALLOW,
ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, function(err, perm) { ACL.DENY,
assert(perm.permission === ACL.DENY); ]);
done();
}); });
}); });
it('should honor static ACLs from the model', function() { it('should honor static ACLs from the model', function(done) {
var Customer = ds.createModel('Customer', { const Customer = ds.createModel('Customer', {
name: { name: {
type: String, type: String,
acls: [ acls: [
@ -317,34 +374,27 @@ describe('security ACLs', function() {
]; ];
*/ */
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, async.parallel([
function(err, perm) { cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, cb),
assert(perm.permission === ACL.DENY); cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, cb),
}); cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL, cb),
cb => ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.READ, cb),
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, cb => ACL.checkPermission(ACL.USER, 'u003', 'Customer', 'name', ACL.WRITE, cb),
function(err, perm) { ], (err, perms) => {
assert(perm.permission === ACL.ALLOW); if (err) return done(err);
}); assert.deepEqual(perms.map(p => p.permission), [
ACL.DENY,
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL, ACL.ALLOW,
function(err, perm) { ACL.ALLOW,
assert(perm.permission === ACL.ALLOW); ACL.ALLOW,
}); ACL.DENY,
]);
ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.READ, done();
function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u003', 'Customer', 'name', ACL.WRITE,
function(err, perm) {
assert(perm.permission === ACL.DENY);
}); });
}); });
it('should filter static ACLs by model/property', function() { it('should filter static ACLs by model/property', function() {
var Model1 = ds.createModel('Model1', { const Model1 = ds.createModel('Model1', {
name: { name: {
type: String, type: String,
acls: [ acls: [
@ -365,7 +415,7 @@ describe('security ACLs', function() {
], ],
}); });
var staticACLs = ACL.getStaticACLs('Model1', 'name'); let staticACLs = ACL.getStaticACLs('Model1', 'name');
assert(staticACLs.length === 3); assert(staticACLs.length === 3);
staticACLs = ACL.getStaticACLs('Model1', 'findOne'); staticACLs = ACL.getStaticACLs('Model1', 'findOne');
@ -376,18 +426,17 @@ describe('security ACLs', function() {
assert(staticACLs[0].property === 'findById'); assert(staticACLs[0].property === 'findById');
}); });
it('should check access against LDL, ACL, and Role', function() { it('should check access against LDL, ACL, and Role', function(done) {
// var log = console.log; const log = function() {};
var log = function() {};
// Create // Create
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) { User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {
log('User: ', user.toObject()); log('User: ', user.toObject());
var userId = user.id; const userId = user.id;
// Define a model with static ACLs // Define a model with static ACLs
var Customer = ds.createModel('Customer', { const Customer = ds.createModel('Customer', {
name: { name: {
type: String, type: String,
acls: [ acls: [
@ -416,39 +465,47 @@ describe('security ACLs', function() {
log('Role: ', myRole.toObject()); log('Role: ', myRole.toObject());
myRole.principals.create({principalType: RoleMapping.USER, principalId: userId}, myRole.principals.create({principalType: RoleMapping.USER, principalId: userId},
function(err, p) { function(err, p) {
log('Principal added to role: ', p.toObject()); log('Principal added to role: ', p.toObject());
ACL.create({ ACL.create({
principalType: ACL.ROLE, principalId: 'MyRole', principalType: ACL.ROLE, principalId: 'MyRole',
model: 'Customer', property: ACL.ALL, model: 'Customer', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY, accessType: ACL.READ, permission: ACL.DENY,
}, function(err, acl) { }, function(err, acl) {
log('ACL 2: ', acl.toObject()); log('ACL 2: ', acl.toObject());
ACL.checkAccessForContext({ async.parallel([
principals: [ cb => {
{type: ACL.USER, id: userId}, ACL.checkAccessForContext({
], principals: [
model: 'Customer', {type: ACL.USER, id: userId},
property: 'name', ],
accessType: ACL.READ, model: 'Customer',
}, function(err, access) { property: 'name',
assert(!err && access.permission === ACL.ALLOW); accessType: ACL.READ,
}); }, function(err, access) {
assert.ifError(err);
ACL.checkAccessForContext({ assert.equal(access.permission, ACL.ALLOW);
principals: [ cb();
{type: ACL.ROLE, id: Role.EVERYONE}, });
], },
model: 'Customer', cb => {
property: 'name', ACL.checkAccessForContext({
accessType: ACL.READ, principals: [
}, function(err, access) { {type: ACL.ROLE, id: Role.EVERYONE},
assert(!err && access.permission === ACL.DENY); ],
model: 'Customer',
property: 'name',
accessType: ACL.READ,
}, function(err, access) {
assert.ifError(err);
assert.equal(access.permission, ACL.DENY);
cb();
});
}], done);
}); });
}); });
});
}); });
}); });
}); });
@ -457,10 +514,10 @@ describe('security ACLs', function() {
describe('access check', function() { describe('access check', function() {
it('should occur before other remote hooks', function(done) { it('should occur before other remote hooks', function(done) {
var app = loopback(); const app = loopback();
var MyTestModel = app.registry.createModel('MyTestModel'); const MyTestModel = app.registry.createModel('MyTestModel');
var checkAccessCalled = false; let checkAccessCalled = false;
var beforeHookCalled = false; let beforeHookCalled = false;
app.use(loopback.rest()); app.use(loopback.rest());
app.set('remoting', {errorHandler: {debug: true, log: false}}); app.set('remoting', {errorHandler: {debug: true, log: false}});
@ -470,9 +527,9 @@ describe('access check', function() {
// fake / spy on the checkAccess method // fake / spy on the checkAccess method
MyTestModel.checkAccess = function() { MyTestModel.checkAccess = function() {
var cb = arguments[arguments.length - 1]; const cb = arguments[arguments.length - 1];
checkAccessCalled = true; checkAccessCalled = true;
var allowed = true; const allowed = true;
cb(null, allowed); cb(null, allowed);
}; };
@ -497,8 +554,8 @@ describe('access check', function() {
}); });
describe('authorized roles propagation in RemotingContext', function() { describe('authorized roles propagation in RemotingContext', function() {
var app, request, accessToken; let app, request, accessToken;
var models = {}; let models = {};
beforeEach(setupAppAndRequest); beforeEach(setupAppAndRequest);
@ -508,35 +565,35 @@ describe('authorized roles propagation in RemotingContext', function() {
{permission: ACL.ALLOW, principalId: '$authenticated'}, {permission: ACL.ALLOW, principalId: '$authenticated'},
{permission: ACL.ALLOW, principalId: 'myRole'}, {permission: ACL.ALLOW, principalId: 'myRole'},
]) ])
.then(makeAuthorizedHttpRequestOnMyTestModel) .then(makeAuthorizedHttpRequestOnMyTestModel)
.then(function() { .then(function() {
var ctx = models.MyTestModel.lastRemotingContext; const ctx = models.MyTestModel.lastRemotingContext;
expect(ctx.args.options.authorizedRoles).to.eql( expect(ctx.args.options.authorizedRoles).to.eql(
{ {
$everyone: true, $everyone: true,
$authenticated: true, $authenticated: true,
myRole: true, myRole: true,
} },
); );
}); });
}); });
it('does not contain any denied role even if query is allowed', function() { it('does not contain any denied role even if query is allowed', function() {
return createACLs('MyTestModel', [ return createACLs('MyTestModel', [
{permission: ACL.ALLOW, principalId: '$everyone'}, {permission: ACL.ALLOW, principalId: '$everyone'},
{permission: ACL.DENY, principalId: '$authenticated'}, {permission: ACL.DENY, principalId: '$authenticated'},
{permission: ACL.ALLOW, principalId: 'myRole'}, {permission: ACL.ALLOW, principalId: 'myRole'},
]) ])
.then(makeAuthorizedHttpRequestOnMyTestModel) .then(makeAuthorizedHttpRequestOnMyTestModel)
.then(function() { .then(function() {
var ctx = models.MyTestModel.lastRemotingContext; const ctx = models.MyTestModel.lastRemotingContext;
expect(ctx.args.options.authorizedRoles).to.eql( expect(ctx.args.options.authorizedRoles).to.eql(
{ {
$everyone: true, $everyone: true,
myRole: true, myRole: true,
} },
); );
}); });
}); });
it('honors default permission setting', function() { it('honors default permission setting', function() {
@ -545,17 +602,17 @@ describe('authorized roles propagation in RemotingContext', function() {
return createACLs('MyTestModel', [ return createACLs('MyTestModel', [
{permission: ACL.DEFAULT, principalId: '$everyone'}, {permission: ACL.DEFAULT, principalId: '$everyone'},
{permission: ACL.DENY, principalId: '$authenticated'}, {permission: ACL.DENY, principalId: '$authenticated'},
{permission: ACL.ALLOW, principalId: 'myRole'}, {permission: ACL.ALLOW, principalId: 'myRole'},
]) ])
.then(makeAuthorizedHttpRequestOnMyTestModel) .then(makeAuthorizedHttpRequestOnMyTestModel)
.then(function() { .then(function() {
var ctx = models.MyTestModel.lastRemotingContext; const ctx = models.MyTestModel.lastRemotingContext;
expect(ctx.args.options.authorizedRoles).to.eql( expect(ctx.args.options.authorizedRoles).to.eql(
// '$everyone' is not expected as default permission is DENY // '$everyone' is not expected as default permission is DENY
{myRole: true} {myRole: true},
); );
}); });
}); });
// helpers // helpers
@ -569,6 +626,9 @@ describe('authorized roles propagation in RemotingContext', function() {
app.enableAuth({dataSource: 'db'}); app.enableAuth({dataSource: 'db'});
models = app.models; models = app.models;
// Speed up the password hashing algorithm for tests
models.User.settings.saltWorkFactor = 4;
// creating a custom model // creating a custom model
const MyTestModel = app.registry.createModel('MyTestModel'); const MyTestModel = app.registry.createModel('MyTestModel');
app.model(MyTestModel, {dataSource: 'db'}); app.model(MyTestModel, {dataSource: 'db'});
@ -584,15 +644,15 @@ describe('authorized roles propagation in RemotingContext', function() {
models.User.create({username: 'myUser', email: 'myuser@example.com', password: 'pass'}), models.User.create({username: 'myUser', email: 'myuser@example.com', password: 'pass'}),
models.Role.create({name: 'myRole'}), models.Role.create({name: 'myRole'}),
]) ])
.spread(function(myUser, myRole) { .spread(function(myUser, myRole) {
return Promise.all([ return Promise.all([
myRole.principals.create({principalType: 'USER', principalId: myUser.id}), myRole.principals.create({principalType: 'USER', principalId: myUser.id}),
models.User.login({username: 'myUser', password: 'pass'}), models.User.login({username: 'myUser', password: 'pass'}),
]); ]);
}) })
.spread(function(role, token) { .spread(function(role, token) {
accessToken = token; accessToken = token;
}); });
} }
function createACLs(model, acls) { function createACLs(model, acls) {
@ -607,7 +667,7 @@ describe('authorized roles propagation in RemotingContext', function() {
}); });
}); });
return Promise.all(acls); return Promise.all(acls);
}; }
function makeAuthorizedHttpRequestOnMyTestModel() { function makeAuthorizedHttpRequestOnMyTestModel() {
return request.get('/MyTestModels') return request.get('/MyTestModels')
@ -615,3 +675,14 @@ describe('authorized roles propagation in RemotingContext', function() {
.expect(200); .expect(200);
} }
}); });
function setupTestModels() {
ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
testModel = loopback.PersistedModel.extend('testModel');
ACL.attachTo(ds);
Role.attachTo(ds);
RoleMapping.attachTo(ds);
User.attachTo(ds);
Scope.attachTo(ds);
testModel.attachTo(ds);
}

View File

@ -1,38 +1,39 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
var async = require('async'); const async = require('async');
var path = require('path'); const path = require('path');
var http = require('http'); const http = require('http');
var express = require('express'); const express = require('express');
var loopback = require('../'); const loopback = require('../');
var PersistedModel = loopback.PersistedModel; const PersistedModel = loopback.PersistedModel;
var describe = require('./util/describe'); const describe = require('./util/describe');
var expect = require('./helpers/expect'); const expect = require('./helpers/expect');
var it = require('./util/it'); const it = require('./util/it');
var request = require('supertest'); const request = require('supertest');
const sinon = require('sinon');
describe('app', function() { describe('app', function() {
var app; let app;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback({localRegistry: true, loadBuiltinModels: true});
}); });
describe.onServer('.middleware(phase, handler)', function() { describe.onServer('.middleware(phase, handler)', function() {
var steps; let steps;
beforeEach(function setup() { beforeEach(function setup() {
steps = []; steps = [];
}); });
it('runs middleware in phases', function(done) { it('runs middleware in phases', function(done) {
var PHASES = [ const PHASES = [
'initial', 'session', 'auth', 'parse', 'initial', 'session', 'auth', 'parse',
'routes', 'files', 'final', 'routes', 'files', 'final',
]; ];
@ -87,10 +88,10 @@ describe('app', function() {
return namedHandler(name); return namedHandler(name);
} }
var myHandler; let myHandler;
app.middleware('routes:before', app.middleware('routes:before',
myHandler = handlerThatAddsHandler('my-handler')); myHandler = handlerThatAddsHandler('my-handler'));
var found = app._findLayerByHandler(myHandler); const found = app._findLayerByHandler(myHandler);
expect(found).to.be.an('object'); expect(found).to.be.an('object');
expect(myHandler).to.equal(found.handle); expect(myHandler).to.equal(found.handle);
expect(found).have.property('phase', 'routes:before'); expect(found).have.property('phase', 'routes:before');
@ -105,13 +106,35 @@ describe('app', function() {
it('allows handlers to be wrapped as __NR_handler on express stack', it('allows handlers to be wrapped as __NR_handler on express stack',
function(done) { function(done) {
var myHandler = namedHandler('my-handler'); const myHandler = namedHandler('my-handler');
var wrappedHandler = function(req, res, next) { const wrappedHandler = function(req, res, next) {
myHandler(req, res, next); myHandler(req, res, next);
}; };
wrappedHandler['__NR_handler'] = myHandler; wrappedHandler['__NR_handler'] = myHandler;
app.middleware('routes:before', wrappedHandler); app.middleware('routes:before', wrappedHandler);
var found = app._findLayerByHandler(myHandler); const found = app._findLayerByHandler(myHandler);
expect(found).to.be.an('object');
expect(found).have.property('phase', 'routes:before');
executeMiddlewareHandlers(app, function(err) {
if (err) return done(err);
expect(steps).to.eql(['my-handler']);
done();
});
});
it('allows handlers to be wrapped as __appdynamicsProxyInfo__ on express stack',
function(done) {
const myHandler = namedHandler('my-handler');
const wrappedHandler = function(req, res, next) {
myHandler(req, res, next);
};
wrappedHandler['__appdynamicsProxyInfo__'] = {
orig: myHandler,
};
app.middleware('routes:before', wrappedHandler);
const found = app._findLayerByHandler(myHandler);
expect(found).to.be.an('object'); expect(found).to.be.an('object');
expect(found).have.property('phase', 'routes:before'); expect(found).have.property('phase', 'routes:before');
executeMiddlewareHandlers(app, function(err) { executeMiddlewareHandlers(app, function(err) {
@ -125,13 +148,13 @@ describe('app', function() {
it('allows handlers to be wrapped as a property on express stack', it('allows handlers to be wrapped as a property on express stack',
function(done) { function(done) {
var myHandler = namedHandler('my-handler'); const myHandler = namedHandler('my-handler');
var wrappedHandler = function(req, res, next) { const wrappedHandler = function(req, res, next) {
myHandler(req, res, next); myHandler(req, res, next);
}; };
wrappedHandler['__handler'] = myHandler; wrappedHandler['__handler'] = myHandler;
app.middleware('routes:before', wrappedHandler); app.middleware('routes:before', wrappedHandler);
var found = app._findLayerByHandler(myHandler); const found = app._findLayerByHandler(myHandler);
expect(found).to.be.an('object'); expect(found).to.be.an('object');
expect(found).have.property('phase', 'routes:before'); expect(found).have.property('phase', 'routes:before');
executeMiddlewareHandlers(app, function(err) { executeMiddlewareHandlers(app, function(err) {
@ -144,7 +167,7 @@ describe('app', function() {
}); });
it('injects error from previous phases into the router', function(done) { it('injects error from previous phases into the router', function(done) {
var expectedError = new Error('expected error'); const expectedError = new Error('expected error');
app.middleware('initial', function(req, res, next) { app.middleware('initial', function(req, res, next) {
steps.push('initial'); steps.push('initial');
@ -170,7 +193,7 @@ describe('app', function() {
}); });
it('passes unhandled error to callback', function(done) { it('passes unhandled error to callback', function(done) {
var expectedError = new Error('expected error'); const expectedError = new Error('expected error');
app.middleware('initial', function(req, res, next) { app.middleware('initial', function(req, res, next) {
next(expectedError); next(expectedError);
@ -184,8 +207,8 @@ describe('app', function() {
}); });
it('passes errors to error handlers in the same phase', function(done) { it('passes errors to error handlers in the same phase', function(done) {
var expectedError = new Error('this should be handled by middleware'); const expectedError = new Error('this should be handled by middleware');
var handledError; let handledError;
app.middleware('initial', function(req, res, next) { app.middleware('initial', function(req, res, next) {
// continue in the next tick, this verifies that the next // continue in the next tick, this verifies that the next
@ -222,7 +245,8 @@ describe('app', function() {
expect(steps).to.eql(['/scope', '/scope/item']); expect(steps).to.eql(['/scope', '/scope/item']);
done(); done();
}); },
);
}); });
it('scopes middleware to a regex path', function(done) { it('scopes middleware to a regex path', function(done) {
@ -237,7 +261,8 @@ describe('app', function() {
expect(steps).to.eql(['/a', '/b']); expect(steps).to.eql(['/a', '/b']);
done(); done();
}); },
);
}); });
it('scopes middleware to a list of scopes', function(done) { it('scopes middleware to a list of scopes', function(done) {
@ -252,7 +277,8 @@ describe('app', function() {
expect(steps).to.eql(['/a', '/b', '/scope']); expect(steps).to.eql(['/a', '/b', '/scope']);
done(); done();
}); },
);
}); });
it('sets req.url to a sub-path', function(done) { it('sets req.url to a sub-path', function(done) {
@ -272,7 +298,7 @@ describe('app', function() {
}); });
it('exposes express helpers on req and res objects', function(done) { it('exposes express helpers on req and res objects', function(done) {
var req, res; let req, res;
app.middleware('initial', function(rq, rs, next) { app.middleware('initial', function(rq, rs, next) {
req = rq; req = rq;
@ -310,7 +336,7 @@ describe('app', function() {
}); });
it('sets req.baseUrl and req.originalUrl', function(done) { it('sets req.baseUrl and req.originalUrl', function(done) {
var reqProps; let reqProps;
app.middleware('initial', function(req, res, next) { app.middleware('initial', function(req, res, next) {
reqProps = {baseUrl: req.baseUrl, originalUrl: req.originalUrl}; reqProps = {baseUrl: req.baseUrl, originalUrl: req.originalUrl};
@ -348,7 +374,7 @@ describe('app', function() {
// we need at least 9 elements to expose non-stability // we need at least 9 elements to expose non-stability
// of the built-in sort function // of the built-in sort function
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
numbers.forEach(function(n) { numbers.forEach(function(n) {
app.middleware('routes', namedHandler(n)); app.middleware('routes', namedHandler(n));
}); });
@ -363,8 +389,8 @@ describe('app', function() {
}); });
it('correctly mounts express apps', function(done) { it('correctly mounts express apps', function(done) {
var data, mountWasEmitted; let data, mountWasEmitted;
var subapp = express(); const subapp = express();
subapp.use(function(req, res, next) { subapp.use(function(req, res, next) {
data = { data = {
mountpath: req.app.mountpath, mountpath: req.app.mountpath,
@ -391,10 +417,10 @@ describe('app', function() {
}); });
it('restores req & res on return from mounted express app', function(done) { it('restores req & res on return from mounted express app', function(done) {
var expected = {}; const expected = {};
var actual = {}; const actual = {};
var subapp = express(); const subapp = express();
subapp.use(function verifyTestAssumptions(req, res, next) { subapp.use(function verifyTestAssumptions(req, res, next) {
expect(req.__proto__).to.not.equal(expected.req); expect(req.__proto__).to.not.equal(expected.req);
expect(res.__proto__).to.not.equal(expected.res); expect(res.__proto__).to.not.equal(expected.res);
@ -442,8 +468,8 @@ describe('app', function() {
} }
function getObjectAndPrototypeKeys(obj) { function getObjectAndPrototypeKeys(obj) {
var result = []; const result = [];
for (var k in obj) { for (const k in obj) {
result.push(k); result.push(k);
} }
result.sort(); result.sort();
@ -453,11 +479,11 @@ describe('app', function() {
describe.onServer('.middlewareFromConfig', function() { describe.onServer('.middlewareFromConfig', function() {
it('provides API for loading middleware from JSON config', function(done) { it('provides API for loading middleware from JSON config', function(done) {
var steps = []; const steps = [];
var expectedConfig = {key: 'value'}; const expectedConfig = {key: 'value'};
var handlerFactory = function() { const handlerFactory = function() {
var args = Array.prototype.slice.apply(arguments); const args = Array.prototype.slice.apply(arguments);
return function(req, res, next) { return function(req, res, next) {
steps.push(args); steps.push(args);
@ -523,8 +549,8 @@ describe('app', function() {
}); });
}); });
it('scopes middleware to a list of scopes', function(done) { it('scopes middleware from config to a list of scopes', function(done) {
var steps = []; const steps = [];
app.middlewareFromConfig( app.middlewareFromConfig(
function factory() { function factory() {
return function(req, res, next) { return function(req, res, next) {
@ -536,7 +562,8 @@ describe('app', function() {
{ {
phase: 'initial', phase: 'initial',
paths: ['/scope', /^\/(a|b)/], paths: ['/scope', /^\/(a|b)/],
}); },
);
async.eachSeries( async.eachSeries(
['/', '/a', '/b', '/c', '/scope', '/other'], ['/', '/a', '/b', '/c', '/scope', '/other'],
@ -547,12 +574,13 @@ describe('app', function() {
expect(steps).to.eql(['/a', '/b', '/scope']); expect(steps).to.eql(['/a', '/b', '/scope']);
done(); done();
}); },
);
}); });
}); });
describe.onServer('.defineMiddlewarePhases(nameOrArray)', function() { describe.onServer('.defineMiddlewarePhases(nameOrArray)', function() {
var app; let app;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
}); });
@ -598,7 +626,7 @@ describe('app', function() {
}); });
function verifyMiddlewarePhases(names, done) { function verifyMiddlewarePhases(names, done) {
var steps = []; const steps = [];
names.forEach(function(it) { names.forEach(function(it) {
app.middleware(it, function(req, res, next) { app.middleware(it, function(req, res, next) {
steps.push(it); steps.push(it);
@ -618,7 +646,7 @@ describe('app', function() {
}); });
describe('app.model(Model)', function() { describe('app.model(Model)', function() {
var app, db, MyTestModel; let app, db, MyTestModel;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
app.set('remoting', {errorHandler: {debug: true, log: false}}); app.set('remoting', {errorHandler: {debug: true, log: false}});
@ -627,7 +655,7 @@ describe('app', function() {
}); });
it('Expose a `Model` to remote clients', function() { it('Expose a `Model` to remote clients', function() {
var Color = PersistedModel.extend('color', {name: String}); const Color = PersistedModel.extend('color', {name: String});
app.model(Color); app.model(Color);
Color.attachTo(db); Color.attachTo(db);
@ -635,22 +663,22 @@ describe('app', function() {
}); });
it('uses singular name as app.remoteObjects() key', function() { it('uses singular name as app.remoteObjects() key', function() {
var Color = PersistedModel.extend('color', {name: String}); const Color = PersistedModel.extend('color', {name: String});
app.model(Color); app.model(Color);
Color.attachTo(db); Color.attachTo(db);
expect(app.remoteObjects()).to.eql({color: Color}); expect(app.remoteObjects()).to.eql({color: Color});
}); });
it('uses singular name as shared class name', function() { it('uses singular name as shared class name', function() {
var Color = PersistedModel.extend('color', {name: String}); const Color = PersistedModel.extend('color', {name: String});
app.model(Color); app.model(Color);
Color.attachTo(db); Color.attachTo(db);
var classes = app.remotes().classes().map(function(c) { return c.name; }); const classes = app.remotes().classes().map(function(c) { return c.name; });
expect(classes).to.contain('color'); expect(classes).to.contain('color');
}); });
it('registers existing models to app.models', function() { it('registers existing models to app.models', function() {
var Color = db.createModel('color', {name: String}); const Color = db.createModel('color', {name: String});
app.model(Color); app.model(Color);
expect(Color.app).to.be.equal(app); expect(Color.app).to.be.equal(app);
expect(Color.shared).to.equal(true); expect(Color.shared).to.equal(true);
@ -659,9 +687,9 @@ describe('app', function() {
}); });
it('emits a `modelRemoted` event', function() { it('emits a `modelRemoted` event', function() {
var Color = PersistedModel.extend('color', {name: String}); const Color = PersistedModel.extend('color', {name: String});
Color.shared = true; Color.shared = true;
var remotedClass; let remotedClass;
app.on('modelRemoted', function(sharedClass) { app.on('modelRemoted', function(sharedClass) {
remotedClass = sharedClass; remotedClass = sharedClass;
}); });
@ -671,9 +699,9 @@ describe('app', function() {
}); });
it('emits a `remoteMethodDisabled` event', function() { it('emits a `remoteMethodDisabled` event', function() {
var Color = PersistedModel.extend('color', {name: String}); const Color = PersistedModel.extend('color', {name: String});
Color.shared = true; Color.shared = true;
var remoteMethodDisabledClass, disabledRemoteMethod; let remoteMethodDisabledClass, disabledRemoteMethod;
app.on('remoteMethodDisabled', function(sharedClass, methodName) { app.on('remoteMethodDisabled', function(sharedClass, methodName) {
remoteMethodDisabledClass = sharedClass; remoteMethodDisabledClass = sharedClass;
disabledRemoteMethod = methodName; disabledRemoteMethod = methodName;
@ -688,23 +716,23 @@ describe('app', function() {
it('emits a `remoteMethodAdded` event', function() { it('emits a `remoteMethodAdded` event', function() {
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
var Book = app.registry.createModel( const Book = app.registry.createModel(
'Book', 'Book',
{name: 'string'}, {name: 'string'},
{plural: 'books'} {plural: 'books'},
); );
app.model(Book, {dataSource: 'db'}); app.model(Book, {dataSource: 'db'});
var Page = app.registry.createModel( const Page = app.registry.createModel(
'Page', 'Page',
{name: 'string'}, {name: 'string'},
{plural: 'pages'} {plural: 'pages'},
); );
app.model(Page, {dataSource: 'db'}); app.model(Page, {dataSource: 'db'});
Book.hasMany(Page); Book.hasMany(Page);
var remoteMethodAddedClass; let remoteMethodAddedClass;
app.on('remoteMethodAdded', function(sharedClass) { app.on('remoteMethodAdded', function(sharedClass) {
remoteMethodAddedClass = sharedClass; remoteMethodAddedClass = sharedClass;
}); });
@ -713,17 +741,6 @@ describe('app', function() {
expect(remoteMethodAddedClass).to.eql(Book.sharedClass); expect(remoteMethodAddedClass).to.eql(Book.sharedClass);
}); });
it.onServer('updates REST API when a new model is added', function(done) {
app.use(loopback.rest());
request(app).get('/colors').expect(404, function(err, res) {
if (err) return done(err);
var Color = PersistedModel.extend('color', {name: String});
app.model(Color);
Color.attachTo(db);
request(app).get('/colors').expect(200, done);
});
});
it('accepts null dataSource', function(done) { it('accepts null dataSource', function(done) {
app.model(MyTestModel, {dataSource: null}); app.model(MyTestModel, {dataSource: null});
expect(MyTestModel.dataSource).to.eql(null); expect(MyTestModel.dataSource).to.eql(null);
@ -742,14 +759,14 @@ describe('app', function() {
}); });
it('throws error if model typeof string is passed', function() { it('throws error if model typeof string is passed', function() {
var fn = function() { app.model('MyTestModel'); }; const fn = function() { app.model('MyTestModel'); };
expect(fn).to.throw(/app(\.model|\.registry)/); expect(fn).to.throw(/app(\.model|\.registry)/);
}); });
}); });
describe('app.model(ModelCtor, config)', function() { describe('app.model(ModelCtor, config)', function() {
it('attaches the model to a datasource', function() { it('attaches the model to a datasource', function() {
var previousModel = loopback.registry.findModel('TestModel'); const previousModel = loopback.registry.findModel('TestModel');
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
if (previousModel) { if (previousModel) {
@ -757,19 +774,79 @@ describe('app', function() {
} }
assert(!previousModel || !previousModel.dataSource); assert(!previousModel || !previousModel.dataSource);
var TestModel = app.registry.createModel('TestModel'); const TestModel = app.registry.createModel('TestModel');
app.model(TestModel, {dataSource: 'db'}); app.model(TestModel, {dataSource: 'db'});
expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db); expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db);
}); });
}); });
describe('app.deleteModelByName()', () => {
let TestModel;
beforeEach(setupTestModel);
it('removes the model from app registries', () => {
expect(Object.keys(app.models))
.to.contain('test-model')
.and.contain('TestModel')
.and.contain('testModel');
expect(app.models().map(m => m.modelName))
.to.contain('test-model');
app.deleteModelByName('test-model');
expect(Object.keys(app.models))
.to.not.contain('test-model')
.and.not.contain('TestModel')
.and.not.contain('testModel');
expect(app.models().map(m => m.modelName))
.to.not.contain('test-model');
});
it('removes the model from juggler registries', () => {
expect(Object.keys(app.registry.modelBuilder.models))
.to.contain('test-model');
app.deleteModelByName('test-model');
expect(Object.keys(app.registry.modelBuilder.models))
.to.not.contain('test-model');
});
it('removes the model from remoting registries', () => {
expect(Object.keys(app.remotes()._classes))
.to.contain('test-model');
app.deleteModelByName('test-model');
expect(Object.keys(app.remotes()._classes))
.to.not.contain('test-model');
});
it('emits "modelDeleted" event', () => {
const spy = sinon.spy();
app.on('modelDeleted', spy);
app.deleteModelByName('test-model');
sinon.assert.calledWith(spy, TestModel);
});
function setupTestModel() {
TestModel = app.registry.createModel({
name: 'test-model',
base: 'Model',
});
app.model(TestModel, {dataSource: null});
}
});
describe('app.models', function() { describe('app.models', function() {
it('is unique per app instance', function() { it('is unique per app instance', function() {
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
var Color = app.registry.createModel('Color'); const Color = app.registry.createModel('Color');
app.model(Color, {dataSource: 'db'}); app.model(Color, {dataSource: 'db'});
expect(app.models.Color).to.equal(Color); expect(app.models.Color).to.equal(Color);
var anotherApp = loopback(); const anotherApp = loopback();
expect(anotherApp.models.Color).to.equal(undefined); expect(anotherApp.models.Color).to.equal(undefined);
}); });
}); });
@ -778,7 +855,7 @@ describe('app', function() {
it('is unique per app instance', function() { it('is unique per app instance', function() {
app.dataSource('ds', {connector: 'memory'}); app.dataSource('ds', {connector: 'memory'});
expect(app.datasources.ds).to.not.equal(undefined); expect(app.datasources.ds).to.not.equal(undefined);
var anotherApp = loopback(); const anotherApp = loopback();
expect(anotherApp.datasources.ds).to.equal(undefined); expect(anotherApp.datasources.ds).to.equal(undefined);
}); });
}); });
@ -787,7 +864,7 @@ describe('app', function() {
it('looks up the connector in `app.connectors`', function() { it('looks up the connector in `app.connectors`', function() {
app.connector('custom', loopback.Memory); app.connector('custom', loopback.Memory);
app.dataSource('custom', {connector: 'custom'}); app.dataSource('custom', {connector: 'custom'});
expect(app.dataSources.custom.name).to.equal(loopback.Memory.name); expect(app.dataSources.custom.name).to.equal('custom');
}); });
it('adds data source name to error messages', function() { it('adds data source name to error messages', function() {
@ -809,11 +886,11 @@ describe('app', function() {
describe.onServer('listen()', function() { describe.onServer('listen()', function() {
it('starts http server', function(done) { it('starts http server', function(done) {
var app = loopback(); const app = loopback();
app.set('port', 0); app.set('port', 0);
app.get('/', function(req, res) { res.status(200).send('OK'); }); app.get('/', function(req, res) { res.status(200).send('OK'); });
var server = app.listen(); const server = app.listen();
expect(server).to.be.an.instanceOf(require('http').Server); expect(server).to.be.an.instanceOf(require('http').Server);
@ -823,7 +900,7 @@ describe('app', function() {
}); });
it('updates port on `listening` event', function(done) { it('updates port on `listening` event', function(done) {
var app = loopback(); const app = loopback();
app.set('port', 0); app.set('port', 0);
app.listen(function() { app.listen(function() {
@ -834,13 +911,12 @@ describe('app', function() {
}); });
it('updates `url` on `listening` event', function(done) { it('updates `url` on `listening` event', function(done) {
var app = loopback(); const app = loopback();
app.set('port', 0); app.set('port', 0);
app.set('host', undefined); app.set('host', undefined);
app.listen(function() { app.listen(function() {
var host = process.platform === 'win32' ? 'localhost' : app.get('host'); const expectedUrl = 'http://localhost:' + app.get('port') + '/';
var expectedUrl = 'http://' + host + ':' + app.get('port') + '/';
expect(app.get('url'), 'url').to.equal(expectedUrl); expect(app.get('url'), 'url').to.equal(expectedUrl);
done(); done();
@ -848,7 +924,7 @@ describe('app', function() {
}); });
it('forwards to http.Server.listen on more than one arg', function(done) { it('forwards to http.Server.listen on more than one arg', function(done) {
var app = loopback(); const app = loopback();
app.set('port', 1); app.set('port', 1);
app.listen(0, '127.0.0.1', function() { app.listen(0, '127.0.0.1', function() {
expect(app.get('port'), 'port').to.not.equal(0).and.not.equal(1); expect(app.get('port'), 'port').to.not.equal(0).and.not.equal(1);
@ -858,20 +934,18 @@ describe('app', function() {
}); });
}); });
it('forwards to http.Server.listen when the single arg is not a function', it('forwards to http.Server.listen when the single arg is not a function', function(done) {
function(done) { const app = loopback();
var app = loopback(); app.set('port', 1);
app.set('port', 1); app.listen(0).on('listening', function() {
app.listen(0).on('listening', function() { expect(app.get('port'), 'port') .to.not.equal(0).and.not.equal(1);
expect(app.get('port'), 'port') .to.not.equal(0).and.not.equal(1);
done(); done();
}); });
} });
);
it('uses app config when no parameter is supplied', function(done) { it('uses app config when no parameter is supplied', function(done) {
var app = loopback(); const app = loopback();
// Http listens on all interfaces by default // Http listens on all interfaces by default
// Custom host serves as an indicator whether // Custom host serves as an indicator whether
// the value was used by app.listen // the value was used by app.listen
@ -893,26 +967,26 @@ describe('app', function() {
}); });
it('auto-configures required models to provided dataSource', function() { it('auto-configures required models to provided dataSource', function() {
var AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping']; const AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];
var app = loopback({localRegistry: true, loadBuiltinModels: true}); const app = loopback({localRegistry: true, loadBuiltinModels: true});
require('../lib/builtin-models')(app.registry); require('../lib/builtin-models')(app.registry);
var db = app.dataSource('db', {connector: 'memory'}); const db = app.dataSource('db', {connector: 'memory'});
app.enableAuth({dataSource: 'db'}); app.enableAuth({dataSource: 'db'});
expect(Object.keys(app.models)).to.include.members(AUTH_MODELS); expect(Object.keys(app.models)).to.include.members(AUTH_MODELS);
AUTH_MODELS.forEach(function(m) { AUTH_MODELS.forEach(function(m) {
var Model = app.models[m]; const Model = app.models[m];
expect(Model.dataSource, m + '.dataSource').to.equal(db); expect(Model.dataSource, m + '.dataSource').to.equal(db);
expect(Model.shared, m + '.shared').to.equal(m === 'User'); expect(Model.shared, m + '.shared').to.equal(m === 'User');
}); });
}); });
it('detects already configured subclass of a required model', function() { it('detects already configured subclass of a required model', function() {
var app = loopback({localRegistry: true, loadBuiltinModels: true}); const app = loopback({localRegistry: true, loadBuiltinModels: true});
var db = app.dataSource('db', {connector: 'memory'}); const db = app.dataSource('db', {connector: 'memory'});
var Customer = app.registry.createModel('Customer', {}, {base: 'User'}); const Customer = app.registry.createModel('Customer', {}, {base: 'User'});
app.model(Customer, {dataSource: 'db'}); app.model(Customer, {dataSource: 'db'});
// Fix AccessToken's "belongsTo user" relation to use our new Customer model // Fix AccessToken's "belongsTo user" relation to use our new Customer model
@ -927,7 +1001,7 @@ describe('app', function() {
describe.onServer('app.get(\'/\', loopback.status())', function() { describe.onServer('app.get(\'/\', loopback.status())', function() {
it('should return the status of the application', function(done) { it('should return the status of the application', function(done) {
var app = loopback(); const app = loopback();
app.get('/', loopback.status()); app.get('/', loopback.status());
request(app) request(app)
.get('/') .get('/')
@ -939,7 +1013,7 @@ describe('app', function() {
expect(res.body).to.have.property('started'); expect(res.body).to.have.property('started');
expect(res.body.uptime, 'uptime').to.be.gte(0); expect(res.body.uptime, 'uptime').to.be.gte(0);
var elapsed = Date.now() - Number(new Date(res.body.started)); const elapsed = Date.now() - Number(new Date(res.body.started));
// elapsed should be a small positive number... // elapsed should be a small positive number...
expect(elapsed, 'elapsed').to.be.within(0, 300); expect(elapsed, 'elapsed').to.be.within(0, 300);
@ -952,7 +1026,7 @@ describe('app', function() {
describe('app.connectors', function() { describe('app.connectors', function() {
it('is unique per app instance', function() { it('is unique per app instance', function() {
app.connectors.foo = 'bar'; app.connectors.foo = 'bar';
var anotherApp = loopback(); const anotherApp = loopback();
expect(anotherApp.connectors.foo).to.equal(undefined); expect(anotherApp.connectors.foo).to.equal(undefined);
}); });
@ -995,8 +1069,8 @@ describe('app', function() {
}); });
it('is unique per app instance', function() { it('is unique per app instance', function() {
var app1 = loopback(); const app1 = loopback();
var app2 = loopback(); const app2 = loopback();
expect(app1.settings).to.not.equal(app2.settings); expect(app1.settings).to.not.equal(app2.settings);
@ -1006,36 +1080,131 @@ describe('app', function() {
}); });
it('exposes loopback as a property', function() { it('exposes loopback as a property', function() {
var app = loopback(); const app = loopback();
expect(app.loopback).to.equal(loopback); expect(app.loopback).to.equal(loopback);
}); });
describe('normalizeHttpPath option', function() { function setupUserModels(app, options, done) {
var app, db; app.dataSource('db', {connector: 'memory'});
const UserAccount = app.registry.createModel(
'UserAccount',
{name: 'string'},
options,
);
const UserRole = app.registry.createModel(
'UserRole',
{name: 'string'},
);
app.model(UserAccount, {dataSource: 'db'});
app.model(UserRole, {dataSource: 'db'});
UserAccount.hasMany(UserRole);
UserAccount.create({
name: 'user',
}, function(err, user) {
if (err) return done(err);
app.use(loopback.rest());
done();
});
}
describe('Model-level normalizeHttpPath option', function() {
let app;
beforeEach(function() { beforeEach(function() {
app = loopback(); app = loopback();
db = loopback.createDataSource({connector: loopback.Memory});
}); });
it.onServer('normalizes the http path', function(done) { it.onServer('honours Model-level setting of `false`', function(done) {
var UserAccount = PersistedModel.extend( setupUserModels(app, {
'UserAccount', remoting: {normalizeHttpPath: false},
{name: String}, }, function(err) {
{ if (err) return done(err);
remoting: {normalizeHttpPath: true}, request(app).get('/UserAccounts').expect(200, function(err) {
if (err) return done(err);
request(app).get('/UserAccounts/1/userRoles').expect(200, function(err) {
if (err) return done(err);
done();
});
}); });
app.model(UserAccount); });
UserAccount.attachTo(db); });
app.use(loopback.rest()); it.onServer('honours Model-level setting of `true`', function(done) {
request(app).get('/user-accounts').expect(200, done); setupUserModels(app, {
remoting: {normalizeHttpPath: true},
}, function(err) {
if (err) return done(err);
request(app).get('/user-accounts').expect(200, function(err) {
if (err) return done(err);
request(app).get('/user-accounts/1/user-roles').expect(200, function(err) {
if (err) return done(err);
done();
});
});
});
});
});
describe('app-level normalizeHttpPath option', function() {
let app;
beforeEach(function() {
app = loopback();
});
it.onServer('honours app-level setting of `false`', function(done) {
app.set('remoting', {rest: {normalizeHttpPath: false}});
setupUserModels(app, null, function(err) {
if (err) return done(err);
request(app).get('/UserAccounts').expect(200, function(err) {
if (err) return done(err);
request(app).get('/UserAccounts/1/userRoles').expect(200, function(err) {
if (err) return done(err);
done();
});
});
});
});
it.onServer('honours app-level setting of `true`', function(done) {
app.set('remoting', {rest: {normalizeHttpPath: true}});
setupUserModels(app, null, function(err) {
if (err) return done(err);
request(app).get('/user-accounts').expect(200, function(err) {
if (err) return done(err);
request(app).get('/user-accounts/1/user-roles').expect(200, function(err) {
if (err) return done(err);
done();
});
});
});
});
});
describe('Model-level and app-level normalizeHttpPath options', function() {
let app;
beforeEach(function() {
app = loopback();
});
it.onServer('prioritizes Model-level setting over the app-level one', function(done) {
app.set('remoting', {rest: {normalizeHttpPath: true}});
setupUserModels(app, {
remoting: {normalizeHttpPath: false},
}, function(err) {
if (err) return done(err);
request(app).get('/UserAccounts').expect(200, function(err) {
if (err) return done(err);
request(app).get('/UserAccounts/1/userRoles').expect(200, function(err) {
if (err) return done(err);
done();
});
});
});
}); });
}); });
}); });
function executeMiddlewareHandlers(app, urlPath, callback) { function executeMiddlewareHandlers(app, urlPath, callback) {
var handlerError = undefined; let handlerError = undefined;
var server = http.createServer(function(req, res) { const server = http.createServer(function(req, res) {
app.handle(req, res, function(err) { app.handle(req, res, function(err) {
if (err) { if (err) {
handlerError = err; handlerError = err;

View File

@ -1,8 +1,17 @@
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const loopback = require('../'); const loopback = require('../');
const supertest = require('supertest'); const supertest = require('supertest');
const strongErrorHandler = require('strong-error-handler'); const strongErrorHandler = require('strong-error-handler');
const loggers = require('./helpers/error-loggers');
const logAllServerErrors = loggers.logAllServerErrors;
const logServerErrorsOtherThan = loggers.logServerErrorsOtherThan;
describe('Authorization scopes', () => { describe('Authorization scopes', () => {
const CUSTOM_SCOPE = 'read:custom'; const CUSTOM_SCOPE = 'read:custom';
@ -15,28 +24,28 @@ describe('Authorization scopes', () => {
beforeEach(givenScopedToken); beforeEach(givenScopedToken);
it('denies regular token to invoke custom-scoped method', () => { it('denies regular token to invoke custom-scoped method', () => {
logServerErrorsOtherThan(401); logServerErrorsOtherThan(401, app);
return request.get('/users/scoped') return request.get('/users/scoped')
.set('Authorization', regularToken.id) .set('Authorization', regularToken.id)
.expect(401); .expect(401);
}); });
it('allows regular tokens to invoke default-scoped method', () => { it('allows regular tokens to invoke default-scoped method', () => {
logAllServerErrors(); logAllServerErrors(app);
return request.get('/users/' + testUser.id) return request.get('/users/' + testUser.id)
.set('Authorization', regularToken.id) .set('Authorization', regularToken.id)
.expect(200); .expect(200);
}); });
it('allows scoped token to invoke custom-scoped method', () => { it('allows scoped token to invoke custom-scoped method', () => {
logAllServerErrors(); logAllServerErrors(app);
return request.get('/users/scoped') return request.get('/users/scoped')
.set('Authorization', scopedToken.id) .set('Authorization', scopedToken.id)
.expect(204); .expect(204);
}); });
it('denies scoped token to invoke default-scoped method', () => { it('denies scoped token to invoke default-scoped method', () => {
logServerErrorsOtherThan(401); logServerErrorsOtherThan(401, app);
return request.get('/users/' + testUser.id) return request.get('/users/' + testUser.id)
.set('Authorization', scopedToken.id) .set('Authorization', scopedToken.id)
.expect(401); .expect(401);
@ -45,7 +54,7 @@ describe('Authorization scopes', () => {
describe('token granted both default and custom scope', () => { describe('token granted both default and custom scope', () => {
beforeEach('given token with default and custom scope', beforeEach('given token with default and custom scope',
() => givenScopedToken(['DEFAULT', CUSTOM_SCOPE])); () => givenScopedToken(['DEFAULT', CUSTOM_SCOPE]));
beforeEach(logAllServerErrors); beforeEach(() => logAllServerErrors(app));
it('allows invocation of default-scoped method', () => { it('allows invocation of default-scoped method', () => {
return request.get('/users/' + testUser.id) return request.get('/users/' + testUser.id)
@ -116,19 +125,4 @@ describe('Authorization scopes', () => {
return testUser.accessTokens.create({ttl: 60, scopes}) return testUser.accessTokens.create({ttl: 60, scopes})
.then(t => scopedToken = t); .then(t => scopedToken = t);
} }
function logAllServerErrors() {
logServerErrorsOtherThan(-1);
}
function logServerErrorsOtherThan(statusCode) {
app.use((err, req, res, next) => {
if ((err.statusCode || 500) !== statusCode) {
console.log('Unhandled error for request %s %s: %s',
req.method, req.url, err.stack || err);
}
res.statusCode = err.statusCode || 500;
res.json(err);
});
}
}); });

View File

@ -1,33 +1,35 @@
// Copyright IBM Corp. 2015,2016. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var expect = require('./helpers/expect'); const expect = require('./helpers/expect');
var loopback = require('../'); const sinon = require('sinon');
const loopback = require('../');
describe('PersistedModel.createChangeStream()', function() { describe('PersistedModel.createChangeStream()', function() {
describe('configured to source changes locally', function() { describe('configured to source changes locally', function() {
before(function() { before(function() {
var test = this; const test = this;
var app = loopback({localRegistry: true}); const app = loopback({localRegistry: true});
var ds = app.dataSource('ds', {connector: 'memory'}); const ds = app.dataSource('ds', {connector: 'memory'});
var Score = app.registry.createModel('Score'); const Score = app.registry.createModel('Score');
this.Score = app.model(Score, { this.Score = app.model(Score, {
dataSource: 'ds', dataSource: 'ds',
changeDataSource: false, // use only local observers changeDataSource: false, // use only local observers
}); });
}); });
afterEach(verifyObserversRemoval);
it('should detect create', function(done) { it('should detect create', function(done) {
var Score = this.Score; const Score = this.Score;
Score.createChangeStream(function(err, changes) { Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) { changes.on('data', function(change) {
expect(change.type).to.equal('create'); expect(change.type).to.equal('create');
changes.destroy(); changes.destroy();
done(); done();
}); });
@ -36,7 +38,7 @@ describe('PersistedModel.createChangeStream()', function() {
}); });
it('should detect update', function(done) { it('should detect update', function(done) {
var Score = this.Score; const Score = this.Score;
Score.create({team: 'foo'}, function(err, newScore) { Score.create({team: 'foo'}, function(err, newScore) {
Score.createChangeStream(function(err, changes) { Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) { changes.on('data', function(change) {
@ -53,7 +55,7 @@ describe('PersistedModel.createChangeStream()', function() {
}); });
it('should detect delete', function(done) { it('should detect delete', function(done) {
var Score = this.Score; const Score = this.Score;
Score.create({team: 'foo'}, function(err, newScore) { Score.create({team: 'foo'}, function(err, newScore) {
Score.createChangeStream(function(err, changes) { Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) { changes.on('data', function(change) {
@ -67,15 +69,73 @@ describe('PersistedModel.createChangeStream()', function() {
}); });
}); });
}); });
it('should apply "where" and "fields" to create events', function() {
const Score = this.Score;
const data = [
{team: 'baz', player: 'baz', value: 1},
{team: 'bar', player: 'baz', value: 2},
{team: 'foo', player: 'bar', value: 3},
];
const options = {where: {player: 'bar'}, fields: ['team', 'value']};
const changes = [];
let changeStream;
return Score.createChangeStream(options)
.then(stream => {
changeStream = stream;
changeStream.on('data', function(change) {
changes.push(change);
});
return Score.create(data);
})
.then(scores => {
changeStream.destroy();
expect(changes).to.have.length(1);
expect(changes[0]).to.have.property('type', 'create');
expect(changes[0].data).to.eql({
'team': 'foo',
value: 3,
});
});
});
it('should not emit changes after destroy', function(done) {
const Score = this.Score;
const spy = sinon.spy();
Score.createChangeStream(function(err, changes) {
changes.on('data', function() {
spy();
changes.destroy();
});
Score.create({team: 'foo'})
.then(() => Score.deleteAll())
.then(() => {
expect(spy.calledOnce);
done();
});
});
});
function verifyObserversRemoval() {
const Score = this.Score;
expect(Score._observers['after save']).to.be.empty();
expect(Score._observers['after delete']).to.be.empty();
}
}); });
// TODO(ritch) implement multi-server support // TODO(ritch) implement multi-server support
describe.skip('configured to source changes using pubsub', function() { describe.skip('configured to source changes using pubsub', function() {
before(function() { before(function() {
var test = this; const test = this;
var app = loopback({localRegistry: true}); const app = loopback({localRegistry: true});
var db = app.dataSource('ds', {connector: 'memory'}); const db = app.dataSource('ds', {connector: 'memory'});
var ps = app.dataSource('ps', { const ps = app.dataSource('ps', {
host: 'localhost', host: 'localhost',
port: '12345', port: '12345',
connector: 'pubsub', connector: 'pubsub',
@ -88,7 +148,7 @@ describe('PersistedModel.createChangeStream()', function() {
}); });
it('should detect a change', function(done) { it('should detect a change', function(done) {
var Score = this.Score; const Score = this.Score;
Score.createChangeStream(function(err, changes) { Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) { changes.on('data', function(change) {

View File

@ -1,19 +1,19 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
var async = require('async'); const async = require('async');
var expect = require('./helpers/expect'); const expect = require('./helpers/expect');
var loopback = require('../'); const loopback = require('../');
describe('Change', function() { describe('Change', function() {
let Change, TestModel; let Change, TestModel;
beforeEach(function() { beforeEach(function() {
var memory = loopback.createDataSource({ const memory = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
TestModel = loopback.PersistedModel.extend('ChangeTestModel', TestModel = loopback.PersistedModel.extend('ChangeTestModel',
@ -29,7 +29,7 @@ describe('Change', function() {
}); });
beforeEach(function(done) { beforeEach(function(done) {
var test = this; const test = this;
test.data = { test.data = {
foo: 'bar', foo: 'bar',
}; };
@ -52,13 +52,13 @@ describe('Change', function() {
describe('change.id', function() { describe('change.id', function() {
it('should be a hash of the modelName and modelId', function() { it('should be a hash of the modelName and modelId', function() {
var change = new Change({ const change = new Change({
rev: 'abc', rev: 'abc',
modelName: 'foo', modelName: 'foo',
modelId: 'bar', modelId: 'bar',
}); });
var hash = Change.hash([change.modelName, change.modelId].join('-')); const hash = Change.hash([change.modelName, change.modelId].join('-'));
assert.equal(change.id, hash); assert.equal(change.id, hash);
}); });
@ -67,7 +67,7 @@ describe('Change', function() {
describe('Change.rectifyModelChanges(modelName, modelIds, callback)', function() { describe('Change.rectifyModelChanges(modelName, modelIds, callback)', function() {
describe('using an existing untracked model', function() { describe('using an existing untracked model', function() {
beforeEach(function(done) { beforeEach(function(done) {
var test = this; const test = this;
Change.rectifyModelChanges(this.modelName, [this.modelId], function(err, trackedChanges) { Change.rectifyModelChanges(this.modelName, [this.modelId], function(err, trackedChanges) {
if (err) return done(err); if (err) return done(err);
@ -76,7 +76,7 @@ describe('Change', function() {
}); });
it('should create an entry', function(done) { it('should create an entry', function(done) {
var test = this; const test = this;
Change.find(function(err, trackedChanges) { Change.find(function(err, trackedChanges) {
assert.equal(trackedChanges[0].modelId, test.modelId.toString()); assert.equal(trackedChanges[0].modelId, test.modelId.toString());
@ -97,7 +97,7 @@ describe('Change', function() {
describe('Change.rectifyModelChanges - promise variant', function() { describe('Change.rectifyModelChanges - promise variant', function() {
describe('using an existing untracked model', function() { describe('using an existing untracked model', function() {
beforeEach(function(done) { beforeEach(function(done) {
var test = this; const test = this;
Change.rectifyModelChanges(this.modelName, [this.modelId]) Change.rectifyModelChanges(this.modelName, [this.modelId])
.then(function(trackedChanges) { .then(function(trackedChanges) {
done(); done();
@ -106,7 +106,7 @@ describe('Change', function() {
}); });
it('should create an entry', function(done) { it('should create an entry', function(done) {
var test = this; const test = this;
Change.find() Change.find()
.then(function(trackedChanges) { .then(function(trackedChanges) {
assert.equal(trackedChanges[0].modelId, test.modelId.toString()); assert.equal(trackedChanges[0].modelId, test.modelId.toString());
@ -131,7 +131,7 @@ describe('Change', function() {
describe('Change.findOrCreateChange(modelName, modelId, callback)', function() { describe('Change.findOrCreateChange(modelName, modelId, callback)', function() {
describe('when a change doesnt exist', function() { describe('when a change doesnt exist', function() {
beforeEach(function(done) { beforeEach(function(done) {
var test = this; const test = this;
Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) { Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {
if (err) return done(err); if (err) return done(err);
@ -142,7 +142,7 @@ describe('Change', function() {
}); });
it('should create an entry', function(done) { it('should create an entry', function(done) {
var test = this; const test = this;
Change.findById(this.result.id, function(err, change) { Change.findById(this.result.id, function(err, change) {
if (err) return done(err); if (err) return done(err);
@ -155,18 +155,18 @@ describe('Change', function() {
describe('when a change doesnt exist - promise variant', function() { describe('when a change doesnt exist - promise variant', function() {
beforeEach(function(done) { beforeEach(function(done) {
var test = this; const test = this;
Change.findOrCreateChange(this.modelName, this.modelId) Change.findOrCreateChange(this.modelName, this.modelId)
.then(function(result) { .then(function(result) {
test.result = result; test.result = result;
done(); done();
}) })
.catch(done); .catch(done);
}); });
it('should create an entry', function(done) { it('should create an entry', function(done) {
var test = this; const test = this;
Change.findById(this.result.id, function(err, change) { Change.findById(this.result.id, function(err, change) {
if (err) return done(err); if (err) return done(err);
@ -179,7 +179,7 @@ describe('Change', function() {
describe('when a change does exist', function() { describe('when a change does exist', function() {
beforeEach(function(done) { beforeEach(function(done) {
var test = this; const test = this;
Change.create({ Change.create({
modelName: test.modelName, modelName: test.modelName,
modelId: test.modelId, modelId: test.modelId,
@ -191,7 +191,7 @@ describe('Change', function() {
}); });
beforeEach(function(done) { beforeEach(function(done) {
var test = this; const test = this;
Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) { Change.findOrCreateChange(this.modelName, this.modelId, function(err, result) {
if (err) return done(err); if (err) return done(err);
@ -202,7 +202,7 @@ describe('Change', function() {
}); });
it('should find the entry', function(done) { it('should find the entry', function(done) {
var test = this; const test = this;
assert.equal(test.existingChange.id, test.result.id); assert.equal(test.existingChange.id, test.result.id);
done(); done();
@ -211,7 +211,7 @@ describe('Change', function() {
}); });
describe('change.rectify(callback)', function() { describe('change.rectify(callback)', function() {
var change; let change;
beforeEach(function(done) { beforeEach(function(done) {
Change.findOrCreate( Change.findOrCreate(
{ {
@ -222,11 +222,12 @@ describe('Change', function() {
change = ch; change = ch;
done(err); done(err);
}); },
);
}); });
it('should create a new change with the correct revision', function(done) { it('should create a new change with the correct revision', function(done) {
var test = this; const test = this;
change.rectify(function(err, ch) { change.rectify(function(err, ch) {
assert.equal(ch.rev, test.revisionForModel); assert.equal(ch.rev, test.revisionForModel);
@ -237,9 +238,9 @@ describe('Change', function() {
// This test is a low-level equivalent of the test in replication.test.js // This test is a low-level equivalent of the test in replication.test.js
// called "replicates multiple updates within the same CP" // called "replicates multiple updates within the same CP"
it('should merge updates within the same checkpoint', function(done) { it('should merge updates within the same checkpoint', function(done) {
var test = this; const test = this;
var originalRev = this.revisionForModel; const originalRev = this.revisionForModel;
var cp; let cp;
async.series([ async.series([
rectify, rectify,
@ -273,7 +274,7 @@ describe('Change', function() {
} }
function update(next) { function update(next) {
var model = test.model; const model = test.model;
model.name += 'updated'; model.name += 'updated';
model.save(function(err) { model.save(function(err) {
@ -285,9 +286,9 @@ describe('Change', function() {
}); });
it('should not change checkpoint when rev is the same', function(done) { it('should not change checkpoint when rev is the same', function(done) {
var test = this; const test = this;
var originalCheckpoint = change.checkpoint; const originalCheckpoint = change.checkpoint;
var originalRev = change.rev; const originalRev = change.rev;
TestModel.checkpoint(function(err, inst) { TestModel.checkpoint(function(err, inst) {
if (err) return done(err); if (err) return done(err);
@ -305,7 +306,7 @@ describe('Change', function() {
}); });
describe('change.rectify - promise variant', function() { describe('change.rectify - promise variant', function() {
var change; let change;
beforeEach(function(done) { beforeEach(function(done) {
Change.findOrCreateChange(this.modelName, this.modelId) Change.findOrCreateChange(this.modelName, this.modelId)
.then(function(ch) { .then(function(ch) {
@ -317,7 +318,7 @@ describe('Change', function() {
}); });
it('should create a new change with the correct revision', function(done) { it('should create a new change with the correct revision', function(done) {
var test = this; const test = this;
change.rectify() change.rectify()
.then(function(ch) { .then(function(ch) {
assert.equal(ch.rev, test.revisionForModel); assert.equal(ch.rev, test.revisionForModel);
@ -329,8 +330,8 @@ describe('Change', function() {
describe('change.currentRevision(callback)', function() { describe('change.currentRevision(callback)', function() {
it('should get the correct revision', function(done) { it('should get the correct revision', function(done) {
var test = this; const test = this;
var change = new Change({ const change = new Change({
modelName: this.modelName, modelName: this.modelName,
modelId: this.modelId, modelId: this.modelId,
}); });
@ -345,27 +346,27 @@ describe('Change', function() {
describe('change.currentRevision - promise variant', function() { describe('change.currentRevision - promise variant', function() {
it('should get the correct revision', function(done) { it('should get the correct revision', function(done) {
var test = this; const test = this;
var change = new Change({ const change = new Change({
modelName: this.modelName, modelName: this.modelName,
modelId: this.modelId, modelId: this.modelId,
}); });
change.currentRevision() change.currentRevision()
.then(function(rev) { .then(function(rev) {
assert.equal(rev, test.revisionForModel); assert.equal(rev, test.revisionForModel);
done(); done();
}) })
.catch(done); .catch(done);
}); });
}); });
describe('Change.hash(str)', function() { describe('Change.hash(str)', function() {
// todo(ritch) test other hashing algorithms // todo(ritch) test other hashing algorithms
it('should hash the given string', function() { it('should hash the given string', function() {
var str = 'foo'; const str = 'foo';
var hash = Change.hash(str); const hash = Change.hash(str);
assert(hash !== str); assert(hash !== str);
assert(typeof hash === 'string'); assert(typeof hash === 'string');
}); });
@ -373,54 +374,54 @@ describe('Change', function() {
describe('Change.revisionForInst(inst)', function() { describe('Change.revisionForInst(inst)', function() {
it('should return the same revision for the same data', function() { it('should return the same revision for the same data', function() {
var a = { const a = {
b: { b: {
b: ['c', 'd'], b: ['c', 'd'],
c: ['d', 'e'], c: ['d', 'e'],
}, },
}; };
var b = { const b = {
b: { b: {
c: ['d', 'e'], c: ['d', 'e'],
b: ['c', 'd'], b: ['c', 'd'],
}, },
}; };
var aRev = Change.revisionForInst(a); const aRev = Change.revisionForInst(a);
var bRev = Change.revisionForInst(b); const bRev = Change.revisionForInst(b);
assert.equal(aRev, bRev); assert.equal(aRev, bRev);
}); });
}); });
describe('change.type()', function() { describe('change.type()', function() {
it('CREATE', function() { it('CREATE', function() {
var change = new Change({ const change = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
}); });
assert.equal(Change.CREATE, change.type()); assert.equal(Change.CREATE, change.type());
}); });
it('UPDATE', function() { it('UPDATE', function() {
var change = new Change({ const change = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
prev: this.revisionForModel, prev: this.revisionForModel,
}); });
assert.equal(Change.UPDATE, change.type()); assert.equal(Change.UPDATE, change.type());
}); });
it('DELETE', function() { it('DELETE', function() {
var change = new Change({ const change = new Change({
prev: this.revisionForModel, prev: this.revisionForModel,
}); });
assert.equal(Change.DELETE, change.type()); assert.equal(Change.DELETE, change.type());
}); });
it('UNKNOWN', function() { it('UNKNOWN', function() {
var change = new Change(); const change = new Change();
assert.equal(Change.UNKNOWN, change.type()); assert.equal(Change.UNKNOWN, change.type());
}); });
}); });
describe('change.getModelCtor()', function() { describe('change.getModelCtor()', function() {
it('should get the correct model class', function() { it('should get the correct model class', function() {
var change = new Change({ const change = new Change({
modelName: this.modelName, modelName: this.modelName,
}); });
@ -430,11 +431,11 @@ describe('Change', function() {
describe('change.equals(otherChange)', function() { describe('change.equals(otherChange)', function() {
it('should return true when the change is equal', function() { it('should return true when the change is equal', function() {
var change = new Change({ const change = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
}); });
var otherChange = new Change({ const otherChange = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
}); });
@ -442,13 +443,13 @@ describe('Change', function() {
}); });
it('should return true when both changes are deletes', function() { it('should return true when both changes are deletes', function() {
var REV = 'foo'; const REV = 'foo';
var change = new Change({ const change = new Change({
rev: null, rev: null,
prev: REV, prev: REV,
}); });
var otherChange = new Change({ const otherChange = new Change({
rev: undefined, rev: undefined,
prev: REV, prev: REV,
}); });
@ -462,11 +463,11 @@ describe('Change', function() {
describe('change.isBasedOn(otherChange)', function() { describe('change.isBasedOn(otherChange)', function() {
it('should return true when the change is based on the other', function() { it('should return true when the change is based on the other', function() {
var change = new Change({ const change = new Change({
prev: this.revisionForModel, prev: this.revisionForModel,
}); });
var otherChange = new Change({ const otherChange = new Change({
rev: this.revisionForModel, rev: this.revisionForModel,
}); });
@ -484,7 +485,7 @@ describe('Change', function() {
}); });
it('should return delta and conflict lists', function(done) { it('should return delta and conflict lists', function(done) {
var remoteChanges = [ const remoteChanges = [
// an update => should result in a delta // an update => should result in a delta
{rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1}, {rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1},
// no change => should not result in a delta / conflict // no change => should not result in a delta / conflict
@ -504,7 +505,7 @@ describe('Change', function() {
}); });
it('should return delta and conflict lists - promise variant', function(done) { it('should return delta and conflict lists - promise variant', function(done) {
var remoteChanges = [ const remoteChanges = [
// an update => should result in a delta // an update => should result in a delta
{rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1}, {rev: 'foo2', prev: 'foo', modelName: this.modelName, modelId: 9, checkpoint: 1},
// no change => should not result in a delta / conflict // no change => should not result in a delta / conflict
@ -524,7 +525,7 @@ describe('Change', function() {
}); });
it('should set "prev" to local revision in non-conflicting delta', function(done) { it('should set "prev" to local revision in non-conflicting delta', function(done) {
var updateRecord = { const updateRecord = {
rev: 'foo-new', rev: 'foo-new',
prev: 'foo', prev: 'foo',
modelName: this.modelName, modelName: this.modelName,
@ -536,7 +537,7 @@ describe('Change', function() {
expect(diff.conflicts, 'conflicts').to.have.length(0); expect(diff.conflicts, 'conflicts').to.have.length(0);
expect(diff.deltas, 'deltas').to.have.length(1); expect(diff.deltas, 'deltas').to.have.length(1);
var actual = diff.deltas[0].toObject(); const actual = diff.deltas[0].toObject();
delete actual.id; delete actual.id;
expect(actual).to.eql({ expect(actual).to.eql({
checkpoint: 2, checkpoint: 2,
@ -551,7 +552,7 @@ describe('Change', function() {
}); });
it('should set "prev" to local revision in remote-only delta', function(done) { it('should set "prev" to local revision in remote-only delta', function(done) {
var updateRecord = { const updateRecord = {
rev: 'foo-new', rev: 'foo-new',
prev: 'foo-prev', prev: 'foo-prev',
modelName: this.modelName, modelName: this.modelName,
@ -565,7 +566,7 @@ describe('Change', function() {
expect(diff.conflicts, 'conflicts').to.have.length(0); expect(diff.conflicts, 'conflicts').to.have.length(0);
expect(diff.deltas, 'deltas').to.have.length(1); expect(diff.deltas, 'deltas').to.have.length(1);
var actual = diff.deltas[0].toObject(); const actual = diff.deltas[0].toObject();
delete actual.id; delete actual.id;
expect(actual).to.eql({ expect(actual).to.eql({
checkpoint: 2, checkpoint: 2,
@ -580,7 +581,7 @@ describe('Change', function() {
}); });
it('should set "prev" to null for a new instance', function(done) { it('should set "prev" to null for a new instance', function(done) {
var updateRecord = { const updateRecord = {
rev: 'new-rev', rev: 'new-rev',
prev: 'new-prev', prev: 'new-prev',
modelName: this.modelName, modelName: this.modelName,
@ -593,7 +594,7 @@ describe('Change', function() {
expect(diff.conflicts).to.have.length(0); expect(diff.conflicts).to.have.length(0);
expect(diff.deltas).to.have.length(1); expect(diff.deltas).to.have.length(1);
var actual = diff.deltas[0].toObject(); const actual = diff.deltas[0].toObject();
delete actual.id; delete actual.id;
expect(actual).to.eql({ expect(actual).to.eql({
checkpoint: 2, checkpoint: 2,
@ -613,7 +614,7 @@ describe('Change with with custom properties', function() {
let Change, TestModel; let Change, TestModel;
beforeEach(function() { beforeEach(function() {
let memory = loopback.createDataSource({ const memory = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
@ -629,7 +630,7 @@ describe('Change with with custom properties', function() {
this.modelName = TestModel.modelName; this.modelName = TestModel.modelName;
TestModel.prototype.fillCustomChangeProperties = function(change, cb) { TestModel.prototype.fillCustomChangeProperties = function(change, cb) {
var inst = this; const inst = this;
if (inst && inst.tenantId) { if (inst && inst.tenantId) {
change.tenantId = inst.tenantId; change.tenantId = inst.tenantId;

View File

@ -1,19 +1,19 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var async = require('async'); const async = require('async');
var loopback = require('../'); const loopback = require('../');
var expect = require('./helpers/expect'); const expect = require('./helpers/expect');
var Checkpoint = loopback.Checkpoint.extend('TestCheckpoint'); const Checkpoint = loopback.Checkpoint.extend('TestCheckpoint');
describe('Checkpoint', function() { describe('Checkpoint', function() {
describe('bumpLastSeq() and current()', function() { describe('bumpLastSeq() and current()', function() {
beforeEach(function() { beforeEach(function() {
var memory = loopback.createDataSource({ const memory = loopback.createDataSource({
connector: loopback.Memory, connector: loopback.Memory,
}); });
Checkpoint.attachTo(memory); Checkpoint.attachTo(memory);
@ -78,13 +78,13 @@ describe('Checkpoint', function() {
}); });
it('Checkpoint.current() for non existing checkpoint should initialize checkpoint', it('Checkpoint.current() for non existing checkpoint should initialize checkpoint',
function(done) { function(done) {
Checkpoint.current(function(err, seq) { Checkpoint.current(function(err, seq) {
expect(seq).to.equal(1); expect(seq).to.equal(1);
done(err); done(err);
});
}); });
});
it('bumpLastSeq() works when singleton instance does not exists yet', function(done) { it('bumpLastSeq() works when singleton instance does not exists yet', function(done) {
Checkpoint.bumpLastSeq(function(err, cp) { Checkpoint.bumpLastSeq(function(err, cp) {

View File

@ -1,16 +1,16 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2016,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var expect = require('chai').expect; const expect = require('chai').expect;
var loopback = require('..'); const loopback = require('..');
var supertest = require('supertest'); const supertest = require('supertest');
describe('OptionsFromRemotingContext', function() { describe('OptionsFromRemotingContext', function() {
var app, request, accessToken, userId, Product, actualOptions; let app, request, accessToken, userId, Product, actualOptions;
beforeEach(setupAppAndRequest); beforeEach(setupAppAndRequest);
beforeEach(resetActualOptions); beforeEach(resetActualOptions);
@ -133,7 +133,7 @@ describe('OptionsFromRemotingContext', function() {
// despite the fact that under the hood a method on "modelTo" is called. // despite the fact that under the hood a method on "modelTo" is called.
context('hasManyThrough', function() { context('hasManyThrough', function() {
var Category, ThroughModel; let Category, ThroughModel;
beforeEach(givenCategoryHasManyProductsThroughAnotherModel); beforeEach(givenCategoryHasManyProductsThroughAnotherModel);
beforeEach(givenCategoryAndProduct); beforeEach(givenCategoryAndProduct);
@ -215,7 +215,8 @@ describe('OptionsFromRemotingContext', function() {
Category = app.registry.createModel( Category = app.registry.createModel(
'Category', 'Category',
{name: String}, {name: String},
{forceId: false, replaceOnPUT: true}); {forceId: false, replaceOnPUT: true},
);
app.model(Category, {dataSource: 'db'}); app.model(Category, {dataSource: 'db'});
// This is a shortcut for creating CategoryProduct "through" model // This is a shortcut for creating CategoryProduct "through" model
@ -241,7 +242,7 @@ describe('OptionsFromRemotingContext', function() {
}); });
context('hasOne', function() { context('hasOne', function() {
var Category; let Category;
beforeEach(givenCategoryHasOneProduct); beforeEach(givenCategoryHasOneProduct);
beforeEach(givenCategoryId1); beforeEach(givenCategoryId1);
@ -287,7 +288,8 @@ describe('OptionsFromRemotingContext', function() {
Category = app.registry.createModel( Category = app.registry.createModel(
'Category', 'Category',
{name: String}, {name: String},
{forceId: false, replaceOnPUT: true}); {forceId: false, replaceOnPUT: true},
);
app.model(Category, {dataSource: 'db'}); app.model(Category, {dataSource: 'db'});
Category.hasOne(Product); Category.hasOne(Product);
@ -311,7 +313,7 @@ describe('OptionsFromRemotingContext', function() {
}); });
context('belongsTo', function() { context('belongsTo', function() {
var Category; let Category;
beforeEach(givenCategoryBelongsToProduct); beforeEach(givenCategoryBelongsToProduct);
@ -331,7 +333,8 @@ describe('OptionsFromRemotingContext', function() {
Category = app.registry.createModel( Category = app.registry.createModel(
'Category', 'Category',
{name: String}, {name: String},
{forceId: false, replaceOnPUT: true}); {forceId: false, replaceOnPUT: true},
);
app.model(Category, {dataSource: 'db'}); app.model(Category, {dataSource: 'db'});
Category.belongsTo(Product); Category.belongsTo(Product);
@ -361,7 +364,8 @@ describe('OptionsFromRemotingContext', function() {
Product = app.registry.createModel( Product = app.registry.createModel(
'Product', 'Product',
{name: String}, {name: String},
{forceId: false, replaceOnPUT: true}); {forceId: false, replaceOnPUT: true},
);
Product.createOptionsFromRemotingContext = function(ctx) { Product.createOptionsFromRemotingContext = function(ctx) {
return {injectedFrom: 'Product'}; return {injectedFrom: 'Product'};
@ -378,7 +382,7 @@ describe('OptionsFromRemotingContext', function() {
} }
function observeOptionsBeforeSave() { function observeOptionsBeforeSave() {
var Model = arguments[0] || Product; const Model = arguments[0] || Product;
Model.observe('before save', function(ctx, next) { Model.observe('before save', function(ctx, next) {
actualOptions = ctx.options; actualOptions = ctx.options;
next(); next();
@ -386,7 +390,7 @@ describe('OptionsFromRemotingContext', function() {
} }
function observeOptionsBeforeDelete() { function observeOptionsBeforeDelete() {
var Model = arguments[0] || Product; const Model = arguments[0] || Product;
Model.observe('before delete', function(ctx, next) { Model.observe('before delete', function(ctx, next) {
actualOptions = ctx.options; actualOptions = ctx.options;
next(); next();
@ -394,7 +398,7 @@ describe('OptionsFromRemotingContext', function() {
} }
function observeOptionsOnAccess() { function observeOptionsOnAccess() {
var Model = arguments[0] || Product; const Model = arguments[0] || Product;
Model.observe('access', function(ctx, next) { Model.observe('access', function(ctx, next) {
actualOptions = ctx.options; actualOptions = ctx.options;
next(); next();

View File

@ -1,14 +1,14 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var assert = require('assert'); const assert = require('assert');
var loopback = require('../'); const loopback = require('../');
describe('DataSource', function() { describe('DataSource', function() {
var memory; let memory;
beforeEach(function() { beforeEach(function() {
memory = loopback.createDataSource({ memory = loopback.createDataSource({
@ -20,7 +20,7 @@ describe('DataSource', function() {
describe('dataSource.createModel(name, properties, settings)', function() { describe('dataSource.createModel(name, properties, settings)', function() {
it('Define a model and attach it to a `DataSource`', function() { it('Define a model and attach it to a `DataSource`', function() {
var Color = memory.createModel('color', {name: String}); const Color = memory.createModel('color', {name: String});
assert.isFunc(Color, 'find'); assert.isFunc(Color, 'find');
assert.isFunc(Color, 'findById'); assert.isFunc(Color, 'findById');
assert.isFunc(Color, 'findOne'); assert.isFunc(Color, 'findOne');
@ -45,31 +45,31 @@ describe('DataSource', function() {
}); });
it('should honor settings.base', function() { it('should honor settings.base', function() {
var Base = memory.createModel('base'); const Base = memory.createModel('base');
var Color = memory.createModel('color', {name: String}, {base: Base}); const Color = memory.createModel('color', {name: String}, {base: Base});
assert(Color.prototype instanceof Base); assert(Color.prototype instanceof Base);
assert.equal(Color.base, Base); assert.equal(Color.base, Base);
}); });
it('should use loopback.PersistedModel as the base for DBs', function() { it('should use loopback.PersistedModel as the base for DBs', function() {
var Color = memory.createModel('color', {name: String}); const Color = memory.createModel('color', {name: String});
assert(Color.prototype instanceof loopback.PersistedModel); assert(Color.prototype instanceof loopback.PersistedModel);
assert.equal(Color.base, loopback.PersistedModel); assert.equal(Color.base, loopback.PersistedModel);
}); });
it('should use loopback.Model as the base for non DBs', function() { it('should use loopback.Model as the base for non DBs', function() {
// Mock up a non-DB connector // Mock up a non-DB connector
var Connector = function() { const Connector = function() {
}; };
Connector.prototype.getTypes = function() { Connector.prototype.getTypes = function() {
return ['rest']; return ['rest'];
}; };
var ds = loopback.createDataSource({ const ds = loopback.createDataSource({
connector: new Connector(), connector: new Connector(),
}); });
var Color = ds.createModel('color', {name: String}); const Color = ds.createModel('color', {name: String});
assert(Color.prototype instanceof Color.registry.getModel('Model')); assert(Color.prototype instanceof Color.registry.getModel('Model'));
assert.equal(Color.base.modelName, 'PersistedModel'); assert.equal(Color.base.modelName, 'PersistedModel');
}); });
@ -77,7 +77,7 @@ describe('DataSource', function() {
describe.skip('PersistedModel Methods', function() { describe.skip('PersistedModel Methods', function() {
it('List the enabled and disabled methods', function() { it('List the enabled and disabled methods', function() {
var TestModel = loopback.PersistedModel.extend('TestPersistedModel'); const TestModel = loopback.PersistedModel.extend('TestPersistedModel');
TestModel.attachTo(loopback.memory()); TestModel.attachTo(loopback.memory());
// assert the defaults // assert the defaults
@ -109,9 +109,9 @@ describe('DataSource', function() {
existsAndShared('reload', false); existsAndShared('reload', false);
function existsAndShared(Model, name, isRemoteEnabled, isProto) { function existsAndShared(Model, name, isRemoteEnabled, isProto) {
var scope = isProto ? Model.prototype : Model; const scope = isProto ? Model.prototype : Model;
var fn = scope[name]; const fn = scope[name];
var actuallyEnabled = Model.getRemoteMethod(name); const actuallyEnabled = Model.getRemoteMethod(name);
assert(fn, name + ' should be defined!'); assert(fn, name + ' should be defined!');
assert(actuallyEnabled === isRemoteEnabled, assert(actuallyEnabled === isRemoteEnabled,
name + ' ' + (isRemoteEnabled ? 'should' : 'should not') + name + ' ' + (isRemoteEnabled ? 'should' : 'should not') +
@ -121,7 +121,7 @@ describe('DataSource', function() {
}); });
}); });
var assertValidDataSource = function(dataSource) { function assertValidDataSource(dataSource) {
// has methods // has methods
assert.isFunc(dataSource, 'createModel'); assert.isFunc(dataSource, 'createModel');
assert.isFunc(dataSource, 'discoverModelDefinitions'); assert.isFunc(dataSource, 'discoverModelDefinitions');
@ -130,7 +130,7 @@ var assertValidDataSource = function(dataSource) {
assert.isFunc(dataSource, 'disableRemote'); assert.isFunc(dataSource, 'disableRemote');
assert.isFunc(dataSource, 'defineOperation'); assert.isFunc(dataSource, 'defineOperation');
assert.isFunc(dataSource, 'operations'); assert.isFunc(dataSource, 'operations');
}; }
assert.isFunc = function(obj, name) { assert.isFunc = function(obj, name) {
assert(obj, 'cannot assert function ' + name + ' on object that doesnt exist'); assert(obj, 'cannot assert function ' + name + ' on object that doesnt exist');

View File

@ -1,19 +1,19 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var path = require('path'); const path = require('path');
var loopback = require('../../'); const loopback = require('../../');
var models = require('../fixtures/e2e/models'); const models = require('../fixtures/e2e/models');
var TestModel = models.TestModel; const TestModel = models.TestModel;
var assert = require('assert'); const assert = require('assert');
describe('RemoteConnector', function() { describe('RemoteConnector', function() {
before(function() { before(function() {
// setup the remote connector // setup the remote connector
var ds = loopback.createDataSource({ const ds = loopback.createDataSource({
url: 'http://127.0.0.1:3000/api', url: 'http://127.0.0.1:3000/api',
connector: loopback.Remote, connector: loopback.Remote,
}); });
@ -33,7 +33,7 @@ describe('RemoteConnector', function() {
}); });
it('should be able to call save', function(done) { it('should be able to call save', function(done) {
var m = new TestModel({ const m = new TestModel({
foo: 'bar', foo: 'bar',
}); });
m.save(function(err, data) { m.save(function(err, data) {

View File

@ -1,32 +1,32 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved. // Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var path = require('path'); const path = require('path');
var loopback = require('../../'); const loopback = require('../../');
var models = require('../fixtures/e2e/models'); const models = require('../fixtures/e2e/models');
var TestModel = models.TestModel; const TestModel = models.TestModel;
var LocalTestModel = TestModel.extend('LocalTestModel', {}, { const LocalTestModel = TestModel.extend('LocalTestModel', {}, {
trackChanges: true, trackChanges: true,
}); });
var assert = require('assert'); const assert = require('assert');
describe('Replication', function() { describe('Replication', function() {
before(function() { before(function() {
// setup the remote connector // setup the remote connector
var ds = loopback.createDataSource({ const ds = loopback.createDataSource({
url: 'http://127.0.0.1:3000/api', url: 'http://127.0.0.1:3000/api',
connector: loopback.Remote, connector: loopback.Remote,
}); });
TestModel.attachTo(ds); TestModel.attachTo(ds);
var memory = loopback.memory(); const memory = loopback.memory();
LocalTestModel.attachTo(memory); LocalTestModel.attachTo(memory);
}); });
it('should replicate local data to the remote', function(done) { it('should replicate local data to the remote', function(done) {
var RANDOM = Math.random(); const RANDOM = Math.random();
LocalTestModel.create({ LocalTestModel.create({
n: RANDOM, n: RANDOM,

View File

@ -1,38 +1,38 @@
// Copyright IBM Corp. 2013,2016. All Rights Reserved. // Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../'); const loopback = require('../');
var MyEmail; let MyEmail;
var assert = require('assert'); const assert = require('assert');
var MailConnector = require('../lib/connectors/mail'); const MailConnector = require('../lib/connectors/mail');
describe('Email connector', function() { describe('Email connector', function() {
it('should set up SMTP', function() { it('should set up SMTP', function() {
var connector = new MailConnector({transports: [ const connector = new MailConnector({transports: [
{type: 'smtp', service: 'gmail'}, {type: 'smtp', service: 'gmail'},
]}); ]});
assert(connector.transportForName('smtp')); assert(connector.transportForName('smtp'));
}); });
it('should set up DIRECT', function() { it('should set up DIRECT', function() {
var connector = new MailConnector({transports: [ const connector = new MailConnector({transports: [
{type: 'direct', name: 'localhost'}, {type: 'direct', name: 'localhost'},
]}); ]});
assert(connector.transportForName('direct')); assert(connector.transportForName('direct'));
}); });
it('should set up STUB', function() { it('should set up STUB', function() {
var connector = new MailConnector({transports: [ const connector = new MailConnector({transports: [
{type: 'stub', service: 'gmail'}, {type: 'stub', service: 'gmail'},
]}); ]});
assert(connector.transportForName('stub')); assert(connector.transportForName('stub'));
}); });
it('should set up a single transport for SMTP', function() { it('should set up a single transport for SMTP', function() {
var connector = new MailConnector({transport: const connector = new MailConnector({transport:
{type: 'smtp', service: 'gmail'}, {type: 'smtp', service: 'gmail'},
}); });
@ -40,7 +40,7 @@ describe('Email connector', function() {
}); });
it('should set up a aliased transport for SMTP', function() { it('should set up a aliased transport for SMTP', function() {
var connector = new MailConnector({transport: const connector = new MailConnector({transport:
{type: 'smtp', service: 'ses-us-east-1', alias: 'ses-smtp'}, {type: 'smtp', service: 'ses-us-east-1', alias: 'ses-smtp'},
}); });
@ -51,7 +51,7 @@ describe('Email connector', function() {
describe('Email and SMTP', function() { describe('Email and SMTP', function() {
beforeEach(function() { beforeEach(function() {
MyEmail = loopback.Email.extend('my-email'); MyEmail = loopback.Email.extend('my-email');
var ds = loopback.createDataSource('email', { const ds = loopback.createDataSource('email', {
connector: loopback.Mail, connector: loopback.Mail,
transports: [{type: 'STUB'}], transports: [{type: 'STUB'}],
}); });
@ -65,7 +65,7 @@ describe('Email and SMTP', function() {
describe('MyEmail', function() { describe('MyEmail', function() {
it('MyEmail.send(options, callback)', function(done) { it('MyEmail.send(options, callback)', function(done) {
var options = { const options = {
to: 'to@to.com', to: 'to@to.com',
from: 'from@from.com', from: 'from@from.com',
subject: 'subject', subject: 'subject',
@ -84,7 +84,7 @@ describe('Email and SMTP', function() {
}); });
it('myEmail.send(callback)', function(done) { it('myEmail.send(callback)', function(done) {
var message = new MyEmail({ const message = new MyEmail({
to: 'to@to.com', to: 'to@to.com',
from: 'from@from.com', from: 'from@from.com',
subject: 'subject', subject: 'subject',

View File

@ -1,14 +1,14 @@
// Copyright IBM Corp. 2015,2016. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../'); const loopback = require('../');
var app; let app;
var assert = require('assert'); const assert = require('assert');
var request = require('supertest'); const request = require('supertest');
var expect = require('./helpers/expect'); const expect = require('./helpers/expect');
describe('loopback.errorHandler(options)', function() { describe('loopback.errorHandler(options)', function() {
it('should throw a descriptive error', function() { it('should throw a descriptive error', function() {

View File

@ -1,20 +1,20 @@
// Copyright IBM Corp. 2015,2016. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../../../..'); const loopback = require('../../../..');
var boot = require('loopback-boot'); const boot = require('loopback-boot');
var app = module.exports = loopback({ const app = module.exports = loopback({
localRegistry: true, localRegistry: true,
loadBuiltinModels: true, loadBuiltinModels: true,
}); });
var errorHandler = require('strong-error-handler'); const errorHandler = require('strong-error-handler');
boot(app, __dirname); boot(app, __dirname);
var apiPath = '/api'; const apiPath = '/api';
app.use(loopback.token({model: app.models.accessToken})); app.use(loopback.token({model: app.models.accessToken}));
app.use(apiPath, loopback.rest()); app.use(apiPath, loopback.rest());

View File

@ -1,11 +1,11 @@
// Copyright IBM Corp. 2015,2016. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../../../../index'); const loopback = require('../../../../index');
var PersistedModel = loopback.PersistedModel; const PersistedModel = loopback.PersistedModel;
exports.TestModel = PersistedModel.extend('TestModel', {}, { exports.TestModel = PersistedModel.extend('TestModel', {}, {
trackChanges: true, trackChanges: true,

View File

@ -1,15 +1,15 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var loopback = require('../../../../index'); const loopback = require('../../../../index');
var app = module.exports = loopback({localRegistry: true}); const app = module.exports = loopback({localRegistry: true});
var models = require('./models'); const models = require('./models');
var TestModel = models.TestModel; const TestModel = models.TestModel;
var apiPath = '/api'; const apiPath = '/api';
app.use(apiPath, loopback.rest()); app.use(apiPath, loopback.rest());
TestModel.attachTo(loopback.memory()); TestModel.attachTo(loopback.memory());

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var boot = require('loopback-boot'); const boot = require('loopback-boot');
var loopback = require('../../../../../index'); const loopback = require('../../../../../index');
var app = module.exports = loopback(); const app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var boot = require('loopback-boot'); const boot = require('loopback-boot');
var loopback = require('../../../../../index'); const loopback = require('../../../../../index');
var app = module.exports = loopback(); const app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var boot = require('loopback-boot'); const boot = require('loopback-boot');
var loopback = require('../../../../../index'); const loopback = require('../../../../../index');
var app = module.exports = loopback(); const app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

View File

@ -1,4 +1,4 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT

View File

@ -1,12 +1,12 @@
// Copyright IBM Corp. 2015. All Rights Reserved. // Copyright IBM Corp. 2015,2019. All Rights Reserved.
// Node module: loopback // Node module: loopback
// This file is licensed under the MIT License. // This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
var boot = require('loopback-boot'); const boot = require('loopback-boot');
var loopback = require('../../../../../index'); const loopback = require('../../../../../index');
var app = module.exports = loopback(); const app = module.exports = loopback();
boot(app, __dirname); boot(app, __dirname);
app.use(loopback.rest()); app.use(loopback.rest());

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