Compare commits

...

753 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
Miroslav Bajtoš 6a4bd6d09f
3.7.0
* Remote method /user/:id/verify (ebarault)
 * Implement more secure password flow (Miroslav Bajtoš)
 * Add User.setPassword(id, new, cb) (Miroslav Bajtoš)
 * Fix method setup in authorization-scopes.test (Miroslav Bajtoš)
 * Add missing tests for reset password flow (Miroslav Bajtoš)
 * forwarding context options in user.verify (ebarault)
 * update deprecated dependencies (Diana Lau)
 * Add support for scoped access tokens (Miroslav Bajtoš)
 * Fix user-literal rewrite for anonymous requests (Aaron Buchanan)
2017-04-27 13:19:17 +02:00
Eric Barault d1719fd9a8 Merge pull request #3314 from strongloop/feature/enable-email-verification-replay
Provide a solution to finish the user's registration when the user identity verification message is lost or has expired
2017-04-26 20:01:38 +02:00
ebarault b9fbf51b27 Remote method /user/:id/verify
This commit adds:
- user.prototype.verify(verifyOptions, options, cb)
- remote method /user/:id/verify
- User.getVerifyOptions()

The remote method can be used to replay the sending of a user
identity/email verification message.

`getVerifyOptions()` can be fully customized programmatically
or partially customized using user model's `.settings.verifyOptions`

`getVerifyOptions()` is called under the hood when calling the
/user/:id/verify remote method

`getVerifyOptions()` can also be used to ease the building
of identity verifyOptions:

```js
var verifyOptions = {
  type: 'email',
  from: 'noreply@example.com'
  template: 'verify.ejs',
  redirect: '/',
  generateVerificationToken: function (user, options, cb) {
    cb('random-token');
  }
};

user.verify(verifyOptions);
```

NOTE: the `User.login()` has been modified to return the userId when
failing due to unverified identity/email. This userId can then be used
to call the /user/:id/verify remote method.
2017-04-26 19:05:41 +02:00
Miroslav Bajtoš b96605c63a Merge pull request #3350 from strongloop/feature/set-password-with-token
Set password with token, disable password changes via patch/replace
2017-04-20 10:47:04 +02:00
Miroslav Bajtoš c5ca2e1c2e
Implement more secure password flow
Improve the flow for setting/changing/resetting User password to make
it more secure.

 1. Modify `User.resetPassword` to create a token scoped to allow
    invocation of a single remote method: `User.setPassword`.

 2. Scope the method `User.setPassword` so that regular tokens created
    by `User.login` are not allowed to execute it.

For backwards compatibility, this new mode (flow) is enabled only
when User model setting `restrictResetPasswordTokenScope` is set to
`true`.

 3. Changing the password via `User.prototype.patchAttributes`
    (and similar DAO methods) is no longer allowed. Applications
    must call `User.changePassword` and ask the user to provide
    the current (old) password.

For backwards compatibility, this new mode (flow) is enabled only
when User model setting `rejectPasswordChangesViaPatchOrReplace` is set
to `true`.
2017-04-20 10:22:21 +02:00
Miroslav Bajtoš e27419086c
Add User.setPassword(id, new, cb)
Implement a new method for changing user password with password-reset
token but without the old password.

REST API

    POST /api/users/reset-password
    Authorization: your-password-reset-token-id
    Content-Type: application/json

    {"newPassword": "new-pass"}

JavaScript API

    User.setPassword(userId, newPassword[, cb])
    userInstance.setPassword(newPassword[, cb])

Note: the new REST endpoint is not protected by scopes yet, therefore
any valid access token can invoke it (similarly to how any valid access
token can change the password via PATCH /api/users/:id).
2017-04-20 10:18:49 +02:00
Miroslav Bajtoš d95ec66a23
Fix method setup in authorization-scopes.test
Fix the code builing a scoped method to correctly handle the case
when the setup method is called twice and the previously defined
method has to be overriden with new remoting metadata.
2017-04-18 13:01:15 +02:00
Miroslav Bajtoš 9c63abef52
Add missing tests for reset password flow 2017-04-18 13:01:14 +02:00
Miroslav Bajtoš faa4975b78 Merge pull request #3344 from strongloop/maintenance/passing-context-options-in-user.verify
forward context options in user.verify
2017-04-12 09:13:20 +02:00
ebarault 912aad8b35 forwarding context options in user.verify
change original "options" argument to "verifyOptions"
adds ctx options argument as "options"
forward context "options" down to relevant downstream functions
review verifyOptions assertions
code cleaning
tests code cleaning
2017-04-11 17:54:45 +02:00
Diana Lau 31e22d3b3f Merge pull request #3345 from strongloop/3.x/update-dependencies
update deprecated dependencies
2017-04-10 15:10:26 +00:00
Diana Lau 52d3faed80 update deprecated dependencies 2017-04-07 15:09:31 -04:00
Miroslav Bajtoš 95240598b3 Merge pull request #3313 from strongloop/feature/access-token-scopes
Add support for scoped access tokens
2017-04-07 13:28:32 +02:00
Miroslav Bajtoš c5145bdf34
Add support for scoped access tokens
Define a new property `AccessToken.scopes` to contain the list of
scopes granted to this access token.

Define a new remote method metadata `accessScopes` to contain a list
of scope name required by this method.

Define a special built-in scope name "DEFAULT" that's used when
a method/token does not provide any scopes. This allows access
tokens to grant access to both the default scope and any additional
custom scopes at the same time.

Modify the authorization algorithm to ensure that at least one
of the scopes required by a remote method is allowed by the scopes
granted to the requesting access token.

The "DEFAULT" scope preserve backwards compatibility because existing
remote methods with no `accessScopes` can be accessed by (existing)
access tokens with no `scopes` defined.

Impact on existing applications:

 - Database schema must be updated after upgrading the loopback version

 - If the application was already using a custom `AccessToken.scopes`
   property with a type different from an array, then the relevant code
   must be updated to work with the new type "array of strings".
2017-04-07 13:04:40 +02:00
Miroslav Bajtoš 9fef5284c7 Merge pull request #3292 from aaronbuchanan/fix/unauthorized-current-user-literal
Fix user-literal rewrite for anonymous requests
2017-04-04 18:52:08 +02:00
Aaron Buchanan fbf818b2dc
Fix user-literal rewrite for anonymous requests
Currently any `currentUserLiteral` routes when accessed with a bad
token throw a 500 due to a SQL error that is raised because
`Model.findById` is invoked with `id={currentUserLiteral}`
(`id=me` in our case) when the url rewrite fails.

This commit changes the token middleware to return 401 Not Authorized
when the client is requesting a currentUserLiteral route without
a valid access token.
2017-04-04 16:30:08 +02:00
Miroslav Bajtoš 0499d09d6b
3.6.0
* Add new event "remoteMethodAdded" (Flavien DAVID)
 * Forward options in prepareForTokenInvalidation (Miroslav Bajtoš)
 * Check max password length in User.changePassword (Miroslav Bajtoš)
 * Add User.changePassword(id, old, new, cb) (Miroslav Bajtoš)
 * Propagate authorized roles in remoting context (ebarault)
 * Run the latest Node.js 7 version on Travis again (Miroslav Bajtoš)
 * Lock down Travis CI Node 7 version to 7.7.1 (Miroslav Bajtoš)
 * README: add a link to our announcements list (Miroslav Bajtoš)
 * Allow custom properties of Change Model (agriwebb build)
 * Fix User.verify to convert uid to string (phairow)
 * Pass options.verificationToken to templateFn (Hiran del Castillo)
 * fix custom token model in token middleware (ebarault)
 * Update runtime dependencies (Miroslav Bajtoš)
 * Verify User and AccessToken relations at startup (Miroslav Bajtoš)
 * Deep-clone model settings in lib/builtin-models (Miroslav Bajtoš)
 * Use local registry in test/replication.rest.test (Miroslav Bajtoš)
 * Fix test/access-token.test to use local registry (Miroslav Bajtoš)
 * Fix context passing in OWNER role resolver (Benjamin Schuster-Boeckler)
2017-03-31 16:09:09 +02:00
Miroslav Bajtoš 75fdd3cf32 Merge pull request #3322 from DAVIDFlavien/fix/nestRemotingRoutes
Add new event "remoteMethodAdded"
2017-03-31 16:02:32 +02:00
Flavien DAVID 981bd309fc
Add new event "remoteMethodAdded"
Emit a new method "remoteMethodAdded" whenever a new method is added,
typically when `Model.remoteMethod` or `Model.nestRemoting` is called.

The method is emitted both by the Model affected and the application
object.
2017-03-31 15:32:19 +02:00
Miroslav Bajtoš 53182affef Merge pull request #3316 from strongloop/fix/options-in-token-invalidations-master
Forward options in prepareForTokenInvalidation
2017-03-28 16:17:39 +02:00
Miroslav Bajtoš 94f267884f
Forward options in prepareForTokenInvalidation 2017-03-28 15:59:20 +02:00
Miroslav Bajtoš f736da64f4 Merge pull request #3312 from strongloop/fix/change-password-validation
Check max password length in User.changePassword
2017-03-28 15:59:07 +02:00
Miroslav Bajtoš b550cdcf43
Check max password length in User.changePassword 2017-03-28 15:46:01 +02:00
Miroslav Bajtoš 048110ee01 Merge pull request #3299 from strongloop/feature/change-password-api
Add User.changePassword(id, old, new, cb)
2017-03-24 12:05:41 +01:00
Miroslav Bajtoš 27ed712528
Add User.changePassword(id, old, new, cb)
Implement a new method for changing user passwords the secure way.
The method requires the old password to be provided before a new
password can be used.

REST API:

    POST /api/users/change-password
    Authorization: your-token-id
    Content-Type: application/json

    {"oldPassword":"old-pass", "newPassword": "new-pass"}

JavaScript API:

    User.changePassword(userId, oldPassword, newPassword[, cb])

There is also an instance-level (prototype) method that can be used
from JavaScript:

    userInstance.changePassword(oldPassword, newPassword[, cb])
2017-03-24 11:01:04 +01:00
Miroslav Bajtoš cb7e2114ec Merge pull request #2957 from ebarault/propagate-resolved-roles-in-context
propagate resolved roles in remoting context
2017-03-20 13:34:12 +01:00
ebarault 8aa98a80ef Propagate authorized roles in remoting context
Adds an authorizedRoles object to remotingContext.args.options
which contains all the roles (static and dynamic) that are
granted to the user when performing a request through
strong-remoting to an app with authentication enabled.

The authorizedRoles object for example looks like:
{
  $everyone: true,
  $authenticated: true,
  myRole: true
}

NOTE: this pr also covers a number of jsdoc fixes as well
as refactoring in ACL.js and access-context.js
2017-03-20 12:29:33 +01:00
Miroslav Bajtoš 960e118c4e Merge pull request #3285 from strongloop/fix/travis-config
Run the latest Node.js 7 version on Travis again
2017-03-15 16:58:25 +01:00
Miroslav Bajtoš 89c9f5bb04
Run the latest Node.js 7 version on Travis again 2017-03-15 10:22:12 +01:00
Miroslav Bajtoš 5da8075fc9 Merge pull request #3261 from strongloop/add/ann-list
README: add a link to our announcements list
2017-03-13 09:27:38 +01:00
Miroslav Bajtoš 3a93167685 Merge pull request #3262 from strongloop/fix/ci-on-node-7.7.2
Fix CI on Node 7.7.2
2017-03-09 13:32:42 +01:00
Miroslav Bajtoš 6467658fdc
Lock down Travis CI Node 7 version to 7.7.1 2017-03-09 13:17:43 +01:00
Miroslav Bajtoš 0448184a6c Merge pull request #2959 from kobaska/add-multi-tenancy
Support multi-tenancy in change replication - allow apps to extend Change model with extra properties
2017-03-09 12:08:16 +01:00
Miroslav Bajtoš 55adb6c50e
README: add a link to our announcements list 2017-03-09 09:23:11 +01:00
agriwebb build 3f4b5ece71
Allow custom properties of Change Model
Allow custom properties to be added to Change Model,
and make change filter customizable through mixins
to allow to add the custom property to the filter
used to look up relevant changes during change replication.
2017-03-09 08:58:42 +01:00
Miroslav Bajtoš 011dc1f6b8 Merge pull request #3253 from phairow/patch-1
Fix User.verify to convert ObjectID uid to string
2017-03-08 16:26:36 +01:00
phairow 0969493ab7
Fix User.verify to convert uid to string
Applications using MongoDB connectors typically have `user.id`
property of type ObjectID.

This commit fixes the code building the verification URL to
correctly convert the user id value into string.
2017-03-08 16:03:53 +01:00
Miroslav Bajtoš 48bf16bd5a Merge pull request #3245 from strongloop/fix/token-middleware-custom-model
fix custom token model in token middleware
2017-03-06 18:10:41 +01:00
Miroslav Bajtoš b5a8564956 Merge pull request #3249 from islamixo/feature-verificationToken
Pass options.verificationToken to templateFn
2017-03-06 18:05:44 +01:00
Hiran del Castillo a22b1e13f1
Pass options.verificationToken to templateFn
Enhance User.prototype.verify to pass the generated token
to the templating function in addition to other existing properties.

This allows application to build multiple URLs in the email template,
for example in order to provide a different URL for desktop and
mobile browsers.
2017-03-06 17:08:35 +01:00
ebarault cf98d379c4 fix custom token model in token middleware
Fixing server/middleware/token.js to handle correctly the
setup of a custom AccessToken model by name in either
middleware.json or using any of :
	app.use(loopback.token({...}));
	app.middlewareFromConfig(loopback.token, {...})
	app.middleware('auth', loopback.token({...})
2017-03-06 16:10:25 +01:00
Miroslav Bajtoš 01ce9b5f5a Merge pull request #3247 from strongloop/update-deps
Update runtime dependencies
2017-03-06 11:19:21 +01:00
Miroslav Bajtoš 5ebc9b6a2e Merge pull request #3230 from strongloop/fix/context-passing-for-isOwner
Fix context passing in OWNER role resolver
2017-03-03 16:19:52 +01:00
Miroslav Bajtoš 8510982bea
Update runtime dependencies
- isemail@2
 - loopback-phase@3

Skip update of nodemailer because it changed the license to GPL-like one
2017-03-03 15:53:49 +01:00
Miroslav Bajtoš ed4f56c7f8 Merge pull request #3231 from strongloop/warn-on-misconfigured-accessToken-user
Warn on misconfigured access token user
2017-03-03 10:57:20 +01:00
Miroslav Bajtoš 79f441b9c4
Verify User and AccessToken relations at startup
Modify `app.enableAuth()` to verify that (custom) User and AccessToken
models have correctly configured their hasMany/belongsTo relations
and print a warning otherwise.
2017-03-03 10:18:58 +01:00
Miroslav Bajtoš f0c9700e1d
Deep-clone model settings in lib/builtin-models
Fix the code loading builtin models to always clone the JSON object
used as model settings/definition. This is needed to allow applications
to modify model settings while not affecting settings of new models
created in the local registry of another app.
2017-02-24 15:13:06 +01:00
Miroslav Bajtoš c45954cdaa
Use local registry in test/replication.rest.test 2017-02-24 15:13:06 +01:00
Miroslav Bajtoš abf8246382
Fix test/access-token.test to use local registry 2017-02-24 14:17:12 +01:00
Benjamin Schuster-Boeckler 4570626e9d
Fix context passing in OWNER role resolver 2017-02-24 12:28:30 +01:00
Miroslav Bajtoš 2570dda984
3.4.0
* Fix access-token invalidation for missing relation (Miroslav Bajtoš)
 * Configure Travis CI to cache phantomjs binaries (Miroslav Bajtoš)
 * Optimise replication (kobaska)
 * Improve "filter" arg description (Raymond Camden)
2017-02-24 09:36:42 +01:00
Miroslav Bajtoš ec9274c5aa Merge pull request #3227 from strongloop/fix/invalidate-tokens
Fix access-token invalidation for missing relation
2017-02-24 09:20:17 +01:00
Miroslav Bajtoš 2cac589860
Fix access-token invalidation for missing relation
Fix the code invalidating access tokens on user email/password changes
to correctly handle the case when the relation
"AccessToken belongs to (subclassed) user" is not configured.
2017-02-23 12:56:13 +01:00
Miroslav Bajtoš 4796b7daf9 Merge pull request #3221 from strongloop/ci/cache-phantomjs-on-travis
Configure Travis CI to cache phantomjs binaries
2017-02-22 15:36:26 +01:00
Miroslav Bajtoš 37b49194f2 Merge pull request #2961 from kobaska/optimise-replication
Optimize replication by sending multiple smaller requests to the server
2017-02-22 15:33:30 +01:00
Miroslav Bajtoš f871358688
Configure Travis CI to cache phantomjs binaries
This should speed up our CI builds and also save a lot of bandwidth
for people providing phantomjs-prebuilt module.

See also
https://www.npmjs.com/package/phantomjs-prebuilt#continuous-integration
2017-02-22 15:19:55 +01:00
kobaska 7078c5d0e5
Optimise replication
Add a new model-level setting "replicationChunkSize" which allows
users to configure change replication algorithm to issue several
smaller requests to fetch changes and upload updates.
2017-02-22 15:12:54 +01:00
Miroslav Bajtoš 92317e811a Merge pull request #3218 from strongloop/fix/filter-desc
Improve "filter" arg description
2017-02-21 15:00:28 +01:00
Raymond Camden 440b9a52a6
Improve "filter" arg description
Add an example showing how to serialize object values as JSON.
2017-02-21 14:00:09 +01:00
Miroslav Bajtoš c69e010670 3.3.0
* Fix Role.isOwner() for multiple user models (ebarault)
 * Update ISSUE_TEMPLATE.md (Simon Ho)
 * Upgrade supertest to 3.x (Miroslav Bajtoš)
 * Fix creation of verification links (Miroslav Bajtoš)
 * Enable multiple user models (Eric)
 * Babelify juggler for Karma tests (Miroslav Bajtoš)
 * Fix Karma config to babelify node_modules too (Miroslav Bajtoš)
 * Add promise support to built-in model RoleMapping (ebarault)
 * Add promise support to built-in model ACL (ebarault)
 * Add nyc coverage, report data to coveralls.io (Miroslav Bajtoš)
 * Upgrade eslint config, fix linter errors (Miroslav Bajtoš)
 * Add missing type to Role properties definition (David Hernandez)
 * Preserve sessions on User.save() making no changes (Miroslav Bajtoš)
 * Fix logout to handle no or missing accessToken (Ritchie Martori)
 * Promise-ify built-in Role model (Miroslav Bajtoš)
 * Remove .jscsrc that's no longer used (Miroslav Bajtoš)
 * Enable ES6/ES2015 goodness (Miroslav Bajtoš)
 * Remove test/support.js from karma config (Miroslav Bajtoš)
 * Use English when running Mocha tests (Miroslav Bajtoš)
 * Update ISSUE_TEMPLATE (Simon Ho)
 * Updating README - add cli and remove arc (Joe Sepi)
 * Fix User methods to use correct Primary Key (Aris Kemper)
 * Fix User.resetPassword to call createAccessToken() (João Ribeiro)
 * Role model: resolves related models by name (Benjamin Kroeger)
2017-02-17 15:14:55 +01:00
Miroslav Bajtoš c8271491af Merge pull request #3180 from ebarault/enable-multiple-user-models
Fix Role.isOwner() for multiple user models
2017-02-17 12:49:29 +01:00
ebarault f0e70dd8a9 Fix Role.isOwner() for multiple user models
Fix `Role.isOwner()` to check both principalId and principalType.
This fixes a bug where users from different User model were treated
as owners as long as their user id was the same as owner's id.
2017-02-17 11:19:07 +01:00
Simon Ho 0dbfe8970f Update ISSUE_TEMPLATE.md 2017-02-14 10:17:43 -08:00
Miroslav Bajtoš 88c38e7ff8 Merge pull request #3196 from strongloop/upgrade-supertest
Upgrade supertest to 3.x
2017-02-09 16:37:30 +01:00
Miroslav Bajtoš a80f844e88 Upgrade supertest to 3.x
Also fix tests relying on internal supertest API to keep working with
then new supertest internals after the upgrade.
2017-02-09 16:17:10 +01:00
Miroslav Bajtoš d80957819a Merge pull request #3185 from strongloop/fix/hash-path-in-redirect
Fix creation of verification links
2017-02-09 13:30:58 +01:00
Miroslav Bajtoš ea05a2c4dc Fix creation of verification links
Fix User.prototype.verify to call `querystring.stringify` instead
of concatenating query-string components directly.

In particular, this fixes the bug where `options.redirect` containing
a hash fragment like `#/home?arg1=value1&arg2=value2` produced incorrect
URL, because the `redirect` value was not correctly encoded.
2017-02-06 12:39:21 +01:00
Miroslav Bajtoš 304ecc4784 Merge pull request #2971 from ebarault/enable-multiple-user-models
Enable multiple user models inheriting from base class User
2017-02-02 10:03:01 +01:00
Eric 9fe084fffd Enable multiple user models
Allow LoopBack applications to configure multiple User models and share
the same AccessToken model.

To enable this feature:

1) In your custom AccessToken model:

 - add a new property "principalType" of type "string".
 - configure the relation "belongsTo user" as polymorphic,
   using "principalType" as the discriminator

2) In your User models:

 - Configure the "hasMany accessTokens" relation as polymorphic,
   using "principalType" as the discriminator

When creating custom Role and Principal instances, set your
User model's name as the value of "prinicipalType".
2017-02-02 09:42:30 +01:00
Miroslav Bajtoš 798ebfba81 Merge pull request #3174 from strongloop/fix/juggler-in-karma
Babelify juggler for Karma tests
2017-02-01 14:22:08 +01:00
Miroslav Bajtoš 5b404cad6c Babelify juggler for Karma tests
Fix configuration of Karma:

 - Disable ES6 modules. The ES6 module transpiler is adding
  "use strict" to all source files, this breaks e.g. chai or juggler
 - Relax "ignore" setting to exclude only strong-task-emitter,
   thus bring back Babel transpilation for chai and juggler.
2017-02-01 14:00:43 +01:00
Miroslav Bajtoš db3c7b6dbd Merge pull request #3173 from strongloop/fix/strong-remoting-in-karma
Fix Karma config to babelify node_modules too
2017-02-01 12:46:18 +01:00
Miroslav Bajtoš 3c209ee1de Fix Karma config to babelify node_modules too
Before this change, dependencies in node_modules (e.g. strong-remoting)
were not transformed to ES5 and thus crashed the tests in PhantomJS.

Note that loopback-datasource-juggler cannot be babelified to ES5
because it does not correctly support strict mode yet.
2017-02-01 12:27:42 +01:00
Miroslav Bajtoš 76dd35e1d8 Merge pull request #3169 from ebarault/feature/promisify-role-mapping
Add promise support to built-in model RoleMapping
2017-02-01 10:29:50 +01:00
Miroslav Bajtoš e19c0dd974 Merge pull request #3165 from strongloop/enable/coveralls
Add nyc coverage, report data to coveralls.io
2017-02-01 10:11:58 +01:00
ebarault ceceb44e78 Add promise support to built-in model RoleMapping 2017-02-01 09:51:43 +01:00
Miroslav Bajtoš c099d8c731 Merge pull request #3163 from ebarault/feature/promisify-acl
Add promise support to built-in model ACL
2017-01-31 14:54:26 +01:00
Miroslav Bajtoš dcb2f159ec Merge pull request #3160 from strongloop/fix/token-invalidation-on-save
Preserve sessions on User.save() making no changes
2017-01-31 14:38:48 +01:00
ebarault a63fad402e Add promise support to built-in model ACL 2017-01-31 14:09:43 +01:00
Miroslav Bajtoš 7eeadc4289 Add nyc coverage, report data to coveralls.io 2017-01-31 14:09:02 +01:00
Miroslav Bajtoš c7e0a15a44 Merge pull request #3166 from strongloop/upgrade/eslint-config
Upgrade eslint config, fix linter errors
2017-01-31 14:08:31 +01:00
Miroslav Bajtoš 3b17a0cf5c Upgrade eslint config, fix linter errors 2017-01-31 13:44:32 +01:00
Miroslav Bajtoš bb939bec07 Merge pull request #3161 from strongloop/fix/role-properties
Add missing type to Role properties definition
2017-01-30 16:08:33 +01:00
David Hernandez c072d0695d Add missing type to Role properties definition
Fix "created" and "modified" to be defined with type "date".
2017-01-30 11:37:33 +01:00
Miroslav Bajtoš 5fd79b14f4 Merge pull request #3146 from strongloop/feature/promisify-role
Promise-ify built-in Role model
2017-01-30 11:32:28 +01:00
Miroslav Bajtoš 8f80aecc1f Preserve sessions on User.save() making no changes 2017-01-30 11:30:05 +01:00
Miroslav Bajtoš 5607460854 Merge pull request #3149 from strongloop/fix/es6-in-phantomjs
Enable ES6/ES2015 goodness
2017-01-30 09:36:02 +01:00
Miroslav Bajtoš 1d02e049b2 Merge pull request #1496 from strongloop/fix/logout-without-token
Fail with "401 Unauthorized" when token is not provided to User.logout()
2017-01-30 09:23:12 +01:00
Ritchie Martori d45c1ae7bb Fix logout to handle no or missing accessToken
Return 401 when the request does not provide any accessToken argument
or the token was not found.

Also simplify the implementation of the `logout` method to make only
a single database call (`deleteById`) instead of `findById` + `delete`.
2017-01-30 08:56:18 +01:00
Miroslav Bajtoš 0fd8f8af72 Promise-ify built-in Role model 2017-01-27 17:16:59 +01:00
Miroslav Bajtoš 82d6ede086 Merge pull request #3150 from strongloop/remove/forgotten-jscsrc
Remove .jscsrc that's no longer used
2017-01-27 16:06:14 +01:00
Miroslav Bajtoš 19c3b47c4b Remove .jscsrc that's no longer used 2017-01-27 15:23:59 +01:00
Miroslav Bajtoš be8dd0ded8 Enable ES6/ES2015 goodness
- Remove ES5 parser exception from .eslintrc
 - Configure Karma to use Babel to transpile ES6 sources to ES5,
   because PhantomJS does not support ES6
 - Upgrade es5-shim to es6-shim
2017-01-27 11:31:57 +01:00
Miroslav Bajtoš 450aa84ae8 Merge pull request #3138 from strongloop/fix/language-in-tests
Use English when running Mocha tests
2017-01-27 11:24:52 +01:00
Miroslav Bajtoš 78f8b9124a Remove test/support.js from karma config
The file no longer exists.
2017-01-26 10:11:08 +01:00
Miroslav Bajtoš 93ab8644c4 Use English when running Mocha tests 2017-01-26 10:11:08 +01:00
Miroslav Bajtoš dfa1f6035b Merge pull request #3107 from benkroeger/master
Role model: resolve related models by name
2017-01-25 10:48:15 +01:00
Simon Ho 035f4d095b Merge pull request #3131 from strongloop/update-issue-template
Update ISSUE_TEMPLATE
2017-01-23 11:17:08 -08:00
Simon Ho c6427065be Update ISSUE_TEMPLATE
- Minor formatting edits to reduce scrolling
- Clean up sentence in the last comment
2017-01-23 10:26:50 -08:00
Miroslav Bajtoš 4c9c6236e3 Merge pull request #3074 from ariskemper/fix_user_id
Fix User model to handle custom PK name
2017-01-23 09:43:19 +01:00
Rand McKinney 9a1f86baef Merge pull request #3126 from joesepi/updatereadme
Updating README to show loopback-cli and remove arc mention
2017-01-20 14:21:37 -08:00
Joe Sepi 746265b393 Updating README - add cli and remove arc 2017-01-20 17:15:01 -05:00
Aris Kemper efd8237dc6 Fix User methods to use correct Primary Key
Do not use hard-coded "id" property name, call `idName()` to get the
name of the PK property.
2017-01-20 16:24:59 +01:00
Miroslav Bajtoš a7d93f32b5 Merge pull request #3120 from strongloop/fix/user-reset-password
Fix User.resetPassword to call createAccessToken()
2017-01-20 10:58:31 +01:00
João Ribeiro e63fea83f7 Fix User.resetPassword to call createAccessToken()
This allows User subclasses to override the algorithm used for building
one-time access tokens for password recovery.
2017-01-19 16:27:22 +01:00
Benjamin Kroeger a6d511d8b4 Role model: resolves related models by name
Resolve models related to the `Role` model by name instead of class.
2017-01-16 15:48:24 +01:00
Miroslav Bajtoš ff53933085 3.2.1
* Preserve current session when invalidating tokens (Miroslav Bajtoš)
 * Clean up access-token-invalidation tests (Miroslav Bajtoš)
 * Update docs.json (Rand McKinney)
 * Simplify issue template (#3083) (Simon Ho)
 * Warn about injectOptionsFromRemoteContext (Miroslav Bajtoš)
2017-01-16 12:01:47 +01:00
Miroslav Bajtoš 4ffe5fe17f Merge pull request #3098 from strongloop/preserve-current-access-token
Preserve current session when invalidating access tokens
2017-01-16 10:35:39 +01:00
Miroslav Bajtoš e17cc3d23a Preserve current session when invalidating tokens
Fix User model to preserve the current session (provided via
"options.accessToken") when invalidating access tokens after a change
of email or password property.
2017-01-16 10:08:30 +01:00
Miroslav Bajtoš 24bb15233d Clean up access-token-invalidation tests 2017-01-16 10:08:30 +01:00
Miroslav Bajtoš 3946462828 Merge pull request #3097 from strongloop/remove-context-api-doc
Update docs.json
2017-01-13 14:28:29 +01:00
Rand McKinney 9fec6e1615 Update docs.json
Remove files that are no longer used.
2017-01-12 14:25:38 -08:00
Simon Ho d2e9e7ee77 Simplify issue template (#3083)
Favour form editing over going down a checklist. Also fix automatic 2
subtasks creation side effect from old issue template.
2017-01-11 17:04:19 -08:00
Miroslav Bajtoš b86eac5cf6 Merge pull request #3080 from strongloop/feature/warn-on-injectOptionsFromRemoteContext
Warn about injectOptionsFromRemoteContext
2017-01-10 15:38:24 +01:00
Miroslav Bajtoš 3e2ac9217f Warn about injectOptionsFromRemoteContext
The option injectOptionsFromRemoteContext was added to LoopBack 2.x only
and is not available in LoopBack 3.x (and newer).

When a model with this option is encountered, we are printing a warning
message now, to let the user know about this change between 2.x and 3.x.
2017-01-09 14:45:23 +01:00
Miroslav Bajtoš 3ed525f753 3.2.0
* Upgrade eslint-config to 7.x (Miroslav Bajtoš)
 * Allow password reset request for users in realms (Bram Borggreve)
 * Fix construction of sharedCtor remoting metadata (Miroslav Bajtoš)
 * Add option disabling periodic change rectification (kobaska)
 * Fix annotation for persistedModel.count (lschricke)
 * Applied as reviewed by @flowersinthesand (박대선)
 * Fix false emailVerified on user model update (박대선)
 * Contextify DAO and relation methods (Miroslav Bajtoš)
 * Implement new http arg mapping optionsFromRequest (Miroslav Bajtoš)
 * Emit resetPasswordRequest event with options (Sergey Reus)
2017-01-09 13:00:18 +01:00
Miroslav Bajtoš 4e6f9b978a Merge pull request #3075 from strongloop/upgrade-eslint
Upgrade eslint-config to 7.x
2017-01-06 12:54:58 +01:00
Miroslav Bajtoš 70eecfab70 Upgrade eslint-config to 7.x 2017-01-06 12:12:35 +01:00
David Cheung 12be94e0db Merge pull request #2998 from fullcube/bb/password-reset-realms-3.x
Allow password reset request for users in realms (v3.x)
2017-01-05 12:34:23 -05:00
Bram Borggreve cddfb9c77d
Allow password reset request for users in realms 2017-01-05 09:47:18 -05:00
Miroslav Bajtoš 298635dad1 Merge pull request #2992 from DA-14/feature/resetPasswordRequest
Emit resetPasswordRequest event with options
2017-01-05 15:33:22 +01:00
Miroslav Bajtoš 8a2c01b3d8 Merge pull request #3070 from strongloop/fix/sharedCtor-remoting
Fix construction of sharedCtor remoting metadata
2017-01-05 11:37:30 +01:00
Miroslav Bajtoš f4b167698a Merge pull request #3053 from yumenohosi/fix/email-verified
Fix false emailVerified on user model update
2017-01-05 11:02:54 +01:00
Miroslav Bajtoš 0a6740cc30 Fix construction of sharedCtor remoting metadata
Prevent the situation when we are configuring remoting metadata after
strong-remoting has already picked up data from our parent (base) model.
2017-01-05 10:24:04 +01:00
Miroslav Bajtoš 0d1f74e2cd Merge pull request #3047 from strongloop/forward-port/avoid-change-cleanup
Add option disabling periodic change rectification
2017-01-04 16:39:44 +01:00
David Cheung 355e40d750 Merge pull request #3066 from lschricke/patch-1
Fix annotation for persistedModel.count
2017-01-04 10:06:06 -05:00
kobaska e15a656714 Add option disabling periodic change rectification
When `Model.settings.changeCleanupInterval` is set to a negative value,
no periodic cleanup is performed at all.
2017-01-04 15:58:44 +01:00
lschricke baa50518cf Fix annotation for persistedModel.count 2017-01-03 20:09:42 -05:00
박대선 697614dd45 Applied as reviewed by @flowersinthesand 2016-12-23 14:47:08 +09:00
박대선 d9ae32429b Fix false emailVerified on user model update
Yesterday, the loopback we are using in our system was upgraded
via npm, and since the upgrade, we noticed that every time
the user model updates, the emailVerified column would change to false.

I took a look and realized there might be an error in
https://github.com/strongloop/loopback/commit/eb640d8

The intent of the commit just mention is to make emailVerified false
when the email gets changed, but notice that ctx.data.email is null
on updates, so the condition is always met and emailVerified always
becomes false.

This commit fixes the issue just mentioned.
2016-12-23 14:04:44 +09:00
Miroslav Bajtoš a21fca6089 Merge pull request #3023 from strongloop/feature/options-from-context-v2
Inject remoting context to options arg
2016-12-22 12:01:02 +01:00
Miroslav Bajtoš 60ea3e96bc Contextify DAO and relation methods
Modify remoting metadata of data-access methods in PersistedModel
and relation method in Model and add an "options" argument to "accepts"
list.
2016-12-22 10:26:09 +01:00
Miroslav Bajtoš 4de3aa77e3 Implement new http arg mapping optionsFromRequest
Define a new Model method "createOptionsFromRemotingContext" that allows
models to define what "options" should be passed to methods invoked
via strong-remoting (e.g. REST).

Define a new http mapping `http: 'optionsFromRequest'` that invokes
`Model.createOptionsFromRemotingContext` to build the value from
remoting context.

This should provide enough infrastructure for components and
applications to implement their own ways of building the "options"
object.
2016-12-22 10:26:01 +01:00
Miroslav Bajtoš 668a9d0ed6 3.1.1
* Update package.json for LB3 release (Simon Ho)
 * Invalidate AccessTokens on password change (Miroslav Bajtoš)
 * Fix registration of operation hooks in User model (Miroslav Bajtoš)
 * Remove "options.template" from Email payload (Miroslav Bajtoš)
 * Upgrade eslint config and grunt-eslint to latest (Miroslav Bajtoš)
 * Update paid support URL (siddhipai)
 * Update paid support URL (Siddhi Pai)
 * Remove duplicate warning in issue template (Siddhi Pai)
2016-12-21 16:05:58 +01:00
Miroslav Bajtoš f71030f8c2 Merge pull request #3044 from strongloop/release/3.x
Prepare for 3.x GA
2016-12-21 10:52:06 +01:00
Simon Ho 92ab09338e Update package.json for LB3 release 2016-12-21 00:54:07 -08:00
Miroslav Bajtoš f27cd2ccfb Merge pull request #3018 from strongloop/fix/session-expiry2
Invalidate AccessTokens on password change
2016-12-12 13:50:15 +01:00
Miroslav Bajtoš 29a17f39d5 Invalidate AccessTokens on password change
Invalidate all existing sessions (delete all access tokens)
after user's password was changed.
2016-12-12 13:30:53 +01:00
Sergey Reus fa8bca8d6e Emit resetPasswordRequest event with options 2016-12-09 18:14:32 +02:00
Miroslav Bajtoš dac1295ad7 Merge pull request #3016 from strongloop/fix/repeated-user-hooks2
Fix registration of operation hooks in User model (part 2)
2016-12-09 15:06:02 +01:00
Miroslav Bajtoš f476613ab1 Fix registration of operation hooks in User model
Follow-up for 4edce47 which moved only two out of three hooks.
2016-12-09 14:29:30 +01:00
Miroslav Bajtoš 57a053bbf2 Merge pull request #3014 from strongloop/fix/repeated-user-hooks
Fix registration of operation hooks in User model
2016-12-09 14:09:12 +01:00
Miroslav Bajtoš 4edce47b24 Fix registration of operation hooks in User model
Operation hooks are inherited by subclassed models, therefore they must
be registered outside of `Model.setup()` function.

This commit fixes this problem in the built-in User model.

There are not tests verifying this change, as writing a test would be
too cumbersome and not worth the cost IMO.
2016-12-09 13:16:42 +01:00
Miroslav Bajtoš 63beaa21fe Merge pull request #3004 from strongloop/fix/email-template-in-transport
Remove "options.template" from Email payload
2016-12-07 10:53:04 +01:00
Miroslav Bajtoš 7f2cdc106c Merge pull request #3002 from strongloop/eslint-es6
Upgrade eslint config and grunt-eslint to latest
2016-12-07 10:52:52 +01:00
Miroslav Bajtoš 5016703f21 Remove "options.template" from Email payload
Fix User.confirm to exclude "options.template" when sending the
confirmation email. Certain nodemailer transport plugins are rejecting
such requests.
2016-12-06 16:18:19 +01:00
Miroslav Bajtoš acdfb432d0 Upgrade eslint config and grunt-eslint to latest
- disable ES6 because PhantomJS does not support it yet
 - fix linter errors reported after the upgrade.
2016-12-06 16:05:13 +01:00
siddhipai fab5bd4fc5 Update paid support URL
Update paid support URL
2016-12-05 12:38:05 -08:00
Siddhi Pai e4cb2afdb7 Update paid support URL 2016-12-05 12:23:14 -08:00
Siddhi Pai 91970f975a Remove duplicate warning in issue template
* First item is not polite enough

* Second item conveys the same message as the first item
2016-12-05 12:09:19 -08:00
Miroslav Bajtoš 3e1ae2d413 3.1.0
* Fix use-strict issue with connectors after merge (Loay)
 * Fix connector naming in strict mode (ebarault)
 * Add "returnOnlyRoleNames" option to Role.getRoles (Eric)
 * Update translation files (Candy)
 * Fix broken document for `upsertWithWhere` (Amir Jafarian)
 * Fix js doc for deleteAll event (Candy)
 * add allowArray to relations' create remoteMethod (David Cheung)
 * Remove workaround for default value (Loay)
 * Fix remote method example (Amir Jafarian)
 * Remove `example/context` (Amir Jafarian)
 * Turn on "no-unused-expressions" rule for eslint (Miroslav Bajtoš)
 * Update eslint to loopback config v5 (Loay)
 * Fix total calculation in example (Candy)
 * make test individually runable (David Cheung)
 * Add options to bulkUpdate (Kogulan Baskaran)
 * Fix context within listByPrincipalType role method (codyolsen)
 * Add Node v7 to Travis CI platforms (Miroslav Bajtoš)
 * Drop support for Node v0.10 and v0.12 (Miroslav Bajtoš)
 * Add templateFn option to User#verify() (Adrien Kiren)
 * Require verification after email change (Loay)
 * Update doc links (Candy)
 * adding check of string for case insensitive emails (Dhaval Trivedi)
 * Update test confirmation text in PR template (#2897) (Simon Ho)
 * allow batch create for persisted models (David Cheung)
 * Fix PR template to not link all PRs to #49 (#2887) (Miroslav Bajtoš)
 * Need index on principalId for performance. (#2883) (Simon Ho)
 * Remove redundant items in PR template (#2877) (Simon Ho)
 * Refactor PR template based on feedback (#2865) (Simon Ho)
 * Add pull request template (#2843) (Simon Ho)
 * Update README.md (Rand McKinney)
 * Reword ticking checkbox note in issue template (#2854) (Simon Ho)
 * Add how to tick checkbox in issue template (#2851) (Simon Ho)
 * Fix description of updateAll response (Miroslav Bajtoš)
 * Allow tokens with eternal TTL (value -1) (Miroslav Bajtoš)
 * Use GitHub issue templates (#2810) (Simon Ho)
 * Update ja and nl translation files (Candy)
 * Remove 3.0 DEVELOPING & RELEASE-NOTES (Miroslav Bajtoš)
 * Fix support for remote hooks returning a Promise (Tim van der Staaij)
 * Validate non-email property partial update (Loay)
 * Update release notes (Amir Jafarian)
 * Update translation files - round#2 (Candy)
 * Add license text (Candy)
 * Temporarily disable Karma tests on Windows CI (Miroslav Bajtoš)
2016-12-05 10:15:39 +01:00
Loay a34f321d2b Fix use-strict issue with connectors after merge
fixing use-strict issue with connectors after merge #2632
2016-11-30 14:36:51 -05:00
ebarault 94c786f2f7 Fix connector naming in strict mode
In strict mode, creating properties on strings is not allowed.
As a result, creating a new datasource fails with the following
error:

    TypeError: Cannot create data source "db":
    Cannot create property 'name' on string 'mongodb'

In this commit, we fix the code to assign the connector name only
if the connector is an object (not a string).

Add "returnOnlyRoleNames" option to Role.getRoles

Currently the return type of Role.getRoles() method is inconsistent:
role names are returned for smart roles and role ids are returned for
static roles (configured through user-role mapping).

This commit adds a new option to Role.getRoles() allowing the caller
to request role names to be returned for all types of roles.
2016-11-30 14:08:28 -05:00
Miroslav Bajtoš ca3ec134d6 Merge pull request #2975 from ebarault/getRoles-to-return-only-role-names
Add "returnOnlyRoleNames" option to Role.getRoles
2016-11-30 17:00:57 +01:00
Eric b0d6c4a7d2 Add "returnOnlyRoleNames" option to Role.getRoles
Currently the return type of Role.getRoles() method is inconsistent:
role names are returned for smart roles and role ids are returned for
static roles (configured through user-role mapping).

This commit adds a new option to Role.getRoles() allowing the caller
to request role names to be returned for all types of roles.
2016-11-30 16:46:59 +01:00
Loay 3ecb5e1cfe Merge pull request #2974 from strongloop/defaultValue
Remove workaround for default value
2016-11-28 11:07:09 -05:00
Candy 80b0bb8392 Merge pull request #2985 from strongloop/add_translation7
Update translation files
2016-11-25 16:28:18 -05:00
Candy bee157a792 Update translation files 2016-11-25 16:01:46 -05:00
Amirali Jafarian 1d96a678a5 Merge pull request #2983 from strongloop/broken_documemnt
Fix broken document for `upsertWithWhere`
2016-11-24 16:14:55 -05:00
Amir Jafarian d4e0efcab3 Fix broken document for `upsertWithWhere` 2016-11-24 15:58:21 -05:00
David Cheung a673a3884c Merge pull request #2947 from strongloop/related-models-allow-array
add allowArray to relations' create remoteMethod
2016-11-23 14:51:08 -05:00
Amirali Jafarian ea4f3ecb05 Merge pull request #2972 from strongloop/delete_context_example
Remove `example/context`
2016-11-23 13:45:33 -05:00
Candy f3ee41a0bc Merge pull request #2979 from strongloop/fix_jsdoc
Fix js doc for deleteAll event
2016-11-23 13:37:01 -05:00
Candy 5815ca8211 Fix js doc for deleteAll event 2016-11-23 12:02:49 -05:00
David Cheung fa7cb923cd add allowArray to relations' create remoteMethod
this is needed because we added allowArray flag to persisted model's
remoteMethod, but when relations try to rebuild such methods, it does
not carry over such flags
2016-11-23 12:00:18 -05:00
Miroslav Bajtoš d4bee2b764 Merge pull request #2970 from strongloop/fix/remove-unused-expressions
Re-enable "no-unused-expressions" rule for eslint
2016-11-23 10:01:09 +01:00
Loay f72a29671f Remove workaround for default value 2016-11-22 20:58:27 -05:00
Amirali Jafarian 2c1ebdcc2e Merge pull request #2973 from strongloop/remote_method_fix_example
Fix remote method example
2016-11-22 17:55:07 -05:00
Amir Jafarian 9f4e9d8532 Fix remote method example
- callback error and not ignore cartId
2016-11-22 14:24:19 -05:00
Amir Jafarian 17512f6e77 Remove `example/context`
* Remove `example/context` since it is deprecated in LB 3.0
2016-11-22 13:56:46 -05:00
Miroslav Bajtoš 1ec7cf0a37 Turn on "no-unused-expressions" rule for eslint
Fix unit-tests relying on property-based assertions to use function
calls instead, using "dirty-chai" to modify chai's property checkers.
2016-11-22 15:30:04 +01:00
Miroslav Bajtoš c3ab932012 Merge pull request #2632 from strongloop/eslint2
Update eslint infrastructure
2016-11-22 14:20:41 +01:00
Loay 06cb481c3f Update eslint to loopback config v5
Notable side-effects:
 - loopback no longer exports "caller" and "arguments" properties
 - kv-memory connector is now properly added to the connector registry
 - the file "test/support.js" was finally removed
2016-11-22 14:08:02 +01:00
Candy ef0478cc97 Merge pull request #2968 from strongloop/fix_total_calculation
Fix total calculation in example
2016-11-21 13:54:50 -05:00
Candy 308c18b1e0 Fix total calculation in example 2016-11-21 11:49:18 -05:00
David Cheung 61c0c85ab5 Merge pull request #2888 from strongloop/make-test-individually-runable
make test individually runable
2016-11-17 18:29:00 -05:00
David Cheung b3c43b60cd make test individually runable
previously when you do something like mocha test/model.test.js
you would get an error like "assert is not a function"
2016-11-17 18:05:58 -05:00
Miroslav Bajtoš a0bc204159 Merge pull request #2945 from strongloop/feature/bulkupdate-options-3x
Add options to bulkUpdate
2016-11-16 08:57:11 +01:00
Kogulan Baskaran b4f1b2f02c Add options to bulkUpdate 2016-11-15 17:40:44 +01:00
Miroslav Bajtoš 54ee8d8bb5 Merge pull request #2742 from codyolsen/feature/role-context
Fix context within listByPrincipalType role method
2016-11-15 16:05:19 +01:00
codyolsen 3f5e49c3d6 Fix context within listByPrincipalType role method
- Fix for current implimentation that returned all models that had any
  assigned roles. Context was not carried into listByPrincipalType,
  setting roleId as null.
2016-11-15 14:51:05 +01:00
Miroslav Bajtoš 7d64a92ce7 Merge pull request #2942 from strongloop/drop-support-node-0x
Drop support for Node v0.10 and v0.12
2016-11-15 14:17:53 +01:00
Miroslav Bajtoš ff347dd170 Add Node v7 to Travis CI platforms 2016-11-15 13:49:12 +01:00
Miroslav Bajtoš d146476757 Drop support for Node v0.10 and v0.12 2016-11-15 13:48:29 +01:00
Miroslav Bajtoš e07feb838f Merge pull request #2937 from strongloop/feature/verify-template-fn
Add templateFn option to User#verify()
2016-11-14 16:51:29 +01:00
Adrien Kiren 85da50cbc8 Add templateFn option to User#verify() 2016-11-14 16:22:10 +01:00
Candy d85ff32abb Merge pull request #2930 from strongloop/add_translation5
Update translation files
2016-11-10 14:28:14 -05:00
Candy 2c3b0b4d0b Update translation files 2016-11-10 12:38:16 -05:00
Miroslav Bajtoš 6b6ca95e2d Merge pull request #2765 from strongloop/verifyEmail
Require verification after email change
2016-11-09 13:24:27 +01:00
Loay eb640d8da0 Require verification after email change
When the User model is configured to require email verification,
then any change of the email address should trigger re-verification.
2016-11-09 13:06:25 +01:00
Rand McKinney fcbe028e11 Merge pull request #2916 from strongloop/update_doc_links
Update doc links
2016-11-07 09:56:59 -08:00
Candy 8f08398c30 Update doc links 2016-11-04 16:47:12 -04:00
David Cheung d1ae8aad90 Merge pull request #2912 from strongloop/geekguy-case-senstive-email-bug
adding check of string for case insensitive emails
2016-11-03 10:58:33 -04:00
Dhaval Trivedi 4922f425fc adding check of string for case insensitive emails 2016-11-01 18:13:56 -04:00
Simon Ho 6d5d7f0d4a Update test confirmation text in PR template (#2897)
* Update test confirmation text in PR template

We only ask users to check when adding new tests, but
some PRs only modify existing tests. Also added
instructions to mark with an "x" for checking off items.
2016-10-27 23:58:25 -07:00
David Cheung ae4d53d08d Merge pull request #2879 from strongloop/batch-create
allow batch create for persisted models
2016-10-26 17:04:35 -04:00
David Cheung 5252fba376 allow batch create for persisted models
In strong-remoting 3.x, we have stricken the coercion of inputs
methods that are expecting an Object will nolonger accept an array
as input, to preserve backwards compatibility we have added flag
allowArray in remote arguments, which would accept an array of objects
2016-10-25 15:45:00 -04:00
Miroslav Bajtoš 8cc71a4dc0 Fix PR template to not link all PRs to #49 (#2887)
- Add comment with syntax examples
- Remove direct links to #49 in section header
2016-10-24 17:08:40 -07:00
Simon Ho dcc58a9d50 Need index on principalId for performance. (#2883) 2016-10-21 16:13:16 -07:00
Simon Ho 9cc473ce52 Remove redundant items in PR template (#2877)
- Remove sign CLA since CI already shows unsigned CLAs
- Remove all tests must pass CI since we have to check each on a case by
  case basis anyways
- Update example to include linking to the current repo using number
  sign (ie. #49 in addition to org/repo#49)
2016-10-19 17:21:48 -07:00
Simon Ho cd447144e2 Refactor PR template based on feedback (#2865)
Updated based on feedback received during sprint demo.
2016-10-18 23:48:42 -07:00
Simon Ho ed76a3475a Add pull request template (#2843) 2016-10-14 12:46:03 -07:00
Rand McKinney 2e1ca9e3d0 Merge pull request #2858 from strongloop/update-readme
Update README.md
2016-10-13 16:56:19 -07:00
Rand McKinney df061b09d7 Update README.md 2016-10-13 15:57:19 -07:00
Simon Ho 9a28ed103e Reword ticking checkbox note in issue template (#2854) 2016-10-12 17:37:08 -07:00
Simon Ho 046491f1b6 Add how to tick checkbox in issue template (#2851)
Many people are putting "*" instead of x, which causes the markdown to
render funny. Adding instructions to that particular section.
2016-10-12 16:50:19 -07:00
Miroslav Bajtoš 46ab65cf50 Merge pull request #2842 from strongloop/fix/metadata-update-delete-all
Fix description of updateAll response
2016-10-12 13:03:32 +02:00
Miroslav Bajtoš 1439446b36 Fix description of updateAll response
Correctly describe the first non-error callback arg as an `info` object
containing a `count` property.
2016-10-12 12:43:53 +02:00
Miroslav Bajtoš 3229fdbc08 Merge pull request #2841 from strongloop/feature/allow-eternal-access-tokens
Allow tokens with eternal TTL (value -1)
2016-10-12 12:29:38 +02:00
Miroslav Bajtoš 6808159427 Allow tokens with eternal TTL (value -1)
- Add a new User setting 'allowEternalTokens'
 - Enhance 'AccessToken.validate' to support eternal tokens with ttl
   value -1 when the user model allows it.
2016-10-10 13:27:22 +02:00
Candy 953cfa9e2d Merge pull request #2833 from strongloop/add_translation3
Update ja and nl translation files
2016-10-06 15:32:32 -04:00
Simon Ho ee4d79fdf0 Use GitHub issue templates (#2810) 2016-10-06 09:35:05 -07:00
Candy 927387502c Update ja and nl translation files 2016-10-06 11:06:49 -04:00
Miroslav Bajtoš 31913c048d Merge pull request #2823 from strongloop/docs/remove-release-notes
Remove 3.0 DEVELOPING & RELEASE-NOTES
2016-10-06 10:26:49 +02:00
Miroslav Bajtoš 602eebf2fb Remove 3.0 DEVELOPING & RELEASE-NOTES
The release notes were moved to loopback.io docs site.

The instructions for developers are no longer needed.
2016-10-05 16:00:49 +02:00
Miroslav Bajtoš 4e66009963 Merge pull request #2820 from strongloop/fix/promise-remote-hooks
Fix support for remote hooks returning a Promise
2016-10-05 10:53:58 +02:00
Tim van der Staaij c58bff6c3d Fix support for remote hooks returning a Promise
Fix beforeRemote/afterRemote to correctly return promises returned
by the user-provided hook callback.
2016-10-05 10:32:44 +02:00
Loay f9a58083ae Merge pull request #2795 from strongloop/PartialUpdate
Add partial update test case
2016-10-03 16:33:11 -04:00
Loay 5f5e874564 Validate non-email property partial update 2016-10-03 15:45:52 -04:00
Amirali Jafarian 9fb393196e Merge pull request #2809 from strongloop/update_release_notes
Update release notes
2016-10-02 15:55:00 -04:00
Amir Jafarian 1abbbc18e1 Update release notes 2016-10-02 15:42:34 -04:00
Rand McKinney 18d9c75abd Update README.md
Update link to doc site.
2016-09-30 08:54:16 -07:00
Candy 72b57ca68d Merge pull request #2802 from strongloop/add_translation2
Update translation files - round#2
2016-09-29 22:56:05 -04:00
Candy a31d5eea5b Update translation files - round#2 2016-09-28 13:56:14 -04:00
Candy ed8b651b98 Merge pull request #2784 from strongloop/add_license
Add license text
2016-09-23 13:23:25 -04:00
Candy 246da8fc5e Add license text 2016-09-23 10:32:22 -04:00
Miroslav Bajtoš 8a6ae998e4 Merge pull request #2779 from strongloop/fix/windows-ci
Temporarily disable Karma tests on Windows CI
2016-09-23 09:56:52 +02:00
Miroslav Bajtoš 8d0f319dd6 3.0.0
* Update deps to 3.0.0 RC (Miroslav Bajtoš)
 * Update globalization structure (Candy)
 * Call new disable remote method from model class. (Richard Pringle)
 * Add translation strings (Candy)
 * Support uniqueness for realm users (David Cheung)
 * Invalidate sessions after email change (Loay)
 * Add docs for KeyValue model (Simon Ho)
 * Fix remote method inheritance (Candy)
 * Fix double-slash in confirmation URL (Miroslav Bajtoš)
2016-09-22 12:48:40 +02:00
Miroslav Bajtoš a835d09bc8 Merge pull request #2780 from strongloop/update-deps-3.0RC
Update deps to 3.0.0 RC
2016-09-22 12:47:33 +02:00
Miroslav Bajtoš 9d259ce5a3 Update deps to 3.0.0 RC 2016-09-22 12:31:51 +02:00
Miroslav Bajtoš 54bf395249 Merge pull request #2754 from strongloop/use_common_globalize
Update globalization structure
2016-09-22 12:14:04 +02:00
Candy 640f3a8ca7 Update globalization structure 2016-09-22 11:58:00 +02:00
Miroslav Bajtoš 0cd157ca3d Temporarily disable Karma tests on Windows CI
We are observing frequent test failures on Windows CI, where PhantomJS
cannot start because there are no free handles available. Finding
and fixing the process leaking handles is non-trivial and will take
long time.

This commit disables Karma tests on Windows CI machines to prevent
build failures that we are ignoring anyways.
2016-09-22 10:49:00 +02:00
Miroslav Bajtoš 2f0dd6d6ec Merge pull request #2758 from strongloop/fix/disableMethodByName
Expose new method for disabling remote methods.
2016-09-22 09:39:04 +02:00
Richard Pringle 0ab33a82d4 Call new disable remote method from model class. 2016-09-21 14:09:24 -04:00
Candy 92ed2138fb Merge pull request #2755 from strongloop/add_translations
Add translation strings
2016-09-21 11:05:38 -04:00
Candy d5679666d9 Add translation strings 2016-09-21 10:29:38 -04:00
David Cheung 489ed919a5 Merge pull request #2298 from strongloop/user-realm-composite-key
Composite Key validation for Realm enabled users
2016-09-20 14:15:10 -04:00
David Cheung d544ae1bf8 Support uniqueness for realm users 2016-09-20 11:26:56 -04:00
Simon Ho c3ba632aa3 Merge pull request #2743 from strongloop/docs-for-kv-model
Add docs for KeyValue model
2016-09-19 15:29:47 -07:00
Loay 8061d1216d Merge pull request #2693 from strongloop/sessEmail
Invalidate sessions after email change
2016-09-19 13:54:31 -04:00
Loay bcc2d99a95 Invalidate sessions after email change 2016-09-19 10:24:30 -04:00
Simon Ho 845b73d4eb Add docs for KeyValue model 2016-09-18 19:45:13 -07:00
Candy 6752dd3af3 Merge pull request #2703 from strongloop/fix_remoting
Fix remote method inheritance
2016-09-13 13:56:43 -04:00
Candy d4b8cf670f Fix remote method inheritance 2016-09-13 13:23:09 -04:00
Miroslav Bajtoš 3eb9009741 Merge pull request #2738 from strongloop/fix/user-verify-email-with-empty-rest-root
Fix double-slash in confirmation URL
2016-09-13 09:07:30 +02:00
Miroslav Bajtoš 21ff383eb3 Fix double-slash in confirmation URL
Fix the code building the URL used in the email-verification email
to prevent double-slash in the URL when e.g. restApiRoot is '/'.

Before:

  http://example.com//users/confirm?...

Now:

  http://example.com/users/confirm?...
2016-09-13 08:52:49 +02:00
Miroslav Bajtoš eec85367e7 3.0.0-alpha.5
* Use strong-remoting's new TypeRegistry (Miroslav Bajtoš)
 * test/user: don't attach User model twice (Miroslav Bajtoš)
 * app.enableAuth: correctly detect attached models (Miroslav Bajtoš)
 * Fix remoting metadata for "data" arguments (Miroslav Bajtoš)
 * Add instructions for upgrading context (Miroslav Bajtoš)
 * Discard sugar method for model creation (gunjpan)
 * Remove one-var exceptions no longer needed (Miroslav Bajtoš)
 * Rework email validation to use isemail (Miroslav Bajtoš)
 * Expose upsertWithWhere method (Sonali Samantaray)
2016-09-09 10:34:59 +02:00
Miroslav Bajtoš 252b6f41c6 Merge pull request #2696 from strongloop/feature/coercion-overhaul
Use strong-remoting's new TypeRegistry
2016-09-09 10:34:12 +02:00
Miroslav Bajtoš 6e1defcb18 Use strong-remoting's new TypeRegistry 2016-09-09 10:01:29 +02:00
Miroslav Bajtoš 92a5a08671 test/user: don't attach User model twice 2016-09-09 09:02:41 +02:00
Miroslav Bajtoš 32bdeccebf app.enableAuth: correctly detect attached models
Fix a typo in "app.enableAuth" that caused the method to not detect
the situation when e.g. the built-in User model is already attached
to a datasource.
2016-09-09 09:02:41 +02:00
Miroslav Bajtoš f5acf6aebd Merge pull request #2727 from strongloop/fix/data-object-arguments-3x
Fix remoting metadata for "data" arguments
2016-09-07 14:37:15 +02:00
Miroslav Bajtoš f76edd5d61 Fix remoting metadata for "data" arguments
Fix the definition of "data" argument to

    { type: 'object', model: modelName, ... }

That way strong-remoting passed the request body directly to the model
method (does not create a new model instance), but the swagger will
still provide correct schema for these arguments.

This fixes a bug where upsert in relation methods was adding default
property values to request payload.
2016-09-07 14:27:58 +02:00
Miroslav Bajtoš 554ccbd035 Merge pull request #2721 from strongloop/docs/context-upgrade-2x-3x
Add instructions for upgrading context
2016-09-07 13:03:39 +02:00
Miroslav Bajtoš 94f9a1bd05 Add instructions for upgrading context
Describe how to upgrade LoopBack 2.x apps to 3.x and prevent
"Error: remoting.context option was removed in version 3.0."
2016-09-07 12:48:24 +02:00
Miroslav Bajtoš da09876585 Merge pull request #2401 from strongloop/compat_flag_cleanup
[SEMVER-MAJOR] Discard sugar method for model creation
2016-09-07 12:43:53 +02:00
gunjpan 832e2c391c Discard sugar method for model creation
Current implementation of `app.model(modelName, settings)`
works as a sugar for model creation. In 3.0, this is
not supported anymore. This implementation reports an
error when sugar is used for model creation.
Includes:
 - Updated app.model() method
 - Fixed test cases reflecting the change
2016-09-07 10:40:23 +02:00
Amirali Jafarian a6f8ec672d Merge pull request #2539 from mountain1234585/upsertWithWhere
Add upsertWithWhere
2016-09-06 15:54:21 -04:00
Miroslav Bajtoš d78d4c9c29 Merge pull request #2720 from strongloop/fix/eslint-one-var
Remove one-var exceptions no longer needed
2016-09-06 17:20:02 +02:00
Miroslav Bajtoš d13d2a7ab0 Remove one-var exceptions no longer needed 2016-09-06 15:47:35 +02:00
Miroslav Bajtoš b48f7173ee Merge pull request #2717 from strongloop/feature/isemail
Rework email validation to use isemail
2016-09-06 14:36:36 +02:00
Miroslav Bajtoš 9a75ee6f30 Rework email validation to use isemail
Drop hand-crafted RegExp in favour of a 3rd-party module that supports
RFC5321, RFC5322 and other relevant standards.
2016-09-06 14:09:00 +02:00
Miroslav Bajtoš 6220d1f986 3.0.0-alpha.4
* Update loopback-connector-remote to 2.0-alpha (Miroslav Bajtoš)
 * Add remoting for KeyValue model TTL feature (Simon Ho)
 * Add lint NPM script (Simon Ho)
 * Make the app instance available to connectors (Subramanian Krishnan)
 * Update pre-release dependencies (Miroslav Bajtoš)
 * Apply g.f to literal strings (Setogit)
 * Allow resetPassword if  emailVerified (Loay)
 * Reorder PATCH Vs PUT endpoints (Amir Jafarian)
 * streamline use if `self` (Benjamin Kroeger)
 * resolve related models from correct registry (Benjamin Kroeger)
 * KeyValueModel: add API for listing keys (Miroslav Bajtoš)
 * Fix token middleware crash (Carl Fürstenberg)
 * loopback#context: fix missing "g" symbol (Miroslav Bajtoš)
 * Update acl.js (Rand McKinney)
 * Support 'alias' in mail transport config. (Samuel Reed)
 * Remove unnecessary g.log (Setogit)
 * Revert globalization of Swagger descriptions (Miroslav Bajtoš)
 * Revert globalization of assert() messages (Miroslav Bajtoš)
 * Add bcrypt validation (Loay)
2016-09-05 15:05:29 +02:00
Sonali Samantaray aef6dca30c Expose upsertWithWhere method 2016-09-02 18:10:47 +05:30
Miroslav Bajtoš 387be29185 Merge pull request #2699 from strongloop/update/remote-connector
Update loopback-connector-remote to 2.0-alpha
2016-09-01 13:32:58 +02:00
Miroslav Bajtoš 3dc0d5a64e Update loopback-connector-remote to 2.0-alpha 2016-09-01 13:13:32 +02:00
Simon Ho 69f7f0941f Merge pull request #2676 from strongloop/add-remoting-for-kv-model-ttl
Add remoting for kv model ttl
2016-08-29 17:13:29 -07:00
Simon Ho 9db0682b07 Add remoting for KeyValue model TTL feature 2016-08-29 14:46:41 -07:00
Simon Ho 32b879cf73 Add lint NPM script 2016-08-29 14:45:59 -07:00
Simon Ho 6c0b159a84 Merge pull request #2684 from strongloop/fix-glob-user
Apply g.f to literal strings
2016-08-29 10:40:18 -07:00
Miroslav Bajtoš 987f29e609 Merge pull request #2679 from SubuIBM/newRef
Make the app instance available to connectors

Close #2679
2016-08-29 15:16:30 +02:00
Subramanian Krishnan 40f0690573 Make the app instance available to connectors 2016-08-29 15:15:53 +02:00
Miroslav Bajtoš ca21243067 Merge pull request #2680 from strongloop/update/juggler-remoting
Update pre-release dependencies
2016-08-29 10:10:01 +02:00
Miroslav Bajtoš 3437d782d9 Update pre-release dependencies 2016-08-29 09:55:42 +02:00
Setogit 0f5136d072 Apply g.f to literal strings 2016-08-27 22:42:21 -07:00
Amirali Jafarian 358fdbf184 Merge pull request #2670 from strongloop/reorder_patch_put
Reorder PATCH Vs PUT endpoints
2016-08-26 16:09:43 -04:00
Loay 4ec0ac2218 Merge pull request #2671 from strongloop/Password-Security
Allow resetPassword by email only if email verification was done
2016-08-26 15:58:39 -04:00
Loay 5567917c12 Allow resetPassword if emailVerified 2016-08-26 13:11:42 -04:00
Amir Jafarian b80666a507 Reorder PATCH Vs PUT endpoints
*Reorder PATCH Vs PUT endpoints for update* methods
2016-08-26 11:08:35 -04:00
Miroslav Bajtoš 98eed7238d Merge pull request #2628 from benkroeger/master
Fix acl related model resolution

Close #2628
2016-08-25 13:10:59 +02:00
Benjamin Kroeger 4ff9a4c2ef streamline use if `self` 2016-08-25 12:51:51 +02:00
Benjamin Kroeger 01c1656fc2 resolve related models from correct registry
Also modify setup of test servers when ACL was used, force the app
to `loadBuiltinModels` with localRegistry.
2016-08-25 12:51:29 +02:00
Miroslav Bajtoš cc95860c68 Merge pull request #2638 from strongloop/feature/kvao-iterate-keys
KeyValueModel: add API for listing keys
2016-08-18 13:30:25 +02:00
Miroslav Bajtoš 88e4de5341 KeyValueModel: add API for listing keys
- Expose "keys()" at "GET /keys"
 - Add a dummy implementation for "iterateKeys" to serve a useful error
   message when the model is not attached correctly.
2016-08-18 10:50:45 +02:00
Miroslav Bajtoš 3dfd86f6ff Merge pull request #2650 from strongloop/fix/token-in-context-3x
Fix token middleware crash
2016-08-17 15:05:09 +02:00
Carl Fürstenberg edd5275b8b Fix token middleware crash
Fix token middleware to check if `req.loopbackContext` is active.
The context is not active for example when express-session calls
setImmediate which breaks CLS.
2016-08-17 14:44:00 +02:00
Miroslav Bajtoš ba5f36fb91 loopback#context: fix missing "g" symbol 2016-08-17 14:43:59 +02:00
Rand McKinney 01a9fa2ab2 Update acl.js
Fix typo in JS doc.
2016-08-16 10:14:17 -07:00
Samuel Reed 22345cfcda Support 'alias' in mail transport config.
Useful if you need to set up multiple transports of the same type.

[forward-port of #2489]
2016-08-16 16:24:56 +02:00
Tetsuo Seto 21ce174939 Merge pull request #2634 from strongloop/fixup-glob
Remove unnecessary g.log
2016-08-16 07:01:37 -07:00
Miroslav Bajtoš da0a543983 Merge pull request #2622 from strongloop/fix/unglobalize-swagger
Revert globalization of Swagger descriptions
2016-08-16 13:59:26 +02:00
Setogit d4769c7adf Remove unnecessary g.log 2016-08-15 21:52:44 -07:00
Miroslav Bajtoš 1a62ed7f27 Merge pull request #2623 from strongloop/fix/unglobalize-asserts
Revert globalization of assert() messages
2016-08-15 14:10:13 +02:00
Miroslav Bajtoš eec326dc80 Revert globalization of Swagger descriptions 2016-08-15 11:06:05 +02:00
Miroslav Bajtoš 80a0b7d7ad Revert globalization of assert() messages 2016-08-15 08:53:25 +02:00
Loay 21bdb28d37 Merge pull request #2580 from strongloop/bcrypt
Add bcrypt validation
2016-08-13 00:37:05 -04:00
Loay 7aebf0d132 Add bcrypt validation 2016-08-12 21:34:50 -04:00
Miroslav Bajtoš 899ab457e9 3.0.0-alpha.3
* common: add KeyValueModel (Miroslav Bajtoš)
 * Globalize current-context error messages (Miroslav Bajtoš)
 * Remove current-context API (Miroslav Bajtoš)
 * Fix forceId in tests (jannyHou)
 * test: increase timeout to prevent CI failures (Miroslav Bajtoš)
 * Update globalization string (Candy)
 * Update globalization (Candy)
 * Add globalization (Candy)
 * test: fix "socket hang up" error in app.test (Miroslav Bajtoš)
 * test: increate timeout in Role test (Miroslav Bajtoš)
 * test: make status test more robust (Miroslav Bajtoš)
 * test: fix broken Role tests (Miroslav Bajtoš)
 * Update dependencies to their latest versions (Miroslav Bajtoš)
 * Increase timeout (jannyHou)
 * test: fix change-tracking setup (Miroslav Bajtoš)
 * test: use local registry in test fixtures (Miroslav Bajtoš)
 * Update loopback.js (Rand McKinney)
 * Fix test case error (Loay)
 * Update user.js (Loay)
 * Fix security issue 580 (Loay)
 * Update URLs in CONTRIBUTING.md (#2503) (Ryan Graham)
 * Remove legacyExplorer (gunjpan)
 * Remove `rectifyAllChanges` and `rectifyChange` (Candy)
 * Fix verificationToken bug (Loay)
 * update express version (Loay)
 * Cleanup unit-test added in 1fc51d129 (Miroslav Bajtoš)
 * update errorHandler template (Loay)
2016-08-11 13:34:01 +02:00
Miroslav Bajtoš 32ecd2fc5c Merge pull request #2594 from strongloop/feature/key-value-model
common: add KeyValueModel
2016-08-10 15:25:03 +02:00
Miroslav Bajtoš a259e59afc common: add KeyValueModel 2016-08-10 14:15:22 +02:00
Miroslav Bajtoš f13e584686 Merge pull request #2564 from strongloop/feature/remove-current-context
[SEMVER-MAJOR] Remove current-context API
2016-08-10 14:14:38 +02:00
Miroslav Bajtoš 59a82a9d5e Globalize current-context error messages 2016-08-10 13:43:40 +02:00
Miroslav Bajtoš b087c930ed Remove current-context API
Change all current-context APIs to throw a helpful error.
2016-08-10 13:43:40 +02:00
Janny 5fd1766a81 Merge pull request #2501 from strongloop/fix/forceId
Set forceId = false for AccessToken
2016-08-08 16:24:04 -04:00
jannyHou 19618209c8 Fix forceId in tests 2016-08-08 15:06:29 -04:00
Miroslav Bajtoš 57ca624979 Merge pull request #2591 from strongloop/fix/ci-timeout
test: increase timeout to prevent CI failures
2016-08-08 16:04:24 +02:00
Candy 360ec41180 Merge pull request #2589 from strongloop/update_globalize2
Update globalization string
2016-08-08 10:04:19 -04:00
Miroslav Bajtoš a751230cd9 test: increase timeout to prevent CI failures 2016-08-08 15:45:53 +02:00
Candy bb9a1b5b24 Update globalization string 2016-08-05 15:49:43 -04:00
Candy 79a21b824d Merge pull request #2583 from strongloop/update_globalize
Update globalization
2016-08-04 18:14:15 -04:00
Candy 3239942ff1 Update globalization 2016-08-04 17:35:21 -04:00
Candy 1c1f2c2e8c Merge pull request #2407 from strongloop/initialize_glob
Add globalization
2016-08-04 16:33:40 -04:00
Candy b52a7217a9 Add globalization 2016-08-04 15:08:16 -04:00
Janny de0f51b664 Merge pull request #2577 from strongloop/update-deps
Update dependencies to their latest versions
2016-08-04 14:32:37 -04:00
Miroslav Bajtoš 39da31bb5a test: fix "socket hang up" error in app.test
Rework the test to always wait for the client request to finish before
calling the test done.
2016-08-04 15:51:53 +02:00
Miroslav Bajtoš 5d18d41b28 test: increate timeout in Role test 2016-08-04 13:32:47 +02:00
Miroslav Bajtoš caaa296a82 test: make status test more robust
Rework assertions to report helpful messages on failure.

Increase the "elapsed" limit from 100ms to 300ms to support our
slow CI machines.
2016-08-04 11:00:00 +02:00
Miroslav Bajtoš 48205fb2bd test: fix broken Role tests
Rework the test suite to always report errors and correctly signal
when async tests are done.

This should prevent spurious test failures on CI servers that are
difficult to troubleshoot, because the error is reported for different
test case.
2016-08-03 16:17:58 +02:00
Miroslav Bajtoš 7546ee531d Update dependencies to their latest versions 2016-08-03 16:17:58 +02:00
Miroslav Bajtoš 46435bde0f Merge pull request #2567 from strongloop/fix/CI-timeout
Increase timeout
2016-08-03 16:15:32 +02:00
jannyHou 17a046d7a1 Increase timeout 2016-07-29 14:54:34 -04:00
Candy 389fd85218 Merge pull request #2565 from strongloop/fix/misconfigured-change-replication
test: fix change-tracking setup
2016-07-29 11:27:52 -04:00
Miroslav Bajtoš fd0b6fcb96 test: fix change-tracking setup
The remote-connector test has misconfigured the client (remote) model,
where the client model was trying to keep track of changes. That's
redundant because it's up to the server model (attached directly to the
database) to track changes.

This commit fixes that configuration and also cleans up the test code
a little bit to make it easier to distinguish between the remote
(client) model and the server model.
2016-07-29 16:49:29 +02:00
Miroslav Bajtoš ea21169da0 Merge pull request #2551 from strongloop/fix/global-registry-in-fixtures
test: use local registry in test fixtures
2016-07-27 14:59:49 +02:00
Miroslav Bajtoš 98816217c9 test: use local registry in test fixtures
Use local registry in test fixtures to prevent collision in globally
shared models.

Fix issues discoverd in auth implementation where the global registry
was used instead of the correct local one.
2016-07-27 10:07:49 +02:00
Rand McKinney 1b55d35542 Update loopback.js
Fix doc comment per #2534
2016-07-26 15:52:40 -07:00
Loay 68ed6166d8 Merge pull request #2549 from strongloop/fix/issue580-test
Fix test case error
2016-07-26 13:23:41 -04:00
Loay 0fa3327112 Fix test case error 2016-07-26 10:26:44 -04:00
Loay 8f7e032a01 Update user.js 2016-07-25 00:55:55 -04:00
Loay c22803c3a3 Merge pull request #2492 from strongloop/fix-issue580
Fix security issue 580
2016-07-22 22:20:27 -04:00
Loay b53a22bfb3 Fix security issue 580 2016-07-22 17:48:57 -04:00
Gunjan Pandya 7ed003e973 Merge pull request #2419 from strongloop/remove-legacyExplorer
[SEMVER-MAJOR] Remove legacyExplorer
2016-07-14 14:35:38 -04:00
Ryan Graham 7117dbcf12 Update URLs in CONTRIBUTING.md (#2503) 2016-07-13 17:45:00 -07:00
gunjpan 1b053d44fe Remove legacyExplorer
- Removes backward compatibility
for legacy end points `/models` & `/routes`
- Removes `legacyExplorer` flag which
enabled these routes
- Update related tests & tests using the
legacyExplorer flag
2016-06-20 16:05:11 -04:00
Candy 2e06ded55f Merge pull request #2437 from strongloop/remove_rectify_change
Remove `rectifyAllChanges` and `rectifyChange`
2016-06-20 14:07:54 -04:00
Candy 3bf9065203 Remove `rectifyAllChanges` and `rectifyChange` 2016-06-20 10:46:16 -04:00
Loay 45e523d66c Merge pull request #2440 from strongloop/issue414_token2
Fix verificationToken bug
2016-06-17 11:12:16 -04:00
Loay ec51e833b6 Fix verificationToken bug 2016-06-17 10:21:59 -04:00
Loay 03e85b838d Merge pull request #2446 from strongloop/issue2445
update express version
2016-06-17 09:53:15 -04:00
Loay 4a02f97572 update express version 2016-06-17 03:52:37 -04:00
Miroslav Bajtoš 42c83f69a0 Cleanup unit-test added in 1fc51d129 2016-06-14 14:47:41 +02:00
Loay 74aba5f80c Merge pull request #2411 from strongloop/strongErrorHandler
[SEMVER-MAJOR] Remove `loopback#errorHandler` middleware
2016-06-13 16:47:24 -04:00
Loay 1fc51d1296 update errorHandler template 2016-06-13 11:18:09 -04:00
Miroslav Bajtoš a2e199f025 3.0.0-alpha.2
* add missing unit tests for #2108 (Benjamin Kroeger)
 * Expose `Replace*` methods (Amir Jafarian)
 * Update tests for strong-error-handler (David Cheung)
 * Remove legacy express 3.x middleware getters (Miroslav Bajtoš)
 * Docuemtation for `replace*` methods (Amir Jafarian)
 * Make the doc clear for `findORCreate` cb (Amir Jafarian)
 * Fix JSCS unsupported rule error (Jason)
 * Remove env.json and strong-pm dir (Ritchie Martori)
 * Throw error upon extending unknown model (David Cheung)
 * Remove unused UserModel properties (David Cheung)
 * Remove Change.handleError (Candy)
 * Update user.js (Rik)
 * Separate error-checking and next/done logic from other logic in the test suite (Supasate Choochaisri)
 * Clean up by removing unnecessary comments (Supasate Choochaisri)
 * Add feature to not allow duplicate role name (Supasate Choochaisri)
 * update copyright statements (Ryan Graham)
 * relicense as MIT only (Ryan Graham)
 * Upgrade phantomjs to 2.x (Miroslav Bajtoš)
 * app: send port:0 instead of port:undefined (Miroslav Bajtoš)
 * travis: drop node@5, add node@6 (Miroslav Bajtoš)
 * Disable DEBUG output for eslint on Jenkins CI (Miroslav Bajtoš)
 * Remove "loopback.autoAttach()" (Miroslav Bajtoš)
 * test/rest.middleware: use local registry (Miroslav Bajtoš)
 * Fix role.isOwner to support app-local registry (Miroslav Bajtoš)
 * test/user: use local registry (Miroslav Bajtoš)
 * Resolver support return promise (juehou)
 * remove @private from jsdoc (Manu Phatak)
 * Fixes for emit `remoteMethodDisabled` PR (Simon Ho)
 * Add new feature to emit a `remoteMethodDisabled` event when disabling a remote method. (Supasate Choochaisri)
 * Fix typo in Model.nestRemoting (Tim Needham)
 * Update loopback.js (Rand McKinney)
 * Allow built-in token middleware to run repeatedly (Benjamin Kröger)
 * Use eslint with loopback config (Miroslav Bajtoš)
 * promise docs (Jue Hou)
 * Update JSDoc (sghung@ca.ibm.com)
 * Remove constraint making isStatic required (Candy)
 * Fix inconsistencies in JSDoc (sghung@ca.ibm.com)
 * Improve error message on connector init error (Miroslav Bajtoš)
 * application: correct spelling of "cannont" (Sam Roberts)
 * Remove sl-blip from dependency (Candy)
 * Use new strong-remoting API (Candy)
 * test: remove forgotten console.trace logs (Miroslav Bajtoš)
 * Fix race condition in replication tests (Miroslav Bajtoš)
 * Fix race condition in error handler test (Miroslav Bajtoš)
 * test: remove errant console.log from test (Ryan Graham)
 * Promisify Model Change (Jue Hou)
 * Travis: drop iojs, add v4.x and v5.x (Miroslav Bajtoš)
 * test: use ephemeral port for e2e server (Ryan Graham)
 * test: fail on error instead of crash (Ryan Graham)
 * ensure app is booted before integration tests (Ryan Graham)
 * Remove "loopback.DataModel" (Miroslav Bajtoš)
 * Correct JSDoc findOrCreate() callback in PersistedModel (Chris Coggburn)
 * Fix typo in package.json (publishConfig) (Miroslav Bajtoš)
 * Start development of 3.0 (Candy)
 * Hide verificationToken (Samuel Gaus)
 * Fix description for User.prototype.hasPassword (Jue Hou)
 * Checkpoint speedup (Amir Jafarian)
 * Always use bluebird as promise library Replace `global.Promise` with `bluebird` (Jue Hou)
 * Remove unused code from loopback-testing-helper (Simon Ho)
 * Make juggler a regular dependency (Miroslav Bajtoš)
 * Remove dependency on loopback-testing (Simon Ho)
 * Fix failing tests (Simon Ho)
 * Update persisted-model.js (Rand McKinney)
 * Update persisted-model.js (linguofeng)
2016-06-13 17:04:46 +02:00
Miroslav Bajtoš 103935cef7 Merge pull request #2227 from benkroeger/hotfix/issue-2226
add missing unit tests for #2108

Close #2227
2016-06-13 15:29:26 +02:00
Benjamin Kroeger 83b5d72073 add missing unit tests for #2108
subsequent token middleware tries to read `token.id` when `enableDoublecheck: true`. That caused a "Cannot read property `id` of `null`" error when the first middleware didn't actually find a valid accessToken.
2016-06-13 15:27:59 +02:00
Amir-61 6bbe7be266 Merge pull request #2316 from strongloop/expose_replace_methods
Expose `Replace*` methods
2016-06-13 09:17:02 -04:00
Amir Jafarian 6502309e34 Expose `Replace*` methods
*Re-mapping `updateAttributes` endpoint to use
`PATCH` and `PUT`(configurable) verb
*Exposing `replaceById` and `replaceOrCreate` via
`POST` and `PUT`(configurable) verb
2016-06-10 14:56:44 -04:00
David Cheung ed45358be8 Merge pull request #2375 from strongloop/fix-remoting-strongerrorhandler
[SEMVER-MAJOR] Fix remoting strong-error-handler
2016-06-07 17:36:47 -04:00
David Cheung ddb5327e64 Update tests for strong-error-handler
Fix rest-adapter related test case switching to strong-error-handler
Only affect the test-cases calling rest methods
2016-06-07 13:26:18 -04:00
Miroslav Bajtoš 276fb5bf69 Merge pull request #2394 from strongloop/remove-express-middleware
[SEMVER-MAJOR] Remove legacy express 3.x middleware getters
2016-06-01 08:33:39 +02:00
Miroslav Bajtoš 8d295b70f6 Remove legacy express 3.x middleware getters
Remove middleware-getter properties that were simlifying upgrade from
LoopBack 1.x/Express 3.x applications:

  - loopback.compress
  - loopback.timeout
  - loopback.cookieParser
  - loopback.cookieSession
  - loopback.csrf
  - loopback.errorHandler
  - loopback.session
  - loopback.methodOverride
  - loopback.logger
  - loopback.responseTime
  - loopback.favicon
  - loopback.directory
  - loopback.vhost

Also remove `loopback.mime`, which was set to `undefined` anyways.
2016-05-31 18:59:21 +02:00
Rand McKinney 70cec0755a Merge pull request #2360 from strongloop/doc/replace
Docuemtation for `replace*` methods
2016-05-20 14:07:25 -07:00
Amir Jafarian eb8f398c6a Docuemtation for `replace*` methods 2016-05-20 16:29:25 -04:00
Amir-61 a9628b9f63 Merge pull request #2349 from strongloop/findORCreate_doc
Make the doc clear for `findORCreate` cb
2016-05-17 18:03:26 -04:00
Amir Jafarian 5c3d021fe7 Make the doc clear for `findORCreate` cb 2016-05-17 17:50:13 -04:00
Miroslav Bajtoš 4fd1d2bcd4 Merge pull request #2327 from strongloop/fix/extra-strong-pm-file
Remove env.json and strong-pm dir
2016-05-13 13:44:09 +02:00
Simon Ho 9ec52ec415 Merge pull request #2336 from jasonwoan/fix/jscs-validatejsdoc-error
Fix JSCS unsupported rule error
2016-05-12 16:52:37 -07:00
Jason 31158f0427 Fix JSCS unsupported rule error
Replace 'validateJSDoc' rule with 'jsDoc'. 'validateJSDoc' was
deprecated in v1.7.0.

In related news, JSCS was recently deprecated in favor of ESlint
so .jscrc can be removed once features have been rolled over.
2016-05-11 23:06:22 -07:00
David Cheung aa47f79ca6 Merge pull request #2301 from strongloop/error-extend-unknown-model
Throw descriptive error upon extending unknown model
2016-05-11 10:39:21 -04:00
Ritchie Martori 09da46a34d Remove env.json and strong-pm dir 2016-05-10 15:23:38 -07:00
David Cheung e723d8b641 Throw error upon extending unknown model
Create Model now uses findModel to retrieve base instead of getModel
and throws error upon base model not found
2016-05-10 18:00:56 -04:00
David Cheung a6f8c07301 Merge pull request #2299 from strongloop/removed-unused-user-properties
[SEMVER-MAJOR] Removed unused user properties
2016-05-10 14:32:32 -04:00
David Cheung 817e76e424 Remove unused UserModel properties
- credentials
- challenges
- status
- created
- lastUpdated
2016-05-10 14:29:08 -04:00
Simon Ho 363bc4d6c1 Merge pull request #2310 from ambrt/ambrt-patch-1
Update user.js
2016-05-09 12:30:17 -07:00
Candy 8a6deb868c Merge pull request #2308 from strongloop/remove_change_handleError
Remove Change.handleError
2016-05-09 12:05:50 -04:00
Candy 8ab6fccdea Remove Change.handleError 2016-05-09 11:45:27 -04:00
Rik 341390a74e Update user.js
allow to change all {href} instances in user.verify() mail into generated url instead of just one
2016-05-08 13:10:56 +02:00
Simon Ho ca1baee0f9 Merge pull request #2297 from supasate/refactor/separate-error-checking-and-next-and-done-logic-from-others
Separate error-checking and next/done logic from other logic
2016-05-06 13:27:29 -07:00
Supasate Choochaisri 04e26fae5c Separate error-checking and next/done logic from other logic in the test suite
Signed-off-by: Supasate Choochaisri <supasate.c@gmail.com>
2016-05-05 11:12:48 +07:00
Simon Ho 1559db2ca3 Merge pull request #2269 from supasate/feature/do-not-allow-duplicate-role-name
Add feature to not allow duplicate role name
2016-05-04 20:42:13 -07:00
Supasate Choochaisri dd78b36a17 Clean up by removing unnecessary comments
Signed-off-by: Supasate Choochaisri <supasate.c@gmail.com>
2016-05-05 08:18:32 +07:00
Supasate Choochaisri d4a869bddf Add feature to not allow duplicate role name
Signed-off-by: Supasate Choochaisri <supasate.c@gmail.com>
2016-05-05 08:18:17 +07:00
Ryan Graham 6964914bab
update copyright statements 2016-05-03 15:50:21 -07:00
Ryan Graham 8acac40ec3
relicense as MIT only 2016-05-03 15:49:10 -07:00
Miroslav Bajtoš 097984154b Merge pull request #2283 from strongloop/add-node-v6-to-travis
travis: drop node@5, add node@6
2016-05-03 18:59:12 +02:00
Miroslav Bajtoš ed766f55b4 Upgrade phantomjs to 2.x 2016-05-03 16:43:45 +02:00
Miroslav Bajtoš 4d7154a31a app: send port:0 instead of port:undefined
Node v6 no longer supports port:undefined, this commit is fixing
app.listen() to correctly send port:0 when no port is specified.
2016-05-03 16:20:07 +02:00
Miroslav Bajtoš 462cec4c1c travis: drop node@5, add node@6 2016-05-03 16:20:07 +02:00
Miroslav Bajtoš 7bc303b4c5 Merge pull request #1989 from strongloop/feature/remove-auto-attach
[SEMVER-MAJOR] Remove "loopback.autoAttach()"
2016-05-03 14:35:51 +02:00
Miroslav Bajtoš 9b39a59813 Disable DEBUG output for eslint on Jenkins CI 2016-05-03 14:01:40 +02:00
Miroslav Bajtoš 87bbf4502a Remove "loopback.autoAttach()"
The method was deprecated since LoopBack 2.0, there is no need to keep
it around in 3.0.
2016-05-03 14:01:39 +02:00
Miroslav Bajtoš 0e6db30640 test/rest.middleware: use local registry
Rework tests in `test/rest.middleware.test.js` to not depend
on `app.autoAttach()` and global shared registry of Models. Instead,
each tests creates a fresh app instance with a new in-memory datasource
and a new set of Models.
2016-05-03 14:01:39 +02:00
Miroslav Bajtoš 35d9fa4b54 Fix role.isOwner to support app-local registry 2016-05-03 14:01:39 +02:00
Miroslav Bajtoš 095dce0373 test/user: use local registry
Rework User tests to not depend on `app.autoAttach()` and global shared
registry of Models. Instead, each tests creates a fresh app instance
with a new in-memory datasource and a new set of Models.
2016-05-03 14:01:39 +02:00
Janny 6b40c69bb3 Merge pull request #2259 from strongloop/feature/resolver-support-return-promise
Resolver support return promise
2016-05-02 21:07:35 -04:00
juehou dcf88baf68 Resolver support return promise 2016-05-02 17:47:14 -04:00
Simon Ho 972a657759 Merge pull request #2273 from bionikspoon/patch-1
remove @private from jsdoc
2016-04-30 18:21:27 -07:00
Manu Phatak 455f0fc0ac remove @private from jsdoc 2016-04-30 18:14:52 -05:00
Simon Ho 69b2b41692 Merge pull request #2272 from strongloop/fix/emit-remote-disabled-event
Fix for `remoteMethodDisabled` PR
2016-04-29 19:36:25 -07:00
Simon Ho d16c789638 Fixes for emit `remoteMethodDisabled` PR
See https://github.com/strongloop/loopback/pull/2266#issuecomment-215689358
2016-04-29 16:42:13 -07:00
Simon Ho 04f5434894 Merge pull request #2266 from supasate/feature/emit-remote-method-disabled-event
Emit a remoteMethodDisabled event when disabling a remote method
2016-04-27 19:53:00 -07:00
Supasate Choochaisri 0ca24389b2 Add new feature to emit a `remoteMethodDisabled` event when disabling a remote method.
Signed-off-by: Supasate Choochaisri <supasate.c@gmail.com>
2016-04-28 09:03:54 +07:00
Miroslav Bajtoš 4cd84dcd8a Merge pull request #2245 from strongloop/fix/nest-remoting-with-hooks
Fix typo in Model.nestRemoting
2016-04-20 09:34:43 +02:00
Tim Needham 159aaf5afa Fix typo in Model.nestRemoting
Prevent apps from crashing when using `Model.nestRemoting` without
`{ hooks: false }` option.

Note that it is not possible to reproduce this bug using our current
Mocha test suite, because other tests modify the global state in such
way that the bug no longer occurs.
2016-04-19 16:51:52 +02:00
Rand McKinney 58f32dd70e Update loopback.js
Update comment line that was creating invalid info in API docs.
2016-04-18 09:42:56 -07:00
Rand McKinney 88d41f84fd Merge pull request #2155 from sghung/master
Fix inconsistencies in JSDoc
2016-04-06 16:01:31 -07:00
Miroslav Bajtoš a9c7801380 Merge pull request #2108 from benkroeger/hotfix/issue-2106
Allow built-in token middleware to run repeatedly
2016-04-06 15:45:21 +02:00
Benjamin Kröger 9e0405de9f Allow built-in token middleware to run repeatedly
Add two new options:

  - When `enableDoublecheck` is true, the middleware will run
    even if a previous middleware has already set `req.accessToken`
    (possibly to `null` for anonymous requests)

  - When `overwriteExistingToken` is true (and `enableDoublecheck` too),
    the middleware will overwrite `req.accessToken` set by a previous
    middleware instances.
2016-04-06 15:44:20 +02:00
Miroslav Bajtoš 1e7adb21ae Merge pull request #2193 from strongloop/feature/eslint
Use eslint with loopback config
2016-04-06 13:05:01 +02:00
Miroslav Bajtoš f9702b0ace Use eslint with loopback config
Drop jshint and jscs in favour of eslint.

Fix style violations.

While we are at this, reduce the max line length from 150 to 100.
2016-04-06 10:45:30 +02:00
Janny 2a86e9535b Merge pull request #2040 from strongloop/feature/add-promise-doc
Feature/add promise doc
2016-04-05 01:39:08 +08:00
Jue Hou eb09681f21 promise docs
Add promise jsdoc in loopback
2016-04-04 12:35:35 -04:00
sghung@ca.ibm.com d69ab1e411 Update JSDoc 2016-04-01 14:00:01 -04:00
Candy 982b8ac228 Merge pull request #2174 from strongloop/change_remote_method
Remove constraint making isStatic required
2016-03-31 09:59:53 -04:00
Candy b5b900e0ff Remove constraint making isStatic required 2016-03-30 11:16:32 -04:00
Simon Ho b4e230389d Merge pull request #1780 from linguofeng/patch-1
Update persisted-model.js
2016-03-22 16:10:22 -07:00
sghung@ca.ibm.com 3302391ac7 Fix inconsistencies in JSDoc 2016-03-17 23:55:12 -04:00
Miroslav Bajtoš 913dd3a188 Merge pull request #2105 from strongloop/fix/err-msg-on-connector-error
Improve error message on connector init error
2016-03-02 13:17:59 +01:00
Miroslav Bajtoš 93d487ffc0 Improve error message on connector init error 2016-02-26 14:46:26 +01:00
Miroslav Bajtoš 05f35731b9 Merge pull request #2088 from strongloop/fix-cannont-misspelling
application: correct spelling of "cannont"
2016-02-19 13:12:12 +01:00
Sam Roberts b9acace932 application: correct spelling of "cannont" 2016-02-18 20:36:07 -08:00
Candy 804265b801 Remove sl-blip from dependency 2016-02-18 10:38:54 -05:00
Candy 632bbeec8f Merge pull request #2039 from strongloop/remote_method
Use new strong-remoting API
2016-02-08 09:13:18 -05:00
Candy 0e637962d5 Use new strong-remoting API 2016-02-05 11:11:38 -05:00
Miroslav Bajtoš 782e89758e test: remove forgotten console.trace logs 2016-02-05 12:39:47 +01:00
Miroslav Bajtoš 21ec3c8899 Merge pull request #2036 from strongloop/fix/ci-3.0
Fix race conditions in unit tests
2016-02-05 12:37:50 +01:00
Miroslav Bajtoš 91c1df96f0 Fix race condition in replication tests 2016-02-05 12:24:27 +01:00
Miroslav Bajtoš e7f49af7f4 Fix race condition in error handler test 2016-02-05 09:21:47 +01:00
Miroslav Bajtoš 7ed5cf88ca Merge pull request #2035 from strongloop/clean-xunit
test: remove errant console.log from test
2016-02-05 09:14:15 +01:00
Janny f86ee167c1 Merge pull request #1999 from strongloop/promisify-change
Promisify change
2016-02-04 15:50:19 -05:00
Ryan Graham ab5254fcba test: remove errant console.log from test
Using console.log like this can result in invalid xml when the xunit
reporter is used.
2016-02-04 08:35:37 -08:00
Jue Hou d26d6ff3ed Promisify Model Change
* Change.diff
* Change.findOrCreateChange
* Change.rectifyModelChanges
* Change.prototype.currentRevision
* Change.prototype.rectify
2016-02-04 11:05:23 -05:00
Miroslav Bajtoš 524058d8fc Travis: drop iojs, add v4.x and v5.x 2016-02-04 16:45:28 +01:00
Miroslav Bajtoš cd9e5d4173 Merge pull request #2030 from strongloop/safer-tests-step1
Safer tests - step 1
2016-02-04 16:09:11 +01:00
Ryan Graham b0959b7ad8 test: use ephemeral port for e2e server 2016-02-04 15:35:19 +01:00
Ryan Graham ef9ad587c8 test: fail on error instead of crash
If the supertest request fails its basic assertions, there may not even
be a body to perform checks against, so bail early when possible.
2016-02-04 15:35:19 +01:00
Ryan Graham c317204c74 ensure app is booted before integration tests 2016-02-04 15:35:19 +01:00
Miroslav Bajtoš 0ad150cb6e Merge pull request #1851 from gausie/patch-4
Hide verificationToken from JSON output
2016-01-25 14:23:52 +01:00
Miroslav Bajtoš 18da6993d9 Merge pull request #1988 from strongloop/feature/remove-data-model
[SEMVER-MAJOR] Remove "loopback.DataModel"
2016-01-25 10:21:07 +01:00
Miroslav Bajtoš 804c71d7c6 Remove "loopback.DataModel"
The model was just a temporary alias to simplify migration of code
based on <=2.0.0-beta3
2016-01-25 10:17:45 +01:00
Simon Ho 76bd587198 Merge pull request #1983 from noderat/patch-1
Correct JSDoc findOrCreate() callback in PersistedModel
2016-01-22 10:21:17 -08:00
Chris Coggburn 7252c7686c Correct JSDoc findOrCreate() callback in PersistedModel
Update PersistedModel.findOrCreate() JSDoc to reflect the callback accepts an additional created boolean parameter.
2016-01-21 20:40:19 -07:00
Candy 77eee6a817 Merge pull request #1957 from strongloop/start-3.0
Start development of 3.0
2016-01-20 11:43:16 -05:00
Amir-61 c9be67e4d3 Merge pull request #1908 from strongloop/checkpoint_speedup
Checkpoint speedup
2016-01-19 10:25:15 -05:00
Miroslav Bajtoš e06bd1a8b0 Fix typo in package.json (publishConfig) 2016-01-19 14:49:23 +01:00
Candy 492e7da3e9 Start development of 3.0 2016-01-13 16:38:37 -05:00
Janny 27c9e263e0 Merge pull request #1953 from strongloop/doc/user-hasPassword
Fix description for User.prototype.hasPassword
2016-01-13 03:14:18 +08:00
Samuel Gaus 2741d50342 Hide verificationToken
We should never be showing this publically.

Adds unit test for hiding verification token.
2016-01-12 15:48:03 +00:00
Simon Ho f1f0100311 Merge pull request #1944 from strongloop/remove-unused-code
Remove unused code from loopback-testing-helper
2016-01-11 12:11:30 -08:00
Jue Hou 865789017d Fix description for User.prototype.hasPassword 2016-01-11 14:28:10 -05:00
Amir Jafarian 08a2786b04 Checkpoint speedup 2016-01-09 01:56:13 -05:00
Janny 70984bd5c0 Merge pull request #1896 from strongloop/feature/upgrade-to-bluebird
[SEMVER-MAJOR] Always use bluebird as promise library
2016-01-09 03:31:32 +08:00
Jue Hou 889c561ed3 Always use bluebird as promise library
Replace `global.Promise` with `bluebird`
2016-01-08 13:58:12 -05:00
Simon Ho 4b30c27fa2 Remove unused code from loopback-testing-helper 2016-01-06 18:07:33 -08:00
Miroslav Bajtoš dd7fb60b41 Merge pull request #1943 from strongloop/feature/make-juggler-a-regular-dependency
[SEMVER-MAJOR] Make juggler a regular dependency
2016-01-06 18:44:32 +01:00
Miroslav Bajtoš 4ff035aac7 Make juggler a regular dependency 2016-01-06 15:53:35 +01:00
Simon Ho 05556ff661 Merge pull request #1935 from strongloop/remove-loopback-testing
Remove dependency on loopback-testing
2015-12-31 17:18:50 -08:00
Simon Ho 186e3e8f92 Remove dependency on loopback-testing
- Copy depedent source from loopback-testing into test/helpers
- Removed loopback-testing from package.json
2015-12-31 15:59:03 -08:00
Simon Ho cdb2605633 Merge pull request #1934 from strongloop/fix-failing-tests
Fix failing tests
2015-12-31 14:55:37 -08:00
Simon Ho e8179e119d Fix failing tests
JSCS is detecting improper whitespaces lib/persisted-model.js.
2015-12-31 14:04:09 -08:00
Rand McKinney d4040dc39d Merge pull request #1910 from strongloop/fix-findOrCreate-doc
Update persisted-model.js
2015-12-22 17:11:31 -08:00
Rand McKinney 1af03613cc Update persisted-model.js
- Correct doc for findOrCreate.
- Fix indentation
- Remove quotes in example `where`
- Fix typo
- Fix spacing in notional where clause
2015-12-22 17:08:19 -08:00
Miroslav Bajtoš b0a62422c1 3.0.0-alpha.1
* Update juggler to ^3.0.0-alpha.1 (Miroslav Bajtoš)
 * Start development of 3.0 (Miroslav Bajtoš)
2015-12-22 14:01:29 +01:00
Miroslav Bajtoš 326f11c8d0 Merge pull request #1909 from strongloop/start-3.0
Start development of 3.0
2015-12-22 13:36:15 +01:00
Miroslav Bajtoš d630b764a3 Update juggler to ^3.0.0-alpha.1 2015-12-22 13:17:04 +01:00
Miroslav Bajtoš e0ce1fc446 Start development of 3.0
- Update version number in package.json, publish under "next" tag
 - Add 3.0-DEVELOPING.md describing the process
 - Add 3.0-RELEASE-NOTES.md to incrementally build release docs
2015-12-22 12:45:56 +01:00
linguofeng 4717fb3dbd Update persisted-model.js
`find` to `findById`
2015-10-28 12:48:36 +08:00
197 changed files with 19805 additions and 5964 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
dist
coverage

10
.eslintrc Normal file
View File

@ -0,0 +1,10 @@
{
"extends": "loopback",
"rules": {
"max-len": ["error", 100, 4, {
"ignoreComments": true,
"ignoreUrls": true,
"ignorePattern": "^\\s*var\\s.+=\\s*(require\\s*\\()|(/)"
}]
}
}

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.

18
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,18 @@
<!--
Please provide a high-level description of the changes made by your pull request.
Include references to all related GitHub issues and other pull requests, for example:
Fixes #123
Implements #254
See also #23
-->
## Checklist
👉 [Read and sign the CLA (Contributor License Agreement)](https://cla.strongloop.com/agreements/strongloop/loopback) 👈
- [ ] `npm test` passes on your machine
- [ ] New tests added or existing tests modified to cover all changes
- [ ] Code conforms with the [style guide](https://loopback.io/doc/en/contrib/style-guide-es6.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.

2
.gitignore vendored
View File

@ -1,6 +1,7 @@
.idea
.project
.DS_Store
.vscode/
*.sublime*
*.seed
*.log
@ -13,3 +14,4 @@
node_modules
dist
*xunit.xml
.nyc_output/

23
.jscsrc
View File

@ -1,23 +0,0 @@
{
"preset": "google",
"requireCurlyBraces": [
"else",
"for",
"while",
"do",
"try",
"catch"
],
"disallowMultipleVarDecl": "exceptUndefined",
"disallowSpacesInsideObjectBrackets": null,
"maximumLineLength": {
"value": 150,
"allowComments": true,
"allowRegex": true
},
"validateJSDoc": {
"checkParamNames": false,
"checkRedundantParams": false,
"requireParamTypes": true
}
}

View File

@ -1 +0,0 @@
node_modules

View File

@ -1,34 +0,0 @@
{
"node": true,
"camelcase": true,
"eqnull": true,
"indent": 2,
"undef": true,
"quotmark": "single",
"newcap": true,
"nonew": true,
"sub": true,
"laxcomma": true,
"laxbreak": true,
"globals": {
/* mocha */
"after": true,
"afterEach": true,
"assert": true,
"before": true,
"beforeEach": true,
"context": true,
"describe": true,
"expect": true,
"it": true,
/* loopback */
"app": true,
"assertValidDataSource": true,
"GeoPoint": true,
"loopback": true,
"memoryConnector": true,
"request": true,
"TaskEmitter": true
}
}

1
.npmrc Normal file
View File

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

7
.nycrc Normal file
View File

@ -0,0 +1,7 @@
{
"exclude": [
"Gruntfile.js",
"test/**/*.js"
],
"cache": true
}

View File

View File

@ -1,7 +1,15 @@
sudo: false
language: node_js
node_js:
- "0.10"
- "0.12"
- "iojs"
- "8"
- "10"
- "12"
- "14"
addons:
chrome: stable
after_success: npm run coverage
before_install:
- npm config set registry http://ci.strongloop.com:4873/

1010
CHANGES.md

File diff suppressed because it is too large Load Diff

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

@ -147,5 +147,5 @@ Contributing to `loopback` is easy. In a few simple steps:
inaccurate in any respect. Email us at callback@strongloop.com.
```
[Google C++ Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
[Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
[Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html
[Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml

View File

@ -1,6 +1,11 @@
/*global module:false*/
module.exports = function(grunt) {
// Copyright IBM Corp. 2014,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';
module.exports = function(grunt) {
// Do not report warnings from unit-tests exercising deprecated paths
process.env.NO_DEPRECATION = 'loopback';
@ -18,58 +23,56 @@ module.exports = function(grunt) {
// Task configuration.
uglify: {
options: {
banner: '<%= banner %>'
banner: '<%= banner %>',
},
dist: {
files: {
'dist/loopback.min.js': ['dist/loopback.js']
}
}
},
jshint: {
options: {
jshintrc: true
'dist/loopback.min.js': ['dist/loopback.js'],
},
},
},
eslint: {
gruntfile: {
src: 'Gruntfile.js'
src: 'Gruntfile.js',
},
lib: {
src: ['lib/**/*.js']
src: ['lib/**/*.js'],
},
common: {
src: ['common/**/*.js']
},
browser: {
src: ['browser/**/*.js']
src: ['common/**/*.js'],
},
server: {
src: ['server/**/*.js']
src: ['server/**/*.js'],
},
test: {
src: ['test/**/*.js']
}
},
jscs: {
gruntfile: 'Gruntfile.js',
lib: ['lib/**/*.js'],
common: ['common/**/*.js'],
server: ['server/**/*.js'],
browser: ['browser/**/*.js'],
test: ['test/**/*.js']
src: ['test/**/*.js'],
},
},
watch: {
gruntfile: {
files: '<%= jshint.gruntfile.src %>',
tasks: ['jshint:gruntfile']
files: '<%= eslint.gruntfile.src %>',
tasks: ['eslint:gruntfile'],
},
browser: {
files: ['<%= eslint.browser.src %>'],
tasks: ['eslint:browser'],
},
common: {
files: ['<%= eslint.common.src %>'],
tasks: ['eslint:common'],
},
lib: {
files: ['<%= jshint.lib.src %>'],
tasks: ['jshint:lib']
files: ['<%= eslint.lib.src %>'],
tasks: ['eslint:lib'],
},
server: {
files: ['<%= eslint.server.src %>'],
tasks: ['eslint:server'],
},
test: {
files: ['<%= jshint.test.src %>'],
tasks: ['jshint:test']
}
files: ['<%= eslint.test.src %>'],
tasks: ['eslint:test'],
},
},
browserify: {
dist: {
@ -78,29 +81,30 @@ module.exports = function(grunt) {
},
options: {
ignore: ['nodemailer', 'passport', 'bcrypt'],
standalone: 'loopback'
}
}
standalone: 'loopback',
},
},
},
mochaTest: {
'unit': {
src: 'test/*.js',
options: {
reporter: 'dot',
}
require: require.resolve('./test/helpers/use-english.js'),
},
},
'unit-xml': {
src: 'test/*.js',
options: {
reporter: 'xunit',
captureFile: 'xunit.xml'
}
}
captureFile: 'xunit.xml',
},
},
},
karma: {
'unit-once': {
configFile: 'test/karma.conf.js',
browsers: ['PhantomJS'],
browsers: ['ChromeDocker'],
singleRun: true,
reporters: ['dots', 'junit'],
@ -109,7 +113,7 @@ module.exports = function(grunt) {
// CI friendly test output
junitReporter: {
outputFile: 'karma-xunit.xml'
outputFile: 'karma-xunit.xml',
},
browserify: {
@ -117,8 +121,8 @@ module.exports = function(grunt) {
// Fatal error: Maximum call stack size exceeded
debug: false,
// Disable watcher, grunt will exit after the first run
watch: false
}
watch: false,
},
},
unit: {
configFile: 'test/karma.conf.js',
@ -134,7 +138,7 @@ module.exports = function(grunt) {
// list of files / patterns to load in the browser
files: [
'test/e2e/remote-connector.e2e.js',
'test/e2e/replication.e2e.js'
'test/e2e/replication.e2e.js',
],
// list of files to exclude
@ -171,7 +175,7 @@ module.exports = function(grunt) {
// - PhantomJS
// - IE (only Windows)
browsers: [
'Chrome'
'Chrome',
],
// If browser does not capture in given timeout [ms], kill it
@ -190,7 +194,7 @@ module.exports = function(grunt) {
'passport-local',
'superagent',
'supertest',
'bcrypt'
'bcrypt',
],
// transform: ['coffeeify'],
// debug: true,
@ -199,25 +203,32 @@ module.exports = function(grunt) {
},
// Add browserify to preprocessors
preprocessors: {'test/e2e/*': ['browserify']}
}
}
}
preprocessors: {'test/e2e/*': ['browserify']},
},
},
},
});
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-jscs');
grunt.loadNpmTasks('grunt-karma');
grunt.registerTask('e2e-server', function() {
var done = this.async();
var app = require('./test/fixtures/e2e/app');
app.listen(3000, done);
const done = this.async();
const app = require('./test/fixtures/e2e/app');
app.listen(0, function() {
process.env.PORT = this.address().port;
done();
});
});
grunt.registerTask('skip-karma', function() {
console.log(`*** SKIPPING PHANTOM-JS BASED TESTS ON ${process.platform}` +
` ${process.arch} ***`);
});
grunt.registerTask('e2e', ['e2e-server', 'karma:e2e']);
@ -226,10 +237,12 @@ module.exports = function(grunt) {
grunt.registerTask('default', ['browserify']);
grunt.registerTask('test', [
'jscs',
'jshint',
'eslint',
process.env.JENKINS_HOME ? 'mochaTest:unit-xml' : 'mochaTest:unit',
'karma:unit-once']);
process.env.JENKINS_HOME && (/^win/.test(process.platform) ||
/^s390x/.test(process.arch) || /^ppc64/.test(process.arch)) ?
'skip-karma' : 'karma:unit-once',
]);
// alias for sl-ci-run and `npm test`
grunt.registerTask('mocha-and-karma', ['test']);

25
LICENSE Normal file
View File

@ -0,0 +1,25 @@
Copyright (c) IBM Corp. 2013,2018. All Rights Reserved.
Node module: loopback
This project is licensed under the MIT License, full text below.
--------
MIT license
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,9 +0,0 @@
Copyright (c) 2013-2015 StrongLoop, Inc and other contributors.
loopback uses a dual license model.
You may use this library under the terms of the [MIT License][],
or under the terms of the [StrongLoop Subscription Agreement][].
[MIT License]: http://opensource.org/licenses/MIT
[StrongLoop Subscription Agreement]: http://strongloop.com/license

View File

@ -1,6 +1,24 @@
# 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)
[![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:
@ -18,10 +36,24 @@ LoopBack consists of:
* Client SDKs for iOS, Android, and web clients.
LoopBack tools include:
* Command-line tool `slc loopback` to create applications, models, data sources, and so on.
* StrongLoop Arc, a graphical tool for editing LoopBack applications; and for deploying and monitoring applications.
* 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/).
## Module Long Term Support Policy
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
@ -48,7 +80,7 @@ The LoopBack framework is a set of Node.js modules that you can use independentl
### Community Connectors
The LoopBack community has created and supports a number of additional connectors. See [Community connectors](http://docs.strongloop.com/display/LB/Community+connectors) 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
* [loopback-component-push](https://github.com/strongloop/loopback-component-push)
@ -71,22 +103,23 @@ 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).
See [loopback-example](https://github.com/strongloop/loopback-example) for details.
See [examples at loopback.io](https://loopback.io/examples/) for details.
## Resources
* [Documentation](http://docs.strongloop.com/display/LB/LoopBack).
* [API documentation](http://apidocs.strongloop.com/loopback).
* [Documentation](https://loopback.io/doc/).
* [API documentation](https://apidocs.strongloop.com/loopback).
* [LoopBack Announcements](https://groups.google.com/forum/#!forum/loopbackjs-announcements)
* [LoopBack Google Group](https://groups.google.com/forum/#!forum/loopbackjs).
* [GitHub issues](https://github.com/strongloop/loopback/issues).
* [Gitter chat](https://gitter.im/strongloop/loopback).
## Contributing
See https://github.com/strongloop/loopback/wiki/Contributing-code
Contributions to the LoopBack project are welcome! See [Contributing to LoopBack](https://loopback.io/doc/en/contrib/index.html) for more information.
## Issues
## Reporting issues
See https://github.com/strongloop/loopback/wiki/Reporting-issues
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)

View File

@ -1,10 +0,0 @@
module.exports = function(loopback) {
loopback.getCurrentContext = function() {
return null;
};
loopback.runInContext =
loopback.createContext = function() {
throw new Error('Current context is not supported in the browser.');
};
};

View File

@ -1,11 +1,18 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*!
* Module Dependencies.
*/
var loopback = require('../../lib/loopback');
var assert = require('assert');
var uid = require('uid2');
var DEFAULT_TOKEN_LEN = 64;
'use strict';
const g = require('../../lib/globalize');
const loopback = require('../../lib/loopback');
const assert = require('assert');
const uid = require('uid2');
const DEFAULT_TOKEN_LEN = 64;
/**
* Token based authentication and access control.
@ -27,13 +34,6 @@ var DEFAULT_TOKEN_LEN = 64;
*/
module.exports = function(AccessToken) {
// Workaround for https://github.com/strongloop/loopback/issues/292
AccessToken.definition.rawProperties.created.default =
AccessToken.definition.properties.created.default = function() {
return new Date();
};
/**
* Anonymous Token
*
@ -79,94 +79,25 @@ module.exports = function(AccessToken) {
});
/**
* 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
* Extract the access token id from the HTTP request
* @param {Request} req HTTP request object
* @options {Object} [options] Each option array is used to add additional keys to find an `accessToken` for a `request`.
* @property {Array} [cookies] Array of cookie names.
* @property {Array} [headers] Array of header names.
* @property {Array} [params] Array of param names.
* @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.findForRequest = function(req, options, cb) {
if (cb === undefined && typeof options === 'function') {
cb = options;
options = {};
}
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('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 now = Date.now();
var created = this.created.getTime();
var elapsedSeconds = (now - created) / 1000;
var secondsToLive = this.ttl;
var isValid = elapsedSeconds < secondsToLive;
if (isValid) {
cb(null, isValid);
} else {
this.destroy(function(err) {
cb(err, isValid);
});
}
} catch (e) {
cb(e);
}
};
function tokenIdForRequest(req, options) {
var params = options.params || [];
var headers = options.headers || [];
var cookies = options.cookies || [];
var i = 0;
var length;
var id;
AccessToken.getIdForRequest = function(req, options) {
options = options || {};
let params = options.params || [];
let headers = options.headers || [];
let cookies = options.cookies || [];
let i = 0;
let length, id;
// https://github.com/strongloop/loopback/issues/1326
if (options.searchDefaultTokenKeys !== false) {
@ -176,12 +107,12 @@ module.exports = function(AccessToken) {
}
for (length = params.length; i < length; i++) {
var param = params[i];
const param = params[i];
// replacement for deprecated req.param()
id = req.params && req.params[param] !== undefined ? req.params[param] :
req.body && req.body[param] !== undefined ? req.body[param] :
req.query && req.query[param] !== undefined ? req.query[param] :
undefined;
req.query && req.query[param] !== undefined ? req.query[param] :
undefined;
if (typeof id === 'string') {
return id;
@ -194,11 +125,18 @@ module.exports = function(AccessToken) {
if (typeof id === 'string') {
// Add support for oAuth 2.0 bearer token
// 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) {
id = id.substring(7);
// Decode from base64
var buf = new Buffer(id, 'base64');
id = buf.toString('utf8');
if (options.bearerTokenBase64Encoded) {
// Decode from base64
const buf = new Buffer(id, 'base64');
id = buf.toString('utf8');
}
} else if (/^Basic /i.test(id)) {
id = id.substring(6);
id = (new Buffer(id, 'base64')).toString('utf8');
@ -209,7 +147,7 @@ module.exports = function(AccessToken) {
// "a2b2c3:" (curl http://a2b2c3@localhost:3000/)
// "token:a2b2c3" (curl http://token:a2b2c3@localhost:3000/)
// ":a2b2c3"
var parts = /^([^:]*):(.*)$/.exec(id);
const parts = /^([^:]*):(.*)$/.exec(id);
if (parts) {
id = parts[2].length > parts[1].length ? parts[2] : parts[1];
}
@ -228,5 +166,116 @@ module.exports = function(AccessToken) {
}
}
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

@ -11,8 +11,13 @@
"default": 1209600,
"description": "time to live in seconds (2 weeks by default)"
},
"scopes": {
"type": ["string"],
"description": "Array of scopes granted to this access token."
},
"created": {
"type": "Date"
"type": "Date",
"defaultFn": "now"
}
},
"relations": {
@ -27,12 +32,6 @@
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"property": "create",
"permission": "ALLOW"
}
]
}

View File

@ -1,10 +1,13 @@
// Copyright IBM Corp. 2014,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';
/*!
Schema ACL options
Object level permissions, for example, an album owned by a user
Factors to be authorized against:
* model name: Album
* model instance properties: userId of the album, friends, shared
* methods
@ -16,32 +19,31 @@
** none
** everyone
** relations: owner/friend/granted
Class level permissions, for example, Album
* model name: Album
* methods
URL/Route level permissions
* url pattern
* application id
* ip addresses
* http headers
Map to oAuth 2.0 scopes
*/
var loopback = require('../../lib/loopback');
var async = require('async');
var assert = require('assert');
var debug = require('debug')('loopback:security:acl');
const g = require('../../lib/globalize');
const loopback = require('../../lib/loopback');
const utils = require('../../lib/utils');
const async = require('async');
const extend = require('util')._extend;
const assert = require('assert');
const debug = require('debug')('loopback:security:acl');
var ctx = require('../../lib/access-context');
var AccessContext = ctx.AccessContext;
var Principal = ctx.Principal;
var AccessRequest = ctx.AccessRequest;
const ctx = require('../../lib/access-context');
const AccessContext = ctx.AccessContext;
const Principal = ctx.Principal;
const AccessRequest = ctx.AccessRequest;
var Role = loopback.Role;
const Role = loopback.Role;
assert(Role, 'Role model must be defined before ACL model');
/**
@ -67,7 +69,7 @@ assert(Role, 'Role model must be defined before ACL model');
* - 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.
* - DENY: Explicitly denies access to the resource.
* @property {String} principalType Type of the principal; one of: Application, Use, 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 {Object} settings Extends the `Model.settings` object.
* @property {String} settings.defaultPermission Default permission setting: ALLOW, DENY, ALARM, or AUDIT. Default is ALLOW.
@ -78,7 +80,6 @@ assert(Role, 'Role model must be defined before ACL model');
*/
module.exports = function(ACL) {
ACL.ALL = AccessContext.ALL;
ACL.DEFAULT = AccessContext.DEFAULT; // Not specified
@ -97,6 +98,8 @@ module.exports = function(ACL) {
ACL.ROLE = Principal.ROLE;
ACL.SCOPE = Principal.SCOPE;
ACL.DEFAULT_SCOPE = ctx.DEFAULT_SCOPES[0];
/**
* Calculate the matching score for the given rule and request
* @param {ACL} rule The ACL entry
@ -104,17 +107,18 @@ module.exports = function(ACL) {
* @returns {Number}
*/
ACL.getMatchingScore = function getMatchingScore(rule, req) {
var props = ['model', 'property', 'accessType'];
var score = 0;
const props = ['model', 'property', 'accessType'];
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
score = score * 4;
var ruleValue = rule[props[i]] || ACL.ALL;
var requestedValue = req[props[i]] || ACL.ALL;
var isMatchingMethodName = props[i] === 'property' && req.methodNames.indexOf(ruleValue) !== -1;
const ruleValue = rule[props[i]] || ACL.ALL;
const requestedValue = req[props[i]] || ACL.ALL;
const isMatchingMethodName = props[i] === 'property' &&
req.methodNames.indexOf(ruleValue) !== -1;
var isMatchingAccessType = ruleValue === requestedValue;
let isMatchingAccessType = ruleValue === requestedValue;
if (props[i] === 'accessType' && !isMatchingAccessType) {
switch (ruleValue) {
case ACL.EXECUTE:
@ -203,22 +207,23 @@ module.exports = function(ACL) {
/*!
* Resolve permission from the ACLs
* @param {Object[]) acls The list of ACLs
* @param {Object} req The request
* @returns {AccessRequest} result The effective ACL
* @param {AccessRequest} req The access request
* @returns {AccessRequest} result The resolved access request
*/
ACL.resolvePermission = function resolvePermission(acls, req) {
if (!(req instanceof AccessRequest)) {
req.registry = this.registry;
req = new AccessRequest(req);
}
// Sort by the matching score in descending order
acls = acls.sort(function(rule1, rule2) {
return ACL.getMatchingScore(rule2, req) - ACL.getMatchingScore(rule1, req);
});
var permission = ACL.DEFAULT;
var score = 0;
let permission = ACL.DEFAULT;
let score = 0;
for (var i = 0; i < acls.length; i++) {
var candidate = acls[i];
for (let i = 0; i < acls.length; i++) {
const candidate = acls[i];
score = ACL.getMatchingScore(candidate, req);
if (score < 0) {
// the highest scored ACL did not match
@ -234,10 +239,11 @@ module.exports = function(ACL) {
break;
}
// For wildcard match, find the strongest permission
var candidateOrder = AccessContext.permissionOrder[candidate.permission];
var permissionOrder = AccessContext.permissionOrder[permission];
const candidateOrder = AccessContext.permissionOrder[candidate.permission];
const permissionOrder = AccessContext.permissionOrder[permission];
if (candidateOrder > permissionOrder) {
permission = candidate.permission;
break;
}
}
}
@ -249,9 +255,16 @@ module.exports = function(ACL) {
debug('with score:', acl.score(req));
});
}
const res = new AccessRequest({
model: req.model,
property: req.property,
accessType: req.accessType,
permission: permission || ACL.DEFAULT,
registry: this.registry});
// Elucidate permission status if DEFAULT
res.settleDefaultPermission();
var res = new AccessRequest(req.model, req.property, req.accessType,
permission || ACL.DEFAULT);
return res;
};
@ -263,11 +276,11 @@ module.exports = function(ACL) {
* @return {Object[]} An array of ACLs
*/
ACL.getStaticACLs = function getStaticACLs(model, property) {
var modelClass = loopback.findModel(model);
var staticACLs = [];
const modelClass = this.registry.findModel(model);
const staticACLs = [];
if (modelClass && modelClass.settings.acls) {
modelClass.settings.acls.forEach(function(acl) {
var prop = acl.property;
let prop = acl.property;
// We support static ACL property with array of string values.
if (Array.isArray(prop) && prop.indexOf(property) >= 0)
prop = property;
@ -278,12 +291,12 @@ module.exports = function(ACL) {
principalType: acl.principalType,
principalId: acl.principalId, // TODO: Should it be a name?
accessType: acl.accessType || ACL.ALL,
permission: acl.permission
permission: acl.permission,
}));
}
});
}
var prop = modelClass && (
const prop = modelClass && (
// regular property
modelClass.definition.properties[property] ||
// relation/scope
@ -300,7 +313,7 @@ module.exports = function(ACL) {
principalType: acl.principalType,
principalId: acl.principalId,
accessType: acl.accessType,
permission: acl.permission
permission: acl.permission,
}));
});
}
@ -315,51 +328,51 @@ module.exports = function(ACL) {
* @param {String} property The property/method/relation name.
* @param {String} accessType The access type.
* @callback {Function} callback Callback function.
* @param {String|Error} err The error object
* @param {AccessRequest} result The access permission
* @param {String|Error} err The error object.
* @param {AccessRequest} result The resolved access request.
*/
ACL.checkPermission = function checkPermission(principalType, principalId,
model, property, accessType,
callback) {
model, property, accessType,
callback) {
if (!callback) callback = utils.createPromiseCallback();
if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) {
principalId = principalId.toString();
}
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;
var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL, ACL.EXECUTE]};
const accessTypeQuery = (accessType === ACL.ALL) ? undefined :
{inq: [accessType, ACL.ALL, ACL.EXECUTE]};
var req = new AccessRequest(model, property, accessType);
const req = new AccessRequest({model, property, accessType, registry: this.registry});
var acls = this.getStaticACLs(model, property);
let acls = this.getStaticACLs(model, property);
var resolved = this.resolvePermission(acls, req);
// resolved is an instance of AccessRequest
let resolved = this.resolvePermission(acls, req);
if (resolved && resolved.permission === ACL.DENY) {
debug('Permission denied by statically resolved permission');
debug(' Resolved Permission: %j', resolved);
process.nextTick(function() {
if (callback) callback(null, resolved);
callback(null, resolved);
});
return;
return callback.promise;
}
var self = this;
const self = this;
this.find({where: {principalType: principalType, principalId: principalId,
model: model, property: propertyQuery, accessType: accessTypeQuery}},
function(err, dynACLs) {
if (err) {
if (callback) callback(err);
return;
}
acls = acls.concat(dynACLs);
resolved = self.resolvePermission(acls, req);
if (resolved && resolved.permission === ACL.DEFAULT) {
var modelClass = loopback.findModel(model);
resolved.permission = (modelClass && modelClass.settings.defaultPermission) || ACL.ALLOW;
}
if (callback) callback(null, resolved);
});
model: model, property: propertyQuery, accessType: accessTypeQuery}},
function(err, dynACLs) {
if (err) {
return callback(err);
}
acls = acls.concat(dynACLs);
// resolved is an instance of AccessRequest
resolved = self.resolvePermission(acls, req);
return callback(null, resolved);
});
return callback.promise;
};
ACL.prototype.debug = function() {
@ -374,62 +387,117 @@ module.exports = function(ACL) {
}
};
// NOTE Regarding ACL.isAllowed() and ACL.prototype.isAllowed()
// Extending existing logic, including from ACL.checkAccessForContext() method,
// ACL instance with missing property `permission` are not promoted to
// permission = ACL.DEFAULT config. Such ACL instances will hence always be
// inefective
/**
* Test if ACL's permission is ALLOW
* @param {String} permission The permission to test, expects one of 'ALLOW', 'DENY', 'DEFAULT'
* @param {String} defaultPermission The default permission to apply if not providing a finite one in the permission parameter
* @returns {Boolean} true if ACL permission is ALLOW
*/
ACL.isAllowed = function(permission, defaultPermission) {
if (permission === ACL.DEFAULT) {
permission = defaultPermission || ACL.ALLOW;
}
return permission !== loopback.ACL.DENY;
};
/**
* Test if ACL's permission is ALLOW
* @param {String} defaultPermission The default permission to apply if missing in ACL instance
* @returns {Boolean} true if ACL permission is ALLOW
*/
ACL.prototype.isAllowed = function(defaultPermission) {
return this.constructor.isAllowed(this.permission, defaultPermission);
};
/**
* Check if the request has the permission to access.
* @options {Object} context See below.
* @options {AccessContext|Object} context
* An AccessContext instance or a plain object with the following properties.
* @property {Object[]} principals An array of principals.
* @property {String|Model} model The model name or model class.
* @property {*} id The model instance ID.
* @property {*} modelId The model instance ID.
* @property {String} property The property/method/relation name.
* @property {String} accessType The access type:
* READ, REPLICATE, WRITE, or EXECUTE.
* @param {Function} callback Callback function
* READ, REPLICATE, WRITE, or EXECUTE.
* @callback {Function} callback Callback function
* @param {String|Error} err The error object.
* @param {AccessRequest} result The resolved access request.
*/
ACL.checkAccessForContext = function(context, callback) {
var registry = this.registry;
if (!callback) callback = utils.createPromiseCallback();
const self = this;
self.resolveRelatedModels();
const roleModel = self.roleModel;
if (!(context instanceof AccessContext)) {
context.registry = this.registry;
context = new AccessContext(context);
}
var model = context.model;
var property = context.property;
var accessType = context.accessType;
var modelName = context.modelName;
let authorizedRoles = {};
const remotingContext = context.remotingContext;
const model = context.model;
const modelDefaultPermission = model && model.settings.defaultPermission;
const property = context.property;
const accessType = context.accessType;
const modelName = context.modelName;
var methodNames = context.methodNames;
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])};
const methodNames = context.methodNames;
const propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])};
var accessTypeQuery = (accessType === ACL.ALL) ?
const accessTypeQuery = (accessType === ACL.ALL) ?
undefined :
(accessType === ACL.REPLICATE) ?
{inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL]} :
{inq: [accessType, ACL.ALL]};
var req = new AccessRequest(modelName, property, accessType, ACL.DEFAULT, methodNames);
const req = new AccessRequest({
model: modelName,
property,
accessType,
permission: ACL.DEFAULT,
methodNames,
registry: this.registry});
var effectiveACLs = [];
var staticACLs = this.getStaticACLs(model.modelName, property);
if (!context.isScopeAllowed()) {
req.permission = ACL.DENY;
debug('--Denied by scope config--');
debug('Scopes allowed:', context.accessToken.scopes || ctx.DEFAULT_SCOPES);
debug('Scope required:', context.getScopes());
context.debug();
callback(null, req);
return callback.promise;
}
var self = this;
var roleModel = registry.getModelByType(Role);
this.find({where: {model: model.modelName, property: propertyQuery,
accessType: accessTypeQuery}}, function(err, acls) {
if (err) {
if (callback) callback(err);
return;
}
var inRoleTasks = [];
const effectiveACLs = [];
const staticACLs = self.getStaticACLs(model.modelName, property);
const query = {
where: {
model: {inq: [model.modelName, ACL.ALL]},
property: propertyQuery,
accessType: accessTypeQuery,
},
};
this.find(query, function(err, acls) {
if (err) return callback(err);
const inRoleTasks = [];
acls = acls.concat(staticACLs);
acls.forEach(function(acl) {
// Check exact matches
for (var i = 0; i < context.principals.length; i++) {
var p = context.principals[i];
var typeMatch = p.type === acl.principalType;
var idMatch = String(p.id) === String(acl.principalId);
for (let i = 0; i < context.principals.length; i++) {
const p = context.principals[i];
const typeMatch = p.type === acl.principalType;
const idMatch = String(p.id) === String(acl.principalId);
if (typeMatch && idMatch) {
effectiveACLs.push(acl);
return;
@ -443,6 +511,9 @@ module.exports = function(ACL) {
function(err, inRole) {
if (!err && inRole) {
effectiveACLs.push(acl);
// add the role to authorizedRoles if allowed
if (acl.isAllowed(modelDefaultPermission))
authorizedRoles[acl.principalId] = true;
}
done(err, acl);
});
@ -451,22 +522,33 @@ module.exports = function(ACL) {
});
async.parallel(inRoleTasks, function(err, results) {
if (err) {
if (callback) callback(err, null);
return;
}
if (err) return callback(err, null);
var resolved = self.resolvePermission(effectiveACLs, req);
if (resolved && resolved.permission === ACL.DEFAULT) {
resolved.permission = (model && model.settings.defaultPermission) || ACL.ALLOW;
}
// resolved is an instance of AccessRequest
const resolved = self.resolvePermission(effectiveACLs, req);
debug('---Resolved---');
resolved.debug();
if (callback) callback(null, resolved);
// set authorizedRoles in remotingContext options argument if
// resolved AccessRequest permission is ALLOW, else set it to empty object
authorizedRoles = resolved.isAllowed() ? authorizedRoles : {};
saveAuthorizedRolesToRemotingContext(remotingContext, authorizedRoles);
return callback(null, resolved);
});
});
return callback.promise;
};
function saveAuthorizedRolesToRemotingContext(remotingContext, authorizedRoles) {
const options = remotingContext && remotingContext.args && remotingContext.args.options;
// authorizedRoles key/value map is added to the options argument only if
// the latter exists and is an object. This means that the feature's availability
// will depend on the app configuration
if (options && typeof options === 'object') { // null is object too
options.authorizedRoles = authorizedRoles;
}
}
/**
* Check if the given access token can invoke the method
* @param {AccessToken} token The access token
@ -479,31 +561,30 @@ module.exports = function(ACL) {
*/
ACL.checkAccessForToken = function(token, model, modelId, method, callback) {
assert(token, 'Access token is required');
var context = new AccessContext({
if (!callback) callback = utils.createPromiseCallback();
const context = new AccessContext({
registry: this.registry,
accessToken: token,
model: model,
property: method,
method: method,
modelId: modelId
modelId: modelId,
});
this.checkAccessForContext(context, function(err, access) {
if (err) {
if (callback) callback(err);
return;
}
if (callback) callback(null, access.permission !== ACL.DENY);
this.checkAccessForContext(context, function(err, accessRequest) {
if (err) callback(err);
else callback(null, accessRequest.isAllowed());
});
return callback.promise;
};
ACL.resolveRelatedModels = function() {
if (!this.roleModel) {
var reg = this.registry;
this.roleModel = reg.getModelByType(loopback.Role);
this.roleMappingModel = reg.getModelByType(loopback.RoleMapping);
this.userModel = reg.getModelByType(loopback.User);
this.applicationModel = reg.getModelByType(loopback.Application);
const reg = this.registry;
this.roleModel = reg.getModelByType('Role');
this.roleMappingModel = reg.getModelByType('RoleMapping');
this.userModel = reg.getModelByType('User');
this.applicationModel = reg.getModelByType('Application');
}
};
@ -511,30 +592,47 @@ module.exports = function(ACL) {
* Resolve a principal by type/id
* @param {String} type Principal type - ROLE/APP/USER
* @param {String|Number} id Principal id or name
* @param {Function} cb Callback function
* @callback {Function} callback Callback function
* @param {String|Error} err The error object
* @param {Object} result An instance of principal (Role, Application or User)
*/
ACL.resolvePrincipal = function(type, id, cb) {
cb = cb || utils.createPromiseCallback();
type = type || ACL.ROLE;
this.resolveRelatedModels();
switch (type) {
case ACL.ROLE:
this.roleModel.findOne({where: {or: [{name: id}, {id: id}]}}, cb);
break;
case ACL.USER:
this.userModel.findOne(
{where: {or: [{username: id}, {email: id}, {id: id}]}}, cb);
{where: {or: [{username: id}, {email: id}, {id: id}]}}, cb,
);
break;
case ACL.APP:
this.applicationModel.findOne(
{where: {or: [{name: id}, {email: id}, {id: id}]}}, cb);
{where: {or: [{name: id}, {email: id}, {id: id}]}}, cb,
);
break;
default:
process.nextTick(function() {
var err = new Error('Invalid principal type: ' + type);
err.statusCode = 400;
cb(err);
});
// try resolving a user model with a name matching the principalType
const userModel = this.registry.findModel(type);
if (userModel) {
userModel.findOne(
{where: {or: [{username: id}, {email: id}, {id: id}]}},
cb,
);
} else {
process.nextTick(function() {
const err = new Error(g.f('Invalid principal type: %s', type));
err.statusCode = 400;
err.code = 'INVALID_PRINCIPAL_TYPE';
cb(err);
});
}
}
return cb.promise;
};
/**
@ -542,10 +640,13 @@ module.exports = function(ACL) {
* @param {String} principalType Principal type
* @param {String|*} principalId Principal id/name
* @param {String|*} role Role id/name
* @param {Function} cb Callback function
* @callback {Function} callback Callback function
* @param {String|Error} err The error object
* @param {Boolean} isMapped is the ACL mapped to the role
*/
ACL.isMappedToRole = function(principalType, principalId, role, cb) {
var self = this;
cb = cb || utils.createPromiseCallback();
const self = this;
this.resolvePrincipal(principalType, principalId,
function(err, principal) {
if (err) return cb(err);
@ -559,13 +660,14 @@ module.exports = function(ACL) {
where: {
roleId: role.id,
principalType: principalType,
principalId: String(principalId)
}
principalId: String(principalId),
},
}, function(err, result) {
if (err) return cb(err);
return cb(null, !!result);
});
});
});
return cb.promise;
};
};

View File

@ -1,20 +1,26 @@
var assert = require('assert');
var utils = require('../../lib/utils');
// Copyright IBM Corp. 2014,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 assert = require('assert');
const utils = require('../../lib/utils');
/*!
* Application management functions
*/
var crypto = require('crypto');
const crypto = require('crypto');
function generateKey(hmacKey, algorithm, encoding) {
hmacKey = hmacKey || 'loopback';
algorithm = algorithm || 'sha1';
encoding = encoding || 'hex';
var hmac = crypto.createHmac(algorithm, hmacKey);
var buf = crypto.randomBytes(32);
const hmac = crypto.createHmac(algorithm, hmacKey);
const buf = crypto.randomBytes(32);
hmac.update(buf);
var key = hmac.digest(encoding);
const key = hmac.digest(encoding);
return key;
}
@ -65,19 +71,6 @@ function generateKey(hmacKey, algorithm, encoding) {
*/
module.exports = function(Application) {
// Workaround for https://github.com/strongloop/loopback/issues/292
Application.definition.rawProperties.created.default =
Application.definition.properties.created.default = function() {
return new Date();
};
// Workaround for https://github.com/strongloop/loopback/issues/292
Application.definition.rawProperties.modified.default =
Application.definition.properties.modified.default = function() {
return new Date();
};
/*!
* A hook to generate keys before creation
* @param next
@ -90,7 +83,7 @@ module.exports = function(Application) {
return next();
}
var app = ctx.instance;
const app = ctx.instance;
app.created = app.modified = new Date();
if (!app.id) {
app.id = generateKey('id', 'md5');
@ -108,7 +101,9 @@ module.exports = function(Application) {
* @param {String} owner Owner's user ID.
* @param {String} name Name of the application
* @param {Object} options Other options
* @param {Function} callback Callback function
* @callback {Function} callback Callback function
* @param {Error} err
* @promise
*/
Application.register = function(owner, name, options, cb) {
assert(owner, 'owner is required');
@ -120,8 +115,8 @@ module.exports = function(Application) {
}
cb = cb || utils.createPromiseCallback();
var props = {owner: owner, name: name};
for (var p in options) {
const props = {owner: owner, name: name};
for (const p in options) {
if (!(p in props)) {
props[p] = options[p];
}
@ -150,6 +145,7 @@ module.exports = function(Application) {
* @param {Any} appId
* @callback {Function} callback
* @param {Error} err
* @promise
*/
Application.resetKeys = function(appId, cb) {
cb = cb || utils.createPromiseCallback();
@ -176,7 +172,7 @@ module.exports = function(Application) {
* - restApiKey
* - windowsKey
* - masterKey
*
* @promise
*/
Application.authenticate = function(appId, key, cb) {
cb = cb || utils.createPromiseCallback();
@ -186,13 +182,13 @@ module.exports = function(Application) {
cb(err, null);
return cb.promise;
}
var result = null;
var keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey'];
for (var i = 0; i < keyNames.length; i++) {
let result = null;
const keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey'];
for (let i = 0; i < keyNames.length; i++) {
if (app[keyNames[i]] === key) {
result = {
application: app,
keyType: keyNames[i]
keyType: keyNames[i],
};
break;
}

View File

@ -118,7 +118,13 @@
"description": "Status of the application, production/sandbox/disabled"
},
"created": "date",
"modified": "date"
"created": {
"type": "date",
"defaultFn": "now"
},
"modified": {
"type": "date",
"defaultFn": "now"
}
}
}

View File

@ -1,15 +1,22 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*!
* Module Dependencies.
*/
var PersistedModel = require('../../lib/loopback').PersistedModel;
var loopback = require('../../lib/loopback');
var crypto = require('crypto');
var CJSON = {stringify: require('canonical-json')};
var async = require('async');
var assert = require('assert');
var debug = require('debug')('loopback:change');
var deprecate = require('depd')('loopback');
'use strict';
const g = require('../../lib/globalize');
const PersistedModel = require('../../lib/loopback').PersistedModel;
const loopback = require('../../lib/loopback');
const utils = require('../../lib/utils');
const crypto = require('crypto');
const CJSON = {stringify: require('canonical-json')};
const async = require('async');
const assert = require('assert');
const debug = require('debug')('loopback:change');
/**
* Change list entry.
@ -30,7 +37,6 @@ var deprecate = require('depd')('loopback');
*/
module.exports = function(Change) {
/*!
* Constants
*/
@ -52,10 +58,10 @@ module.exports = function(Change) {
Change.setup = function() {
PersistedModel.setup.call(this);
var Change = this;
const Change = this;
Change.getter.id = function() {
var hasModel = this.modelName && this.modelId;
const hasModel = this.modelName && this.modelId;
if (!hasModel) return null;
return Change.idForModel(this.modelName, this.modelId);
@ -74,10 +80,12 @@ module.exports = function(Change) {
*/
Change.rectifyModelChanges = function(modelName, modelIds, callback) {
var Change = this;
var errors = [];
const Change = this;
const errors = [];
var tasks = modelIds.map(function(id) {
callback = callback || utils.createPromiseCallback();
const tasks = modelIds.map(function(id) {
return function(cb) {
Change.findOrCreateChange(modelName, id, function(err, change) {
if (err) return next(err);
@ -98,19 +106,20 @@ module.exports = function(Change) {
async.parallel(tasks, function(err) {
if (err) return callback(err);
if (errors.length) {
var desc = errors
const desc = errors
.map(function(e) {
return '#' + e.modelId + ' - ' + e.toString();
})
.join('\n');
var msg = 'Cannot rectify ' + modelName + ' changes:\n' + desc;
const msg = g.f('Cannot rectify %s changes:\n%s', modelName, desc);
err = new Error(msg);
err.details = { errors: errors };
err.details = {errors: errors};
return callback(err);
}
callback();
});
return callback.promise;
};
/**
@ -137,24 +146,26 @@ module.exports = function(Change) {
*/
Change.findOrCreateChange = function(modelName, modelId, callback) {
assert(loopback.findModel(modelName), modelName + ' does not exist');
var id = this.idForModel(modelName, modelId);
var Change = this;
assert(this.registry.findModel(modelName), modelName + ' does not exist');
callback = callback || utils.createPromiseCallback();
const id = this.idForModel(modelName, modelId);
const Change = this;
this.findById(id, function(err, change) {
if (err) return callback(err);
if (change) {
callback(null, change);
} else {
var ch = new Change({
const ch = new Change({
id: id,
modelName: modelName,
modelId: modelId
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
@ -166,34 +177,48 @@ module.exports = function(Change) {
*/
Change.prototype.rectify = function(cb) {
var change = this;
var currentRev = this.rev;
const change = this;
const currentRev = this.rev;
change.debug('rectify change');
cb = cb || function(err) {
if (err) throw new Error(err);
};
cb = cb || utils.createPromiseCallback();
change.currentRevision(function(err, rev) {
const model = this.getModelCtor();
const id = this.getModelId();
model.findById(id, function(err, inst) {
if (err) return cb(err);
if (inst) {
inst.fillCustomChangeProperties(change, function() {
const rev = Change.revisionForInst(inst);
prepareAndDoRectify(rev);
});
} else {
prepareAndDoRectify(null);
}
});
return cb.promise;
function prepareAndDoRectify(rev) {
// avoid setting rev and prev to the same value
if (currentRev === rev) {
change.debug('rev and prev are equal (not updating anything)');
return cb(null, change);
}
// FIXME(@bajtos) Allo callers to pass in the checkpoint value
// FIXME(@bajtos) Allow callers to pass in the checkpoint value
// (or even better - a memoized async function to get the cp value)
// That will enable `rectifyAll` to cache the checkpoint value
change.constructor.getCheckpointModel().current(
function(err, checkpoint) {
if (err) return cb(err);
doRectify(checkpoint, rev);
}
},
);
});
}
function doRectify(checkpoint, rev) {
if (rev) {
@ -218,7 +243,7 @@ module.exports = function(Change) {
if (currentRev) {
change.prev = currentRev;
} else if (!change.prev) {
change.debug('ERROR - could not determing prev');
change.debug('ERROR - could not determine prev');
change.prev = Change.UNKNOWN;
}
change.debug('updated prev');
@ -248,8 +273,9 @@ module.exports = function(Change) {
*/
Change.prototype.currentRevision = function(cb) {
var model = this.getModelCtor();
var id = this.getModelId();
cb = cb || utils.createPromiseCallback();
const model = this.getModelCtor();
const id = this.getModelId();
model.findById(id, function(err, inst) {
if (err) return cb(err);
if (inst) {
@ -258,6 +284,7 @@ module.exports = function(Change) {
cb(null, null);
}
});
return cb.promise;
};
/**
@ -318,8 +345,8 @@ module.exports = function(Change) {
Change.prototype.equals = function(change) {
if (!change) return false;
var thisRev = this.rev || null;
var thatRev = change.rev || null;
const thisRev = this.rev || null;
const thatRev = change.rev || null;
return thisRev === thatRev;
};
@ -390,11 +417,14 @@ module.exports = function(Change) {
*/
Change.diff = function(modelName, since, remoteChanges, callback) {
callback = callback || utils.createPromiseCallback();
if (!Array.isArray(remoteChanges) || remoteChanges.length === 0) {
return callback(null, {deltas: [], conflicts: []});
callback(null, {deltas: [], conflicts: []});
return callback.promise;
}
var remoteChangeIndex = {};
var modelIds = [];
const remoteChangeIndex = {};
const modelIds = [];
remoteChanges.forEach(function(ch) {
modelIds.push(ch.modelId);
remoteChangeIndex[ch.modelId] = new Change(ch);
@ -405,22 +435,22 @@ module.exports = function(Change) {
this.find({
where: {
modelName: modelName,
modelId: {inq: modelIds}
}
modelId: {inq: modelIds},
},
}, function(err, allLocalChanges) {
if (err) return callback(err);
var deltas = [];
var conflicts = [];
var localModelIds = [];
const deltas = [];
const conflicts = [];
const localModelIds = [];
var localChanges = allLocalChanges.filter(function(c) {
const localChanges = allLocalChanges.filter(function(c) {
return c.checkpoint >= since;
});
localChanges.forEach(function(localChange) {
localChange = new Change(localChange);
localModelIds.push(localChange.modelId);
var remoteChange = remoteChangeIndex[localChange.modelId];
const remoteChange = remoteChangeIndex[localChange.modelId];
if (remoteChange && !localChange.equals(remoteChange)) {
if (remoteChange.conflictsWith(localChange)) {
remoteChange.debug('remote conflict');
@ -436,8 +466,8 @@ module.exports = function(Change) {
modelIds.forEach(function(id) {
if (localModelIds.indexOf(id) !== -1) return;
var d = remoteChangeIndex[id];
var oldChange = allLocalChanges.filter(function(c) {
const d = remoteChangeIndex[id];
const oldChange = allLocalChanges.filter(function(c) {
return c.modelId === id;
})[0];
@ -452,9 +482,10 @@ module.exports = function(Change) {
callback(null, {
deltas: deltas,
conflicts: conflicts
conflicts: conflicts,
});
});
return callback.promise;
};
/**
@ -464,14 +495,15 @@ module.exports = function(Change) {
Change.rectifyAll = function(cb) {
debug('rectify all');
var Change = this;
const Change = this;
// this should be optimized
this.find(function(err, changes) {
if (err) return cb(err);
async.each(
changes,
function(c, next) { c.rectify(next); },
cb);
cb,
);
});
};
@ -481,7 +513,7 @@ module.exports = function(Change) {
*/
Change.getCheckpointModel = function() {
var checkpointModel = this.Checkpoint;
let checkpointModel = this.Checkpoint;
if (checkpointModel) return checkpointModel;
// FIXME(bajtos) This code creates multiple different models with the same
// model name, which is not a valid supported usage of juggler's API.
@ -492,18 +524,9 @@ module.exports = function(Change) {
return checkpointModel;
};
Change.handleError = function(err) {
deprecate('Change.handleError is deprecated, ' +
'you should pass errors to your callback instead.');
if (!this.settings.ignoreErrors) {
throw err;
}
};
Change.prototype.debug = function() {
if (debug.enabled) {
var args = Array.prototype.slice.call(arguments);
const args = Array.prototype.slice.call(arguments);
args[0] = args[0] + ' %s';
args.push(this.modelName);
debug.apply(this, args);
@ -528,16 +551,16 @@ module.exports = function(Change) {
Change.prototype.getModelId = function() {
// TODO(ritch) get rid of the need to create an instance
var Model = this.getModelCtor();
var id = this.modelId;
var m = new Model();
const Model = this.getModelCtor();
const id = this.modelId;
const m = new Model();
m.setId(id);
return m.getId();
};
Change.prototype.getModel = function(callback) {
var Model = this.constructor.settings.trackModel;
var id = this.getModelId();
const Model = this.constructor.settings.trackModel;
const id = this.getModelId();
Model.findById(id, callback);
};
@ -572,15 +595,14 @@ module.exports = function(Change) {
*/
Conflict.prototype.models = function(cb) {
var conflict = this;
var SourceModel = this.SourceModel;
var TargetModel = this.TargetModel;
var source;
var target;
const conflict = this;
const SourceModel = this.SourceModel;
const TargetModel = this.TargetModel;
let source, target;
async.parallel([
getSourceModel,
getTargetModel
getTargetModel,
], done);
function getSourceModel(cb) {
@ -615,17 +637,16 @@ module.exports = function(Change) {
*/
Conflict.prototype.changes = function(cb) {
var conflict = this;
var sourceChange;
var targetChange;
const conflict = this;
let sourceChange, targetChange;
async.parallel([
getSourceChange,
getTargetChange
getTargetChange,
], done);
function getSourceChange(cb) {
var SourceModel = conflict.SourceModel;
const SourceModel = conflict.SourceModel;
SourceModel.findLastChange(conflict.modelId, function(err, change) {
if (err) return cb(err);
sourceChange = change;
@ -634,7 +655,7 @@ module.exports = function(Change) {
}
function getTargetChange(cb) {
var TargetModel = conflict.TargetModel;
const TargetModel = conflict.TargetModel;
TargetModel.findLastChange(conflict.modelId, function(err, change) {
if (err) return cb(err);
targetChange = change;
@ -663,16 +684,18 @@ module.exports = function(Change) {
*/
Conflict.prototype.resolve = function(cb) {
var conflict = this;
const conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{ prev: targetChange.rev },
cb);
});
{prev: targetChange.rev},
cb,
);
},
);
};
/**
@ -695,16 +718,17 @@ module.exports = function(Change) {
* @param {Error} err
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
const conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
const inst = new conflict.SourceModel(
target.toObject(),
{ persisted: true });
{persisted: true},
);
inst.save(done);
});
@ -727,7 +751,7 @@ module.exports = function(Change) {
* @returns {Conflict} A new Conflict instance.
*/
Conflict.prototype.swapParties = function() {
var Ctor = this.constructor;
const Ctor = this.constructor;
return new Ctor(this.modelId, this.TargetModel, this.SourceModel);
};
@ -741,14 +765,14 @@ module.exports = function(Change) {
*/
Conflict.prototype.resolveManually = function(data, cb) {
var conflict = this;
const conflict = this;
if (!data) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
conflict.models(function(err, source, target) {
if (err) return done(err);
var inst = source || new conflict.SourceModel(target);
const inst = source || new conflict.SourceModel(target);
inst.setAttributes(data);
inst.save(function(err) {
if (err) return done(err);
@ -777,11 +801,11 @@ module.exports = function(Change) {
*/
Conflict.prototype.type = function(cb) {
var conflict = this;
const conflict = this;
this.changes(function(err, sourceChange, targetChange) {
if (err) return cb(err);
var sourceChangeType = sourceChange.type();
var targetChangeType = targetChange.type();
const sourceChangeType = sourceChange.type();
const targetChangeType = targetChange.type();
if (sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) {
return cb(null, Change.UPDATE);
}

View File

@ -1,8 +1,14 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/**
* Module Dependencies.
*/
var assert = require('assert');
'use strict';
const assert = require('assert');
/**
* Checkpoint list entry.
@ -16,7 +22,6 @@ var assert = require('assert');
*/
module.exports = function(Checkpoint) {
// Workaround for https://github.com/strongloop/loopback/issues/292
Checkpoint.definition.rawProperties.time.default =
Checkpoint.definition.properties.time.default = function() {
@ -27,43 +32,45 @@ module.exports = function(Checkpoint) {
* Get the current checkpoint id
* @callback {Function} callback
* @param {Error} err
* @param {Number} checkpointId The current checkpoint id
* @param {Number} checkpoint The current checkpoint seq
*/
Checkpoint.current = function(cb) {
var Checkpoint = this;
this.find({
limit: 1,
order: 'seq DESC'
}, function(err, checkpoints) {
if (err) return cb(err);
var checkpoint = checkpoints[0];
if (checkpoint) {
cb(null, checkpoint.seq);
} else {
Checkpoint.create({ seq: 1 }, function(err, checkpoint) {
if (err) return cb(err);
cb(null, checkpoint.seq);
});
}
const Checkpoint = this;
Checkpoint._getSingleton(function(err, cp) {
cb(err, cp.seq);
});
};
Checkpoint.observe('before save', function(ctx, next) {
if (!ctx.instance) {
// Example: Checkpoint.updateAll() and Checkpoint.updateOrCreate()
return next(new Error('Checkpoint does not support partial updates.'));
}
Checkpoint._getSingleton = function(cb) {
const query = {limit: 1}; // match all instances, return only one
const initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
var model = ctx.instance;
if (!model.getId() && model.seq === undefined) {
model.constructor.current(function(err, seq) {
if (err) return next(err);
model.seq = seq + 1;
next();
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
*/
Checkpoint.bumpLastSeq = function(cb) {
const Checkpoint = this;
Checkpoint._getSingleton(function(err, cp) {
if (err) return cb(err);
const originalSeq = cp.seq;
cp.seq++;
// 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) {
if (err) return cb(err);
// possible outcomes
// 1) seq was updated to seq+1 - exactly what we wanted!
// 2) somebody else already updated seq to seq+1 and our call was a no-op.
// That should be ok, checkpoints are time based, so we reuse the one created just now
// 3) seq was bumped more than once, so we will be using a value that is behind the latest seq.
// @bajtos is not entirely sure if this is ok, but since it wasn't handled by the current implementation either,
// he thinks we can keep it this way.
cb(null, cp);
});
} else {
next();
}
});
});
};
};

View File

@ -1,17 +1,24 @@
// Copyright IBM Corp. 2014,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 g = require('../../lib/globalize');
/**
* Email model. Extends LoopBack base [Model](#model-new-model).
* @property {String} to Email addressee. Required.
* @property {String} from Email sender address. Required.
* @property {String} subject Email subject string. Required.
* @property {String} text Text body of email.
* @property {String} html HTML body of email.
*
* @class Email
* @inherits {Model}
*/
* Email model. Extends LoopBack base [Model](#model-new-model).
* @property {String} to Email addressee. Required.
* @property {String} from Email sender address. Required.
* @property {String} subject Email subject string. Required.
* @property {String} text Text body of email.
* @property {String} html HTML body of email.
*
* @class Email
* @inherits {Model}
*/
module.exports = function(Email) {
/**
* Send an email with the given `options`.
*
@ -39,13 +46,13 @@ module.exports = function(Email) {
*/
Email.send = function() {
throw new Error('You must connect the Email Model to a Mail connector');
throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector'));
};
/**
* A shortcut for Email.send(this).
*/
Email.prototype.send = function() {
throw new Error('You must connect the Email Model to a Mail connector');
throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector'));
};
};

View File

@ -0,0 +1,243 @@
// Copyright IBM Corp. 2016,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 g = require('../../lib/globalize');
/**
* Data model for key-value databases.
*
* @class KeyValueModel
* @inherits {Model}
*/
module.exports = function(KeyValueModel) {
/**
* Return the value associated with a given key.
*
* @param {String} key Key to use when searching the database.
* @options {Object} options
* @callback {Function} callback
* @param {Error} err Error object.
* @param {Any} result Value associated with the given key.
* @promise
*
* @header KeyValueModel.get(key, cb)
*/
KeyValueModel.get = function(key, options, callback) {
throwNotAttached(this.modelName, 'get');
};
/**
* Persist a value and associate it with the given key.
*
* @param {String} key Key to associate with the given value.
* @param {Any} value Value to persist.
* @options {Number|Object} options Optional settings for the key-value
* pair. If a Number is provided, it is set as the TTL (time to live) in ms
* (milliseconds) for the key-value pair.
* @property {Number} ttl TTL for the key-value pair in ms.
* @callback {Function} callback
* @param {Error} err Error object.
* @promise
*
* @header KeyValueModel.set(key, value, cb)
*/
KeyValueModel.set = function(key, value, options, callback) {
throwNotAttached(this.modelName, 'set');
};
/**
* Set the TTL (time to live) in ms (milliseconds) for a given key. TTL is the
* remaining time before a key-value pair is discarded from the database.
*
* @param {String} key Key to use when searching the database.
* @param {Number} ttl TTL in ms to set for the key.
* @options {Object} options
* @callback {Function} callback
* @param {Error} err Error object.
* @promise
*
* @header KeyValueModel.expire(key, ttl, cb)
*/
KeyValueModel.expire = function(key, ttl, options, callback) {
throwNotAttached(this.modelName, 'expire');
};
/**
* Return the TTL (time to live) for a given key. TTL is the remaining time
* before a key-value pair is discarded from the database.
*
* @param {String} key Key to use when searching the database.
* @options {Object} options
* @callback {Function} callback
* @param {Error} error
* @param {Number} ttl Expiration time for the key-value pair. `undefined` if
* TTL was not initially set.
* @promise
*
* @header KeyValueModel.ttl(key, cb)
*/
KeyValueModel.ttl = function(key, options, callback) {
throwNotAttached(this.modelName, 'ttl');
};
/**
* Return all keys in the database.
*
* **WARNING**: This method is not suitable for large data sets as all
* key-values pairs are loaded into memory at once. For large data sets,
* use `iterateKeys()` instead.
*
* @param {Object} filter An optional filter object with the following
* @param {String} filter.match Glob string used to filter returned
* keys (i.e. `userid.*`). All connectors are required to support `*` and
* `?`, but may also support additional special characters specific to the
* database.
* @param {Object} options
* @callback {Function} callback
* @promise
*
* @header KeyValueModel.keys(filter, cb)
*/
KeyValueModel.keys = function(filter, options, callback) {
throwNotAttached(this.modelName, 'keys');
};
/**
* Asynchronously iterate all keys in the database. Similar to `.keys()` but
* instead allows for iteration over large data sets without having to load
* everything into memory at once.
*
* Callback example:
* ```js
* // Given a model named `Color` with two keys `red` and `blue`
* var iterator = Color.iterateKeys();
* it.next(function(err, key) {
* // key contains `red`
* it.next(function(err, key) {
* // key contains `blue`
* });
* });
* ```
*
* Promise example:
* ```js
* // Given a model named `Color` with two keys `red` and `blue`
* var iterator = Color.iterateKeys();
* Promise.resolve().then(function() {
* return it.next();
* })
* .then(function(key) {
* // key contains `red`
* return it.next();
* });
* .then(function(key) {
* // key contains `blue`
* });
* ```
*
* @param {Object} filter An optional filter object with the following
* @param {String} filter.match Glob string to use to filter returned
* keys (i.e. `userid.*`). All connectors are required to support `*` and
* `?`. They may also support additional special characters that are
* specific to the backing database.
* @param {Object} options
* @returns {AsyncIterator} An Object implementing `next(cb) -> Promise`
* function that can be used to iterate all keys.
*
* @header KeyValueModel.iterateKeys(filter)
*/
KeyValueModel.iterateKeys = function(filter, options) {
throwNotAttached(this.modelName, 'iterateKeys');
};
/*!
* Set up remoting metadata for this model.
*
* **Notes**:
* - The method is called automatically by `Model.extend` and/or
* `app.registry.createModel`
* - In general, base models use call this to ensure remote methods are
* inherited correctly, see bug at
* https://github.com/strongloop/loopback/issues/2350
*/
KeyValueModel.setup = function() {
KeyValueModel.base.setup.apply(this, arguments);
this.remoteMethod('get', {
accepts: {
arg: 'key', type: 'string', required: true,
http: {source: 'path'},
},
returns: {arg: 'value', type: 'any', root: true},
http: {path: '/:key', verb: 'get'},
rest: {after: convertNullToNotFoundError},
});
this.remoteMethod('set', {
accepts: [
{arg: 'key', type: 'string', required: true,
http: {source: 'path'}},
{arg: 'value', type: 'any', required: true,
http: {source: 'body'}},
{arg: 'ttl', type: 'number',
http: {source: 'query'},
description: 'time to live in milliseconds'},
],
http: {path: '/:key', verb: 'put'},
});
this.remoteMethod('expire', {
accepts: [
{arg: 'key', type: 'string', required: true,
http: {source: 'path'}},
{arg: 'ttl', type: 'number', required: true,
http: {source: 'form'}},
],
http: {path: '/:key/expire', verb: 'put'},
});
this.remoteMethod('ttl', {
accepts: {
arg: 'key', type: 'string', required: true,
http: {source: 'path'},
},
returns: {arg: 'value', type: 'any', root: true},
http: {path: '/:key/ttl', verb: 'get'},
});
this.remoteMethod('keys', {
accepts: {
arg: 'filter', type: 'object', required: false,
http: {source: 'query'},
},
returns: {arg: 'keys', type: ['string'], root: true},
http: {path: '/keys', verb: 'get'},
});
};
};
function throwNotAttached(modelName, methodName) {
throw new Error(g.f(
'Cannot call %s.%s(). ' +
'The %s method has not been setup. ' +
'The {{KeyValueModel}} has not been correctly attached ' +
'to a {{DataSource}}!',
modelName, methodName, methodName,
));
}
function convertNullToNotFoundError(ctx, cb) {
if (ctx.result !== null) return cb();
const modelName = ctx.method.sharedClass.name;
const id = ctx.getArgByName('id');
const msg = g.f('Unknown "%s" {{key}} "%s".', modelName, id);
const error = new Error(msg);
error.statusCode = error.status = 404;
error.code = 'KEY_NOT_FOUND';
cb(error);
}

View File

@ -0,0 +1,4 @@
{
"name": "KeyValueModel",
"base": "Model"
}

View File

@ -1,4 +1,11 @@
var loopback = require('../../lib/loopback');
// Copyright IBM Corp. 2014,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 loopback = require('../../lib/loopback');
const utils = require('../../lib/utils');
/**
* The `RoleMapping` model extends from the built in `loopback.Model` type.
@ -19,10 +26,10 @@ module.exports = function(RoleMapping) {
RoleMapping.resolveRelatedModels = function() {
if (!this.userModel) {
var reg = this.registry;
this.roleModel = reg.getModelByType(loopback.Role);
this.userModel = reg.getModelByType(loopback.User);
this.applicationModel = reg.getModelByType(loopback.Application);
const reg = this.registry;
this.roleModel = reg.getModelByType('Role');
this.userModel = reg.getModelByType('User');
this.applicationModel = reg.getModelByType('Application');
}
};
@ -33,16 +40,18 @@ module.exports = function(RoleMapping) {
* @param {Application} application
*/
RoleMapping.prototype.application = function(callback) {
callback = callback || utils.createPromiseCallback();
this.constructor.resolveRelatedModels();
if (this.principalType === RoleMapping.APPLICATION) {
var applicationModel = this.constructor.applicationModel;
const applicationModel = this.constructor.applicationModel;
applicationModel.findById(this.principalId, callback);
} else {
process.nextTick(function() {
if (callback) callback(null, null);
callback(null, null);
});
}
return callback.promise;
};
/**
@ -52,15 +61,26 @@ module.exports = function(RoleMapping) {
* @param {User} user
*/
RoleMapping.prototype.user = function(callback) {
callback = callback || utils.createPromiseCallback();
this.constructor.resolveRelatedModels();
let userModel;
if (this.principalType === RoleMapping.USER) {
var userModel = this.constructor.userModel;
userModel = this.constructor.userModel;
userModel.findById(this.principalId, callback);
return callback.promise;
}
// try resolving a user model that matches principalType
userModel = this.constructor.registry.findModel(this.principalType);
if (userModel) {
userModel.findById(this.principalId, callback);
} else {
process.nextTick(function() {
if (callback) callback(null, null);
callback(null, null);
});
}
return callback.promise;
};
/**
@ -70,15 +90,17 @@ module.exports = function(RoleMapping) {
* @param {User} childUser
*/
RoleMapping.prototype.childRole = function(callback) {
callback = callback || utils.createPromiseCallback();
this.constructor.resolveRelatedModels();
if (this.principalType === RoleMapping.ROLE) {
var roleModel = this.constructor.roleModel;
const roleModel = this.constructor.roleModel;
roleModel.findById(this.principalId, callback);
} else {
process.nextTick(function() {
if (callback) callback(null, null);
callback(null, null);
});
}
return callback.promise;
};
};

View File

@ -9,9 +9,12 @@
},
"principalType": {
"type": "string",
"description": "The principal type, such as user, application, or role"
"description": "The principal type, such as USER, APPLICATION, ROLE, or user model name in case of multiple user models"
},
"principalId": "string"
"principalId": {
"type": "string",
"index": true
}
},
"relations": {
"role": {

View File

@ -1,11 +1,18 @@
var loopback = require('../../lib/loopback');
var debug = require('debug')('loopback:security:role');
var assert = require('assert');
var async = require('async');
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var AccessContext = require('../../lib/access-context').AccessContext;
var RoleMapping = loopback.RoleMapping;
'use strict';
const loopback = require('../../lib/loopback');
const debug = require('debug')('loopback:security:role');
const assert = require('assert');
const async = require('async');
const utils = require('../../lib/utils');
const ctx = require('../../lib/access-context');
const AccessContext = ctx.AccessContext;
const Principal = ctx.Principal;
const RoleMapping = loopback.RoleMapping;
assert(RoleMapping, 'RoleMapping model must be defined before Role model');
@ -15,92 +22,121 @@ assert(RoleMapping, 'RoleMapping model must be defined before Role model');
* @header Role object
*/
module.exports = function(Role) {
// Workaround for https://github.com/strongloop/loopback/issues/292
Role.definition.rawProperties.created.default =
Role.definition.properties.created.default = function() {
return new Date();
};
// Workaround for https://github.com/strongloop/loopback/issues/292
Role.definition.rawProperties.modified.default =
Role.definition.properties.modified.default = function() {
return new Date();
};
Role.resolveRelatedModels = function() {
if (!this.userModel) {
var reg = this.registry;
this.roleMappingModel = reg.getModelByType(loopback.RoleMapping);
this.userModel = reg.getModelByType(loopback.User);
this.applicationModel = reg.getModelByType(loopback.Application);
const reg = this.registry;
this.roleMappingModel = reg.getModelByType('RoleMapping');
this.userModel = reg.getModelByType('User');
this.applicationModel = reg.getModelByType('Application');
}
};
// Set up the connection to users/applications/roles once the model
Role.once('dataSourceAttached', function(roleModel) {
['users', 'applications', 'roles'].forEach(function(rel) {
/**
* Fetch all users assigned to this role
* @function Role.prototype#users
* @param {object} [query] query object passed to model find call
* @param {Function} [callback]
* @callback {Function} [callback] The callback function
* @param {String|Error} err The error string or object
* @param {Array} list The list of users.
* @promise
*/
/**
* Fetch all applications assigned to this role
* @function Role.prototype#applications
* @param {object} [query] query object passed to model find call
* @param {Function} [callback]
* @callback {Function} [callback] The callback function
* @param {String|Error} err The error string or object
* @param {Array} list The list of applications.
* @promise
*/
/**
* Fetch all roles assigned to this role
* @function Role.prototype#roles
* @param {object} [query] query object passed to model find call
* @param {Function} [callback]
* @callback {Function} [callback] The callback function
* @param {String|Error} err The error string or object
* @param {Array} list The list of roles.
* @promise
*/
Role.prototype[rel] = function(query, callback) {
if (!callback) {
if (typeof query === 'function') {
callback = query;
query = {};
} else {
callback = utils.createPromiseCallback();
}
}
query = query || {};
query.where = query.where || {};
roleModel.resolveRelatedModels();
var relsToModels = {
const relsToModels = {
users: roleModel.userModel,
applications: roleModel.applicationModel,
roles: roleModel
roles: roleModel,
};
var ACL = loopback.ACL;
var relsToTypes = {
const ACL = loopback.ACL;
const relsToTypes = {
users: ACL.USER,
applications: ACL.APP,
roles: ACL.ROLE
roles: ACL.ROLE,
};
var model = relsToModels[rel];
listByPrincipalType(model, relsToTypes[rel], query, callback);
let principalModel = relsToModels[rel];
let principalType = relsToTypes[rel];
// redefine user model and user type if user principalType is custom (available and not "USER")
const isCustomUserPrincipalType = rel === 'users' &&
query.where.principalType &&
query.where.principalType !== RoleMapping.USER;
if (isCustomUserPrincipalType) {
const registry = this.constructor.registry;
principalModel = registry.findModel(query.where.principalType);
principalType = query.where.principalType;
}
// make sure we don't keep principalType in userModel query
delete query.where.principalType;
if (principalModel) {
listByPrincipalType(this, principalModel, principalType, query, callback);
} else {
process.nextTick(function() {
callback(null, []);
});
}
return callback.promise;
};
});
/**
* Fetch all models assigned to this role
* @private
* @param {object} Context role context
* @param {*} model model type to fetch
* @param {String} [principalType] principalType used in the rolemapping for model
* @param {object} [query] query object passed to model find call
* @param {Function} [callback] callback function called with `(err, models)` arguments.
*/
function listByPrincipalType(model, principalType, query, callback) {
if (callback === undefined) {
function listByPrincipalType(context, model, principalType, query, callback) {
if (callback === undefined && typeof query === 'function') {
callback = query;
query = {};
}
query = query || {};
roleModel.roleMappingModel.find({
where: {roleId: this.id, principalType: principalType}
where: {roleId: context.id, principalType: principalType},
}, function(err, mappings) {
var ids;
if (err) {
return callback(err);
}
ids = mappings.map(function(m) {
const ids = mappings.map(function(m) {
return m.principalId;
});
query.where = query.where || {};
@ -110,7 +146,6 @@ module.exports = function(Role) {
});
});
}
});
// Special roles
@ -123,8 +158,9 @@ module.exports = function(Role) {
/**
* Add custom handler for roles.
* @param {String} role Name of role.
* @param {Function} resolver Function that determines if a principal is in the specified role.
* Signature must be `function(role, context, callback)`
* @param {Function} resolver Function that determines
* if a principal is in the specified role.
* Should provide a callback or return a promise.
*/
Role.registerResolver = function(role, resolver) {
if (!Role.resolvers) {
@ -140,19 +176,20 @@ module.exports = function(Role) {
});
return;
}
var modelClass = context.model;
var modelId = context.modelId;
var userId = context.getUserId();
Role.isOwner(modelClass, modelId, userId, callback);
const modelClass = context.model;
const modelId = context.modelId;
const user = context.getUser();
const userId = user && user.id;
const principalType = user && user.principalType;
const opts = {accessToken: context.accessToken};
Role.isOwner(modelClass, modelId, userId, principalType, opts, callback);
});
function isUserClass(modelClass) {
if (modelClass) {
return modelClass === loopback.User ||
modelClass.prototype instanceof loopback.User;
} else {
return false;
}
if (!modelClass) return false;
const User = modelClass.modelBuilder.models.User;
if (!User) return false;
return modelClass == User || modelClass.prototype instanceof User;
}
/*!
@ -175,62 +212,200 @@ module.exports = function(Role) {
* @param {Function} modelClass The model class
* @param {*} modelId The model ID
* @param {*} userId The user ID
* @param {Function} callback Callback function
* @param {String} principalType The user principalType (optional)
* @options {Object} options
* @property {accessToken} The access token used to authorize the current user.
* @callback {Function} [callback] The callback function
* @param {String|Error} err The error string or object
* @param {Boolean} isOwner True if the user is an owner.
* @promise
*/
Role.isOwner = function isOwner(modelClass, modelId, userId, callback) {
Role.isOwner = function isOwner(modelClass, modelId, userId, principalType, options, callback) {
const _this = this;
if (!callback && typeof options === 'function') {
callback = options;
options = {};
} else if (!callback && typeof principalType === 'function') {
callback = principalType;
principalType = undefined;
options = {};
}
principalType = principalType || Principal.USER;
assert(modelClass, 'Model class is required');
debug('isOwner(): %s %s userId: %s', modelClass && modelClass.modelName, modelId, userId);
// No userId is present
if (!callback) callback = utils.createPromiseCallback();
debug('isOwner(): %s %s userId: %s principalType: %s',
modelClass && modelClass.modelName, modelId, userId, principalType);
// Resolve isOwner false if userId is missing
if (!userId) {
debug('isOwner(): no user id was set, returning false');
process.nextTick(function() {
callback(null, false);
});
return;
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() {
callback(null, false);
});
return callback.promise;
}
// Is the modelClass User or a subclass of User?
if (isUserClass(modelClass)) {
process.nextTick(function() {
callback(null, matches(modelId, userId));
});
return;
const userModelName = modelClass.modelName;
// matching ids is enough if principalType is USER or matches given user model name
if (principalType === Principal.USER || principalType === userModelName) {
process.nextTick(function() {
callback(null, matches(modelId, userId));
});
return callback.promise;
}
// otherwise continue with the regular owner resolution
}
modelClass.findById(modelId, function(err, inst) {
modelClass.findById(modelId, options, function(err, inst) {
if (err || !inst) {
debug('Model not found for id %j', modelId);
if (callback) callback(err, false);
return;
return callback(err, false);
}
debug('Model found: %j', inst);
var ownerId = inst.userId || inst.owner;
// Ensure ownerId exists and is not a function/relation
if (ownerId && 'function' !== typeof ownerId) {
if (callback) callback(null, matches(ownerId, userId));
return;
} else {
// Try to follow belongsTo
for (var r in modelClass.relations) {
var rel = modelClass.relations[r];
if (rel.type === 'belongsTo' && isUserClass(rel.modelTo)) {
debug('Checking relation %s to %s: %j', r, rel.modelTo.modelName, rel);
inst[r](processRelatedUser);
return;
}
}
debug('No matching belongsTo relation found for model %j and user: %j', modelId, userId);
if (callback) callback(null, false);
}
function processRelatedUser(err, user) {
if (!err && user) {
debug('User found: %j', user.id);
if (callback) callback(null, matches(user.id, userId));
} else {
if (callback) callback(err, false);
}
const ownerRelations = modelClass.settings.ownerRelations;
if (!ownerRelations) {
return legacyOwnershipCheck(inst);
} else {
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) {
return callback(null, matches(ownerId, userId));
}
// Try to follow belongsTo
for (const r in modelClass.relations) {
const rel = modelClass.relations[r];
// relation should be belongsTo and target a User based class
const belongsToUser = rel.type === 'belongsTo' && isUserClass(rel.modelTo);
if (!belongsToUser) {
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
if ((!isMultipleUsers && principalType === Principal.USER) ||
(isMultipleUsers && principalType === userModelName)) {
debug('Checking relation %s to %s: %j', r, userModelName, rel);
inst[r](processRelatedUser);
return;
}
}
debug('No matching belongsTo relation found for model %j - user %j principalType %j',
modelId, userId, principalType);
callback(null, false);
function processRelatedUser(err, user) {
if (err || !user) return callback(err, false);
debug('User found: %j', user.id);
callback(null, matches(user.id, userId));
}
}
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) {
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) {
@ -250,11 +425,14 @@ module.exports = function(Role) {
* @callback {Function} callback Callback function.
* @param {Error} err Error object.
* @param {Boolean} isAuthenticated True if the user is authenticated.
* @promise
*/
Role.isAuthenticated = function isAuthenticated(context, callback) {
if (!callback) callback = utils.createPromiseCallback();
process.nextTick(function() {
if (callback) callback(null, context.isAuthenticated());
});
return callback.promise;
};
Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) {
@ -278,22 +456,41 @@ module.exports = function(Role) {
* @callback {Function} callback Callback function.
* @param {Error} err Error object.
* @param {Boolean} isInRole True if the principal is in the specified role.
* @promise
*/
Role.isInRole = function(role, context, callback) {
context.registry = this.registry;
if (!(context instanceof AccessContext)) {
context = new AccessContext(context);
}
if (!callback) {
callback = utils.createPromiseCallback();
// historically, isInRole is returning the Role instance instead of true
// we are preserving that behaviour for callback-based invocation,
// but fixing it when invoked in Promise mode
callback.promise = callback.promise.then(function(isInRole) {
return !!isInRole;
});
}
this.resolveRelatedModels();
debug('isInRole(): %s', role);
context.debug();
var resolver = Role.resolvers[role];
const resolver = Role.resolvers[role];
if (resolver) {
debug('Custom resolver found for role %s', role);
resolver(role, context, callback);
return;
const promise = resolver(role, context, callback);
if (promise && typeof promise.then === 'function') {
promise.then(
function(result) { callback(null, result); },
callback,
);
}
return callback.promise;
}
if (context.principals.length === 0) {
@ -301,13 +498,12 @@ module.exports = function(Role) {
process.nextTick(function() {
if (callback) callback(null, false);
});
return;
return callback.promise;
}
var inRole = context.principals.some(function(p) {
var principalType = p.type || undefined;
var principalId = p.id || undefined;
const inRole = context.principals.some(function(p) {
const principalType = p.type || undefined;
const principalId = p.id || undefined;
// Check if it's the same role
return principalType === RoleMapping.ROLE && principalId === role;
@ -318,10 +514,10 @@ module.exports = function(Role) {
process.nextTick(function() {
if (callback) callback(null, true);
});
return;
return callback.promise;
}
var roleMappingModel = this.roleMappingModel;
const roleMappingModel = this.roleMappingModel;
this.findOne({where: {name: role}}, function(err, result) {
if (err) {
if (callback) callback(err);
@ -335,21 +531,22 @@ module.exports = function(Role) {
// Iterate through the list of principals
async.some(context.principals, function(p, done) {
var principalType = p.type || undefined;
var principalId = p.id || undefined;
var roleId = result.id.toString();
const principalType = p.type || undefined;
let principalId = p.id || undefined;
const roleId = result.id.toString();
const principalIdIsString = typeof principalId === 'string';
if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) {
if (principalId !== null && principalId !== undefined && !principalIdIsString) {
principalId = principalId.toString();
}
if (principalType && principalId) {
roleMappingModel.findOne({where: {roleId: roleId,
principalType: principalType, principalId: principalId}},
function(err, result) {
debug('Role mapping found: %j', result);
done(!err && result); // The only arg is the result
});
principalType: principalType, principalId: principalId}},
function(err, result) {
debug('Role mapping found: %j', result);
done(!err && result); // The only arg is the result
});
} else {
process.nextTick(function() {
done(false);
@ -360,7 +557,7 @@ module.exports = function(Role) {
if (callback) callback(null, inRole);
});
});
return callback.promise;
};
/**
@ -370,23 +567,35 @@ module.exports = function(Role) {
* @callback {Function} callback Callback function.
* @param {Error} err Error object.
* @param {String[]} roles An array of role IDs
* @promise
*/
Role.getRoles = function(context, callback) {
Role.getRoles = function(context, options, callback) {
if (!callback) {
if (typeof options === 'function') {
callback = options;
options = {};
} else {
callback = utils.createPromiseCallback();
}
}
if (!options) options = {};
context.registry = this.registry;
if (!(context instanceof AccessContext)) {
context = new AccessContext(context);
}
var roles = [];
const roles = [];
this.resolveRelatedModels();
var addRole = function(role) {
const addRole = function(role) {
if (role && roles.indexOf(role) === -1) {
roles.push(role);
}
};
var self = this;
const self = this;
// Check against the smart roles
var inRoleTasks = [];
const inRoleTasks = [];
Object.keys(Role.resolvers).forEach(function(role) {
inRoleTasks.push(function(done) {
self.isInRole(role, context, function(err, inRole) {
@ -403,11 +612,11 @@ module.exports = function(Role) {
});
});
var roleMappingModel = this.roleMappingModel;
const roleMappingModel = this.roleMappingModel;
context.principals.forEach(function(p) {
// Check against the role mappings
var principalType = p.type || undefined;
var principalId = p.id == null ? undefined : p.id;
const principalType = p.type || undefined;
let principalId = p.id == null ? undefined : p.id;
if (typeof principalId !== 'string' && principalId != null) {
principalId = principalId.toString();
@ -421,15 +630,24 @@ module.exports = function(Role) {
if (principalType && principalId) {
// Please find() treat undefined matches all values
inRoleTasks.push(function(done) {
roleMappingModel.find({where: {principalType: principalType,
principalId: principalId}}, function(err, mappings) {
const filter = {where: {principalType: principalType, principalId: principalId}};
if (options.returnOnlyRoleNames === true) {
filter.include = ['role'];
}
roleMappingModel.find(filter, function(err, mappings) {
debug('Role mappings found: %s %j', err, mappings);
if (err) {
if (done) done(err);
return;
}
mappings.forEach(function(m) {
addRole(m.roleId);
let role;
if (options.returnOnlyRoleNames === true) {
role = m.toJSON().role.name;
} else {
role = m.roleId;
}
addRole(role);
});
if (done) done();
});
@ -441,5 +659,8 @@ module.exports = function(Role) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};

View File

@ -12,9 +12,14 @@
"required": true
},
"description": "string",
"created": "date",
"modified": "date"
"created": {
"type":"date",
"defaultFn": "now"
},
"modified": {
"type":"date",
"defaultFn": "now"
}
},
"relations": {
"principals": {

View File

@ -1,5 +1,11 @@
var assert = require('assert');
var loopback = require('../../lib/loopback');
// Copyright IBM Corp. 2014,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 assert = require('assert');
const loopback = require('../../lib/loopback');
/**
* Resource owner grants/delegates permissions to client applications
@ -15,7 +21,7 @@ var loopback = require('../../lib/loopback');
module.exports = function(Scope) {
Scope.resolveRelatedModels = function() {
if (!this.aclModel) {
var reg = this.registry;
const reg = this.registry;
this.aclModel = reg.getModelByType(loopback.ACL);
}
};
@ -32,7 +38,7 @@ module.exports = function(Scope) {
*/
Scope.checkPermission = function(scope, model, property, accessType, callback) {
this.resolveRelatedModels();
var aclModel = this.aclModel;
const aclModel = this.aclModel;
assert(aclModel,
'ACL model must be defined before Scope.checkPermission is called');
@ -41,7 +47,8 @@ module.exports = function(Scope) {
if (callback) callback(err);
} else {
aclModel.checkPermission(
aclModel.SCOPE, scope.id, model, property, accessType, callback);
aclModel.SCOPE, scope.id, model, property, accessType, callback,
);
}
});
};

File diff suppressed because it is too large Load Diff

View File

@ -11,28 +11,17 @@
"type": "string",
"required": true
},
"credentials": {
"type": "object",
"deprecated": true
},
"challenges": {
"type": "object",
"deprecated": true
},
"email": {
"type": "string",
"required": true
},
"emailVerified": "boolean",
"verificationToken": "string",
"status": "string",
"created": "date",
"lastUpdated": "date"
"verificationToken": "string"
},
"options": {
"caseSensitiveEmail": true
},
"hidden": ["password"],
"hidden": ["password", "verificationToken"],
"acls": [
{
"principalType": "ROLE",
@ -73,7 +62,20 @@
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "updateAttributes"
"property": "patchAttributes"
},
{
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW",
"property": "replaceById"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "verify",
"accessType": "EXECUTE"
},
{
"principalType": "ROLE",
@ -87,6 +89,20 @@
"permission": "ALLOW",
"property": "resetPassword",
"accessType": "EXECUTE"
},
{
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "changePassword",
"accessType": "EXECUTE"
},
{
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "setPassword",
"accessType": "EXECUTE"
}
],
"relations": {

View File

@ -5,13 +5,11 @@
"lib/server-app.js",
"lib/loopback.js",
"lib/registry.js",
"server/current-context.js",
"lib/access-context.js",
{ "title": "Base models", "depth": 2 },
"lib/model.js",
"lib/persisted-model.js",
{ "title": "Middleware", "depth": 2 },
"server/middleware/context.js",
"server/middleware/favicon.js",
"server/middleware/rest.js",
"server/middleware/static.js",
@ -24,6 +22,7 @@
"common/models/application.js",
"common/models/change.js",
"common/models/email.js",
"common/models/key-value-model.js",
"common/models/role.js",
"common/models/role-mapping.js",
"common/models/scope.js",

View File

@ -1,9 +1,16 @@
var loopback = require('../../');
var client = loopback();
var CartItem = require('./models').CartItem;
var remote = loopback.createDataSource({
// Copyright IBM Corp. 2014,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 g = require('../../lib/globalize');
const loopback = require('../../');
const client = loopback();
const CartItem = require('./models').CartItem;
const remote = loopback.createDataSource({
connector: loopback.Remote,
url: 'http://localhost:3000'
url: 'http://localhost:3000',
});
client.model(CartItem);
@ -11,7 +18,7 @@ CartItem.attachTo(remote);
// call the remote method
CartItem.sum(1, function(err, total) {
console.log('result:', err || total);
g.log('result:%s', err || total);
});
// call a built in remote method

View File

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

View File

@ -1,8 +1,14 @@
var loopback = require('../../');
var server = module.exports = loopback();
var CartItem = require('./models').CartItem;
var memory = loopback.createDataSource({
connector: loopback.Memory
// Copyright IBM Corp. 2014,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 loopback = require('../../');
const server = module.exports = loopback();
const CartItem = require('./models').CartItem;
const memory = loopback.createDataSource({
connector: loopback.Memory,
});
server.use(loopback.rest());
@ -14,7 +20,7 @@ CartItem.attachTo(memory);
CartItem.create([
{item: 'red hat', qty: 6, price: 19.99, cartId: 1},
{item: 'green shirt', qty: 1, price: 14.99, cartId: 1},
{item: 'orange pants', qty: 58, price: 9.99, cartId: 1}
{item: 'orange pants', qty: 58, price: 9.99, cartId: 1},
]);
CartItem.sum(1, function(err, total) {

View File

@ -1,15 +1,22 @@
var loopback = require('../../');
var app = loopback();
// Copyright IBM Corp. 2013,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 g = require('../../lib/globalize');
const loopback = require('../../');
const app = loopback();
app.use(loopback.rest());
var schema = {
name: String
const schema = {
name: String,
};
var Color = app.model('color', schema);
app.dataSource('db', {adapter: 'memory'}).attach(Color);
app.dataSource('db', {connector: 'memory'});
const Color = app.registry.createModel('color', schema);
app.model(Color, {dataSource: 'db'});
Color.create({name: 'red'});
Color.create({name: 'green'});
@ -17,4 +24,4 @@ Color.create({name: 'blue'});
app.listen(3000);
console.log('a list of colors is available at http://localhost:3000/colors');
g.log('a list of colors is available at {{http://localhost:3000/colors}}');

View File

@ -1,29 +0,0 @@
var loopback = require('../../');
var app = loopback();
// Create a LoopBack context for all requests
app.use(loopback.context());
// Store a request property in the context
app.use(function saveHostToContext(req, res, next) {
var ns = loopback.getCurrentContext();
ns.set('host', req.host);
next();
});
app.use(loopback.rest());
var Color = loopback.createModel('color', { 'name': String });
Color.beforeRemote('**', function (ctx, unused, next) {
// Inside LoopBack code, you can read the property from the context
var ns = loopback.getCurrentContext();
console.log('Request to host', ns && ns.get('host'));
next();
});
app.dataSource('db', { connector: 'memory' });
app.model(Color, { dataSource: 'db' });
app.listen(3000, function() {
console.log('A list of colors is available at http://localhost:3000/colors');
});

View File

@ -1,45 +1,52 @@
var models = require('../../lib/models');
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var loopback = require('../../');
var app = loopback();
'use strict';
const g = require('../../lib/globalize');
const models = require('../../lib/models');
const loopback = require('../../');
const app = loopback();
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);
const data = {
pushSettings: [{
'platform': 'apns',
'apns': {
'pushOptions': {
'gateway': 'gateway.sandbox.push.apple.com',
'cert': 'credentials/apns_cert_dev.pem',
'key': 'credentials/apns_key_dev.pem',
},
var data = {pushSettings: [
{ "platform": "apns",
"apns": {
"pushOptions": {
"gateway": "gateway.sandbox.push.apple.com",
"cert": "credentials/apns_cert_dev.pem",
"key": "credentials/apns_key_dev.pem"
},
"feedbackOptions": {
"gateway": "feedback.sandbox.push.apple.com",
"cert": "credentials/apns_cert_dev.pem",
"key": "credentials/apns_key_dev.pem",
"batchFeedback": true,
"interval": 300
}
}}
]}
'feedbackOptions': {
'gateway': 'feedback.sandbox.push.apple.com',
'cert': 'credentials/apns_cert_dev.pem',
'key': 'credentials/apns_key_dev.pem',
'batchFeedback': true,
'interval': 300,
},
},
}],
};
Application.create(data, function(err, data) {
console.log('Created: ', data.toObject());
g.log('Created: %s', data.toObject());
});
Application.register('rfeng', 'MyApp', {description: 'My first mobile application'}, function (err, result) {
Application.register('rfeng', 'MyApp', {description: g.f('My first mobile application')},
function(err, result) {
console.log(result.toObject());
result.resetKeys(function (err, result) {
console.log(result.toObject());
result.resetKeys(function(err, result) {
console.log(result.toObject());
});
});
});

View File

@ -1,62 +1,70 @@
var loopback = require('../../');
var app = loopback();
var db = app.dataSource('db', {connector: loopback.Memory});
var Color = app.model('color', {dataSource: 'db', options: {trackChanges: true}});
var Color2 = app.model('color2', {dataSource: 'db', options: {trackChanges: true}});
var target = Color2;
var source = Color;
var SPEED = process.env.SPEED || 100;
var conflicts;
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var steps = [
'use strict';
const loopback = require('../../');
const app = loopback();
const db = app.dataSource('db', {connector: 'memory'});
const Color = app.registry.createModel('color', {}, {trackChanges: true});
app.model(Color, {dataSource: 'db'});
const Color2 = app.registry.createModel('color2', {}, {trackChanges: true});
app.model(Color2, {dataSource: 'db'});
const target = Color2;
const source = Color;
const SPEED = process.env.SPEED || 100;
let conflicts;
const steps = [
createSomeInitialSourceData,
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),
updateSomeTargetData,
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data '),
list.bind(this, target, 'current TARGET data (includes conflicting update)'),
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data '),
list.bind(this, target, 'current TARGET data (includes conflicting update)'),
updateSomeSourceDataCausingAConflict,
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data (now has a conflict)'),
list.bind(this, target, 'current TARGET data (includes conflicting update)'),
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data (now has a conflict)'),
list.bind(this, target, 'current TARGET data (includes conflicting update)'),
resolveAllConflicts,
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data (conflict resolved)'),
list.bind(this, target, 'current TARGET data (conflict resolved)'),
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data (conflict resolved)'),
list.bind(this, target, 'current TARGET data (conflict resolved)'),
createMoreSourceData,
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),
createEvenMoreSourceData,
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),
deleteAllSourceData,
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data (empty)'),
list.bind(this, target, 'current TARGET data (empty)'),
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data (empty)'),
list.bind(this, target, 'current TARGET data (empty)'),
createSomeNewSourceData,
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data')
replicateSourceToTarget,
list.bind(this, source, 'current SOURCE data'),
list.bind(this, target, 'current TARGET data'),
];
run(steps);
@ -65,7 +73,7 @@ function createSomeInitialSourceData() {
Color.create([
{name: 'red'},
{name: 'blue'},
{name: 'green'}
{name: 'green'},
]);
}
@ -76,7 +84,7 @@ function replicateSourceToTarget() {
}
function resolveAllConflicts() {
if(conflicts.length) {
if (conflicts.length) {
conflicts.forEach(function(conflict) {
conflict.resolve();
});
@ -113,7 +121,7 @@ function createSomeNewSourceData() {
Color.create([
{name: 'violet'},
{name: 'amber'},
{name: 'olive'}
{name: 'olive'},
]);
}
@ -129,8 +137,8 @@ function list(model, msg) {
function run(steps) {
setInterval(function() {
var step = steps.shift();
if(step) {
const step = steps.shift();
if (step) {
console.log(step.name);
step();
}

View File

@ -1,23 +1,29 @@
var loopback = require('../../');
var app = loopback();
// Copyright IBM Corp. 2013,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 g = require('../../lib/globalize');
const loopback = require('../../');
const app = loopback();
app.use(loopback.rest());
const dataSource = app.dataSource('db', {adapter: 'memory'});
var dataSource = app.dataSource('db', {adapter: 'memory'});
var Color = dataSource.define('color', {
'name': String
const Color = dataSource.define('color', {
'name': String,
});
Color.create({name: 'red'});
Color.create({name: 'green'});
Color.create({name: 'blue'});
Color.all(function () {
Color.all(function() {
console.log(arguments);
});
app.listen(3000);
console.log('a list of colors is available at http://localhost:3000/colors');
g.log('a list of colors is available at {{http://localhost:3000/colors}}');

View File

@ -1,9 +1,15 @@
// Copyright IBM Corp. 2013,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';
/**
* loopback ~ public api
*/
var loopback = module.exports = require('./lib/loopback');
var datasourceJuggler = require('loopback-datasource-juggler');
const loopback = module.exports = require('./lib/loopback');
const datasourceJuggler = require('loopback-datasource-juggler');
/**
* Connectors
@ -19,4 +25,5 @@ loopback.Remote = require('loopback-connector-remote');
*/
loopback.GeoPoint = require('loopback-datasource-juggler/lib/geo').GeoPoint;
loopback.DateString = require('loopback-datasource-juggler/lib/date-string');
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}"
}

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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "Kein Änderungssatz gefunden für {0} mit ID {1}",
"04bd8af876f001ceaf443aad6a9002f9": "Für die Authentifizierung muss Modell {0} definiert sein.",
"095afbf2f1f0e5be678f5dac5c54e717": "Zugriff verweigert",
"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.",
"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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t BETREFF:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Erstellt: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Zugehöriges Element nach ID für {0} aktualisieren.",
"275f22ab95671f095640ca99194b7635": "\t VON:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} wurde in Version 3.0 entfernt. Siehe {1} für weitere Details.",
"2d3071e3b18681c80a090dc0efbdb349": "{0} mit ID {1} konnte nicht gefunden werden",
"316e5b82c203cf3de31a449ee07d0650": "Erwartet wurde boolescher Wert, {0} empfangen",
"320c482401afa1207c04343ab162e803": "Ungültiger Prinzipaltyp: {0}",
"3438fab56cc7ab92dfd88f0497e523e0": "Die relations-Eigenschaft der Konfiguration '{0}' muss ein Objekt sein",
"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}",
"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",
"3aecb24fa8bdd3f79d168761ca8a6729": "Unbekannte {{middleware}}-Phase {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} verwendet Modelleinstellung {1}, die nicht mehr verfügbar ist.",
"3caaa84fc103d6d5612173ae6d43b245": "Ungültiges Token: {0}",
"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.",
"4203ab415ec66a78d3164345439ba76e": "{0}.{1}() kann nicht aufgerufen werden. Die Methode {2} wurde nicht konfiguriert. {{PersistedModel}} wurde nicht ordnungsgemäß an eine {{DataSource}} angehängt!",
"42a36bac5cf03c4418d664500c81047a": "{{DataSource}}-Option {{\"defaultForType\"}} wird nicht mehr unterstützt",
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-Mail nicht gefunden",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "E-Mail senden:",
"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}",
"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",
"5fa3afb425819ebde958043e598cb664": "Modell mit {{id}} {0} konnte nicht gefunden werden",
"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.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Zugehöriges Element nach ID für {0} suchen.",
"6bc376432cd9972cf991aad3de371e78": "Fehlende Daten für Änderung: {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} konnte nicht gefunden werden",
"734a7bebb65e10899935126ba63dd51f": "Die options-Eigenschaft der Konfiguration '{0}' muss ein Objekt 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",
"7e0fca41d098607e1c9aa353c67e0fa1": "Ungültiges Zugriffstoken",
"7e287fc885d9fdcf42da3a12f38572c1": "Berechtigung erforderlich",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} muss sich abmelden",
"80a32e80cbed65eba2103201a7c94710": "Modell nicht gefunden: {0}",
"830cb6c862f8f364e9064cea0026f701": "Ruft hasOne-Beziehung {0} ab.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Eigenschaft '{0}' kann für {1} nicht rekonfiguriert werden",
"855eb8db89b4921c42072832d33d2dc2": "Ungültiges Kennwort.",
"855ecd4a64885ba272d782435f72a4d4": "\"{0}\" unbekannt, ID \"{1}\".",
"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",
"8ae418c605b6a45f2651be9b1677c180": "Ungültige Remote-Methode: '{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}.",
"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.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Ruft belongsTo-Beziehung {0} ab.",
"a50d10fc6e0959b220e085454c40381e": "Benutzer nicht gefunden: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" unbekannt, {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} ist erforderlich",
"c0057a569ff9d3b509bac61a4b2f605d": "Löscht alle {0} von diesem Modell.",
"c2b5d51f007178170ca3952d59640ca4": "{0} Änderungen können nicht behoben werden:\n{1}",
"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.",
"c68a93f0a9524fed4ff64372fc90c55f": "Eine gültige E-Mail-Adresse muss angegeben werden",
"cd0412f2f33a4a2a316acc834f3f21a6": "muss {{id}} oder {{data}} angeben",
"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.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" unbekannt, {{id}} \"{1}\".",
"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.",
"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}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTMETHODE:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "Ergebnis:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Konflikt",
"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}"
}

92
intl/en/messages.json Normal file
View File

@ -0,0 +1,92 @@
{
"03f79fa268fe199de2ce4345515431c1": "No change record found for {0} with id {1}",
"04bd8af876f001ceaf443aad6a9002f9": "Authentication requires model {0} to be defined.",
"095afbf2f1f0e5be678f5dac5c54e717": "Access Denied",
"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.",
"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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t SUBJECT:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Created: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Update a related item by id for {0}.",
"275f22ab95671f095640ca99194b7635": "\t FROM:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} was removed in version 3.0. See {1} for more details.",
"2d3071e3b18681c80a090dc0efbdb349": "could not find {0} with id {1}",
"316e5b82c203cf3de31a449ee07d0650": "Expected boolean, got {0}",
"320c482401afa1207c04343ab162e803": "Invalid principal type: {0}",
"3438fab56cc7ab92dfd88f0497e523e0": "The relations property of `{0}` configuration must be an object",
"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}",
"37bcd4b50dfae98734772e39ffb1ea3d": "The password entered was too long. Max length is {0} (entered {1})",
"3aae63bb7e8e046641767571c1591441": "login failed as the email has not been verified",
"3aecb24fa8bdd3f79d168761ca8a6729": "Unknown {{middleware}} phase {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} is using model setting {1} which is no longer available.",
"3caaa84fc103d6d5612173ae6d43b245": "Invalid token: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Thanks for Registering",
"3d63008ccfb2af1db2142e8cc2716ace": "Warning: No email transport specified for sending email. Setup a transport to send mail messages.",
"4203ab415ec66a78d3164345439ba76e": "Cannot call {0}.{1}(). The {2} method has not been setup. The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!",
"42a36bac5cf03c4418d664500c81047a": "{{DataSource}} option {{\"defaultForType\"}} is no longer supported",
"44a6c8b1ded4ed653d19ddeaaf89a606": "Email not found",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Sending Mail:",
"4b494de07f524703ac0879addbd64b13": "Email has not been verified",
"528325f3cbf1b0ab9a08447515daac9a": "Update {0} of this model.",
"543d19bad5e47ee1e9eb8af688e857b4": "Foreign key for {0}.",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Cannot create data source {0}: {1}",
"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",
"5fa3afb425819ebde958043e598cb664": "could not find a model with {{id}} {0}",
"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.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Find a related item by id for {0}.",
"6bc376432cd9972cf991aad3de371e78": "Missing data for change: {0}",
"705c2d456a3e204c4af56e671ec3225c": "Could not find {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "The options property of `{0}` configuration must be an object",
"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",
"7e0fca41d098607e1c9aa353c67e0fa1": "Invalid Access Token",
"7e287fc885d9fdcf42da3a12f38572c1": "Authorization Required",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} is required to logout",
"80a32e80cbed65eba2103201a7c94710": "Model not found: {0}",
"830cb6c862f8f364e9064cea0026f701": "Fetches hasOne relation {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Property `{0}` cannot be reconfigured for `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Invalid password.",
"855ecd4a64885ba272d782435f72a4d4": "Unknown \"{0}\" id \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "The transport does not support HTTP redirects.",
"86254879d01a60826a851066987703f2": "Add a related item by id for {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} or {{email}} is required",
"8ae418c605b6a45f2651be9b1677c180": "Invalid remote method: `{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}.",
"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.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Fetches belongsTo relation {0}.",
"a50d10fc6e0959b220e085454c40381e": "User not found: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Unknown \"{0}\" {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} is required",
"c0057a569ff9d3b509bac61a4b2f605d": "Deletes all {0} of this model.",
"c2b5d51f007178170ca3952d59640ca4": "Cannot rectify {0} changes:\n{1}",
"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}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Must provide a valid email",
"cd0412f2f33a4a2a316acc834f3f21a6": "must specify an {{id}} or {{data}}",
"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}\".",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Unknown \"{0}\" {{id}} \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "Bulk update failed, the connector has deleted unexpected number of records: {0}",
"ea63d226b6968e328bdf6876010786b5": "Cannot apply bulk updates, the connector does not correctly report the number of deleted records.",
"ead044e2b4bce74b4357f8a03fb78ec4": "Cannot call {0}.{1}(). The {2} method has not been setup. The {{KeyValueModel}} has not been correctly attached to a {{DataSource}}!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t TO:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "result:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflict",
"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}"
}

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

@ -0,0 +1,93 @@
{
"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}.",
"095afbf2f1f0e5be678f5dac5c54e717": "Acceso denegado",
"0caffe1d763c8cca6a61814abe33b776": "El correo electrónico es obligatorio",
"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}}",
"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}",
"1e85f822b547a75d7d385048030e4ecb": "Creado: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Actualizar un elemento relacionado por id para {0}.",
"275f22ab95671f095640ca99194b7635": "\t DESDE:{0}",
"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}",
"316e5b82c203cf3de31a449ee07d0650": "Se esperaba un booleano, se ha obtenido {0}",
"320c482401afa1207c04343ab162e803": "Tipo de principal no válido: {0}",
"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}`.",
"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",
"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}",
"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.",
"4203ab415ec66a78d3164345439ba76e": "No se puede llamar a {0}.{1}(). El método {2} no se ha configurado. {{PersistedModel}} no se ha conectado correctamente a un {{DataSource}}.",
"42a36bac5cf03c4418d664500c81047a": "La opción de {{DataSource}} {{\"defaultForType\"}} ya no está soportada",
"44a6c8b1ded4ed653d19ddeaaf89a606": "Correo electrónico no encontrado",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Enviando correo:",
"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}",
"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",
"5fa3afb425819ebde958043e598cb664": "no se ha encontrado un modelo con {{id}} {0}",
"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.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXTO:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Buscar un elemento relacionado por id para {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",
"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",
"7e0fca41d098607e1c9aa353c67e0fa1": "Señal de acceso no válida",
"7e287fc885d9fdcf42da3a12f38572c1": "Autorización necesaria",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "Es necesario {{accessToken}} para cerrar la sesión",
"80a32e80cbed65eba2103201a7c94710": "No se ha encontrado el modelo: {0}",
"830cb6c862f8f364e9064cea0026f701": "Capta la relación hasOne {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "La propiedad `{0}` no puede reconfigurarse para `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Contraseña no válida.",
"855ecd4a64885ba272d782435f72a4d4": "Id de \"{0}\" desconocido \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "El transporte no admite redirecciones HTTP.",
"86254879d01a60826a851066987703f2": "Añadir un elemento relacionado por id para {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} o {{email}} es obligatorio",
"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}",
"8ecab7f534de38360bd1b1c88e880123": "Los modelos hijo de `{0}` no heredarán los métodos remotos definidos recientemente {1}.",
"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.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Capta la relación belongsTo {0}.",
"a50d10fc6e0959b220e085454c40381e": "No se ha encontrado el usuario: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "{{key}} de \"{0}\" desconocido \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} es obligatorio",
"c0057a569ff9d3b509bac61a4b2f605d": "Suprime todos los {0} de este modelo.",
"c2b5d51f007178170ca3952d59640ca4": "No se pueden rectificar los cambios de {0}:\n{1}",
"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}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Debe proporcionar un correo electrónico válido",
"cd0412f2f33a4a2a316acc834f3f21a6": "debe especificar un {{id}} o {{data}}",
"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}\".",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "{{id}} de \"{0}\" desconocido \"{1}\".",
"e92aa25b6b864e3454b65a7c422bd114": "La actualización masiva ha fallado, el conector ha suprimido un número de registros inesperado: {0}",
"ea63d226b6968e328bdf6876010786b5": "No pueden aplicarse actualizaciones masivas, el conector no notifica correctamente el número de registros suprimidos.",
"ead044e2b4bce74b4357f8a03fb78ec4": "No se puede llamar a {0}.{1}(). El método {2} no se ha configurado. {{KeyValueModel}} no se ha conectado correctamente a un {{DataSource}}.",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t A:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTE:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "resultado:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflicto",
"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}"
}

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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "Aucun enregistrement de changement trouvé pour {0} avec l'id {1}",
"04bd8af876f001ceaf443aad6a9002f9": "L'authentification exige que le modèle {0} soit défini.",
"095afbf2f1f0e5be678f5dac5c54e717": "Accès refusé",
"0caffe1d763c8cca6a61814abe33b776": "L'adresse électronique est obligatoire",
"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}}",
"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 OBJET :{0}",
"1e85f822b547a75d7d385048030e4ecb": "Création de : {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Mettez à jour un élément lié par id pour {0}.",
"275f22ab95671f095640ca99194b7635": "\t DE :{0}",
"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}",
"316e5b82c203cf3de31a449ee07d0650": "Valeur booléenne attendue, {0} obtenu",
"320c482401afa1207c04343ab162e803": "Type de principal non valide : {0}",
"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}`.",
"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",
"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}",
"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.",
"4203ab415ec66a78d3164345439ba76e": "Impossible d'appeler {0}.{1}(). La méthode {2} n'a pas été configurée. {{PersistedModel}} n'a pas été associé correctement à {{DataSource}} !",
"42a36bac5cf03c4418d664500c81047a": "L'option {{DataSource}} {{\"defaultForType\"}} n'est plus prise en charge",
"44a6c8b1ded4ed653d19ddeaaf89a606": "Adresse électronique introuvable",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Envoi d'un message électronique :",
"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}",
"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",
"5fa3afb425819ebde958043e598cb664": "impossible de trouver un modèle avec {{id}} {0}",
"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.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXTE :{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Recherchez un élément lié par id pour {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",
"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",
"7e0fca41d098607e1c9aa353c67e0fa1": "Jeton d'accès non valide",
"7e287fc885d9fdcf42da3a12f38572c1": "Autorisation requise",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} est nécessaire pour la déconnexion",
"80a32e80cbed65eba2103201a7c94710": "Modèle introuvable : {0}",
"830cb6c862f8f364e9064cea0026f701": "Extrait la relation hasOne {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "La propriété `{0}` ne peut pas être reconfigurée pour `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Mot de passe non valide.",
"855ecd4a64885ba272d782435f72a4d4": "ID \"{0}\" inconnu \"{1}\".",
"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",
"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}",
"8ecab7f534de38360bd1b1c88e880123": "Les modèles enfant de `{0}` n'hériteront pas des méthodes distantes nouvellement définies {1}.",
"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.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Extrait la relation belongsTo {0}.",
"a50d10fc6e0959b220e085454c40381e": "Utilisateur introuvable : {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" inconnu.",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} est obligatoire",
"c0057a569ff9d3b509bac61a4b2f605d": "Supprime tous les {0} de ce modèle.",
"c2b5d51f007178170ca3952d59640ca4": "Impossible de rectifier les modifications {0} :\n{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}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Obligation de fournir une adresse électronique valide",
"cd0412f2f33a4a2a316acc834f3f21a6": "obligation de spécifier {{id}} ou {{data}}",
"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é.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" inconnu.",
"e92aa25b6b864e3454b65a7c422bd114": "La mise à jour en bloc a échoué ; le connecteur a supprimé un nombre inattendu d'enregistrements : {0}",
"ea63d226b6968e328bdf6876010786b5": "Impossible d'appliquer des mises à jour en bloc ; le connecteur ne signale pas correctement le nombre d'enregistrements supprimés.",
"ead044e2b4bce74b4357f8a03fb78ec4": "Impossible d'appeler {0}.{1}(). La méthode {2} n'a pas été configurée. {{KeyValueModel}} n'a pas été associé correctement à {{DataSource}} !",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t A :{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT :{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "résultat :{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflit",
"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}"
}

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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "Nessun record di modifica trovato per {0} con id {1}",
"04bd8af876f001ceaf443aad6a9002f9": "L'autenticazione richiede che sia definito il modello {0}.",
"095afbf2f1f0e5be678f5dac5c54e717": "Accesso negato",
"0caffe1d763c8cca6a61814abe33b776": "L'email è obbligatoria",
"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}}",
"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}",
"1e85f822b547a75d7d385048030e4ecb": "Creato: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Aggiornare un elemento correlato in base all'ID per {0}.",
"275f22ab95671f095640ca99194b7635": "\t DA:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} è stato rimosso nella versione 3.0. Consultare {1} per ulteriori dettagli.",
"2d3071e3b18681c80a090dc0efbdb349": "impossibile trovare {0} con id {1}",
"316e5b82c203cf3de31a449ee07d0650": "Previsto valore booleano, ricevuto {0}",
"320c482401afa1207c04343ab162e803": "Tipo principal non valido: {0}",
"3438fab56cc7ab92dfd88f0497e523e0": "La proprietà relations della configurazione `{0}` deve essere un oggetto",
"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}",
"37bcd4b50dfae98734772e39ffb1ea3d": "La password immessa è troppo lunga. La lunghezza massima è {0} (immessa {1})",
"3aae63bb7e8e046641767571c1591441": "login non riuscito perché l'email non è stata verificata",
"3aecb24fa8bdd3f79d168761ca8a6729": "Fase {{middleware}} sconosciuta {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} utilizza l'impostazione del modello {1} che non è più disponibile.",
"3caaa84fc103d6d5612173ae6d43b245": "Token non valido: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "Grazie per essersi registrati",
"3d63008ccfb2af1db2142e8cc2716ace": "Avvertenza: nessun trasporto email specificato per l'invio della email. Configurare un trasporto per inviare messaggi email.",
"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",
"44a6c8b1ded4ed653d19ddeaaf89a606": "Email non trovata",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Invio email:",
"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}",
"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",
"5fa3afb425819ebde958043e598cb664": "impossibile trovare un modello con {{id}} {0}",
"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.",
"63a091ced88001ab6acb58f61ec041c5": "\t TESTO:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Trovare un elemento correlato in base all'ID per {0}.",
"6bc376432cd9972cf991aad3de371e78": "Dati mancanti per la modifica: {0}",
"705c2d456a3e204c4af56e671ec3225c": "Impossibile trovare {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "La proprietà options della configurazione `{0}` deve essere un oggetto",
"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",
"7e0fca41d098607e1c9aa353c67e0fa1": "Token di accesso non valido",
"7e287fc885d9fdcf42da3a12f38572c1": "Autorizzazione richiesta",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} deve scollegarsi",
"80a32e80cbed65eba2103201a7c94710": "Modello non trovato: {0}",
"830cb6c862f8f364e9064cea0026f701": "Recupera la relazione hasOne {0}.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Impossibile riconfigurare la proprietà `{0}` per `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Password non valida.",
"855ecd4a64885ba272d782435f72a4d4": "ID sconosciuto \"{0}\" \"{1}\".",
"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}}",
"8ae418c605b6a45f2651be9b1677c180": "Metodo remoto non valido: `{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}.",
"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.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Recupera la relazione belongsTo {0}.",
"a50d10fc6e0959b220e085454c40381e": "Utente non trovato: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "{{key}} \"{0}\" sconosciuto \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} è obbligatorio",
"c0057a569ff9d3b509bac61a4b2f605d": "Elimina tutti i {0} di questo modello.",
"c2b5d51f007178170ca3952d59640ca4": "Impossibile correggere {0} modifiche:\n{1}",
"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}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "È necessario fornire una email valida",
"cd0412f2f33a4a2a316acc834f3f21a6": "è necessario specificare {{id}} o {{data}}",
"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.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "{{id}} \"{0}\" sconosciuto \"{1}\".",
"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.",
"ead044e2b4bce74b4357f8a03fb78ec4": "Impossibile chiamare {0}.{1}(). Il metodo {2} non è stato configurato. {{KeyValueModel}} non è stato correttamente collegato ad una {{DataSource}}!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t A:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRASPORTO:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "risultato:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflitto",
"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}"
}

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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "ID {1} の {0} の変更レコードが見つかりませんでした",
"04bd8af876f001ceaf443aad6a9002f9": "認証では、モデル {0} を定義する必要があります。",
"095afbf2f1f0e5be678f5dac5c54e717": "アクセス拒否",
"0caffe1d763c8cca6a61814abe33b776": "E メールは必須です",
"0da38687fed24275c1547e815914a8e3": "ID を指定して {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": "ID を指定して {0} の関連項目を更新します。",
"275f22ab95671f095640ca99194b7635": "\t 送信元:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} は、バージョン 3.0 で削除されました。 詳しくは、{1} を参照してください。",
"2d3071e3b18681c80a090dc0efbdb349": "ID {1} の {0} が見つかりませんでした",
"316e5b82c203cf3de31a449ee07d0650": "ブール値が必要ですが、{0} が取得されました",
"320c482401afa1207c04343ab162e803": "無効なプリンシパル・タイプ: {0}",
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 構成の関係プロパティーはオブジェクトでなければなりません",
"3591f1d3e115b46f9f195df5ca548a6a": "モデルが見つかりません: モデル `{0}` は不明のモデル `{1}` を拡張しています。",
"35e5252c62d80f8c54a5290d30f4c7d0": "Web ブラウザーで次のリンクを開いて、E メールを検証してください: \n\t{0}",
"37bcd4b50dfae98734772e39ffb1ea3d": "入力したパスワードが長すぎます。最大長は {0} です ({1} が入力されました)",
"3aae63bb7e8e046641767571c1591441": "E メールが検証されていないため、ログインに失敗しました",
"3aecb24fa8bdd3f79d168761ca8a6729": "不明な {{middleware}} フェーズ {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} は、使用できなくなったモデル設定 {1} を使用しています。",
"3caaa84fc103d6d5612173ae6d43b245": "無効なトークン: {0}",
"3d617953470be16d0c2b32f0bcfbb5ee": "ご登録いただき、ありがとうございます。",
"3d63008ccfb2af1db2142e8cc2716ace": "警告: E メール送信用の E メール・トランスポートが指定されていません。 メール・メッセージを送信するためのトランスポートをセットアップしてください。",
"4203ab415ec66a78d3164345439ba76e": "{0}.{1}() を呼び出せません。 {2} メソッドがセットアップされていません。 {{PersistedModel}} は {{DataSource}} に正しく付加されていません。",
"42a36bac5cf03c4418d664500c81047a": "{{DataSource}} オプション {{\"defaultForType\"}} はサポートされなくなりました",
"44a6c8b1ded4ed653d19ddeaaf89a606": "E メールが見つかりません",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "メールの送信:",
"4b494de07f524703ac0879addbd64b13": "E メールが検証されていません",
"528325f3cbf1b0ab9a08447515daac9a": "このモデルの {0} を更新します。",
"543d19bad5e47ee1e9eb8af688e857b4": "{0} の外部キー。",
"57b87ae0e65f6ab7a2e3e6cbdfca49a4": "データ・ソース {0}: {1} を作成できません",
"5858e63efaa0e4ad86b61c0459ea32fa": "{{Email}} モデルを {{Mail}} コネクターに接続する必要があります",
"598ff0255ffd1d1b71e8de55dbe2c034": "ID を指定して項目との {0} 関係があることを確認します。",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "ID を指定して項目との {0} 関係を削除します。",
"5e81ad3847a290dc650b47618b9cbc7e": "ログインに失敗しました",
"5fa3afb425819ebde958043e598cb664": "{{id}} {0} のモデルが見つかりませんでした",
"61e5deebaf44d68f4e6a508f30cc31a3": "モデル `{1}` には関係 `{0}` が存在しません",
"62e8b0a733417978bab22c8dacf5d7e6": "一括更新を適用できません。コネクターは更新されたレコードの数を正しく報告していません。",
"63a091ced88001ab6acb58f61ec041c5": "\t テキスト:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "ID を指定して {0} の関連項目を検索します。",
"6bc376432cd9972cf991aad3de371e78": "変更用のデータがありません: {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} が見つかりませんでした",
"734a7bebb65e10899935126ba63dd51f": "`{0}` 構成のオプション・プロパティーはオブジェクトでなければなりません",
"779467f467862836e19f494a37d6ab77": "`{0}` 構成の ACL プロパティーはオブジェクトの配列でなければなりません",
"7bc7b301ad9c4fc873029d57fb9740fe": "{1} の {0} を照会します。",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0} の外部キー",
"7d5e7ed0efaedf3f55f380caae0df8b8": "最初のモバイル・アプリケーション",
"7e0fca41d098607e1c9aa353c67e0fa1": "無効なアクセス・トークン",
"7e287fc885d9fdcf42da3a12f38572c1": "許可が必要です",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "ログアウトするには {{accessToken}} が必要です",
"80a32e80cbed65eba2103201a7c94710": "モデルが見つかりません: {0}",
"830cb6c862f8f364e9064cea0026f701": "hasOne 関係 {0} をフェッチします。",
"83cbdc2560ba9f09155ccfc63e08f1a1": "`{1}` のプロパティー `{0}` を再構成できません",
"855eb8db89b4921c42072832d33d2dc2": "パスワードが無効です。",
"855ecd4a64885ba272d782435f72a4d4": "\"{0}\" ID \"{1}\" が不明です。",
"860d1a0b8bd340411fb32baa72867989": "トランスポートでは HTTP リダイレクトはサポートされません。",
"86254879d01a60826a851066987703f2": "ID を指定して {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": "\"{0}\" {{key}} \"{1}\" が不明です。",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} は必須です",
"c0057a569ff9d3b509bac61a4b2f605d": "このモデルのすべての {0} を削除します。",
"c2b5d51f007178170ca3952d59640ca4": "{0} の変更を修正できません:\n{1}",
"c4ee6d177c974532c3552d2f98eb72ea": "{0} ミドルウェアは、バージョン 3.0 で削除されました。 詳しくは、{1} を参照してください。",
"c61a5a02ba3801a892308f70f5d55a14": "リモート・メタデータ {{\"isStatic\"}} は非推奨です。 メソッド名では {{isStatic=false}} の代わりに {{\"prototype.name\"}} を指定してください。",
"c68a93f0a9524fed4ff64372fc90c55f": "有効な E メールを指定する必要があります",
"cd0412f2f33a4a2a316acc834f3f21a6": "{{id}} または {{data}} を指定する必要があります",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} は削除されました。代わりに新規モジュール {{loopback-boot}} を使用してください",
"d6f43b266533b04d442bdb3955622592": "このモデルの {0} に新規インスタンスを作成します。",
"da13d3cdf21330557254670dddd8c5c7": "{1} の {0} をカウントします。",
"dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\" の非オブジェクト「メソッド」設定を無視します。",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{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} を参照してください。"
}

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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "ID가 {1}인 {0}에 대한 변경 레코드를 찾을 수 없음",
"04bd8af876f001ceaf443aad6a9002f9": "인증을 위해 {0} 모델이 정의되어야 함",
"095afbf2f1f0e5be678f5dac5c54e717": "액세스 거부",
"0caffe1d763c8cca6a61814abe33b776": "이메일은 필수입니다.",
"0da38687fed24275c1547e815914a8e3": "{0}에 대해 ID로 관련 항목을 삭제하십시오.",
"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}에 대해 ID로 관련 항목을 업데이트하십시오.",
"275f22ab95671f095640ca99194b7635": "\t 발신인:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "버전 3.0에서 {0}이(가) 제거되었습니다. 자세한 정보는 {1}을(를) 참조하십시오.",
"2d3071e3b18681c80a090dc0efbdb349": "ID {1}(으)로 {0}을(를) 찾을 수 없음",
"316e5b82c203cf3de31a449ee07d0650": "예상 부울, 실제 {0}",
"320c482401afa1207c04343ab162e803": "올바르지 않은 프린시펄 유형: {0}",
"3438fab56cc7ab92dfd88f0497e523e0": "`{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": "ID로 항목에 대한 {0} 관계의 존재를 확인하십시오.",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "ID로 항목에 대한 {0} 관계를 제거하십시오.",
"5e81ad3847a290dc650b47618b9cbc7e": "로그인 실패",
"5fa3afb425819ebde958043e598cb664": "{{id}} {0}인 모델을 찾을 수 없음",
"61e5deebaf44d68f4e6a508f30cc31a3": "모델 `{1}`에 대해 관계 `{0}`이(가) 없습니다.",
"62e8b0a733417978bab22c8dacf5d7e6": "벌크 업데이트를 적용할 수 없습니다. 커넥터가 업데이트된 레코드 수를 제대로 보고하지 않습니다.",
"63a091ced88001ab6acb58f61ec041c5": "\t 텍스트:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "{0}에 대해 ID로 관련 항목을 찾으십시오.",
"6bc376432cd9972cf991aad3de371e78": "변경을 위한 데이터 누락: {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}}을(를) 찾을 수 없음",
"734a7bebb65e10899935126ba63dd51f": "`{0}` 구성의 옵션 특성은 오브젝트여야 함",
"779467f467862836e19f494a37d6ab77": "`{0}` 구성의 acls 특성은 오브젝트 배열이어야 함",
"7bc7b301ad9c4fc873029d57fb9740fe": "{1}의 {0}을(를) 조회합니다.",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0}의 외부 키",
"7d5e7ed0efaedf3f55f380caae0df8b8": "내 첫 번째 모바일 애플리케이션",
"7e0fca41d098607e1c9aa353c67e0fa1": "올바르지 않은 액세스 토큰",
"7e287fc885d9fdcf42da3a12f38572c1": "권한 필수",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}}이(가) 로그아웃해야 함",
"80a32e80cbed65eba2103201a7c94710": "모델을 찾을 수 없음: {0}",
"830cb6c862f8f364e9064cea0026f701": "페치에 하나의 관계 {0}이(가) 있습니다.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "`{1}`에 대해 `{0}` 특성을 다시 구성할 수 없음",
"855eb8db89b4921c42072832d33d2dc2": "올바르지 않은 비밀번호입니다.",
"855ecd4a64885ba272d782435f72a4d4": "알 수 없는 \"{0}\" ID \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "전송에서 HTTP 경로 재지원을 지원하지 않습니다.",
"86254879d01a60826a851066987703f2": "{0}에 대해 ID로 관련 항목을 추가하십시오.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} 또는 {{email}}은(는) 필수입니다.",
"8ae418c605b6a45f2651be9b1677c180": "올바르지 않은 원격 메소드: `{0}`",
"8bab6720ecc58ec6412358c858a53484": "벌크 업데이트에 실패했습니다. 커넥터가 예상치 못한 수의 레코드를 수정했습니다. {0}",
"8ecab7f534de38360bd1b1c88e880123": "'{0}'의 하위 모델은 새로 정의된 원격 메소드 {1}을(를) 상속하지 않습니다.",
"93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}",
"97795efe0c3eb7f35ce8cf8cfe70682b": "`{0}`의 구성에 {{`dataSource`}} 특성이 누락되었습니다.\n데이터 소스에 첨부되지 않은 모델을 표시하려면 `null` 또는 `false`를 사용하십시오.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "페치가 관계 {0}에 속합니다.",
"a50d10fc6e0959b220e085454c40381e": "사용자를 찾을 수 없음: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "알 수 없는 \"{0}\" {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}}은(는) 필수입니다.",
"c0057a569ff9d3b509bac61a4b2f605d": "이 모델의 모든 {0}을(를) 삭제합니다.",
"c2b5d51f007178170ca3952d59640ca4": "{0} 변경사항을 교정할 수 없음:\n{1}",
"c4ee6d177c974532c3552d2f98eb72ea": "버전 3.0에서 {0} 미들웨어가 제거되었습니다. 자세한 정보는 {1}을(를) 참조하십시오.",
"c61a5a02ba3801a892308f70f5d55a14": "원격 메타데이터 {{\"isStatic\"}}이(가) 더 이상 사용되지 않습니다. {{isStatic=false}}인 경우 메소드 이름에 대신 {{\"prototype.name\"}}을(를) 지정하십시오.",
"c68a93f0a9524fed4ff64372fc90c55f": "올바른 이메일을 제공해야 함",
"cd0412f2f33a4a2a316acc834f3f21a6": "{{id}} 또는 {{data}}을(를) 지정해야 함",
"d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}}이(가) 제거되었습니다. 대신 새 모듈 {{loopback-boot}}을(를) 사용하십시오.",
"d6f43b266533b04d442bdb3955622592": "이 모델의 {0}에서 새 인스턴스를 작성합니다.",
"da13d3cdf21330557254670dddd8c5c7": "{1}의 {0}을(를) 계수합니다.",
"dc568bee32deb0f6eaf63e73b20e8ceb": "\"{0}\"의 비오브젝트 \"methods\" 설정 무시",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "알 수 없는 \"{0}\" {{id}} \"{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} 참조"
}

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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "Geen wijzigingsrecord gevonden voor {0} met ID {1}",
"04bd8af876f001ceaf443aad6a9002f9": "Voor verificatie moet model {0} worden gedefinieerd.",
"095afbf2f1f0e5be678f5dac5c54e717": "Toegang geweigerd",
"0caffe1d763c8cca6a61814abe33b776": "E-mail is vereist",
"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}}",
"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}",
"1e85f822b547a75d7d385048030e4ecb": "Gemaakt: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Gerelateerd item bijwerken op basis van ID voor {0}.",
"275f22ab95671f095640ca99194b7635": "\t VAN: {0}",
"2860bccdf9ef1e350c1a38932ed12173": "{0} is versie 3.0 verwijderd. Zie {1} voor meer informatie.",
"2d3071e3b18681c80a090dc0efbdb349": "kan {0} met ID {1} niet vinden",
"316e5b82c203cf3de31a449ee07d0650": "Booleaanse waarde verwacht, {0} ontvangen",
"320c482401afa1207c04343ab162e803": "Ongeldig type principal: {0}",
"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}'.",
"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",
"3aecb24fa8bdd3f79d168761ca8a6729": "Onbekende {{middleware}}-fase {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0} werkt met modelinstelling {1}, maar deze is niet meer beschikbaar.",
"3caaa84fc103d6d5612173ae6d43b245": "Ongeldig token: {0}",
"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.",
"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",
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-mail is niet gevonden",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Mail verzenden:",
"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}",
"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",
"5fa3afb425819ebde958043e598cb664": "geen model gevonden met {{id}} {0}",
"61e5deebaf44d68f4e6a508f30cc31a3": "Relatie '{0}' voor model '{1}' bestaat niet",
"62e8b0a733417978bab22c8dacf5d7e6": "Bulkupdates kunnen niet worden toegepast, de connector meldt niet het juiste aantal bijgewerkte records.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEKST: {0}",
"651f0b3cbba001635152ec3d3d954d0a": "Gerelateerd item zoeken op basis van ID voor {0}.",
"6bc376432cd9972cf991aad3de371e78": "Ontbrekende gegevens voor wijziging: {0}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} is niet gevonden",
"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",
"7bc7b301ad9c4fc873029d57fb9740fe": "Query's {0} van {1}.",
"7c837b88fd0e509bd3fc722d7ddf0711": "Externe sleutel voor {0}",
"7d5e7ed0efaedf3f55f380caae0df8b8": "Mijn eerste mobiele toepassing",
"7e0fca41d098607e1c9aa353c67e0fa1": "Ongeldig toegangstoken",
"7e287fc885d9fdcf42da3a12f38572c1": "Verplichte verificatie",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} is vereist voor afmelding",
"80a32e80cbed65eba2103201a7c94710": "Model is niet gevonden: {0}",
"830cb6c862f8f364e9064cea0026f701": "Haalt hasOne-relatie {0} op.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "Eigenschap '{0}' mag niet opnieuw worden geconfigureerd voor '{1}'",
"855eb8db89b4921c42072832d33d2dc2": "Ongeldig wachtwoord.",
"855ecd4a64885ba272d782435f72a4d4": "Onbekend \"{0}\"-ID \"{1}\".",
"860d1a0b8bd340411fb32baa72867989": "Transport biedt geen ondersteuning voor HTTP-omleidingen.",
"86254879d01a60826a851066987703f2": "Gerelateerd item toevoegen op basis van ID voor {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} of {{email}} is verplicht",
"8ae418c605b6a45f2651be9b1677c180": "Ongeldige niet-lokale methode: '{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.",
"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.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Haalt belongsTo-relatie {0} op.",
"a50d10fc6e0959b220e085454c40381e": "Gebruiker is niet gevonden: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Onbekend \"{0}\" {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} is verplicht",
"c0057a569ff9d3b509bac61a4b2f605d": "Verwijdert alle {0} van dit model.",
"c2b5d51f007178170ca3952d59640ca4": "Wijzigingen van {0} kunnen niet worden hersteld:\n{1}",
"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}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "U moet een geldig e-mailadres opgeven",
"cd0412f2f33a4a2a316acc834f3f21a6": "U moet een {{id}} of {{data}} opgeven",
"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.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Onbekend \"{0}\" {{id}} \"{1}\".",
"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.",
"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}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT: {0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "resultaat:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflict",
"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}"
}

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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "Nenhum registro de mudança localizado para {0} com o ID {1}",
"04bd8af876f001ceaf443aad6a9002f9": "Autenticação requer que modelo {0} seja definido.",
"095afbf2f1f0e5be678f5dac5c54e717": "Acesso Negado",
"0caffe1d763c8cca6a61814abe33b776": "E-mail é necessário",
"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}}",
"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}",
"1e85f822b547a75d7d385048030e4ecb": "Criado: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "Atualizar um item relacionado por ID para {0}.",
"275f22ab95671f095640ca99194b7635": "\t DE:{0}",
"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}",
"316e5b82c203cf3de31a449ee07d0650": "Booleano esperado, obteve {0}",
"320c482401afa1207c04343ab162e803": "Tipo principal inválido: {0}",
"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.",
"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",
"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}",
"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.",
"4203ab415ec66a78d3164345439ba76e": "Não é possível chamar {0}.{1}(). O método {2} não foi configurado. O {{PersistedModel}} não foi conectado corretamente a uma {{DataSource}}!",
"42a36bac5cf03c4418d664500c81047a": "Opção {{\"defaultForType\"}} de {{DataSource}} não é mais suportada",
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-mail não encontrado",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Enviando E-mail:",
"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}",
"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",
"5fa3afb425819ebde958043e598cb664": "não foi possível localizar um modelo com {{id}} {0}",
"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.",
"63a091ced88001ab6acb58f61ec041c5": "\t TEXTO:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "Localize um item relacionado por ID para {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",
"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",
"7e0fca41d098607e1c9aa353c67e0fa1": "Token de Acesso Inválido",
"7e287fc885d9fdcf42da3a12f38572c1": "Autorização Necessária",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} é necessário para efetuar logout",
"80a32e80cbed65eba2103201a7c94710": "Modelo não localizado: {0}",
"830cb6c862f8f364e9064cea0026f701": "Busca relação {0} de hasOne.",
"83cbdc2560ba9f09155ccfc63e08f1a1": "A propriedade `{0}` não pode ser reconfigurada para `{1}`",
"855eb8db89b4921c42072832d33d2dc2": "Senha inválida.",
"855ecd4a64885ba272d782435f72a4d4": "ID \"{1}\" de \"{0}\" desconhecido.",
"860d1a0b8bd340411fb32baa72867989": "O transporte não suporta redirecionamentos de HTTP.",
"86254879d01a60826a851066987703f2": "Inclua um item relacionado por ID para {0}.",
"895b1f941d026870b3cc8e6af087c197": "{{username}} ou {{email}} é necessário",
"8ae418c605b6a45f2651be9b1677c180": "Método remoto inválido: `{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}.",
"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.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Busca relação {0} de belongsTo.",
"a50d10fc6e0959b220e085454c40381e": "Usuário não localizado: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "\"{0}\" {{key}} \"{1}\" desconhecido.",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} é obrigatório",
"c0057a569ff9d3b509bac61a4b2f605d": "Exclui todos os {0} deste modelo.",
"c2b5d51f007178170ca3952d59640ca4": "Não é possível retificar mudanças de {0}:\n{1}",
"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}}.",
"c68a93f0a9524fed4ff64372fc90c55f": "Deve-se fornecer um e-mail válido",
"cd0412f2f33a4a2a316acc834f3f21a6": "deve-se especificar um {{id}} ou {{data}}",
"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}\".",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{1}\" desconhecido.",
"e92aa25b6b864e3454b65a7c422bd114": "Atualização em massa falhou, o conector excluiu um número inesperado de registros: {0}",
"ea63d226b6968e328bdf6876010786b5": "Não é possível aplicar atualizações em massa, o conector não relata o número de registros excluídos corretamente.",
"ead044e2b4bce74b4357f8a03fb78ec4": "Não é possível chamar {0}.{1}(). O método {2} não foi configurado. O {{KeyValueModel}} não foi conectado corretamente a uma {{DataSource}}!",
"ecb06666ef95e5db27a5ac1d6a17923b": "\t PARA:{0}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORTE:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "resultado:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Conflito",
"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}"
}

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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "{0} için {1} tanıtıcılı bir değişiklik kaydı bulunamadı",
"04bd8af876f001ceaf443aad6a9002f9": "Kimlik doğrulaması {0} modelinin tanımlanmasını gerektiriyor.",
"095afbf2f1f0e5be678f5dac5c54e717": "Erişim Verilmedi",
"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.",
"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",
"1d7833c3ca2f05fdad8fad7537531c40": "\t KONU:{0}",
"1e85f822b547a75d7d385048030e4ecb": "Yaratıldığı tarih: {0}",
"22fe62fa8d595b72c62208beddaa2a56": "{0} için ilgili bir öğeyi tanıtıcı temelinde günceller.",
"275f22ab95671f095640ca99194b7635": "\t KİMDEN:{0}",
"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ı",
"316e5b82c203cf3de31a449ee07d0650": "Boole beklenirken {0} alındı",
"320c482401afa1207c04343ab162e803": "Geçersiz birincil kullanıcı tipi: {0}",
"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.",
"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",
"3aecb24fa8bdd3f79d168761ca8a6729": "Bilinmeyen {{middleware}} aşaması {0}",
"3ca45aa6f705c46a4c598a900716f086": "{0}, artık kullanılabilir olmayan {1} model ayarını kullanıyor.",
"3caaa84fc103d6d5612173ae6d43b245": "Geçersiz belirteç: {0}",
"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.",
"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.",
"44a6c8b1ded4ed653d19ddeaaf89a606": "E-posta bulunamadı",
"4a4f04a4e480fc5d4ee73b84d9a4b904": "Posta Gönderiliyor:",
"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}",
"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",
"5fa3afb425819ebde958043e598cb664": "{{id}} {0} tanıtıcılı bir model bulunamadı",
"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.",
"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}",
"705c2d456a3e204c4af56e671ec3225c": "{{accessToken}} bulunamadı",
"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.",
"7bc7b301ad9c4fc873029d57fb9740fe": "{1} ile ilişkili {0} öğesini sorgular.",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0} için dış anahtar",
"7d5e7ed0efaedf3f55f380caae0df8b8": "İlk mobil uygulamam",
"7e0fca41d098607e1c9aa353c67e0fa1": "Geçersiz Erişim Belirteci",
"7e287fc885d9fdcf42da3a12f38572c1": "Yetkilendirme Gerekli",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "Oturumu kapatmak için {{accessToken}} zorunludur",
"80a32e80cbed65eba2103201a7c94710": "Model bulunamadı: {0}",
"830cb6c862f8f364e9064cea0026f701": "{0} hasOne ilişkisini alır.",
"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}\".",
"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",
"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}",
"8ecab7f534de38360bd1b1c88e880123": "`{0}` alt modelleri, yeni tanımlanan uzak yöntemleri ({1}) devralmayacaktır.",
"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.",
"9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "{0} belongsTo ilişkisini alır.",
"a50d10fc6e0959b220e085454c40381e": "Kullanıcı bulunamadı: {0}",
"b6f740aeb6f2eb9bee9cb049dbfe6a28": "Bilinmeyen \"{0}\" {{key}} \"{1}\".",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} zorunludur",
"c0057a569ff9d3b509bac61a4b2f605d": "Bu modele ilişkin tüm {0} öğelerini siler.",
"c2b5d51f007178170ca3952d59640ca4": "{0} değişiklik düzeltilemiyor:\n{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.",
"c68a93f0a9524fed4ff64372fc90c55f": "Geçerli bir e-posta belirtilmeli",
"cd0412f2f33a4a2a316acc834f3f21a6": "bir {{id}} ya da {{data}} belirtmelidir",
"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.",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "Bilinmeyen \"{0}\" {{id}} \"{1}\".",
"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.",
"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}",
"f0aed00a3d3d0b97d6594e4b70e0c201": "\t AKTARIM:{0}",
"f0bd73df8714cefb925e3b8da2f4c5f6": "sonuç:{0}",
"f1d4ac54357cc0932f385d56814ba7e4": "Çakışma",
"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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "对于标识为 {1} 的 {0},找不到任何更改记录",
"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": "V3.0 中移除了 {0}。请参阅 {1} 以获取更多详细信息。",
"2d3071e3b18681c80a090dc0efbdb349": "无法找到标识为 {1} 的 {0}",
"316e5b82c203cf3de31a449ee07d0650": "期望布尔值,获取 {0}",
"320c482401afa1207c04343ab162e803": "无效的主体类型:{0}",
"3438fab56cc7ab92dfd88f0497e523e0": "“{0}”配置的关系属性必须是对象。",
"3591f1d3e115b46f9f195df5ca548a6a": "找不到模型:模型“{0}”正在扩展未知的模型“{1}”。",
"35e5252c62d80f8c54a5290d30f4c7d0": "请通过在 Web 浏览器中打开此链接来验证您的电子邮件:\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": "对于模型“{1}”,关系“{0}”不存在",
"62e8b0a733417978bab22c8dacf5d7e6": "无法应用批量更新,连接器未正确报告更新的记录数。",
"63a091ced88001ab6acb58f61ec041c5": "\t 文本:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "按标识查找 {0} 的相关项。",
"6bc376432cd9972cf991aad3de371e78": "缺少更改的数据:{0}",
"705c2d456a3e204c4af56e671ec3225c": "无法找到 {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "“{0}”配置的选项属性必须是对象。",
"779467f467862836e19f494a37d6ab77": "“{0}”配置的 acls 属性必须是对象数组。",
"7bc7b301ad9c4fc873029d57fb9740fe": "查询 {1} 的 {0}。",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0} 的外键",
"7d5e7ed0efaedf3f55f380caae0df8b8": "我的第一个移动应用程序",
"7e0fca41d098607e1c9aa353c67e0fa1": "无效的访问令牌",
"7e287fc885d9fdcf42da3a12f38572c1": "需要授权",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "{{accessToken}} 需要注销",
"80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}",
"830cb6c862f8f364e9064cea0026f701": "访存 hasOne 关系 {0}。",
"83cbdc2560ba9f09155ccfc63e08f1a1": "无法针对“{1}”重新配置属性“{0}”。",
"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": "未知的“{0}”{{key}}“{1}”。",
"ba96498b10c179f9cd75f75c8def4f70": "{{realm}} 是必需的",
"c0057a569ff9d3b509bac61a4b2f605d": "删除此模型的所有 {0}。",
"c2b5d51f007178170ca3952d59640ca4": "无法纠正 {0} 更改:\n{1}",
"c4ee6d177c974532c3552d2f98eb72ea": "V3.0 中移除了 {0} 中间件。请参阅 {1} 以获取更多详细信息。",
"c61a5a02ba3801a892308f70f5d55a14": "不推荐使用远程处理元数据 {{\"isStatic\"}}。而是针对 {{isStatic=false}} 在方法名称中指定 {{\"prototype.name\"}}。",
"c68a93f0a9524fed4ff64372fc90c55f": "必须提供有效电子邮件",
"cd0412f2f33a4a2a316acc834f3f21a6": "必须指定 {{id}} 或 {{data}}",
"d5552322de5605c58b62f47ad26d2716": "已移除 {{`app.boot`}},请改用新模块 {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "在此模型的 {0} 中创建新实例。",
"da13d3cdf21330557254670dddd8c5c7": "计算 {0} 的数量({1})。",
"dc568bee32deb0f6eaf63e73b20e8ceb": "忽略“{0}”的非对象“方法”设置。",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "未知的“{0}”{{id}}“{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

@ -0,0 +1,93 @@
{
"03f79fa268fe199de2ce4345515431c1": "對於 id 為 {1} 的 {0},找不到變更記錄",
"04bd8af876f001ceaf443aad6a9002f9": "需要定義模型 {0} 才能鑑別。",
"095afbf2f1f0e5be678f5dac5c54e717": "拒絕存取",
"0caffe1d763c8cca6a61814abe33b776": "需要電子郵件",
"0da38687fed24275c1547e815914a8e3": "依 id 刪除 {0} 的相關項目。",
"0e21aad369dd09e1965c11949303cefd": "{0} 的遠端 meta 資料。{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": "依 id 更新 {0} 的相關項目。",
"275f22ab95671f095640ca99194b7635": "\t 寄件者:{0}",
"2860bccdf9ef1e350c1a38932ed12173": "3.0 版中已移除 {0}。如需詳細資料,請參閱 {1}。",
"2d3071e3b18681c80a090dc0efbdb349": "找不到 id 為 {1} 的 {0}",
"316e5b82c203cf3de31a449ee07d0650": "預期為布林,但卻取得 {0}",
"320c482401afa1207c04343ab162e803": "無效的主體類型:{0}",
"3438fab56cc7ab92dfd88f0497e523e0": "`{0}` 配置的 relations 內容必須是物件",
"3591f1d3e115b46f9f195df5ca548a6a": "找不到模型:模型 `{0}` 正在延伸不明模型 `{1}`。",
"35e5252c62d80f8c54a5290d30f4c7d0": "請在 Web 瀏覽器中開啟此鏈結來驗證電子郵件:\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": "依 id 檢查項目的 {0} 關係是否存在。",
"5a36cc6ba0cc27c754f6c5ed6015ea3c": "依 id 移除項目的 {0} 關係。",
"5e81ad3847a290dc650b47618b9cbc7e": "登入失敗",
"5fa3afb425819ebde958043e598cb664": "找不到 {{id}} 為 {0} 的模型",
"61e5deebaf44d68f4e6a508f30cc31a3": "模型 `{1}` 的關係 `{0}` 不存在",
"62e8b0a733417978bab22c8dacf5d7e6": "無法套用大量更新,連接器未正確報告已更新的記錄數。",
"63a091ced88001ab6acb58f61ec041c5": "\t 文字:{0}",
"651f0b3cbba001635152ec3d3d954d0a": "依 id 尋找 {0} 的相關項目。",
"6bc376432cd9972cf991aad3de371e78": "遺漏變更的資料:{0}",
"705c2d456a3e204c4af56e671ec3225c": "找不到 {{accessToken}}",
"734a7bebb65e10899935126ba63dd51f": "`{0}` 配置的 options 內容必須是物件",
"779467f467862836e19f494a37d6ab77": "`{0}` 配置的 acls 內容必須是物件陣列",
"7bc7b301ad9c4fc873029d57fb9740fe": "查詢 {0} 個(共 {1} 個)。",
"7c837b88fd0e509bd3fc722d7ddf0711": "{0} 的外部索引鍵",
"7d5e7ed0efaedf3f55f380caae0df8b8": "我的第一個行動式應用程式",
"7e0fca41d098607e1c9aa353c67e0fa1": "存取記號無效",
"7e287fc885d9fdcf42da3a12f38572c1": "需要授權",
"7ea04ea91aac3cb7ce0ddd96b7ff1fa4": "需要 {{accessToken}} 才能登出",
"80a32e80cbed65eba2103201a7c94710": "找不到模型:{0}",
"830cb6c862f8f364e9064cea0026f701": "提取 hasOne 關係 {0}。",
"83cbdc2560ba9f09155ccfc63e08f1a1": "無法為 `{1}` 重新配置內容 `{0}`",
"855eb8db89b4921c42072832d33d2dc2": "密碼無效。",
"855ecd4a64885ba272d782435f72a4d4": "\"{0}\" ID \"{1}\" 不明。",
"860d1a0b8bd340411fb32baa72867989": "傳輸不支援 HTTP 重新導向。",
"86254879d01a60826a851066987703f2": "依 id 新增 {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": "\"{0}\" {{key}} \"{1}\" 不明。",
"ba96498b10c179f9cd75f75c8def4f70": "需要 {{realm}}",
"c0057a569ff9d3b509bac61a4b2f605d": "刪除這個模型的所有 {0}。",
"c2b5d51f007178170ca3952d59640ca4": "無法更正 {0} 個變更:\n{1}",
"c4ee6d177c974532c3552d2f98eb72ea": "3.0 版中已移除 {0} 中介軟體。如需詳細資料,請參閱 {1}。",
"c61a5a02ba3801a892308f70f5d55a14": "遠端 meta 資料 {{\"isStatic\"}} 已淘汰。請在方法名稱中指定 {{\"prototype.name\"}} 來代替 {{isStatic=false}}。",
"c68a93f0a9524fed4ff64372fc90c55f": "必須提供有效的電子郵件",
"cd0412f2f33a4a2a316acc834f3f21a6": "必須指定 {{id}} 或 {{data}}",
"d5552322de5605c58b62f47ad26d2716": "已移除 {{`app.boot`}},請改用新的模組 {{loopback-boot}}",
"d6f43b266533b04d442bdb3955622592": "在這個模型的 {0} 中建立新實例。",
"da13d3cdf21330557254670dddd8c5c7": "計算 {0} 個(共 {1} 個)。",
"dc568bee32deb0f6eaf63e73b20e8ceb": "忽略 \"{0}\" 的非物件 \"methods\" 設定。",
"e4434de4bb8f5a3cd1d416e4d80d7e0b": "\"{0}\" {{id}} \"{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

@ -1,22 +1,42 @@
var assert = require('assert');
var loopback = require('./loopback');
var debug = require('debug')('loopback:security:access-context');
// Copyright IBM Corp. 2014,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 assert = require('assert');
const loopback = require('./loopback');
const debug = require('debug')('loopback:security:access-context');
const DEFAULT_SCOPES = ['DEFAULT'];
/**
* Access context represents the context for a request to access protected
* resources
*
* NOTE While the method expects an array of principals in the AccessContext instance/object,
* it also accepts a single principal defined with the following properties:
* ```js
* {
* // AccessContext instance/object
* // ..
* principalType: 'somePrincipalType', // APP, ROLE, USER, or custom user model name
* principalId: 'somePrincipalId',
* }
* ```
*
* @class
* @options {Object} context The context object
* @options {AccessContext|Object} context An AccessContext instance or an object
* @property {Principal[]} principals An array of principals
* @property {Function} model The model class
* @property {String} modelName The model name
* @property {String} modelId The model id
* @property {*} modelId The model id
* @property {String} property The model property/method/relation name
* @property {String} method The model method to be invoked
* @property {String} accessType The access type
* @property {AccessToken} accessToken The access token
*
* @property {String} accessType The access type: READ, REPLICATE, WRITE, or EXECUTE.
* @property {AccessToken} accessToken The access token resolved for the request
* @property {RemotingContext} remotingContext The request's remoting context
* @property {Registry} registry The application or global registry
* @returns {AccessContext}
* @constructor
*/
@ -26,9 +46,12 @@ function AccessContext(context) {
}
context = context || {};
assert(context.registry,
'Application registry is mandatory in AccessContext but missing in provided context');
this.registry = context.registry;
this.principals = context.principals || [];
var model = context.model;
model = ('string' === typeof model) ? loopback.getModel(model) : model;
let model = context.model;
model = ('string' === typeof model) ? this.registry.getModel(model) : model;
this.model = model;
this.modelName = model && model.modelName;
@ -49,23 +72,24 @@ function AccessContext(context) {
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
var principalType = context.principalType || Principal.USER;
var principalId = context.principalId || undefined;
var principalName = context.principalName || undefined;
if (principalId) {
const principalType = context.principalType || Principal.USER;
const principalId = context.principalId || undefined;
const principalName = context.principalName || undefined;
if (principalId != null) {
this.addPrincipal(principalType, principalId, principalName);
}
var token = this.accessToken || {};
const token = this.accessToken;
if (token.userId) {
this.addPrincipal(Principal.USER, token.userId);
if (token.userId != null) {
this.addPrincipal(token.principalType || Principal.USER, token.userId);
}
if (token.appId) {
if (token.appId != null) {
this.addPrincipal(Principal.APPLICATION, token.appId);
}
this.remotingContext = context.remotingContext;
@ -91,7 +115,7 @@ AccessContext.permissionOrder = {
ALLOW: 1,
ALARM: 2,
AUDIT: 3,
DENY: 4
DENY: 4,
};
/**
@ -102,9 +126,9 @@ AccessContext.permissionOrder = {
* @returns {boolean}
*/
AccessContext.prototype.addPrincipal = function(principalType, principalId, principalName) {
var principal = new Principal(principalType, principalId, principalName);
for (var i = 0; i < this.principals.length; i++) {
var p = this.principals[i];
const principal = new Principal(principalType, principalId, principalName);
for (let i = 0; i < this.principals.length; i++) {
const p = this.principals[i];
if (p.equals(principal)) {
return false;
}
@ -118,13 +142,35 @@ AccessContext.prototype.addPrincipal = function(principalType, principalId, prin
* @returns {*}
*/
AccessContext.prototype.getUserId = function() {
for (var i = 0; i < this.principals.length; i++) {
var p = this.principals[i];
const user = this.getUser();
return user && user.id;
};
/**
* Get the user
* @returns {*}
*/
AccessContext.prototype.getUser = function() {
const BaseUser = this.registry.getModel('User');
for (let i = 0; i < this.principals.length; i++) {
const p = this.principals[i];
const isBuiltinPrincipal = p.type === Principal.APP ||
p.type === Principal.ROLE ||
p.type == Principal.SCOPE;
if (isBuiltinPrincipal) continue;
// the principalType must either be 'USER'
if (p.type === Principal.USER) {
return p.id;
return {id: p.id, principalType: p.type};
}
// or permit to resolve a valid user model
const userModel = this.registry.findModel(p.type);
if (!userModel) continue;
if (userModel.prototype instanceof BaseUser) {
return {id: p.id, principalType: p.type};
}
}
return null;
};
/**
@ -132,8 +178,8 @@ AccessContext.prototype.getUserId = function() {
* @returns {*}
*/
AccessContext.prototype.getAppId = function() {
for (var i = 0; i < this.principals.length; i++) {
var p = this.principals[i];
for (let i = 0; i < this.principals.length; i++) {
const p = this.principals[i];
if (p.type === Principal.APPLICATION) {
return p.id;
}
@ -146,7 +192,46 @@ AccessContext.prototype.getAppId = function() {
* @returns {boolean}
*/
AccessContext.prototype.isAuthenticated = function() {
return !!(this.getUserId() || this.getAppId());
return this.getUserId() != null || this.getAppId() != null;
};
/**
* Get the list of scopes required by the current access context.
*/
AccessContext.prototype.getScopes = function() {
if (!this.sharedMethod)
return DEFAULT_SCOPES;
// For backwards compatibility, methods with no scopes defined
// are assigned a single "DEFAULT" scope
const methodLevel = this.sharedMethod.accessScopes || DEFAULT_SCOPES;
// TODO add model-level and app-level scopes
debug('--Context scopes of %s()--', this.sharedMethod.stringName);
debug(' method-level: %j', methodLevel);
return methodLevel;
};
/**
* Check if the scope required by the remote method is allowed
* by the scopes granted to the requesting access token.
* @return {boolean}
*/
AccessContext.prototype.isScopeAllowed = function() {
if (!this.accessToken) return false;
// For backwards compatibility, tokens with no scopes are treated
// as if they have "DEFAULT" scope granted
const tokenScopes = this.accessToken.scopes || DEFAULT_SCOPES;
const resourceScopes = this.getScopes();
// Scope is allowed when at least one of token's scopes
// is found in method's (resource's) scopes.
return Array.isArray(tokenScopes) && Array.isArray(resourceScopes) &&
resourceScopes.some(s => tokenScopes.indexOf(s) !== -1);
};
/*!
@ -169,10 +254,12 @@ AccessContext.prototype.debug = function() {
debug('property %s', this.property);
debug('method %s', this.method);
debug('accessType %s', this.accessType);
debug('accessScopes %j', this.getScopes());
if (this.accessToken) {
debug('accessToken:');
debug(' id %j', this.accessToken.id);
debug(' ttl %j', this.accessToken.ttl);
debug(' scopes %j', this.accessToken.scopes || DEFAULT_SCOPES);
}
debug('getUserId() %s', this.getUserId());
debug('isAuthenticated() %s', this.isAuthenticated());
@ -183,8 +270,9 @@ AccessContext.prototype.debug = function() {
* This class represents the abstract notion of a principal, which can be used
* to represent any entity, such as an individual, a corporation, and a login id
* @param {String} type The principal type
* @param {*} id The princiapl id
* @param {*} id The principal id
* @param {String} [name] The principal name
* @param {String} modelName The principal model name
* @returns {Principal}
* @class
*/
@ -217,32 +305,44 @@ Principal.prototype.equals = function(p) {
/**
* A request to access protected resources.
* @param {String} model The model name
* @param {String} property
*
* The method can either be called with the following signature or with a single
* argument: an AccessRequest instance or an object containing all the required properties.
*
* @class
* @options {String|AccessRequest|Object} model|req The model name,<br>
* or an AccessRequest instance/object.
* @param {String} property The property/method/relation name
* @param {String} accessType The access type
* @param {String} permission The requested permission
* @param {String[]} methodNames The names of involved methods
* @param {Registry} registry The application or global registry
* @returns {AccessRequest}
* @class
*/
function AccessRequest(model, property, accessType, permission, methodNames) {
function AccessRequest(model, property, accessType, permission, methodNames, registry) {
if (!(this instanceof AccessRequest)) {
return new AccessRequest(model, property, accessType);
return new AccessRequest(model, property, accessType, permission, methodNames);
}
if (arguments.length === 1 && typeof model === 'object') {
// The argument is an object that contains all required properties
var obj = model || {};
const obj = model || {};
this.model = obj.model || AccessContext.ALL;
this.property = obj.property || AccessContext.ALL;
this.accessType = obj.accessType || AccessContext.ALL;
this.permission = obj.permission || AccessContext.DEFAULT;
this.methodNames = methodNames || [];
this.methodNames = obj.methodNames || [];
this.registry = obj.registry;
} else {
this.model = model || AccessContext.ALL;
this.property = property || AccessContext.ALL;
this.accessType = accessType || AccessContext.ALL;
this.permission = permission || AccessContext.DEFAULT;
this.methodNames = methodNames || [];
this.registry = registry;
}
// do not create AccessRequest without a registry
assert(this.registry,
'Application registry is mandatory in AccessRequest but missing in provided argument(s)');
}
/**
@ -263,10 +363,10 @@ AccessRequest.prototype.isWildcard = function() {
*/
AccessRequest.prototype.exactlyMatches = function(acl) {
var matchesModel = acl.model === this.model;
var matchesProperty = acl.property === this.property;
var matchesMethodName = this.methodNames.indexOf(acl.property) !== -1;
var matchesAccessType = acl.accessType === this.accessType;
const matchesModel = acl.model === this.model;
const matchesProperty = acl.property === this.property;
const matchesMethodName = this.methodNames.indexOf(acl.property) !== -1;
const matchesAccessType = acl.accessType === this.accessType;
if (matchesModel && matchesAccessType) {
return matchesProperty || matchesMethodName;
@ -275,6 +375,28 @@ AccessRequest.prototype.exactlyMatches = function(acl) {
return false;
};
/**
* Settle the accessRequest's permission if DEFAULT
* In most situations, the default permission can be resolved from the nested model
* config. An default permission can also be explicitly provided to override it or
* cope with AccessRequest instances without a nested model (e.g. model is '*')
*
* @param {String} defaultPermission (optional) the default permission to apply
*/
AccessRequest.prototype.settleDefaultPermission = function(defaultPermission) {
if (this.permission !== 'DEFAULT')
return;
const modelName = this.model;
if (!defaultPermission) {
const modelClass = this.registry.findModel(modelName);
defaultPermission = modelClass && modelClass.settings.defaultPermission;
}
this.permission = defaultPermission || 'ALLOW';
};
/**
* Is the request for access allowed?
*
@ -300,3 +422,4 @@ AccessRequest.prototype.debug = function() {
module.exports.AccessContext = AccessContext;
module.exports.Principal = Principal;
module.exports.AccessRequest = AccessRequest;
module.exports.DEFAULT_SCOPES = DEFAULT_SCOPES;

View File

@ -1,17 +1,24 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*!
* Module dependencies.
*/
var DataSource = require('loopback-datasource-juggler').DataSource;
var Registry = require('./registry');
var assert = require('assert');
var fs = require('fs');
var extend = require('util')._extend;
var RemoteObjects = require('strong-remoting');
var classify = require('underscore.string/classify');
var camelize = require('underscore.string/camelize');
var path = require('path');
var util = require('util');
'use strict';
const g = require('./globalize');
const DataSource = require('loopback-datasource-juggler').DataSource;
const Registry = require('./registry');
const assert = require('assert');
const fs = require('fs');
const extend = require('util')._extend;
const RemoteObjects = require('strong-remoting');
const classify = require('underscore.string/classify');
const camelize = require('underscore.string/camelize');
const path = require('path');
const util = require('util');
/**
* The `App` object represents a Loopback application.
@ -42,7 +49,7 @@ function App() {
* 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).
@ -55,7 +62,7 @@ app.remotes = function() {
if (this._remotes) {
return this._remotes;
} else {
var options = {};
let options = {};
if (this.get) {
options = this.get('remoting');
@ -71,7 +78,7 @@ app.remotes = function() {
app.disuse = function(route) {
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) {
this.stack.splice(i, 1);
}
@ -94,7 +101,7 @@ app.disuse = function(route) {
* app.model(User, { dataSource: 'db' });
*```
*
* @param {Object|String} Model The model to attach.
* @param {Object} Model The model to attach.
* @options {Object} config The model's configuration.
* @property {String|DataSource} dataSource The `DataSource` to which to attach the model.
* @property {Boolean} [public] Whether the model should be exposed via REST API.
@ -104,32 +111,18 @@ app.disuse = function(route) {
*/
app.model = function(Model, config) {
var isPublic = true;
var registry = this.registry;
let isPublic = true;
const registry = this.registry;
if (typeof Model === 'string') {
const msg = 'app.model(modelName, settings) is no longer supported. ' +
'Use app.registry.createModel(modelName, definition) and ' +
'app.model(ModelCtor, config) instead.';
throw new Error(msg);
}
if (arguments.length > 1) {
config = config || {};
if (typeof Model === 'string') {
// create & attach the model - backwards compatibility
// create config for loopback.modelFromConfig
var modelConfig = extend({}, config);
modelConfig.options = extend({}, config.options);
modelConfig.name = Model;
// modeller does not understand `dataSource` option
delete modelConfig.dataSource;
Model = registry.createModel(modelConfig);
// delete config options already applied
['relations', 'base', 'acls', 'hidden', 'methods'].forEach(function(prop) {
delete config[prop];
if (config.options) delete config.options[prop];
});
delete config.properties;
}
configureModel(Model, config, this);
isPublic = config.public !== false;
} else {
@ -137,7 +130,7 @@ app.model = function(Model, config) {
Model.modelName + ' must be a descendant of loopback.Model');
}
var modelName = Model.modelName;
const modelName = Model.modelName;
this.models[modelName] =
this.models[classify(modelName)] =
this.models[camelize(modelName)] = Model;
@ -145,6 +138,9 @@ app.model = function(Model, config) {
this.models().push(Model);
if (isPublic && Model.sharedClass) {
this.remotes().defineObjectType(Model.modelName, function(data) {
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
@ -153,12 +149,60 @@ app.model = function(Model, config) {
this.emit('modelRemoted', Model.sharedClass);
}
const self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
clearHandlerCache(self);
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
clearHandlerCache(self);
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
Model.app = this;
Model.emit('attached', this);
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()`
*
@ -174,7 +218,7 @@ app.model = function(Model, config) {
* });
* ```
*
* 2. Use `app.model` to access a model by name.
* 2. Use `app.models` to access a model by name.
* `app.models` has properties for all defined models.
*
* The following example illustrates accessing the `Product` and `CustomerReceipt` models
@ -189,8 +233,10 @@ app.model = function(Model, config) {
* }
* });
*
* app.model('product', {dataSource: 'db'});
* app.model('customer-receipt', {dataSource: 'db'});
* var productModel = app.registry.createModel('product');
* app.model(productModel, {dataSource: 'db'});
* var customerReceiptModel = app.registry.createModel('customer-receipt');
* app.model(customerReceiptModel, {dataSource: 'db'});
*
* // available based on the given name
* var Product = app.models.Product;
@ -219,11 +265,20 @@ app.models = function() {
* @param {Object} config The data source config
*/
app.dataSource = function(name, config) {
var ds = dataSourcesFromConfig(name, config, this.connectors, this.registry);
this.dataSources[name] =
this.dataSources[classify(name)] =
this.dataSources[camelize(name)] = ds;
return ds;
try {
const ds = dataSourcesFromConfig(name, config, this.connectors, this.registry);
this.dataSources[name] =
this.dataSources[classify(name)] =
this.dataSources[camelize(name)] = ds;
ds.app = this;
return ds;
} catch (err) {
if (err.message) {
err.message = g.f('Cannot create data source %s: %s',
JSON.stringify(name), err.message);
}
throw err;
}
};
/**
@ -252,7 +307,7 @@ app.connector = function(name, connector) {
*/
app.remoteObjects = function() {
var result = {};
const result = {};
this.remotes().classes().forEach(function(sharedClass) {
result[sharedClass.name] = sharedClass.ctor;
@ -267,13 +322,13 @@ app.remoteObjects = function() {
*/
app.handler = function(type, options) {
var handlers = this._handlers || (this._handlers = {});
const handlers = this._handlers || (this._handlers = {});
if (handlers[type]) {
return handlers[type];
}
var remotes = this.remotes();
var handler = this._handlers[type] = remotes.handler(type, options);
const remotes = this.remotes();
const handler = this._handlers[type] = remotes.handler(type, options);
remotes.classes().forEach(function(sharedClass) {
sharedClass.ctor.emit('mounted', app, sharedClass, remotes);
@ -293,53 +348,56 @@ app.dataSources = app.datasources = {};
*/
app.enableAuth = function(options) {
var AUTH_MODELS = ['User', 'AccessToken', 'ACL', 'Role', 'RoleMapping'];
const AUTH_MODELS = ['User', 'AccessToken', 'ACL', 'Role', 'RoleMapping'];
var remotes = this.remotes();
var app = this;
const remotes = this.remotes();
const app = this;
if (options && options.dataSource) {
var appModels = app.registry.modelBuilder.models;
const appModels = app.registry.modelBuilder.models;
AUTH_MODELS.forEach(function(m) {
var Model = app.registry.findModel(m);
const Model = app.registry.findModel(m);
if (!Model) {
throw new Error(
'Authentication requires model ' + m + ' to be defined.');
g.f('Authentication requires model %s to be defined.', m),
);
}
if (m.dataSource || m.app) return;
if (Model.dataSource || Model.app) return;
for (var name in appModels) {
var candidate = appModels[name];
var isSubclass = candidate.prototype instanceof Model;
var isAttached = !!candidate.dataSource || !!candidate.app;
// Find descendants of Model that are attached,
// for example "Customer" extending "User" model
for (const name in appModels) {
const candidate = appModels[name];
const isSubclass = candidate.prototype instanceof Model;
const isAttached = !!candidate.dataSource || !!candidate.app;
if (isSubclass && isAttached) return;
}
app.model(Model, {
dataSource: options.dataSource,
public: m === 'User'
public: m === 'User',
});
});
}
remotes.authorization = function(ctx, next) {
var method = ctx.method;
var req = ctx.req;
var Model = method.ctor;
var modelInstance = ctx.instance;
const method = ctx.method;
const req = ctx.req;
const Model = method.ctor;
const modelInstance = ctx.instance;
var modelId = modelInstance && modelInstance.id ||
const modelId = modelInstance && modelInstance.id ||
// replacement for deprecated req.param()
(req.params && req.params.id !== undefined ? req.params.id :
req.body && req.body.id !== undefined ? req.body.id :
req.query && req.query.id !== undefined ? req.query.id :
undefined);
req.body && req.body.id !== undefined ? req.body.id :
req.query && req.query.id !== undefined ? req.query.id :
undefined);
var modelName = Model.modelName;
const modelName = Model.modelName;
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
const modelSettings = Model.settings || {};
let errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
@ -357,69 +415,166 @@ app.enableAuth = function(options) {
} else if (allowed) {
next();
} else {
var messages = {
const messages = {
403: {
message: 'Access Denied',
code: 'ACCESS_DENIED'
message: g.f('Access Denied'),
code: 'ACCESS_DENIED',
},
404: {
message: ('could not find ' + modelName + ' with id ' + modelId),
code: 'MODEL_NOT_FOUND'
message: (g.f('could not find %s with id %s', modelName, modelId)),
code: 'MODEL_NOT_FOUND',
},
401: {
message: 'Authorization Required',
code: 'AUTHORIZATION_REQUIRED'
}
message: g.f('Authorization Required'),
code: 'AUTHORIZATION_REQUIRED',
},
};
var e = new Error(messages[errStatusCode].message || messages[403].message);
const e = new Error(messages[errStatusCode].message || messages[403].message);
e.statusCode = errStatusCode;
e.code = messages[errStatusCode].code || messages[403].code;
next(e);
}
}
},
);
} else {
next();
}
};
this._verifyAuthModelRelations();
this.isAuthEnabled = true;
};
app._verifyAuthModelRelations = function() {
// Allow unit-tests (but also LoopBack users) to disable the warnings
if (this.get('_verifyAuthModelRelations') === false) return;
const AccessToken = this.registry.findModel('AccessToken');
const User = this.registry.findModel('User');
this.models().forEach(Model => {
if (Model === AccessToken || Model.prototype instanceof AccessToken) {
scheduleVerification(Model, verifyAccessTokenRelations);
}
if (Model === User || Model.prototype instanceof User) {
scheduleVerification(Model, verifyUserRelations);
}
});
function scheduleVerification(Model, verifyFn) {
if (Model.dataSource) {
verifyFn(Model);
} else {
Model.on('attached', () => verifyFn(Model));
}
}
function verifyAccessTokenRelations(Model) {
const belongsToUser = Model.relations && Model.relations.user;
if (belongsToUser) return;
const relationsConfig = Model.settings.relations || {};
const userName = (relationsConfig.user || {}).model;
if (userName) {
console.warn(
'The model %j configures "belongsTo User-like models" relation ' +
'with target model %j. However, the model %j is not attached to ' +
'the application and therefore cannot be used by this relation. ' +
'This typically happens when the application has a custom ' +
'custom User subclass, but does not fix AccessToken relations ' +
'to use this new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, userName, userName,
);
return;
}
console.warn(
'The model %j does not have "belongsTo User-like model" relation ' +
'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName,
);
}
function verifyUserRelations(Model) {
const hasManyTokens = Model.relations && Model.relations.accessTokens;
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 accessTokenName = (relationsConfig.accessTokens || {}).model;
if (accessTokenName) {
console.warn(
'The model %j configures "hasMany AccessToken-like models" relation ' +
'with target model %j. However, the model %j is not attached to ' +
'the application and therefore cannot be used by this relation. ' +
'This typically happens when the application has a custom ' +
'AccessToken subclass, but does not fix User relations to use this ' +
'new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, accessTokenName, accessTokenName,
);
return;
}
console.warn(
'The model %j does not have "hasMany AccessToken-like models" relation ' +
'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName,
);
}
};
app.boot = function(options) {
throw new Error(
'`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) {
var connectorPath;
let connectorPath;
assert(typeof config === 'object',
'cannont create data source without config object');
'can not create data source without config object');
if (typeof config.connector === 'string') {
name = config.connector;
if (connectorRegistry[name]) {
config.connector = connectorRegistry[name];
const connectorName = config.connector;
if (connectorRegistry[connectorName]) {
config.connector = connectorRegistry[connectorName];
} else {
connectorPath = path.join(__dirname, 'connectors', name + '.js');
connectorPath = path.join(__dirname, 'connectors', connectorName + '.js');
if (fs.existsSync(connectorPath)) {
config.connector = require(connectorPath);
}
}
if (config.connector && typeof config.connector === 'object' && !config.connector.name)
config.connector.name = connectorName;
}
return registry.createDataSource(config);
return registry.createDataSource(name, config);
}
function configureModel(ModelCtor, config, app) {
assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'),
ModelCtor.modelName + ' must be a descendant of loopback.Model');
var dataSource = config.dataSource;
let dataSource = config.dataSource;
if (dataSource) {
if (typeof dataSource === 'string') {
@ -429,61 +584,16 @@ function configureModel(ModelCtor, config, app) {
assert(
dataSource instanceof DataSource,
ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' +
config.dataSource + '"'
config.dataSource + '"',
);
}
config = extend({}, config);
config.dataSource = dataSource;
setSharedMethodSharedProperties(ModelCtor, app, 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('Expected boolean, got ' + 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) {
app._handlers = undefined;
}
@ -517,15 +627,15 @@ function clearHandlerCache(app) {
* as the request handler.
*/
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() {
self.set('port', this.address().port);
var listeningOnAll = false;
var host = self.get('host');
let listeningOnAll = false;
let host = self.get('host');
if (!host) {
listeningOnAll = true;
host = this.address().address;
@ -535,23 +645,26 @@ app.listen = function(cb) {
}
if (!self.get('url')) {
if (process.platform === 'win32' && listeningOnAll) {
// Windows browsers don't support `0.0.0.0` host in the URL
if (listeningOnAll) {
// We are replacing it with localhost to build a URL
// that can be copied and pasted into the browser.
host = 'localhost';
}
var url = 'http://' + host + ':' + self.get('port') + '/';
const url = 'http://' + host + ':' + self.get('port') + '/';
self.set('url', url);
}
});
var useAppConfig =
const useAppConfig =
arguments.length === 0 ||
(arguments.length == 1 && typeof arguments[0] == 'function');
if (useAppConfig) {
server.listen(this.get('port'), this.get('host'), cb);
let port = this.get('port');
// NOTE(bajtos) port:undefined no longer works on node@6,
// we must pass port:0 explicitly
if (port === undefined) port = 0;
server.listen(port, this.get('host'), cb);
} else {
server.listen.apply(server, arguments);
}

View File

@ -1,5 +1,11 @@
var EventEmitter = require('events').EventEmitter;
var util = require('util');
// Copyright IBM Corp. 2014,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 EventEmitter = require('events').EventEmitter;
const util = require('util');
module.exports = browserExpress;

View File

@ -1,68 +1,98 @@
// Copyright IBM Corp. 2014,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 assert = require('assert');
module.exports = function(registry) {
// NOTE(bajtos) we must use static require() due to browserify limitations
registry.KeyValueModel = createModel(
require('../common/models/key-value-model.json'),
require('../common/models/key-value-model.js'),
);
registry.Email = createModel(
require('../common/models/email.json'),
require('../common/models/email.js'));
require('../common/models/email.js'),
);
registry.Application = createModel(
require('../common/models/application.json'),
require('../common/models/application.js'));
require('../common/models/application.js'),
);
registry.AccessToken = createModel(
require('../common/models/access-token.json'),
require('../common/models/access-token.js'));
require('../common/models/access-token.js'),
);
registry.User = createModel(
require('../common/models/user.json'),
require('../common/models/user.js'));
require('../common/models/user.js'),
);
registry.RoleMapping = createModel(
require('../common/models/role-mapping.json'),
require('../common/models/role-mapping.js'));
require('../common/models/role-mapping.js'),
);
registry.Role = createModel(
require('../common/models/role.json'),
require('../common/models/role.js'));
require('../common/models/role.js'),
);
registry.ACL = createModel(
require('../common/models/acl.json'),
require('../common/models/acl.js'));
require('../common/models/acl.js'),
);
registry.Scope = createModel(
require('../common/models/scope.json'),
require('../common/models/scope.js'));
require('../common/models/scope.js'),
);
registry.Change = createModel(
require('../common/models/change.json'),
require('../common/models/change.js'));
require('../common/models/change.js'),
);
registry.Checkpoint = createModel(
require('../common/models/checkpoint.json'),
require('../common/models/checkpoint.js'));
/*!
* Automatically attach these models to dataSources
*/
var dataSourceTypes = {
DB: 'db',
MAIL: 'mail'
};
registry.Email.autoAttach = dataSourceTypes.MAIL;
registry.getModel('PersistedModel').autoAttach = dataSourceTypes.DB;
registry.User.autoAttach = dataSourceTypes.DB;
registry.AccessToken.autoAttach = dataSourceTypes.DB;
registry.Role.autoAttach = dataSourceTypes.DB;
registry.RoleMapping.autoAttach = dataSourceTypes.DB;
registry.ACL.autoAttach = dataSourceTypes.DB;
registry.Scope.autoAttach = dataSourceTypes.DB;
registry.Application.autoAttach = dataSourceTypes.DB;
require('../common/models/checkpoint.js'),
);
function createModel(definitionJson, customizeFn) {
var Model = registry.createModel(definitionJson);
// Clone the JSON definition to allow applications
// to modify model settings while not affecting
// settings of new models created in the local registry
// of another app.
// This is needed because require() always returns the same
// object instance it loaded during the first call.
definitionJson = cloneDeepJson(definitionJson);
const Model = registry.createModel(definitionJson);
customizeFn(Model);
return Model;
}
};
// Because we are cloning objects created by JSON.parse,
// the cloning algorithm can stay much simpler than a general-purpose
// "cloneDeep" e.g. from lodash.
function cloneDeepJson(obj) {
const result = Array.isArray(obj) ? [] : {};
assert.equal(Object.getPrototypeOf(result), Object.getPrototypeOf(obj));
for (const key in obj) {
const value = obj[key];
if (typeof value === 'object') {
result[key] = cloneDeepJson(value);
} else {
result[key] = value;
}
}
return result;
}

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

View File

@ -1,11 +1,18 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/**
* Dependencies.
*/
var mailer = require('nodemailer');
var assert = require('assert');
var debug = require('debug')('loopback:connector:mail');
var loopback = require('../loopback');
'use strict';
const g = require('../globalize');
const mailer = require('nodemailer');
const assert = require('assert');
const debug = require('debug')('loopback:connector:mail');
const loopback = require('../loopback');
/**
* Export the MailConnector class.
@ -18,14 +25,13 @@ module.exports = MailConnector;
*/
function MailConnector(settings) {
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) {
//then wrap single transport in an array and assign to transports
// then wrap single transport in an array and assign to transports
transports = [settings.transport];
}
@ -54,10 +60,11 @@ MailConnector.prototype.DataAccessObject = Mailer;
* Example:
*
* Email.setupTransport({
* type: 'SMTP',
* type: "SMTP",
* host: "smtp.gmail.com", // hostname
* secureConnection: true, // use SSL
* port: 465, // port for secure SMTP
* alias: "gmail", // optional alias for use with 'transport' option when sending
* auth: {
* user: "gmail.user@gmail.com",
* pass: "userpass"
@ -67,23 +74,21 @@ MailConnector.prototype.DataAccessObject = Mailer;
*/
MailConnector.prototype.setupTransport = function(setting) {
var connector = this;
const connector = this;
connector.transports = connector.transports || [];
connector.transportsIndex = connector.transportsIndex || {};
var transport;
var transportType = (setting.type || 'STUB').toLowerCase();
if (transportType === 'direct') {
transport = mailer.createTransport();
} else if (transportType === 'smtp') {
let transport;
const transportType = (setting.type || 'STUB').toLowerCase();
if (transportType === 'smtp') {
transport = mailer.createTransport(setting);
} else {
var transportModuleName = 'nodemailer-' + transportType + '-transport';
var transportModule = require(transportModuleName);
const transportModuleName = 'nodemailer-' + transportType + '-transport';
const transportModule = require(transportModuleName);
transport = mailer.createTransport(transportModule(setting));
}
connector.transportsIndex[setting.type] = transport;
connector.transportsIndex[setting.alias || setting.type] = transport;
connector.transports.push(transport);
};
@ -122,7 +127,8 @@ MailConnector.prototype.defaultTransport = function() {
* to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers
* subject: "Hello ✔", // Subject line
* text: "Hello world ✔", // plaintext body
* html: "<b>Hello world ✔</b>" // html body
* html: "<b>Hello world ✔</b>", // html body
* transport: "gmail", // See 'alias' option above in setupTransport
* }
*
* See https://github.com/andris9/Nodemailer for other supported options.
@ -132,34 +138,35 @@ MailConnector.prototype.defaultTransport = function() {
*/
Mailer.send = function(options, fn) {
var dataSource = this.dataSource;
var settings = dataSource && dataSource.settings;
var connector = dataSource.connector;
const dataSource = this.dataSource;
const settings = dataSource && dataSource.settings;
const connector = dataSource.connector;
assert(connector, 'Cannot send mail without a connector!');
var transport = connector.transportForName(options.transport);
let transport = connector.transportForName(options.transport);
if (!transport) {
transport = connector.defaultTransport();
}
if (debug.enabled || settings && settings.debug) {
console.log('Sending Mail:');
g.log('Sending Mail:');
if (options.transport) {
console.log('\t TRANSPORT:', options.transport);
console.log(g.f('\t TRANSPORT:%s', options.transport));
}
console.log('\t TO:', options.to);
console.log('\t FROM:', options.from);
console.log('\t SUBJECT:', options.subject);
console.log('\t TEXT:', options.text);
console.log('\t HTML:', options.html);
g.log('\t TO:%s', options.to);
g.log('\t FROM:%s', options.from);
g.log('\t SUBJECT:%s', options.subject);
g.log('\t TEXT:%s', options.text);
g.log('\t HTML:%s', options.html);
}
if (transport) {
assert(transport.sendMail, 'You must supply an Email.settings.transports containing a valid transport');
assert(transport.sendMail,
'You must supply an Email.settings.transports containing a valid transport');
transport.sendMail(options, fn);
} else {
console.warn('Warning: No email transport specified for sending email.' +
g.warn('Warning: No email transport specified for sending email.' +
' Setup a transport to send mail messages.');
process.nextTick(function() {
fn(null, options);

View File

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

37
lib/current-context.js Normal file
View File

@ -0,0 +1,37 @@
// Copyright IBM Corp. 2016,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 g = require('./globalize');
const juggler = require('loopback-datasource-juggler');
const remoting = require('strong-remoting');
module.exports = function(loopback) {
juggler.getCurrentContext =
remoting.getCurrentContext =
loopback.getCurrentContext = function() {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.getCurrentContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
};
loopback.runInContext = function(fn) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.runInContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
};
loopback.createContext = function(scopeName) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.createContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
};
};

View File

@ -1,52 +0,0 @@
var path = require('path');
var middlewares = exports;
function safeRequire(m) {
try {
return require(m);
} catch (err) {
return undefined;
}
}
function createMiddlewareNotInstalled(memberName, moduleName) {
return function() {
var msg = 'The middleware loopback.' + memberName + ' is not installed.\n' +
'Run `npm install --save ' + moduleName + '` to fix the problem.';
throw new Error(msg);
};
}
var middlewareModules = {
'compress': 'compression',
'timeout': 'connect-timeout',
'cookieParser': 'cookie-parser',
'cookieSession': 'cookie-session',
'csrf': 'csurf',
'errorHandler': 'errorhandler',
'session': 'express-session',
'methodOverride': 'method-override',
'logger': 'morgan',
'responseTime': 'response-time',
'favicon': 'serve-favicon',
'directory': 'serve-index',
// 'static': 'serve-static',
'vhost': 'vhost'
};
middlewares.bodyParser = safeRequire('body-parser');
middlewares.json = middlewares.bodyParser && middlewares.bodyParser.json;
middlewares.urlencoded = middlewares.bodyParser && middlewares.bodyParser.urlencoded;
for (var m in middlewareModules) {
var moduleName = middlewareModules[m];
middlewares[m] = safeRequire(moduleName) || createMiddlewareNotInstalled(m, moduleName);
}
// serve-favicon requires a path
var favicon = middlewares.favicon;
middlewares.favicon = function(icon, options) {
icon = icon || path.join(__dirname, '../favicon.ico');
return favicon(icon, options);
};

11
lib/globalize.js Normal file
View File

@ -0,0 +1,11 @@
// Copyright IBM Corp. 2016,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 path = require('path');
const SG = require('strong-globalize');
SG.SetRootDir(path.join(__dirname, '..'), {autonomousMsgLoading: 'all'});
module.exports = SG();

View File

@ -1,17 +1,24 @@
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*!
* Module dependencies.
*/
var express = require('express');
var loopbackExpress = require('./server-app');
var proto = require('./application');
var fs = require('fs');
var ejs = require('ejs');
var path = require('path');
var merge = require('util')._extend;
var assert = require('assert');
var Registry = require('./registry');
var juggler = require('loopback-datasource-juggler');
'use strict';
const express = require('express');
const loopbackExpress = require('./server-app');
const proto = require('./application');
const fs = require('fs');
const ejs = require('ejs');
const path = require('path');
const merge = require('util')._extend;
const assert = require('assert');
const Registry = require('./registry');
const juggler = require('loopback-datasource-juggler');
const configureSharedMethods = require('./configure-shared-methods');
/**
* LoopBack core module. It provides static properties and
@ -24,7 +31,6 @@ var juggler = require('loopback-datasource-juggler');
* ```
*
* @property {String} version Version of LoopBack framework. Static read-only property.
* @property {String} mime
* @property {Boolean} isBrowser True if running in a browser environment; false otherwise. Static read-only property.
* @property {Boolean} isServer True if running in a server environment; false otherwise. Static read-only property.
* @property {Registry} registry The global `Registry` object.
@ -34,7 +40,7 @@ var juggler = require('loopback-datasource-juggler');
* @header loopback
*/
var loopback = module.exports = createApplication;
const loopback = module.exports = createApplication;
/*!
* Framework version.
@ -42,27 +48,21 @@ var loopback = module.exports = createApplication;
loopback.version = require('../package.json').version;
/*!
* Expose mime.
*/
loopback.mime = express.mime;
loopback.registry = new Registry();
Object.defineProperties(loopback, {
Model: {
get: function() { return this.registry.getModel('Model'); }
get: function() { return this.registry.getModel('Model'); },
},
PersistedModel: {
get: function() { return this.registry.getModel('PersistedModel'); }
get: function() { return this.registry.getModel('PersistedModel'); },
},
defaultDataSources: {
get: function() { return this.registry.defaultDataSources; }
get: function() { return this.registry.defaultDataSources; },
},
modelBuilder: {
get: function() { return this.registry.modelBuilder; }
}
get: function() { return this.registry.modelBuilder; },
},
});
/*!
@ -73,12 +73,19 @@ Object.defineProperties(loopback, {
*/
function createApplication(options) {
var app = loopbackExpress();
const app = loopbackExpress();
merge(app, proto);
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
app.models = function() {
return proto.models.apply(this, arguments);
@ -95,10 +102,12 @@ function createApplication(options) {
// and thus browserify can process them (include connectors in the bundle)
app.connector('memory', loopback.Memory);
app.connector('remote', loopback.Remote);
app.connector('kv-memory',
require('loopback-datasource-juggler/lib/connectors/kv-memory'));
if (loopback.localRegistry || options && options.localRegistry === true) {
// setup the app registry
var registry = app.registry = new Registry();
const registry = app.registry = new Registry();
if (options && options.loadBuiltinModels === true) {
require('./builtin-models')(registry);
}
@ -110,8 +119,8 @@ function createApplication(options) {
}
function mixin(source) {
for (var key in source) {
var desc = Object.getOwnPropertyDescriptor(source, key);
for (const key in source) {
const desc = Object.getOwnPropertyDescriptor(source, key);
// Fix for legacy (pre-ES5) browsers like PhantomJS
if (!desc) continue;
@ -123,23 +132,11 @@ function mixin(source) {
mixin(require('./runtime'));
/*!
* Expose static express methods like `express.errorHandler`.
* Expose static express methods like `express.Router`.
*/
mixin(express);
/*!
* Expose additional middleware like session as loopback.*
* This will keep the loopback API compatible with express 3.x
*
* ***only in node***
*/
if (loopback.isServer) {
var middlewares = require('./express-middleware');
mixin(middlewares);
}
/*!
* Expose additional loopback middleware
* for example `loopback.configure` etc.
@ -164,11 +161,8 @@ if (loopback.isServer) {
delete loopback['error-handler'];
}
/*
* Expose path to the default favicon file
*
* ***only in node***
*/
// Expose path to the default favicon file
// ***only in node***
if (loopback.isServer) {
/*!
@ -205,19 +199,19 @@ loopback.remoteMethod = function(fn, options) {
* var render = loopback.template('foo.ejs');
* var html = render({foo: 'bar'});
*
* @param {String} path Path to the template file.
* @param {String} file Path to the template file.
* @returns {Function}
*/
loopback.template = function(file) {
var templates = this._templates || (this._templates = {});
var str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8'));
const templates = this._templates || (this._templates = {});
const str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8'));
return ejs.compile(str, {
filename: file
filename: file,
});
};
require('../server/current-context')(loopback);
require('../lib/current-context')(loopback);
/**
* Create a named vanilla JavaScript class constructor with an attached
@ -363,51 +357,6 @@ loopback.createDataSource = function(name, options) {
loopback.memory = function(name) {
return this.registry.memory.apply(this.registry, arguments);
};
/**
* Set the default `dataSource` for a given `type`.
* @param {String} type The datasource type.
* @param {Object|DataSource} dataSource The data source settings or instance
* @returns {DataSource} The data source instance.
*
* @header loopback.setDefaultDataSourceForType(type, dataSource)
*/
loopback.setDefaultDataSourceForType = function(type, dataSource) {
return this.registry.setDefaultDataSourceForType.apply(this.registry, arguments);
};
/**
* Get the default `dataSource` for a given `type`.
* @param {String} type The datasource type.
* @returns {DataSource} The data source instance
*/
loopback.getDefaultDataSourceForType = function(type) {
return this.registry.getDefaultDataSourceForType.apply(this.registry, arguments);
};
/**
* Attach any model that does not have a dataSource to
* the default dataSource for the type the Model requests
*/
loopback.autoAttach = function() {
return this.registry.autoAttach.apply(this.registry, arguments);
};
loopback.autoAttachModel = function(ModelCtor) {
return this.registry.autoAttachModel.apply(this.registry, arguments);
};
// temporary alias to simplify migration of code based on <=2.0.0-beta3
// TODO(bajtos) Remove this in v3.0
Object.defineProperty(loopback, 'DataModel', {
get: function() {
return this.registry.DataModel;
}
});
/*!
* Built in models / services
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,17 @@
var assert = require('assert');
var extend = require('util')._extend;
var juggler = require('loopback-datasource-juggler');
var debug = require('debug')('loopback:registry');
var DataSource = juggler.DataSource;
var ModelBuilder = juggler.ModelBuilder;
// Copyright IBM Corp. 2014,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 g = require('./globalize');
const assert = require('assert');
const extend = require('util')._extend;
const juggler = require('loopback-datasource-juggler');
const debug = require('debug')('loopback:registry');
const DataSource = juggler.DataSource;
const ModelBuilder = juggler.ModelBuilder;
const deprecated = require('depd')('strong-remoting');
module.exports = Registry;
@ -88,9 +96,8 @@ function Registry() {
*/
Registry.prototype.createModel = function(name, properties, options) {
if (arguments.length === 1 && typeof name === 'object') {
var config = name;
const config = name;
name = config.name;
properties = config.properties;
options = buildModelOptionsFromConfig(config);
@ -100,41 +107,29 @@ Registry.prototype.createModel = function(name, properties, options) {
}
options = options || {};
var BaseModel = options.base || options.super;
let BaseModel = options.base || options.super;
if (typeof BaseModel === 'string') {
var baseName = BaseModel;
BaseModel = this.getModel(BaseModel);
if (BaseModel === undefined) {
if (baseName === 'DataModel') {
console.warn('Model `%s` is extending deprecated `DataModel. ' +
'Use `PersistedModel` instead.', name);
BaseModel = this.getModel('PersistedModel');
} else {
console.warn('Model `%s` is extending an unknown model `%s`. ' +
'Using `PersistedModel` as the base.', name, baseName);
}
const baseName = BaseModel;
BaseModel = this.findModel(BaseModel);
if (!BaseModel) {
throw new Error(g.f('Model not found: model `%s` is extending an unknown model `%s`.',
name, baseName));
}
}
BaseModel = BaseModel || this.getModel('PersistedModel');
var model = BaseModel.extend(name, properties, options);
const model = BaseModel.extend(name, properties, options);
model.registry = this;
this._defineRemoteMethods(model, options.methods);
// try to attach
try {
this.autoAttachModel(model);
} catch (e) {}
this._defineRemoteMethods(model, model.settings.methods);
return model;
};
function buildModelOptionsFromConfig(config) {
var options = extend({}, config.options);
for (var key in config) {
const options = extend({}, config.options);
for (const key in config) {
if (['name', 'properties', 'options'].indexOf(key) !== -1) {
// Skip items which have special meaning
continue;
@ -157,7 +152,7 @@ function buildModelOptionsFromConfig(config) {
* @param {Object} 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
if (acls[i].property === acl.property &&
acls[i].accessType === acl.accessType &&
@ -181,51 +176,53 @@ function addACL(acls, acl) {
*/
Registry.prototype.configureModel = function(ModelCtor, config) {
var settings = ModelCtor.settings;
var modelName = ModelCtor.modelName;
const settings = ModelCtor.settings;
const modelName = ModelCtor.modelName;
ModelCtor.config = config;
// Relations
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) {
// FIXME: [rfeng] We probably should check if the relation exists
relations[key] = extend(relations[key] || {}, config.relations[key]);
});
} else if (config.relations != null) {
console.warn('The relations property of `%s` configuration ' +
g.warn('The relations property of `%s` configuration ' +
'must be an object', modelName);
}
// ACLs
if (Array.isArray(config.acls)) {
var acls = settings.acls = settings.acls || [];
const acls = settings.acls = settings.acls || [];
config.acls.forEach(function(acl) {
addACL(acls, acl);
});
} else if (config.acls != null) {
console.warn('The acls property of `%s` configuration ' +
g.warn('The acls property of `%s` configuration ' +
'must be an array of objects', modelName);
}
// Settings
var excludedProperties = {
const excludedProperties = {
base: true,
'super': true,
relations: true,
acls: true,
dataSource: true
dataSource: true,
};
if (typeof config.options === 'object' && config.options !== null) {
for (var p in config.options) {
for (const p in config.options) {
if (!(p in excludedProperties)) {
settings[p] = config.options[p];
} else {
console.warn('Property `%s` cannot be reconfigured for `%s`',
g.warn('Property `%s` cannot be reconfigured for `%s`',
p, modelName);
}
}
} else if (config.options != null) {
console.warn('The options property of `%s` configuration ' +
g.warn('The options property of `%s` configuration ' +
'must be an object', modelName);
}
@ -233,7 +230,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
// configuration, so that the datasource picks up updated relations
if (config.dataSource) {
assert(config.dataSource instanceof DataSource,
'Cannot configure ' + ModelCtor.modelName +
'Cannot configure ' + ModelCtor.modelName +
': config.dataSource must be an instance of DataSource');
ModelCtor.attachTo(config.dataSource);
debug('Attached model `%s` to dataSource `%s`',
@ -244,10 +241,21 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
} else {
debug('Model `%s` is not attached to any DataSource, possibly by a mistake.',
modelName);
console.warn(
'The configuration of `%s` is missing `dataSource` property.\n' +
g.warn(
'The configuration of `%s` is missing {{`dataSource`}} property.\n' +
'Use `null` or `false` to mark models not attached to any data source.',
modelName);
modelName,
);
}
const newMethodNames = config.methods && Object.keys(config.methods);
const hasNewMethods = newMethodNames && newMethodNames.length;
const hasDescendants = this.getModelByType(ModelCtor) !== ModelCtor;
if (hasNewMethods && hasDescendants) {
g.warn(
'Child models of `%s` will not inherit newly defined remote methods %s.',
modelName, newMethodNames,
);
}
// Remote methods
@ -257,19 +265,26 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
Registry.prototype._defineRemoteMethods = function(ModelCtor, methods) {
if (!methods) return;
if (typeof methods !== 'object') {
console.warn('Ignoring non-object "methods" setting of "%s".',
g.warn('Ignoring non-object "methods" setting of "%s".',
ModelCtor.modelName);
return;
}
Object.keys(methods).forEach(function(key) {
var meta = methods[key];
let meta = methods[key];
const m = key.match(/^prototype\.(.*)$/);
const isStatic = !m;
if (typeof meta.isStatic !== 'boolean') {
console.warn('Remoting metadata for "%s.%s" is missing "isStatic" ' +
'flag, the method is registered as an instance method.',
ModelCtor.modelName,
key);
console.warn('This behaviour may change in the next major version.');
key = isStatic ? key : m[1];
meta = Object.assign({}, meta, {isStatic});
} else if (meta.isStatic && m) {
throw new Error(g.f('Remoting metadata for %s.%s {{"isStatic"}} does ' +
'not match new method name-based style.', ModelCtor.modelName, key));
} else {
key = isStatic ? key : m[1];
deprecated(g.f('Remoting metadata {{"isStatic"}} is deprecated. Please ' +
'specify {{"prototype.name"}} in method name instead for {{isStatic=false}}.'));
}
ModelCtor.remoteMethod(key, meta);
});
@ -298,10 +313,10 @@ Registry.prototype.findModel = function(modelName) {
* @header loopback.getModel(modelName)
*/
Registry.prototype.getModel = function(modelName) {
var model = this.findModel(modelName);
const model = this.findModel(modelName);
if (model) return model;
throw new Error('Model not found: ' + modelName);
throw new Error(g.f('Model not found: %s', modelName));
};
/**
@ -314,8 +329,8 @@ Registry.prototype.getModel = function(modelName) {
* @header loopback.getModelByType(modelType)
*/
Registry.prototype.getModelByType = function(modelType) {
var type = typeof modelType;
var accepted = ['function', 'string'];
const type = typeof modelType;
const accepted = ['function', 'string'];
assert(accepted.indexOf(type) > -1,
'The model type must be a constructor or model name');
@ -324,8 +339,8 @@ Registry.prototype.getModelByType = function(modelType) {
modelType = this.getModel(modelType);
}
var models = this.modelBuilder.models;
for (var m in models) {
const models = this.modelBuilder.models;
for (const m in models) {
if (models[m].prototype instanceof modelType) {
return models[m];
}
@ -344,15 +359,15 @@ Registry.prototype.getModelByType = function(modelType) {
*/
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) {
settings = settings || {};
var BaseModel = settings.base || settings.super;
let BaseModel = settings.base || settings.super;
if (!BaseModel) {
// Check the connector types
var connectorTypes = ds.getTypes();
const connectorTypes = ds.getTypes();
if (Array.isArray(connectorTypes) && connectorTypes.indexOf('db') !== -1) {
// Only set up the base model to PersistedModel if the connector is DB
BaseModel = self.PersistedModel;
@ -361,13 +376,14 @@ Registry.prototype.createDataSource = function(name, options) {
}
settings.base = BaseModel;
}
var ModelCtor = self.createModel(name, properties, settings);
const ModelCtor = self.createModel(name, properties, settings);
ModelCtor.attachTo(ds);
return ModelCtor;
};
if (ds.settings && ds.settings.defaultForType) {
this.setDefaultDataSourceForType(ds.settings.defaultForType, ds);
const msg = g.f('{{DataSource}} option {{"defaultForType"}} is no longer supported');
throw new Error(msg);
}
return ds;
@ -382,90 +398,15 @@ Registry.prototype.createDataSource = function(name, options) {
Registry.prototype.memory = function(name) {
name = name || 'default';
var memory = (
let memory = (
this._memoryDataSources || (this._memoryDataSources = {})
)[name];
)[name];
if (!memory) {
memory = this._memoryDataSources[name] = this.createDataSource({
connector: 'memory'
connector: 'memory',
});
}
return memory;
};
/**
* Set the default `dataSource` for a given `type`.
* @param {String} type The datasource type.
* @param {Object|DataSource} dataSource The data source settings or instance
* @returns {DataSource} The data source instance.
*
* @header loopback.setDefaultDataSourceForType(type, dataSource)
*/
Registry.prototype.setDefaultDataSourceForType = function(type, dataSource) {
var defaultDataSources = this.defaultDataSources;
if (!(dataSource instanceof DataSource)) {
dataSource = this.createDataSource(dataSource);
}
defaultDataSources[type] = dataSource;
return dataSource;
};
/**
* Get the default `dataSource` for a given `type`.
* @param {String} type The datasource type.
* @returns {DataSource} The data source instance
*/
Registry.prototype.getDefaultDataSourceForType = function(type) {
return this.defaultDataSources && this.defaultDataSources[type];
};
/**
* Attach any model that does not have a dataSource to
* the default dataSource for the type the Model requests
*/
Registry.prototype.autoAttach = function() {
var models = this.modelBuilder.models;
assert.equal(typeof models, 'object', 'Cannot autoAttach without a models object');
Object.keys(models).forEach(function(modelName) {
var ModelCtor = models[modelName];
// Only auto attach if the model doesn't have an explicit data source
if (ModelCtor && (!(ModelCtor.dataSource instanceof DataSource))) {
this.autoAttachModel(ModelCtor);
}
}, this);
};
Registry.prototype.autoAttachModel = function(ModelCtor) {
if (ModelCtor.autoAttach) {
var ds = this.getDefaultDataSourceForType(ModelCtor.autoAttach);
assert(
ds instanceof DataSource,
'cannot autoAttach model "' + ModelCtor.modelName +
'". No dataSource found of type ' + ModelCtor.autoAttach
);
ModelCtor.attachTo(ds);
}
};
// temporary alias to simplify migration of code based on <=2.0.0-beta3
Object.defineProperty(Registry.prototype, 'DataModel', {
get: function() {
var stackLines = new Error().stack.split('\n');
console.warn('loopback.DataModel is deprecated, ' +
'use loopback.PersistedModel instead.');
// Log the location where loopback.DataModel was called
console.warn(stackLines[2]);
return this.PersistedModel;
}
});

View File

@ -1,10 +1,16 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*
* This is an internal file that should not be used outside of loopback.
* All exported entities can be accessed via the `loopback` object.
* @private
*/
var runtime = exports;
'use strict';
const runtime = exports;
/**
* True if running in a browser environment; false otherwise.

View File

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

View File

@ -1,17 +1,21 @@
// Copyright IBM Corp. 2015,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';
exports.createPromiseCallback = createPromiseCallback;
exports.uploadInChunks = uploadInChunks;
exports.downloadInChunks = downloadInChunks;
exports.concatResults = concatResults;
const Promise = require('bluebird');
const async = require('async');
function createPromiseCallback() {
var cb;
if (!global.Promise) {
cb = function() {};
cb.promise = {};
Object.defineProperty(cb.promise, 'then', { get: throwPromiseNotDefined });
Object.defineProperty(cb.promise, 'catch', { get: throwPromiseNotDefined });
return cb;
}
var promise = new global.Promise(function(resolve, reject) {
let cb;
const promise = new Promise(function(resolve, reject) {
cb = function(err, data) {
if (err) return reject(err);
return resolve(data);
@ -24,5 +28,114 @@ function createPromiseCallback() {
function throwPromiseNotDefined() {
throw new Error(
'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.',
);
}
/**
* Divide an async call with large array into multiple calls using smaller chunks
* @param {Array} largeArray - the large array to be chunked
* @param {Number} chunkSize - size of each chunks
* @param {Function} processFunction - the function to be called multiple times
* @param {Function} cb - the callback
*/
function uploadInChunks(largeArray, chunkSize, processFunction, cb) {
const chunkArrays = [];
if (!chunkSize || chunkSize < 1 || largeArray.length <= chunkSize) {
// if chunking not required
processFunction(largeArray, cb);
} else {
// copying so that the largeArray object does not get affected during splice
const copyOfLargeArray = [].concat(largeArray);
// chunking to smaller arrays
while (copyOfLargeArray.length > 0) {
chunkArrays.push(copyOfLargeArray.splice(0, chunkSize));
}
const tasks = chunkArrays.map(function(chunkArray) {
return function(previousResults, chunkCallback) {
const lastArg = arguments[arguments.length - 1];
if (typeof lastArg === 'function') {
chunkCallback = lastArg;
}
processFunction(chunkArray, function(err, results) {
if (err) {
return chunkCallback(err);
}
// if this is the first async waterfall call or if previous results was not defined
if (typeof previousResults === 'function' || typeof previousResults === 'undefined' ||
previousResults === null) {
previousResults = results;
} else if (results) {
previousResults = concatResults(previousResults, results);
}
chunkCallback(err, previousResults);
});
};
});
async.waterfall(tasks, cb);
}
}
/**
* Page async download calls
* @param {Object} filter - filter object used for the async call
* @param {Number} chunkSize - size of each chunks
* @param {Function} processFunction - the function to be called multiple times
* @param {Function} cb - the callback
*/
function downloadInChunks(filter, chunkSize, processFunction, cb) {
let results = [];
filter = filter ? JSON.parse(JSON.stringify(filter)) : {};
if (!chunkSize || chunkSize < 1) {
// if chunking not required
processFunction(filter, cb);
} else {
filter.skip = 0;
filter.limit = chunkSize;
processFunction(JSON.parse(JSON.stringify(filter)), pageAndConcatResults);
}
function pageAndConcatResults(err, pagedResults) {
if (err) {
return cb(err);
} else {
results = concatResults(results, pagedResults);
if (pagedResults.length >= chunkSize) {
filter.skip += pagedResults.length;
processFunction(JSON.parse(JSON.stringify(filter)), pageAndConcatResults);
} else {
cb(null, results);
}
}
}
}
/**
* Concat current results into previous results
* Assumption made here that the previous results and current results are homogeneous
* @param {Object|Array} previousResults
* @param {Object|Array} currentResults
*/
function concatResults(previousResults, currentResults) {
if (Array.isArray(currentResults)) {
previousResults = previousResults.concat(currentResults);
} else if (typeof currentResults === 'object') {
Object.keys(currentResults).forEach(function(key) {
previousResults[key] = concatResults(previousResults[key], currentResults[key]);
});
} else {
previousResults = currentResults;
}
return previousResults;
}

View File

@ -1,6 +1,6 @@
{
"name": "loopback",
"version": "2.26.2",
"version": "3.28.0",
"description": "LoopBack: Open Source Framework for Node.js",
"homepage": "http://loopback.io",
"keywords": [
@ -29,64 +29,76 @@
"mBaaS"
],
"scripts": {
"test": "grunt mocha-and-karma"
"lint": "grunt eslint",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"test": "nyc grunt mocha-and-karma"
},
"engines": {
"node": ">=8"
},
"dependencies": {
"async": "^0.9.0",
"async": "^2.0.1",
"bcryptjs": "^2.1.0",
"bluebird": "^3.1.1",
"body-parser": "^1.12.0",
"canonical-json": "0.0.4",
"continuation-local-storage": "^3.1.3",
"cookie-parser": "^1.3.4",
"debug": "^2.1.2",
"depd": "^1.0.0",
"ejs": "^2.3.1",
"errorhandler": "^1.3.4",
"express": "^4.12.2",
"express": "^4.14.0",
"inflection": "^1.6.0",
"loopback-connector-remote": "^1.0.3",
"loopback-phase": "^1.2.0",
"nodemailer": "^1.3.1",
"nodemailer-stub-transport": "^0.1.5",
"isemail": "^3.2.0",
"loopback-connector-remote": "^3.0.0",
"loopback-datasource-juggler": "^3.28.0",
"loopback-filters": "^1.0.0",
"loopback-phase": "^3.0.0",
"nodemailer": "^6.4.16",
"nodemailer-direct-transport": "^3.3.2",
"nodemailer-stub-transport": "^1.1.0",
"serve-favicon": "^2.2.0",
"stable": "^0.1.5",
"strong-remoting": "^2.21.0",
"strong-globalize": "^4.1.1",
"strong-remoting": "^3.11.0",
"uid2": "0.0.3",
"underscore.string": "^3.0.3"
},
"peerDependencies": {
"loopback-datasource-juggler": "^2.19.0"
"underscore.string": "^3.3.5"
},
"devDependencies": {
"bluebird": "^2.9.9",
"browserify": "^10.0.0",
"chai": "^2.1.1",
"es5-shim": "^4.1.0",
"grunt": "^0.4.5",
"grunt-browserify": "^3.5.0",
"grunt-cli": "^0.1.13",
"grunt-contrib-jshint": "^0.11.0",
"grunt-contrib-uglify": "^0.9.1",
"grunt-contrib-watch": "^0.6.1",
"grunt-jscs": "^1.5.0",
"grunt-karma": "^0.10.1",
"grunt-mocha-test": "^0.12.7",
"karma": "^0.12.31",
"karma-browserify": "^4.0.0",
"karma-chrome-launcher": "^0.1.7",
"karma-firefox-launcher": "^0.1.4",
"karma-html2js-preprocessor": "^0.1.0",
"karma-junit-reporter": "^0.2.2",
"karma-mocha": "^0.1.10",
"karma-phantomjs-launcher": "^0.1.4",
"karma-script-launcher": "^0.1.0",
"browserify": "^16.5.0",
"chai": "^4.2.0",
"cookie-parser": "^1.3.4",
"coveralls": "^3.0.2",
"dirty-chai": "^2.0.1",
"eslint": "^6.5.1",
"eslint-config-loopback": "^13.1.0",
"express-session": "^1.14.0",
"grunt": "^1.0.1",
"grunt-browserify": "^5.0.0",
"grunt-cli": "^1.2.0",
"grunt-contrib-uglify": "^4.0.1",
"grunt-contrib-watch": "^1.0.0",
"grunt-eslint": "^22.0.0",
"grunt-karma": "^3.0.2",
"grunt-mocha-test": "^0.13.3",
"is-docker": "^2.0.0",
"karma": "^4.1.0",
"karma-browserify": "^6.0.0",
"karma-chrome-launcher": "^3.1.0",
"karma-es6-shim": "^1.0.0",
"karma-firefox-launcher": "^1.0.0",
"karma-html2js-preprocessor": "^1.0.0",
"karma-junit-reporter": "^1.2.0",
"karma-mocha": "^1.1.1",
"karma-script-launcher": "^1.0.0",
"loopback-boot": "^2.7.0",
"loopback-datasource-juggler": "^2.19.1",
"loopback-testing": "~1.1.0",
"mocha": "^2.1.0",
"sinon": "^1.13.0",
"strong-task-emitter": "^0.0.6",
"supertest": "^0.15.0"
"loopback-context": "^1.0.0",
"mocha": "^6.2.1",
"nyc": "^14.1.1",
"sinon": "^7.5.0",
"sinon-chai": "^3.2.0",
"strong-error-handler": "^3.0.0",
"strong-task-emitter": "^0.0.8",
"supertest": "^4.0.2",
"which": "^2.0.1"
},
"repository": {
"type": "git",
@ -95,15 +107,25 @@
"browser": {
"express": "./lib/browser-express.js",
"./lib/server-app.js": "./lib/browser-express.js",
"./server/current-context.js": "./browser/current-context.js",
"connect": false,
"nodemailer": false,
"supertest": false,
"depd": "loopback-datasource-juggler/lib/browser.depd.js",
"bcrypt": false
},
"config": {
"ci": {
"debug": "*,-mocha:*,-eslint:*"
}
},
"copyright.owner": "IBM Corp.",
"license": "MIT",
"optionalDependencies": {
"sl-blip": "http://blip.strongloop.com/loopback@2.26.2"
"author": "IBM Corp.",
"ci": {
"downstreamIgnoreList": [
"bluemix-service-broker",
"gateway-director-bluemix",
"plan-manager"
]
}
}

View File

@ -1,138 +0,0 @@
var juggler = require('loopback-datasource-juggler');
var remoting = require('strong-remoting');
var cls = require('continuation-local-storage');
var domain = require('domain');
module.exports = function(loopback) {
/**
* Get the current context object. The context is preserved
* across async calls, it behaves like a thread-local storage.
*
* @returns {ChainedContext} The context object or null.
*/
loopback.getCurrentContext = function() {
// A placeholder method, see loopback.createContext() for the real version
return null;
};
/**
* Run the given function in such way that
* `loopback.getCurrentContext` returns the
* provided context object.
*
* **NOTE**
*
* The method is supported on the server only, it does not work
* in the browser at the moment.
*
* @param {Function} fn The function to run, it will receive arguments
* (currentContext, currentDomain).
* @param {ChainedContext} context An optional context object.
* When no value is provided, then the default global context is used.
*/
loopback.runInContext = function(fn, context) {
var currentDomain = domain.create();
currentDomain.oldBind = currentDomain.bind;
currentDomain.bind = function(callback, context) {
return currentDomain.oldBind(ns.bind(callback, context), context);
};
var ns = context || loopback.createContext('loopback');
currentDomain.run(function() {
ns.run(function executeInContext(context) {
fn(ns, currentDomain);
});
});
};
/**
* Create a new LoopBackContext instance that can be used
* for `loopback.runInContext`.
*
* **NOTES**
*
* At the moment, `loopback.getCurrentContext` supports
* a single global context instance only. If you call `createContext()`
* multiple times, `getCurrentContext` will return the last context
* created.
*
* The method is supported on the server only, it does not work
* in the browser at the moment.
*
* @param {String} scopeName An optional scope name.
* @return {ChainedContext} The new context object.
*/
loopback.createContext = function(scopeName) {
// Make the namespace globally visible via the process.context property
process.context = process.context || {};
var ns = process.context[scopeName];
if (!ns) {
ns = cls.createNamespace(scopeName);
process.context[scopeName] = ns;
// Set up loopback.getCurrentContext()
loopback.getCurrentContext = function() {
return ns && ns.active ? ns : null;
};
chain(juggler);
chain(remoting);
}
return ns;
};
/**
* Create a chained context
* @param {Object} child The child context
* @param {Object} parent The parent context
* @private
* @constructor
*/
function ChainedContext(child, parent) {
this.child = child;
this.parent = parent;
}
/**
* Get the value by name from the context. If it doesn't exist in the child
* context, try the parent one
* @param {String} name Name of the context property
* @returns {*} Value of the context property
* @private
*/
ChainedContext.prototype.get = function(name) {
var val = this.child && this.child.get(name);
if (val === undefined) {
return this.parent && this.parent.get(name);
}
};
ChainedContext.prototype.set = function(name, val) {
if (this.child) {
return this.child.set(name, val);
} else {
return this.parent && this.parent.set(name, val);
}
};
ChainedContext.prototype.reset = function(name, val) {
if (this.child) {
return this.child.reset(name, val);
} else {
return this.parent && this.parent.reset(name, val);
}
};
function chain(child) {
if (typeof child.getCurrentContext === 'function') {
var childContext = new ChainedContext(child.getCurrentContext(),
loopback.getCurrentContext());
child.getCurrentContext = function() {
return childContext;
};
} else {
child.getCurrentContext = loopback.getCurrentContext;
}
}
};

View File

@ -1,52 +1,15 @@
var loopback = require('../../lib/loopback');
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = context;
'use strict';
const g = require('../../lib/globalize');
var name = 'loopback';
/**
* Context middleware.
* ```js
* var app = loopback();
* app.use(loopback.context(options);
* app.use(loopback.rest());
* app.listen();
* ```
* @options {Object} [options] Options for context
* @property {String} name Context scope name.
* @property {Boolean} enableHttpContext Whether HTTP context is enabled. Default is false.
* @header loopback.context([options])
*/
function context(options) {
options = options || {};
var scope = options.name || name;
var enableHttpContext = options.enableHttpContext || false;
var ns = loopback.createContext(scope);
// Return the middleware
return function contextHandler(req, res, next) {
if (req.loopbackContext) {
return next();
}
loopback.runInContext(function processRequestInContext(ns, domain) {
req.loopbackContext = ns;
// Bind req/res event emitters to the given namespace
ns.bindEmitter(req);
ns.bindEmitter(res);
// Add req/res event emitters to the current domain
domain.add(req);
domain.add(res);
// Run the code in the context of the namespace
if (enableHttpContext) {
// Set up the transport context
ns.set('http', {req: req, res: res});
}
next();
});
};
}
module.exports = function() {
throw new Error(g.f(
'%s middleware was removed in version 3.0. See %s for more details.',
'loopback#context',
'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
};

View File

@ -1,16 +1,10 @@
var expressErrorHandler = require('errorhandler');
expressErrorHandler.title = 'Loopback';
// Copyright IBM Corp. 2015,2018. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
module.exports = errorHandler;
function errorHandler(options) {
if (!options || options.includeStack !== false) {
return expressErrorHandler(options);
} else {
var baseHandler = expressErrorHandler(options);
return function errorHandler(err, req, res, next) {
delete err.stack;
return baseHandler(err, req, res, next);
};
}
}
'use strict';
module.exports = function(options) {
throw new Error('loopback.errorHandler is no longer available.' +
' Please use the module "strong-error-handler" instead.');
};

View File

@ -1,5 +1,17 @@
// Copyright IBM Corp. 2014,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 favicon = require('serve-favicon');
const path = require('path');
/**
* Serve the LoopBack favicon.
* @header loopback.favicon()
*/
module.exports = require('../../lib/express-middleware').favicon;
module.exports = function(icon, options) {
icon = icon || path.join(__dirname, '../../favicon.ico');
return favicon(icon, options);
};

View File

@ -1,10 +1,16 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*!
* Module dependencies.
*/
var loopback = require('../../lib/loopback');
var async = require('async');
var deprecate = require('depd')('loopback');
'use strict';
const g = require('../../lib/globalize');
const loopback = require('../../lib/loopback');
const async = require('async');
/*!
* Export the middleware.
@ -19,45 +25,33 @@ module.exports = rest;
* ```js
* app.use(loopback.rest());
* ```
* For more information, see [Exposing models over a REST API](http://docs.strongloop.com/display/DOC/Exposing+models+over+a+REST+API).
* For more information, see [Exposing models over a REST API](http://loopback.io/doc/en/lb2/Exposing-models-over-REST.html).
* @header loopback.rest()
*/
function rest() {
var handlers; // Cached handlers
let handlers; // Cached handlers
return function restApiHandler(req, res, next) {
var app = req.app;
var registry = app.registry;
// added for https://github.com/strongloop/loopback/issues/1134
if (app.get('legacyExplorer') !== false) {
deprecate(
'Routes "/methods" and "/models" are considered dangerous and should not be used.\n' +
'Disable them by setting "legacyExplorer=false" in "server/config.json" or via "app.set()".'
);
if (req.url === '/routes') {
return res.send(app.handler('rest').adapter.allRoutes());
} else if (req.url === '/models') {
return res.send(app.remotes().toJSON());
}
}
const app = req.app;
const registry = app.registry;
if (!handlers) {
handlers = [];
var remotingOptions = app.get('remoting') || {};
const remotingOptions = app.get('remoting') || {};
var contextOptions = remotingOptions.context;
if (contextOptions !== false) {
if (typeof contextOptions !== 'object') {
contextOptions = {};
}
handlers.push(loopback.context(contextOptions));
const contextOptions = remotingOptions.context;
if (contextOptions !== undefined && contextOptions !== false) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'remoting.context option',
'http://loopback.io/doc/en/lb2/Using-current-context.html',
));
}
if (app.isAuthEnabled) {
var AccessToken = registry.getModelByType('AccessToken');
handlers.push(loopback.token({ model: AccessToken, app: app }));
const AccessToken = registry.getModelByType('AccessToken');
handlers.push(loopback.token({model: AccessToken, app: app}));
}
handlers.push(function(req, res, next) {

View File

@ -1,3 +1,8 @@
// Copyright IBM Corp. 2014,2018. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/**
* Serve static assets of a LoopBack application.
*
@ -8,4 +13,5 @@
* for the full list of available options.
* @header loopback.static(root, [options])
*/
'use strict';
module.exports = require('express').static;

View File

@ -1,7 +1,13 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*!
* Export the middleware.
*/
'use strict';
module.exports = status;
/**
@ -18,12 +24,12 @@ module.exports = status;
* @header loopback.status()
*/
function status() {
var started = new Date();
const started = new Date();
return function(req, res) {
res.send({
started: started,
uptime: (Date.now() - Number(started)) / 1000
uptime: (Date.now() - Number(started)) / 1000,
});
};
}

View File

@ -1,10 +1,17 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*!
* Module dependencies.
*/
var loopback = require('../../lib/loopback');
var assert = require('assert');
var debug = require('debug')('loopback:middleware:token');
'use strict';
const g = require('../../lib/globalize');
const loopback = require('../../lib/loopback');
const assert = require('assert');
const debug = require('debug')('loopback:middleware:token');
/*!
* Export the middleware.
@ -15,18 +22,34 @@ module.exports = token;
/*
* Rewrite the url to replace current user literal with the logged in user id
*/
function rewriteUserLiteral(req, currentUserLiteral) {
if (req.accessToken && req.accessToken.userId && currentUserLiteral) {
function rewriteUserLiteral(req, currentUserLiteral, next) {
if (!currentUserLiteral) return next();
const literalRegExp = new RegExp('/' + currentUserLiteral + '(/|$|\\?)', 'g');
if (req.accessToken && req.accessToken.userId) {
// Replace /me/ with /current-user-id/
var urlBeforeRewrite = req.url;
req.url = req.url.replace(
new RegExp('/' + currentUserLiteral + '(/|$|\\?)', 'g'),
'/' + req.accessToken.userId + '$1');
const urlBeforeRewrite = req.url;
req.url = req.url.replace(literalRegExp,
'/' + req.accessToken.userId + '$1');
if (req.url !== urlBeforeRewrite) {
debug('req.url has been rewritten from %s to %s', urlBeforeRewrite,
req.url);
}
} else if (!req.accessToken && literalRegExp.test(req.url)) {
debug(
'URL %s matches current-user literal %s,' +
' but no (valid) access token was provided.',
req.url, currentUserLiteral,
);
const e = new Error(g.f('Authorization Required'));
e.status = e.statusCode = 401;
e.code = 'AUTHORIZATION_REQUIRED';
return next(e);
}
next();
}
function escapeRegExp(str) {
@ -62,16 +85,21 @@ function escapeRegExp(str) {
* @property {Array} [headers] Array of header names.
* @property {Array} [params] Array of param names.
* @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request
* @property {Boolean} [enableDoublecheck] Execute middleware although an instance mounted earlier in the chain didn't find a token
* @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 {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])
*/
function token(options) {
options = options || {};
var TokenModel;
let TokenModel;
var currentUserLiteral = options.currentUserLiteral;
let currentUserLiteral = options.currentUserLiteral;
if (currentUserLiteral && (typeof currentUserLiteral !== 'string')) {
debug('Set currentUserLiteral to \'me\' as the value is not a string.');
currentUserLiteral = 'me';
@ -80,32 +108,44 @@ function token(options) {
currentUserLiteral = escapeRegExp(currentUserLiteral);
}
if (options.bearerTokenBase64Encoded === undefined) {
options.bearerTokenBase64Encoded = true;
}
const enableDoublecheck = !!options.enableDoublecheck;
const overwriteExistingToken = !!options.overwriteExistingToken;
return function(req, res, next) {
var app = req.app;
var registry = app.registry;
const app = req.app;
const registry = app.registry;
if (!TokenModel) {
if (registry === loopback.registry) {
TokenModel = options.model || loopback.AccessToken;
} else if (options.model) {
TokenModel = registry.getModel(options.model);
} else {
TokenModel = registry.getModel('AccessToken');
}
TokenModel = registry.getModel(options.model || 'AccessToken');
}
assert(typeof TokenModel === 'function',
'loopback.token() middleware requires a AccessToken model');
if (req.accessToken !== undefined) {
rewriteUserLiteral(req, currentUserLiteral);
return next();
if (!enableDoublecheck) {
// req.accessToken is defined already (might also be "null" or "false") and enableDoublecheck
// has not been set --> skip searching for credentials
return rewriteUserLiteral(req, currentUserLiteral, next);
}
if (req.accessToken && req.accessToken.id && !overwriteExistingToken) {
// req.accessToken.id is defined, which means that some other middleware has identified a valid user.
// when overwriteExistingToken is not set to a truthy value, skip searching for credentials.
return rewriteUserLiteral(req, currentUserLiteral, next);
}
// continue normal operation (as if req.accessToken was undefined)
}
TokenModel.findForRequest(req, options, function(err, token) {
req.accessToken = token || null;
rewriteUserLiteral(req, currentUserLiteral);
var ctx = loopback.getCurrentContext();
if (ctx) ctx.set('accessToken', token);
next(err);
const ctx = req.loopbackContext;
if (ctx && ctx.active) ctx.set('accessToken', token);
if (err) return next(err);
rewriteUserLiteral(req, currentUserLiteral, next);
});
};
}

View File

@ -1,8 +1,14 @@
// Copyright IBM Corp. 2014,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/*!
* Export the middleware.
* See discussion in Connect pull request #954 for more details
* https://github.com/senchalabs/connect/pull/954.
*/
'use strict';
module.exports = urlNotFound;
/**
@ -12,7 +18,7 @@ module.exports = urlNotFound;
*/
function urlNotFound() {
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;
next(error);
};

View File

@ -1,17 +1,20 @@
/*jshint -W030 */
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var loopback = require('../');
var lt = require('loopback-testing');
var path = require('path');
var ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control');
var app = require(path.join(ACCESS_CONTROL_APP, 'server/server.js'));
var assert = require('assert');
var USER = {email: 'test@test.test', password: 'test'};
var CURRENT_USER = {email: 'current@test.test', password: 'test'};
var debug = require('debug')('loopback:test:access-control.integration');
'use strict';
const loopback = require('../');
const lt = require('./helpers/loopback-testing-helper');
const path = require('path');
const ACCESS_CONTROL_APP = path.join(__dirname, 'fixtures', 'access-control');
const app = require(path.join(ACCESS_CONTROL_APP, 'server/server.js'));
const assert = require('assert');
const USER = {email: 'test@test.test', password: 'test'};
const CURRENT_USER = {email: 'current@test.test', password: 'test'};
const debug = require('debug')('loopback:test:access-control.integration');
describe('access control - integration', function() {
lt.beforeEach.withApp(app);
/*
@ -61,7 +64,6 @@ describe('access control - integration', function() {
*/
describe('/users', function() {
lt.beforeEach.givenModel('user', USER, 'randomUser');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/users');
@ -73,10 +75,12 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForUser);
lt.it.shouldBeAllowedWhenCalledAnonymously(
'POST', '/api/users', newUserData());
'POST', '/api/users', newUserData(),
);
lt.it.shouldBeAllowedWhenCalledByUser(
CURRENT_USER, 'POST', '/api/users', newUserData());
CURRENT_USER, 'POST', '/api/users', newUserData(),
);
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/logout');
@ -106,15 +110,25 @@ describe('access control - integration', function() {
this.res.statusCode,
this.res.headers,
this.res.text);
var user = this.res.body;
const user = this.res.body;
assert.equal(user.password, undefined);
});
});
// user has replaceOnPUT = false; so then both PUT and PATCH should be allowed for update
lt.describe.whenCalledRemotely('PUT', '/api/users/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('PATCH', '/api/users/:id', function() {
lt.it.shouldBeAllowed();
});
});
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/users/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/users/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForUser);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForUser);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForUser);
@ -123,17 +137,46 @@ describe('access control - integration', function() {
return '/api/users/' + this.randomUser.id;
}
var userCounter;
var userCounter; // eslint-disable-line no-var
function newUserData() {
userCounter = userCounter ? ++userCounter : 1;
return {
email: 'new-' + userCounter + '@test.test',
password: 'test'
password: 'test',
};
}
});
describe('/banks', function() {
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
// SPECIAL_USER's email
before(function() {
const roleModel = app.registry.getModel('Role');
const userModel = app.registry.getModel('user');
roleModel.registerResolver('$dynamic-role', function(role, context, callback) {
if (!(context && context.accessToken && context.accessToken.userId)) {
return process.nextTick(function() {
if (callback) callback(null, false);
});
}
const accessToken = context.accessToken;
userModel.findById(accessToken.userId, function(err, user) {
if (err) {
return callback(err, false);
}
if (user && user.email === SPECIAL_USER.email) {
return callback(null, true);
}
return callback(null, false);
});
});
});
lt.beforeEach.givenModel('bank');
lt.it.shouldBeAllowedWhenCalledAnonymously('GET', '/api/banks');
@ -155,67 +198,93 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForBank);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForBank);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForBank);
lt.it.shouldBeAllowedWhenCalledByUser(SPECIAL_USER, 'DELETE', urlForBank);
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/banks/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/banks/upsertWithWhere');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/banks/upsertWithWhere');
function urlForBank() {
return '/api/banks/' + this.bank.id;
}
});
describe('/accounts', function() {
var count = 0;
describe('/accounts with replaceOnPUT true', function() {
let count = 0;
before(function() {
var roleModel = loopback.getModelByType(loopback.Role);
const roleModel = loopback.getModelByType(loopback.Role);
roleModel.registerResolver('$dummy', function(role, context, callback) {
process.nextTick(function() {
if (context.remotingContext) {
count++;
}
callback && callback(null, false); // Always true
if (callback) callback(null, false); // Always true
});
});
});
lt.beforeEach.givenModel('account');
lt.beforeEach.givenModel('accountWithReplaceOnPUTtrue');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', '/api/accounts-replacing');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', '/api/accounts-replacing');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', '/api/accounts-replacing');
lt.it.shouldBeDeniedWhenCalledAnonymously('GET', urlForAccount);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForAccount);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'GET', urlForAccount);
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/accounts');
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', '/api/accounts-replacing');
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', '/api/accounts-replacing');
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', '/api/accounts-replacing');
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', urlForReplaceAccountPOST);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', urlForReplaceAccountPOST);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', urlForReplaceAccountPOST);
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
beforeEach(function(done) {
var self = this;
lt.it.shouldBeDeniedWhenCalledAnonymously('PATCH', urlForAccount);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PATCH', urlForAccount);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
let actId;
beforeEach(function(done) {
const self = this;
// Create an account under the given user
app.models.account.create({
app.models.accountWithReplaceOnPUTtrue.create({
userId: self.user.id,
balance: 100
balance: 100,
}, function(err, act) {
self.url = '/api/accounts/' + act.id;
actId = act.id;
self.url = '/api/accounts-replacing/' + actId;
done();
});
});
});
lt.describe.whenCalledRemotely('PUT', '/api/accounts/:id', function() {
lt.describe.whenCalledRemotely('PATCH', '/api/accounts-replacing/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('GET', '/api/accounts/:id', function() {
lt.describe.whenCalledRemotely('PUT', '/api/accounts-replacing/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('DELETE', '/api/accounts/:id', function() {
lt.describe.whenCalledRemotely('GET', '/api/accounts-replacing/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('DELETE', '/api/accounts-replacing/:id', function() {
lt.it.shouldBeDenied();
});
describe('replace on POST verb', function() {
beforeEach(function(done) {
this.url = '/api/accounts-replacing/' + actId + '/replace';
done();
});
lt.describe.whenCalledRemotely('POST', '/api/accounts-replacing/:id/replace', function() {
lt.it.shouldBeAllowed();
});
});
});
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);
@ -223,8 +292,76 @@ describe('access control - integration', function() {
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);
function urlForAccount() {
return '/api/accounts/' + this.account.id;
return '/api/accounts-replacing/' + this.accountWithReplaceOnPUTtrue.id;
}
function urlForReplaceAccountPOST() {
return '/api/accounts-replacing/' + this.accountWithReplaceOnPUTtrue.id + '/replace';
}
});
describe('/accounts with replaceOnPUT false', function() {
lt.beforeEach.givenModel('accountWithReplaceOnPUTfalse');
lt.it.shouldBeDeniedWhenCalledAnonymously('POST', urlForReplaceAccountPOST);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('POST', urlForReplaceAccountPOST);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'POST', urlForReplaceAccountPOST);
lt.it.shouldBeDeniedWhenCalledAnonymously('PUT', urlForAccount);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PUT', urlForAccount);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PUT', urlForAccount);
lt.it.shouldBeDeniedWhenCalledAnonymously('PATCH', urlForAccount);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('PATCH', urlForAccount);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'PATCH', urlForAccount);
lt.describe.whenLoggedInAsUser(CURRENT_USER, function() {
let actId;
beforeEach(function(done) {
const self = this;
// Create an account under the given user
app.models.accountWithReplaceOnPUTfalse.create({
userId: self.user.id,
balance: 100,
}, function(err, act) {
actId = act.id;
self.url = '/api/accounts-updating/' + actId;
done();
});
});
lt.describe.whenCalledRemotely('PATCH', '/api/accounts-updating/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('PUT', '/api/accounts-updating/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('GET', '/api/accounts-updating/:id', function() {
lt.it.shouldBeAllowed();
});
lt.describe.whenCalledRemotely('DELETE', '/api/accounts-updating/:id', function() {
lt.it.shouldBeDenied();
});
describe('replace on POST verb', function() {
beforeEach(function(done) {
this.url = '/api/accounts-updating/' + actId + '/replace';
done();
});
lt.describe.whenCalledRemotely('POST', '/api/accounts-updating/:id/replace', function() {
lt.it.shouldBeAllowed();
});
});
});
lt.it.shouldBeDeniedWhenCalledAnonymously('DELETE', urlForAccount);
lt.it.shouldBeDeniedWhenCalledUnauthenticated('DELETE', urlForAccount);
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER, 'DELETE', urlForAccount);
function urlForAccount() {
return '/api/accounts-updating/' + this.accountWithReplaceOnPUTfalse.id;
}
function urlForReplaceAccountPOST() {
return '/api/accounts-updating/' + this.accountWithReplaceOnPUTfalse.id + '/replace';
}
});
});

View File

@ -1,21 +1,128 @@
var loopback = require('../');
var extend = require('util')._extend;
var Token = loopback.AccessToken.extend('MyToken');
var ds = loopback.createDataSource({connector: loopback.Memory});
Token.attachTo(ds);
var ACL = loopback.ACL;
// Copyright IBM Corp. 2013,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 assert = require('assert');
const expect = require('./helpers/expect');
const cookieParser = require('cookie-parser');
const LoopBackContext = require('loopback-context');
const contextMiddleware = require('loopback-context').perRequest;
const loopback = require('../');
const extend = require('util')._extend;
const session = require('express-session');
const request = require('supertest');
let Token, ACL, User, TestModel;
describe('loopback.token(options)', function() {
beforeEach(createTestingToken);
let app;
beforeEach(function(done) {
app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'});
it('should populate req.token from the query string', function(done) {
ACL = app.registry.getModel('ACL');
app.model(ACL, {dataSource: 'db'});
User = app.registry.getModel('User');
app.model(User, {dataSource: 'db'});
Token = app.registry.createModel({
name: 'MyToken',
base: 'AccessToken',
});
app.model(Token, {dataSource: 'db'});
TestModel = app.registry.createModel({
name: 'TestModel',
base: 'Model',
});
TestModel.getToken = function(options, cb) {
cb(null, options && options.accessToken || null);
};
TestModel.remoteMethod('getToken', {
accepts: {arg: 'options', type: 'object', http: 'optionsFromRequest'},
returns: {arg: 'token', type: 'object'},
http: {verb: 'GET', path: '/token'},
});
app.model(TestModel, {dataSource: 'db'});
createTestingToken.call(this, done);
});
it('defaults to built-in AccessToken model', function() {
const BuiltInToken = app.registry.getModel('AccessToken');
app.model(BuiltInToken, {dataSource: 'db'});
app.enableAuth({dataSource: 'db'});
app.use(loopback.token());
app.use(loopback.rest());
return BuiltInToken.create({userId: 123}).then(function(token) {
return request(app)
.get('/TestModels/token?_format=json')
.set('authorization', token.id)
.expect(200)
.expect('Content-Type', /json/)
.then(res => {
expect(res.body.token.id).to.eql(token.id);
});
});
});
it('uses correct custom AccessToken model from model class param', function() {
User.hasMany(Token, {
as: 'accessTokens',
options: {disableInclude: true},
});
app.enableAuth();
app.use(loopback.token({model: Token}));
app.use(loopback.rest());
return Token.create({userId: 123}).then(function(token) {
return request(app)
.get('/TestModels/token?_format=json')
.set('authorization', token.id)
.expect(200)
.expect('Content-Type', /json/)
.then(res => {
expect(res.body.token.id).to.eql(token.id);
});
});
});
it('uses correct custom AccessToken model from string param', function() {
User.hasMany(Token, {
as: 'accessTokens',
options: {disableInclude: true},
});
app.enableAuth();
app.use(loopback.token({model: Token.modelName}));
app.use(loopback.rest());
return Token.create({userId: 123}).then(function(token) {
return request(app)
.get('/TestModels/token?_format=json')
.set('authorization', token.id)
.expect(200)
.expect('Content-Type', /json/)
.then(res => {
expect(res.body.token.id).to.eql(token.id);
});
});
});
it('populates req.token from the query string', function(done) {
createTestAppAndRequest(this.token, done)
.get('/?access_token=' + this.token.id)
.expect(200)
.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)
.get('/')
.set('authorization', this.token.id)
@ -23,7 +130,7 @@ describe('loopback.token(options)', function() {
.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)
.get('/')
.set('X-Access-Token', this.token.id)
@ -31,43 +138,55 @@ describe('loopback.token(options)', function() {
.end(done);
});
it('should not search default keys when searchDefaultTokenKeys is false',
function(done) {
var tokenId = this.token.id;
var app = createTestApp(
this.token,
{ token: { searchDefaultTokenKeys: false } },
done);
var agent = request.agent(app);
it('does not search default keys when searchDefaultTokenKeys is false',
function(done) {
const tokenId = this.token.id;
const app = createTestApp(
this.token,
{token: {searchDefaultTokenKeys: false}},
done,
);
const agent = request.agent(app);
// Set the token cookie
agent.get('/token').expect(200).end(function(err, res) {
if (err) return done(err);
// Set the token cookie
agent.get('/token').expect(200).end(function(err, res) {
if (err) return done(err);
// Make a request that sets the token in all places searched by default
agent.get('/check-access?access_token=' + tokenId)
.set('X-Access-Token', tokenId)
.set('authorization', tokenId)
// Make a request that sets the token in all places searched by default
agent.get('/check-access?access_token=' + tokenId)
.set('X-Access-Token', tokenId)
.set('authorization', tokenId)
// Expect 401 because there is no (non-default) place configured where
// 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);
});
});
it('should populate req.token from an authorization header with bearer token', function(done) {
var token = this.token.id;
token = 'Bearer ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done)
it('populates req.token from an authorization header with bearer token', function(done) {
let token = this.token.id;
token = 'Bearer ' + token;
createTestAppAndRequest(this.token, {token: {bearerTokenBase64Encoded: false}}, done)
.get('/')
.set('authorization', token)
.expect(200)
.end(done);
});
describe('populating req.toen 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) {
var token = this.token.id;
let token = this.token.id;
token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done)
.get('/')
@ -77,7 +196,7 @@ describe('loopback.token(options)', function() {
});
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');
createTestAppAndRequest(this.token, done)
.get('/')
@ -87,7 +206,7 @@ describe('loopback.token(options)', function() {
});
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');
createTestAppAndRequest(this.token, done)
.get('/')
@ -97,7 +216,7 @@ describe('loopback.token(options)', function() {
});
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');
createTestAppAndRequest(this.token, done)
.get('/')
@ -107,8 +226,8 @@ describe('loopback.token(options)', function() {
});
});
it('should populate req.token from a secure cookie', function(done) {
var app = createTestApp(this.token, done);
it('populates req.token from a secure cookie', function(done) {
const app = createTestApp(this.token, done);
request(app)
.get('/token')
@ -120,9 +239,9 @@ describe('loopback.token(options)', function() {
});
});
it('should populate req.token from a header or a secure cookie', function(done) {
var app = createTestApp(this.token, done);
var id = this.token.id;
it('populates req.token from a header or a secure cookie', function(done) {
const app = createTestApp(this.token, done);
const id = this.token.id;
request(app)
.get('/token')
.end(function(err, res) {
@ -134,58 +253,92 @@ 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) {
var app = createTestApp(this.token, done);
var id = this.token.id;
var userId = this.token.userId;
const app = createTestApp(this.token, done);
const id = this.token.id;
const userId = this.token.userId;
request(app)
.get('/users/me')
.set('authorization', id)
.end(function(err, res) {
assert(!err);
assert.deepEqual(res.body, {userId: userId});
done();
});
});
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) {
var app = createTestApp(this.token, done);
var id = this.token.id;
var userId = this.token.userId;
const app = createTestApp(this.token, done);
const id = this.token.id;
const userId = this.token.userId;
request(app)
.get('/users/me?state=1')
.set('authorization', id)
.end(function(err, res) {
assert(!err);
assert.deepEqual(res.body, {userId: userId, state: 1});
done();
});
});
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) {
var app = createTestApp(this.token, done);
var id = this.token.id;
var userId = this.token.userId;
const app = createTestApp(this.token, done);
const id = this.token.id;
const userId = this.token.userId;
request(app)
.get('/users/me/1')
.set('authorization', id)
.end(function(err, res) {
assert(!err);
assert.deepEqual(res.body, {userId: userId, state: 1});
done();
});
});
it('should skip when req.token is already present', function(done) {
var tokenStub = { id: 'stub id' };
it('generates a 401 on a current user literal route without an authToken',
function(done) {
const app = createTestApp(null, done);
request(app)
.get('/users/me')
.set('authorization', null)
.expect(401)
.end(done);
});
it('generates a 401 on a current user literal route with empty authToken',
function(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)
.get('/users/me')
.set('Authorization', 'invald-token-id')
.expect(401)
.end(done);
});
it('skips when req.token is already present', function(done) {
const tokenStub = {id: 'stub id'};
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({ model: Token }));
app.use(loopback.token({model: Token}));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
@ -195,44 +348,257 @@ describe('loopback.token(options)', function() {
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql(tokenStub);
done();
});
});
describe('loading multiple instances of token middleware', function() {
it('skips when req.token is already present and no further options are set',
function(done) {
const tokenStub = {id: 'stub id'};
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({model: Token}));
app.get('/', function(req, res, next) {
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('does not overwrite valid existing token (has "id" property) ' +
' when overwriteExistingToken is falsy',
function(done) {
const tokenStub = {id: 'stub id'};
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({
model: Token,
enableDoublecheck: true,
}));
app.get('/', function(req, res, next) {
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('overwrites invalid existing token (is !== undefined and has no "id" property) ' +
' when enableDoublecheck is true',
function(done) {
const token = this.token;
app.use(function(req, res, next) {
req.accessToken = null;
next();
});
app.use(loopback.token({
model: Token,
enableDoublecheck: true,
}));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql({
id: token.id,
ttl: token.ttl,
userId: token.userId,
created: token.created.toJSON(),
});
done();
});
});
it('overwrites existing token when enableDoublecheck ' +
'and overwriteExistingToken options are truthy',
function(done) {
const token = this.token;
const tokenStub = {id: 'stub id'};
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({
model: Token,
enableDoublecheck: true,
overwriteExistingToken: true,
}));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql({
id: token.id,
ttl: token.ttl,
userId: token.userId,
created: token.created.toJSON(),
});
done();
});
});
});
});
describe('AccessToken', function() {
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.equal(this.token.id.length, 64);
});
it('should auto-generate created date', function() {
it('generates created date automatically', function() {
assert(this.token.created);
assert(Object.prototype.toString.call(this.token.created), '[object Date]');
});
it('should be validateable', function(done) {
this.token.validate(function(err, isValid) {
assert(isValid);
done();
describe('.validate()', function() {
it('accepts valid tokens', function(done) {
this.token.validate(function(err, isValid) {
assert(isValid);
done();
});
});
it('rejects eternal TTL by default', function(done) {
this.token.ttl = -1;
this.token.validate(function(err, isValid) {
if (err) return done(err);
expect(isValid, 'isValid').to.equal(false);
done();
});
});
it('allows eternal tokens when enabled by User.allowEternalTokens',
function(done) {
const Token = givenLocalTokenModel();
// Overwrite User settings - enable eternal tokens
Token.app.models.User.settings.allowEternalTokens = true;
Token.create({userId: '123', ttl: -1}, function(err, token) {
if (err) return done(err);
token.validate(function(err, isValid) {
if (err) return done(err);
expect(isValid, 'isValid').to.equal(true);
done();
});
});
});
});
describe('.findForRequest()', function() {
beforeEach(createTestingToken);
it('supports two-arg variant with no options', function(done) {
var expectedTokenId = this.token.id;
var req = mockRequest({
headers: { 'authorization': expectedTokenId }
const expectedTokenId = this.token.id;
const req = mockRequest({
headers: {'authorization': expectedTokenId},
});
Token.findForRequest(req, function(err, token) {
if (err) return done(err);
expect(token.id).to.eql(expectedTokenId);
done();
});
});
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();
});
});
@ -247,14 +613,34 @@ describe('AccessToken', function() {
// express helpers
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() {
let app;
beforeEach(function setupAuthWithModels() {
app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'});
Token = app.registry.createModel({
name: 'MyToken',
base: 'AccessToken',
});
app.model(Token, {dataSource: 'db'});
ACL = app.registry.getModel('ACL');
// Fix User's "hasMany accessTokens" relation to use our new MyToken model
const User = app.registry.getModel('User');
User.settings.relations.accessTokens.model = 'MyToken';
app.enableAuth({dataSource: 'db'});
});
beforeEach(createTestingToken);
it('prevents remote call with 401 status on denied ACL', function(done) {
@ -266,15 +652,17 @@ describe('app.enableAuth()', function() {
if (err) {
return done(err);
}
var errorResponse = res.body.error;
const errorResponse = res.body.error;
assert(errorResponse);
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
done();
});
});
it('prevent remote call with app setting status on denied ACL', function(done) {
createTestAppAndRequest(this.token, {app:{aclErrorStatus:403}}, done)
it('denies remote call with app setting status 403', function(done) {
createTestAppAndRequest(this.token, {app: {aclErrorStatus: 403}}, done)
.del('/tests/123')
.expect(403)
.set('authorization', this.token.id)
@ -282,15 +670,17 @@ describe('app.enableAuth()', function() {
if (err) {
return done(err);
}
var errorResponse = res.body.error;
const errorResponse = res.body.error;
assert(errorResponse);
assert.equal(errorResponse.code, 'ACCESS_DENIED');
done();
});
});
it('prevent remote call with app setting status on denied ACL', function(done) {
createTestAppAndRequest(this.token, {model:{aclErrorStatus:404}}, done)
it('denies remote call with app setting status 404', function(done) {
createTestAppAndRequest(this.token, {model: {aclErrorStatus: 404}}, done)
.del('/tests/123')
.expect(404)
.set('authorization', this.token.id)
@ -298,14 +688,16 @@ describe('app.enableAuth()', function() {
if (err) {
return done(err);
}
var errorResponse = res.body.error;
const errorResponse = res.body.error;
assert(errorResponse);
assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');
done();
});
});
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)
.del('/tests/123')
.expect(401)
@ -314,32 +706,34 @@ describe('app.enableAuth()', function() {
if (err) {
return done(err);
}
var errorResponse = res.body.error;
const errorResponse = res.body.error;
assert(errorResponse);
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
done();
});
});
it('stores token in the context', function(done) {
var TestModel = loopback.createModel('TestModel', { base: 'Model' });
const TestModel = app.registry.createModel('TestModel', {base: 'Model'});
TestModel.getToken = function(cb) {
cb(null, loopback.getCurrentContext().get('accessToken') || null);
const ctx = LoopBackContext.getCurrentContext();
cb(null, ctx && ctx.get('accessToken') || null);
};
TestModel.remoteMethod('getToken', {
returns: { arg: 'token', type: 'object' },
http: { verb: 'GET', path: '/token' }
returns: {arg: 'token', type: 'object'},
http: {verb: 'GET', path: '/token'},
});
var app = loopback();
app.model(TestModel, { dataSource: null });
app.model(TestModel, {dataSource: null});
app.enableAuth();
app.use(loopback.context());
app.use(loopback.token({ model: Token }));
app.use(contextMiddleware());
app.use(loopback.token({model: Token}));
app.use(loopback.rest());
var token = this.token;
const token = this.token;
request(app)
.get('/TestModels/token?_format=json')
.set('authorization', token.id)
@ -347,42 +741,71 @@ describe('app.enableAuth()', function() {
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err);
expect(res.body.token.id).to.eql(token.id);
done();
});
});
// See https://github.com/strongloop/loopback-context/issues/6
it('checks whether context is active', function(done) {
app.enableAuth();
app.use(contextMiddleware());
app.use(session({
secret: 'kitty',
saveUninitialized: true,
resave: true,
}));
app.use(loopback.token({model: Token}));
app.get('/', function(req, res) { res.send('OK'); });
app.use(loopback.rest());
request(app)
.get('/')
.set('authorization', this.token.id)
.set('cookie', 'connect.sid=s%3AFTyno9_MbGTJuOwdh9bxsYCVxlhlulTZ.' +
'PZvp85jzLXZBCBkhCsSfuUjhij%2Fb0B1K2RYZdxSQU0c')
.expect(200, 'OK')
.end(done);
});
});
function createTestingToken(done) {
var test = this;
const test = this;
Token.create({userId: '123'}, function(err, token) {
if (err) return done(err);
test.token = token;
done();
});
}
function createTestAppAndRequest(testToken, settings, done) {
var app = createTestApp(testToken, settings, done);
const app = createTestApp(testToken, settings, done);
return request(app);
}
function createTestApp(testToken, settings, done) {
done = arguments[arguments.length - 1];
if (settings == done) settings = {};
settings = settings || {};
if (!done && typeof settings === 'function') {
done = settings;
settings = {};
}
var appSettings = settings.app || {};
var modelSettings = settings.model || {};
var tokenSettings = extend({
const appSettings = settings.app || {};
const modelSettings = settings.model || {};
const tokenSettings = extend({
model: Token,
currentUserLiteral: 'me'
currentUserLiteral: 'me',
}, settings.token);
var app = loopback();
const app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'});
app.use(loopback.cookieParser('secret'));
app.use(cookieParser('secret'));
app.use(loopback.token(tokenSettings));
app.set('remoting', {errorHandler: {debug: true, log: false}});
app.get('/token', function(req, res) {
res.cookie('authorization', testToken.id, {signed: true});
res.cookie('access_token', testToken.id, {signed: true});
@ -401,7 +824,7 @@ function createTestApp(testToken, settings, done) {
res.status(req.accessToken ? 200 : 401).end();
});
app.use('/users/:uid', function(req, res) {
var result = {userId: req.params.uid};
const result = {userId: req.params.uid};
if (req.query.state) {
result.state = req.query.state;
} else if (req.url !== '/') {
@ -410,32 +833,43 @@ function createTestApp(testToken, settings, done) {
res.status(200).send(result);
});
app.use(loopback.rest());
app.enableAuth();
app.enableAuth({dataSource: 'db'});
Object.keys(appSettings).forEach(function(key) {
app.set(key, appSettings[key]);
});
var modelOptions = {
const modelOptions = {
acls: [
{
principalType: 'ROLE',
principalId: '$everyone',
accessType: ACL.ALL,
permission: ACL.DENY,
property: 'deleteById'
}
]
property: 'deleteById',
},
],
};
Object.keys(modelSettings).forEach(function(key) {
modelOptions[key] = modelSettings[key];
});
var TestModel = loopback.PersistedModel.extend('test', {}, modelOptions);
TestModel.attachTo(loopback.memory());
app.model(TestModel);
const TestModel = app.registry.createModel('test', {}, modelOptions);
app.model(TestModel, {dataSource: 'db'});
return app;
}
function givenLocalTokenModel() {
const app = loopback({localRegistry: true, loadBuiltinModels: true});
app.dataSource('db', {connector: 'memory'});
const User = app.registry.getModel('User');
app.model(User, {dataSource: 'db'});
const Token = app.registry.getModel('AccessToken');
app.model(Token, {dataSource: 'db'});
return Token;
}

View File

@ -1,225 +1,371 @@
var assert = require('assert');
var loopback = require('../index');
var Scope = loopback.Scope;
var ACL = loopback.ACL;
var Role = loopback.Role;
var RoleMapping = loopback.RoleMapping;
var User = loopback.User;
var testModel;
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
function checkResult(err, result) {
// console.log(err, result);
assert(!err);
}
'use strict';
const assert = require('assert');
const expect = require('./helpers/expect');
const loopback = require('../index');
const Scope = loopback.Scope;
const ACL = loopback.ACL;
const request = require('supertest');
const Promise = require('bluebird');
const supertest = require('supertest');
const Role = loopback.Role;
const RoleMapping = loopback.RoleMapping;
const User = loopback.User;
const async = require('async');
var ds = null;
before(function() {
ds = loopback.createDataSource({connector: loopback.Memory});
// Speed up the password hashing algorithm for tests
User.settings.saltWorkFactor = 4;
let ds = null;
let testModel;
describe('ACL model', function() {
it('provides DEFAULT_SCOPE constant', () => {
expect(ACL).to.have.property('DEFAULT_SCOPE', 'DEFAULT');
});
});
describe('security scopes', function() {
beforeEach(function() {
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);
});
beforeEach(setupTestModels);
it('should allow access to models for the given scope by wildcard', function() {
Scope.create({name: 'userScope', description: 'access user information'}, function(err, scope) {
ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'User', property: ACL.ALL,
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) {
// console.log(resource);
Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, function(err, perm) {
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
});
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, function(err, perm) {
assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
});
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, function(err, perm) {
assert(perm.permission === ACL.DENY);
});
});
it('should allow access to models for the given scope by wildcard', function(done) {
Scope.create({name: 'userScope', description: 'access user information'},
function(err, scope) {
ACL.create({
principalType: ACL.SCOPE, principalId: scope.id,
model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW,
}, function(err, resource) {
async.parallel([
cb => Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, cb),
cb => Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, cb),
cb => Scope.checkPermission('userScope', 'User', 'name', ACL.READ, cb),
], (err) => {
assert.ifError(err);
done();
});
});
});
});
});
it('should allow access to models for the given scope', function(done) {
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) {
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() {
beforeEach(setupTestModels);
it('supports checkPermission() returning a promise', function() {
return ACL.create({
principalType: ACL.USER,
principalId: 'u001',
model: 'testModel',
property: ACL.ALL,
accessType: ACL.ALL,
permission: ACL.ALLOW,
})
.then(function() {
return ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL);
})
.then(function(access) {
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() {
const testModel = ds.createModel('testModel', {
acls: [
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW},
],
});
return ACL.checkAccessForContext({
principals: [{type: ACL.USER, id: 'u001'}],
model: 'testModel',
accessType: ACL.ALL,
})
.then(function(access) {
assert(access.permission === ACL.ALLOW);
});
});
it('should order ACL entries based on the matching score', function() {
var acls = [
let acls = [
{
'model': 'account',
'accessType': '*',
'permission': 'DENY',
'principalType': 'ROLE',
'principalId': '$everyone'
'principalId': '$everyone',
},
{
'model': 'account',
'accessType': '*',
'permission': 'ALLOW',
'principalType': 'ROLE',
'principalId': '$owner'
'principalId': '$owner',
},
{
'model': 'account',
'accessType': 'READ',
'permission': 'ALLOW',
'principalType': 'ROLE',
'principalId': '$everyone'
'principalId': '$everyone',
}];
var req = {
const req = {
model: 'account',
property: 'find',
accessType: 'WRITE'
accessType: 'WRITE',
};
acls = acls.map(function(a) { return new ACL(a); });
var perm = ACL.resolvePermission(acls, req);
assert.deepEqual(perm, { model: 'account',
const perm = ACL.resolvePermission(acls, req);
// remove the registry from AccessRequest instance to ease asserting
delete perm.registry;
assert.deepEqual(perm, {model: 'account',
property: 'find',
accessType: 'WRITE',
permission: 'ALLOW',
methodNames: []});
// NOTE: when fixed in chaijs, use this implement rather than modifying
// the resolved access request
//
// expect(perm).to.deep.include({
// model: 'account',
// property: 'find',
// accessType: 'WRITE',
// permission: 'ALLOW',
// methodNames: [],
// });
});
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() {
it('should allow access to models for the given principal by wildcard', function(done) {
// jscs:disable validateIndentation
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW}, function(err, acl) {
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY}, function(err, acl) {
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function(err, perm) {
assert(perm.permission === ACL.DENY);
});
ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function(err, perm) {
assert(perm.permission === ACL.DENY);
});
});
});
});
it('should allow access to models by exception', function() {
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.DENY}, function(err, acl) {
ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.ALLOW}, function(err, acl) {
ACL.create({principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL,
accessType: ACL.EXECUTE, permission: ACL.ALLOW}, function(err, acl) {
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, function(err, perm) {
assert(perm.permission === ACL.DENY);
});
ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, function(err, perm) {
assert(perm.permission === ACL.DENY);
});
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);
});
ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW,
}, function(err, acl) {
ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY,
}, function(err, acl) {
async.parallel([
cb => ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, cb),
cb => ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, cb),
], (err, perms) => {
if (err) return done(err);
assert.deepEqual(perms.map(p => p.permission), [
ACL.DENY,
ACL.DENY,
]);
done();
});
});
});
});
it('should honor defaultPermission from the model', function() {
var Customer = ds.createModel('Customer', {
it('should allow access to models by exception', function(done) {
ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.DENY,
}, function(err, acl) {
ACL.create({
principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.ALLOW,
}, function(err, acl) {
ACL.create({
principalType: ACL.USER, principalId: 'u002', model: 'testModel', property: ACL.ALL,
accessType: ACL.EXECUTE, permission: ACL.ALLOW,
}, function(err, acl) {
async.parallel([
cb => ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, cb),
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),
cb => ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.WRITE, cb),
cb => ACL.checkPermission(ACL.USER, 'u002', 'testModel', 'name', ACL.READ, cb),
], (err, perms) => {
if (err) return done(err);
assert.deepEqual(perms.map(p => p.permission), [
ACL.ALLOW,
ACL.ALLOW,
ACL.DENY,
ACL.DENY,
ACL.ALLOW,
ACL.ALLOW,
]);
done();
});
});
});
});
});
it('should honor defaultPermission from the model', function(done) {
const Customer = ds.createModel('Customer', {
name: {
type: String,
acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
]
}
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW},
],
},
}, {
acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
]
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW},
],
});
// ACL default permission is to DENY for model Customer
Customer.settings.defaultPermission = ACL.DENY;
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, function(err, perm) {
assert(perm.permission === ACL.DENY);
async.parallel([
cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, cb),
cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, cb),
cb => ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, cb),
], (err, perms) => {
if (err) return done(err);
assert.deepEqual(perms.map(p => p.permission), [
ACL.DENY,
ACL.ALLOW,
ACL.DENY,
]);
done();
});
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, function(err, perm) {
assert(perm.permission === ACL.DENY);
});
});
it('should honor static ACLs from the model', function() {
var Customer = ds.createModel('Customer', {
it('should honor static ACLs from the model', function(done) {
const Customer = ds.createModel('Customer', {
name: {
type: String,
acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
]
}
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW},
],
},
}, {
acls: [
{principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW},
{principalType: ACL.USER, principalId: 'u002', accessType: ACL.EXECUTE, permission: ACL.ALLOW},
{principalType: ACL.USER, principalId: 'u003', accessType: ACL.EXECUTE, permission: ACL.DENY}
]
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW},
{principalType: ACL.USER, principalId: 'u002',
accessType: ACL.EXECUTE, permission: ACL.ALLOW},
{principalType: ACL.USER, principalId: 'u003',
accessType: ACL.EXECUTE, permission: ACL.DENY},
],
});
/*
@ -228,39 +374,36 @@ describe('security ACLs', function() {
];
*/
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, function(err, perm) {
assert(perm.permission === ACL.DENY);
async.parallel([
cb => ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, cb),
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),
cb => ACL.checkPermission(ACL.USER, 'u003', 'Customer', 'name', ACL.WRITE, cb),
], (err, perms) => {
if (err) return done(err);
assert.deepEqual(perms.map(p => p.permission), [
ACL.DENY,
ACL.ALLOW,
ACL.ALLOW,
ACL.ALLOW,
ACL.DENY,
]);
done();
});
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL, function(err, perm) {
assert(perm.permission === ACL.ALLOW);
});
ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.READ, 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() {
var Model1 = ds.createModel('Model1', {
const Model1 = ds.createModel('Model1', {
name: {
type: String,
acls: [
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: 'u001',
accessType: ACL.ALL, permission: ACL.ALLOW}
]
}
accessType: ACL.ALL, permission: ACL.ALLOW},
],
},
}, {
acls: [
{principalType: ACL.USER, principalId: 'u001', property: 'name',
@ -268,11 +411,11 @@ describe('security ACLs', function() {
{principalType: ACL.USER, principalId: 'u002', property: 'findOne',
accessType: ACL.ALL, permission: ACL.ALLOW},
{principalType: ACL.USER, principalId: 'u003', property: ['findOne', 'findById'],
accessType: ACL.ALL, permission: ACL.ALLOW}
]
accessType: ACL.ALL, permission: ACL.ALLOW},
],
});
var staticACLs = ACL.getStaticACLs('Model1', 'name');
let staticACLs = ACL.getStaticACLs('Model1', 'name');
assert(staticACLs.length === 3);
staticACLs = ACL.getStaticACLs('Model1', 'findOne');
@ -283,74 +426,86 @@ describe('security ACLs', function() {
assert(staticACLs[0].property === 'findById');
});
it('should check access against LDL, ACL, and Role', function() {
// var log = console.log;
var log = function() {};
it('should check access against LDL, ACL, and Role', function(done) {
const log = function() {};
// Create
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function(err, user) {
log('User: ', user.toObject());
var userId = user.id;
const userId = user.id;
// Define a model with static ACLs
var Customer = ds.createModel('Customer', {
const Customer = ds.createModel('Customer', {
name: {
type: String,
acls: [
{principalType: ACL.USER, principalId: userId, accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW}
]
}
{principalType: ACL.USER, principalId: userId,
accessType: ACL.WRITE, permission: ACL.DENY},
{principalType: ACL.USER, principalId: userId,
accessType: ACL.ALL, permission: ACL.ALLOW},
],
},
}, {
acls: [
{principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW}
{principalType: ACL.USER, principalId: userId,
accessType: ACL.ALL, permission: ACL.ALLOW},
],
defaultPermission: 'DENY'
defaultPermission: 'DENY',
});
ACL.create({principalType: ACL.USER, principalId: userId, model: 'Customer', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW}, function(err, acl) {
ACL.create({
principalType: ACL.USER, principalId: userId,
model: 'Customer', property: ACL.ALL,
accessType: ACL.ALL, permission: ACL.ALLOW,
}, function(err, acl) {
log('ACL 1: ', acl.toObject());
Role.create({name: 'MyRole'}, function(err, myRole) {
log('Role: ', myRole.toObject());
myRole.principals.create({principalType: RoleMapping.USER, principalId: userId}, function(err, p) {
myRole.principals.create({principalType: RoleMapping.USER, principalId: userId},
function(err, p) {
log('Principal added to role: ', p.toObject());
log('Principal added to role: ', p.toObject());
ACL.create({
principalType: ACL.ROLE, principalId: 'MyRole',
model: 'Customer', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY,
}, function(err, acl) {
log('ACL 2: ', acl.toObject());
ACL.create({principalType: ACL.ROLE, principalId: 'MyRole', model: 'Customer', property: ACL.ALL,
accessType: ACL.READ, permission: ACL.DENY}, function(err, acl) {
log('ACL 2: ', acl.toObject());
ACL.checkAccessForContext({
principals: [
{type: ACL.USER, id: userId}
],
model: 'Customer',
property: 'name',
accessType: ACL.READ
}, function(err, access) {
assert(!err && access.permission === ACL.ALLOW);
async.parallel([
cb => {
ACL.checkAccessForContext({
principals: [
{type: ACL.USER, id: userId},
],
model: 'Customer',
property: 'name',
accessType: ACL.READ,
}, function(err, access) {
assert.ifError(err);
assert.equal(access.permission, ACL.ALLOW);
cb();
});
},
cb => {
ACL.checkAccessForContext({
principals: [
{type: ACL.ROLE, id: Role.EVERYONE},
],
model: 'Customer',
property: 'name',
accessType: ACL.READ,
}, function(err, access) {
assert.ifError(err);
assert.equal(access.permission, ACL.DENY);
cb();
});
}], done);
});
ACL.checkAccessForContext({
principals: [
{type: ACL.ROLE, id: Role.EVERYONE}
],
model: 'Customer',
property: 'name',
accessType: ACL.READ
}, function(err, access) {
assert(!err && access.permission === ACL.DENY);
});
});
});
});
});
});
@ -358,31 +513,32 @@ describe('security ACLs', function() {
});
describe('access check', function() {
var app;
before(function() {
app = loopback();
it('should occur before other remote hooks', function(done) {
const app = loopback();
const MyTestModel = app.registry.createModel('MyTestModel');
let checkAccessCalled = false;
let beforeHookCalled = false;
app.use(loopback.rest());
app.set('remoting', {errorHandler: {debug: true, log: false}});
app.enableAuth();
app.dataSource('test', {connector: 'memory'});
});
it('should occur before other remote hooks', function(done) {
var MyTestModel = app.model('MyTestModel', {base: 'PersistedModel', dataSource: 'test'});
var checkAccessCalled = false;
var beforeHookCalled = false;
app.model(MyTestModel, {dataSource: 'test'});
// fake / spy on the checkAccess method
MyTestModel.checkAccess = function() {
var cb = arguments[arguments.length - 1];
const cb = arguments[arguments.length - 1];
checkAccessCalled = true;
var allowed = true;
const allowed = true;
cb(null, allowed);
};
MyTestModel.beforeRemote('find', function(ctx, next) {
// ensure this is called after checkAccess
if (!checkAccessCalled) return done(new Error('incorrect order'));
beforeHookCalled = true;
next();
});
@ -391,7 +547,142 @@ describe('access check', function() {
.end(function(err, result) {
assert(beforeHookCalled, 'the before hook should be called');
assert(checkAccessCalled, 'checkAccess should have been called');
done();
});
});
});
describe('authorized roles propagation in RemotingContext', function() {
let app, request, accessToken;
let models = {};
beforeEach(setupAppAndRequest);
it('contains all authorized roles for a principal if query is allowed', function() {
return createACLs('MyTestModel', [
{permission: ACL.ALLOW, principalId: '$everyone'},
{permission: ACL.ALLOW, principalId: '$authenticated'},
{permission: ACL.ALLOW, principalId: 'myRole'},
])
.then(makeAuthorizedHttpRequestOnMyTestModel)
.then(function() {
const ctx = models.MyTestModel.lastRemotingContext;
expect(ctx.args.options.authorizedRoles).to.eql(
{
$everyone: true,
$authenticated: true,
myRole: true,
},
);
});
});
it('does not contain any denied role even if query is allowed', function() {
return createACLs('MyTestModel', [
{permission: ACL.ALLOW, principalId: '$everyone'},
{permission: ACL.DENY, principalId: '$authenticated'},
{permission: ACL.ALLOW, principalId: 'myRole'},
])
.then(makeAuthorizedHttpRequestOnMyTestModel)
.then(function() {
const ctx = models.MyTestModel.lastRemotingContext;
expect(ctx.args.options.authorizedRoles).to.eql(
{
$everyone: true,
myRole: true,
},
);
});
});
it('honors default permission setting', function() {
// default permission is set to DENY for MyTestModel
models.MyTestModel.settings.defaultPermission = ACL.DENY;
return createACLs('MyTestModel', [
{permission: ACL.DEFAULT, principalId: '$everyone'},
{permission: ACL.DENY, principalId: '$authenticated'},
{permission: ACL.ALLOW, principalId: 'myRole'},
])
.then(makeAuthorizedHttpRequestOnMyTestModel)
.then(function() {
const ctx = models.MyTestModel.lastRemotingContext;
expect(ctx.args.options.authorizedRoles).to.eql(
// '$everyone' is not expected as default permission is DENY
{myRole: true},
);
});
});
// helpers
function setupAppAndRequest() {
app = loopback({localRegistry: true, loadBuiltinModels: true});
app.use(loopback.rest());
app.set('remoting', {errorHandler: {debug: true, log: true}});
app.dataSource('db', {connector: 'memory'});
request = supertest(app);
app.enableAuth({dataSource: 'db'});
models = app.models;
// Speed up the password hashing algorithm for tests
models.User.settings.saltWorkFactor = 4;
// creating a custom model
const MyTestModel = app.registry.createModel('MyTestModel');
app.model(MyTestModel, {dataSource: 'db'});
// capturing the value of the last remoting context
models.MyTestModel.beforeRemote('find', function(ctx, unused, next) {
models.MyTestModel.lastRemotingContext = ctx;
next();
});
// creating a user, a role and a rolemapping binding that user with that role
return Promise.all([
models.User.create({username: 'myUser', email: 'myuser@example.com', password: 'pass'}),
models.Role.create({name: 'myRole'}),
])
.spread(function(myUser, myRole) {
return Promise.all([
myRole.principals.create({principalType: 'USER', principalId: myUser.id}),
models.User.login({username: 'myUser', password: 'pass'}),
]);
})
.spread(function(role, token) {
accessToken = token;
});
}
function createACLs(model, acls) {
acls = acls.map(function(acl) {
return models.ACL.create({
principalType: acl.principalType || ACL.ROLE,
principalId: acl.principalId,
model: acl.model || model,
property: acl.property || ACL.ALL,
accessType: acl.accessType || ACL.ALL,
permission: acl.permission,
});
});
return Promise.all(acls);
}
function makeAuthorizedHttpRequestOnMyTestModel() {
return request.get('/MyTestModels')
.set('X-Access-Token', accessToken.id)
.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);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,128 @@
// 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';
const loopback = require('../');
const supertest = require('supertest');
const strongErrorHandler = require('strong-error-handler');
const loggers = require('./helpers/error-loggers');
const logAllServerErrors = loggers.logAllServerErrors;
const logServerErrorsOtherThan = loggers.logServerErrorsOtherThan;
describe('Authorization scopes', () => {
const CUSTOM_SCOPE = 'read:custom';
let app, request, User, testUser, regularToken, scopedToken;
beforeEach(givenAppAndRequest);
beforeEach(givenRemoteMethodWithCustomScope);
beforeEach(givenUser);
beforeEach(givenDefaultToken);
beforeEach(givenScopedToken);
it('denies regular token to invoke custom-scoped method', () => {
logServerErrorsOtherThan(401, app);
return request.get('/users/scoped')
.set('Authorization', regularToken.id)
.expect(401);
});
it('allows regular tokens to invoke default-scoped method', () => {
logAllServerErrors(app);
return request.get('/users/' + testUser.id)
.set('Authorization', regularToken.id)
.expect(200);
});
it('allows scoped token to invoke custom-scoped method', () => {
logAllServerErrors(app);
return request.get('/users/scoped')
.set('Authorization', scopedToken.id)
.expect(204);
});
it('denies scoped token to invoke default-scoped method', () => {
logServerErrorsOtherThan(401, app);
return request.get('/users/' + testUser.id)
.set('Authorization', scopedToken.id)
.expect(401);
});
describe('token granted both default and custom scope', () => {
beforeEach('given token with default and custom scope',
() => givenScopedToken(['DEFAULT', CUSTOM_SCOPE]));
beforeEach(() => logAllServerErrors(app));
it('allows invocation of default-scoped method', () => {
return request.get('/users/' + testUser.id)
.set('Authorization', scopedToken.id)
.expect(200);
});
it('allows invocation of custom-scoped method', () => {
return request.get('/users/scoped')
.set('Authorization', scopedToken.id)
.expect(204);
});
});
it('allows invocation when at least one method scope is matched', () => {
givenRemoteMethodWithCustomScope(['read', 'write']);
return givenScopedToken(['read', 'execute']).then(() => {
return request.get('/users/scoped')
.set('Authorization', scopedToken.id)
.expect(204);
});
});
function givenAppAndRequest() {
app = loopback({localRegistry: true, loadBuiltinModels: true});
app.set('remoting', {rest: {handleErrors: false}});
app.dataSource('db', {connector: 'memory'});
app.enableAuth({dataSource: 'db'});
request = supertest(app);
app.use(loopback.rest());
User = app.models.User;
}
function givenRemoteMethodWithCustomScope() {
// Delete any previously registered instance of the method "scoped"
User.sharedClass._methods = User.sharedClass._methods
.filter(m => m.name !== 'scoped');
const accessScopes = arguments[0] || [CUSTOM_SCOPE];
User.scoped = function(cb) { cb(); };
User.remoteMethod('scoped', {
accessScopes,
http: {verb: 'GET', path: '/scoped'},
});
User.settings.acls.push({
principalType: 'ROLE',
principalId: '$authenticated',
permission: 'ALLOW',
property: 'scoped',
accessType: 'EXECUTE',
});
}
function givenUser() {
return User.create({email: 'test@example.com', password: 'pass'})
.then(u => testUser = u);
}
function givenDefaultToken() {
return testUser.createAccessToken(60)
.then(t => regularToken = t);
}
function givenScopedToken() {
const scopes = arguments[0] || [CUSTOM_SCOPE];
return testUser.accessTokens.create({ttl: 60, scopes})
.then(t => scopedToken = t);
}
});

View File

@ -1,17 +1,30 @@
// Copyright IBM Corp. 2015,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 expect = require('./helpers/expect');
const sinon = require('sinon');
const loopback = require('../');
describe('PersistedModel.createChangeStream()', function() {
describe('configured to source changes locally', function() {
before(function() {
var test = this;
var app = loopback({localRegistry: true});
var ds = app.dataSource('ds', {connector: 'memory'});
this.Score = app.model('Score', {
const test = this;
const app = loopback({localRegistry: true});
const ds = app.dataSource('ds', {connector: 'memory'});
const Score = app.registry.createModel('Score');
this.Score = app.model(Score, {
dataSource: 'ds',
changeDataSource: false // use only local observers
changeDataSource: false, // use only local observers
});
});
afterEach(verifyObserversRemoval);
it('should detect create', function(done) {
var Score = this.Score;
const Score = this.Score;
Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) {
@ -25,28 +38,30 @@ describe('PersistedModel.createChangeStream()', function() {
});
it('should detect update', function(done) {
var Score = this.Score;
const Score = this.Score;
Score.create({team: 'foo'}, function(err, newScore) {
Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) {
expect(change.type).to.equal('update');
changes.destroy();
done();
});
newScore.updateAttributes({
bat: 'baz'
bat: 'baz',
});
});
});
});
it('should detect delete', function(done) {
var Score = this.Score;
const Score = this.Score;
Score.create({team: 'foo'}, function(err, newScore) {
Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) {
expect(change.type).to.equal('remove');
changes.destroy();
done();
});
@ -54,28 +69,86 @@ 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
describe.skip('configured to source changes using pubsub', function() {
before(function() {
var test = this;
var app = loopback({localRegistry: true});
var db = app.dataSource('ds', {connector: 'memory'});
var ps = app.dataSource('ps', {
const test = this;
const app = loopback({localRegistry: true});
const db = app.dataSource('ds', {connector: 'memory'});
const ps = app.dataSource('ps', {
host: 'localhost',
port: '12345',
connector: 'pubsub',
pubsubAdapter: 'mqtt'
pubsubAdapter: 'mqtt',
});
this.Score = app.model('Score', {
dataSource: 'db',
changeDataSource: 'ps'
changeDataSource: 'ps',
});
});
it('should detect a change', function(done) {
var Score = this.Score;
const Score = this.Score;
Score.createChangeStream(function(err, changes) {
changes.on('data', function(change) {

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