Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

merge

  • Loading branch information...
commit 3dfe29d2867fa765508f20d44854910d4b1f2cf7 2 parents 7a498ba + de37574
@sixolet sixolet authored
Showing with 4,664 additions and 1,218 deletions.
  1. +35 −0 .mailmap
  2. +170 −7 History.md
  3. +45 −18 LICENSE.txt
  4. +2 −2 README.md
  5. +1 −1  docs/.meteor/release
  6. +62 −36 docs/client/api.html
  7. +84 −11 docs/client/api.js
  8. +2 −2 docs/client/concepts.html
  9. +4 −4 docs/client/docs.js
  10. +1 −0  docs/client/packages.html
  11. +165 −0 docs/client/packages/browser-policy.html
  12. +5 −7 docs/client/packages/random.html
  13. +5 −0 docs/lib/release-override.js
  14. +0 −1  examples/leaderboard/.meteor/packages
  15. +1 −1  examples/leaderboard/.meteor/release
  16. +1 −1  examples/parties/.meteor/release
  17. +10 −9 examples/parties/client/client.js
  18. +12 −2 examples/parties/model.js
  19. +1 −1  examples/todos/.meteor/release
  20. +0 −1  examples/wordplay/.meteor/packages
  21. +1 −1  examples/wordplay/.meteor/release
  22. +2 −3 examples/wordplay/client/wordplay.js
  23. +8 −5 examples/wordplay/model.js
  24. +3 −1 meteor
  25. +11 −6 packages/accounts-base/accounts_client.js
  26. +50 −26 packages/accounts-base/accounts_common.js
  27. +150 −41 packages/accounts-base/accounts_server.js
  28. +30 −0 packages/accounts-base/accounts_tests.js
  29. +1 −1  packages/accounts-password/package.js
  30. +30 −13 packages/accounts-password/password_server.js
  31. +272 −174 packages/accounts-password/password_tests.js
  32. +7 −2 packages/accounts-password/password_tests_setup.js
  33. +1 −1  packages/accounts-ui/package.js
  34. +1 −1  packages/appcache/package.js
  35. +1 −0  packages/browser-policy-common/.gitignore
  36. +27 −0 packages/browser-policy-common/browser-policy-common.js
  37. +10 −0 packages/browser-policy-common/package.js
  38. +1 −0  packages/browser-policy-content/.gitignore
  39. +238 −0 packages/browser-policy-content/browser-policy-content.js
  40. +9 −0 packages/browser-policy-content/package.js
  41. +1 −0  packages/browser-policy-framing/.gitignore
  42. +39 −0 packages/browser-policy-framing/browser-policy-framing.js
  43. +9 −0 packages/browser-policy-framing/package.js
  44. +1 −0  packages/browser-policy/.gitignore
  45. +129 −0 packages/browser-policy/browser-policy-test.js
  46. +13 −0 packages/browser-policy/package.js
  47. +4 −1 packages/ctl-helper/.npm/package/npm-shrinkwrap.json
  48. +3 −2 packages/ctl-helper/package.js
  49. +2 −1  packages/ctl/package.js
  50. +1 −1  packages/d3/package.js
  51. +50 −3 packages/ejson/ejson.js
  52. +104 −0 packages/ejson/ejson_test.js
  53. +1 −0  packages/ejson/package.js
  54. +118 −0 packages/ejson/stringify.js
  55. +2 −2 packages/email/.npm/package/npm-shrinkwrap.json
  56. +6 −3 packages/email/package.js
  57. +1 −0  packages/geojson-utils/.gitignore
  58. +380 −0 packages/geojson-utils/geojson-utils.js
  59. +102 −0 packages/geojson-utils/geojson-utils.tests.js
  60. +16 −0 packages/geojson-utils/package.js
  61. +4 −0 packages/geojson-utils/post.js
  62. +4 −0 packages/geojson-utils/pre.js
  63. +9 −0 packages/google/google_client.js
  64. +8 −3 packages/http/httpcall_tests.js
  65. +0 −39 packages/js-analyze-tests/js_analyze_tests.js
  66. +2 −1  packages/js-analyze-tests/package.js
  67. +6 −4 packages/js-analyze/.npm/package/npm-shrinkwrap.json
  68. +4 −183 packages/js-analyze/js_analyze.js
  69. +1 −4 packages/js-analyze/package.js
  70. +1 −1  packages/less/package.js
  71. +8 −3 packages/livedata/.npm/package/npm-shrinkwrap.json
  72. +24 −11 packages/livedata/livedata_server.js
  73. +2 −0  packages/livedata/livedata_tests.js
  74. +1 −2  packages/livedata/package.js
  75. +1 −1  packages/livedata/sockjs-0.3.4.js
  76. +4 −1 packages/livedata/stream_server.js
  77. +1 −1  packages/logging/.npm/package/npm-shrinkwrap.json
  78. +1 −1  packages/logging/package.js
  79. +1 −1  packages/madewith/package.js
  80. +8 −10 packages/minifiers/.npm/package/npm-shrinkwrap.json
  81. +3 −3 packages/minifiers/package.js
  82. +1 −1  packages/minimongo/NOTES
  83. +102 −24 packages/minimongo/minimongo.js
  84. +151 −13 packages/minimongo/minimongo_tests.js
  85. +83 −9 packages/minimongo/modify.js
  86. +3 −0  packages/minimongo/package.js
  87. +140 −43 packages/minimongo/selector.js
  88. +1 −1  packages/mongo-livedata/.npm/package/npm-shrinkwrap.json
  89. +12 −1 packages/mongo-livedata/allow_tests.js
  90. +97 −32 packages/mongo-livedata/collection.js
  91. +210 −33 packages/mongo-livedata/mongo_driver.js
  92. +660 −7 packages/mongo-livedata/mongo_livedata_tests.js
  93. +1 −1  packages/mongo-livedata/package.js
  94. +2 −2 packages/mongo-livedata/remote_collection_driver.js
  95. +28 −11 packages/oauth1/oauth1_binding.js
  96. +20 −10 packages/oauth1/oauth1_server.js
  97. +3 −1 packages/oauth1/oauth1_tests.js
  98. +88 −48 packages/random/random.js
  99. +14 −0 packages/random/random_tests.js
  100. +5 −0 packages/reload/reload.js
  101. +1 −1  packages/spark/spark_tests.js
  102. +1 −1  packages/spiderable/package.js
  103. +2 −1  packages/star-translate/package.js
  104. +1 −1  packages/stylus/package.js
  105. +1 −1  packages/test-helpers/seeded_random.js
  106. +89 −59 packages/underscore/underscore.js
  107. +40 −10 packages/webapp/.npm/package/npm-shrinkwrap.json
  108. +8 −3 packages/webapp/package.js
  109. +39 −10 packages/webapp/webapp_server.js
  110. +2 −3 scripts/admin/banner.txt
  111. +6 −5 scripts/admin/bless-release.js
  112. +6 −0 scripts/admin/notices.json
  113. +13 −11 scripts/admin/publish-release/server/publish-release.js
  114. +1 −1  scripts/cli-test.sh
  115. +19 −21 scripts/generate-dev-bundle.sh
  116. +1 −3 tools/app.html.in
  117. +2 −2 tools/bundler.js
  118. +4 −3 tools/deploy-galaxy.js
  119. +13 −12 tools/deploy.js
  120. +0 −53 tools/files.js
  121. +95 −0 tools/http-helpers.js
  122. +1 −1  tools/meteor.js
  123. +42 −4 tools/meteor_npm.js
  124. +13 −2 tools/mongo_runner.js
  125. +1 −1  tools/packages.js
  126. +49 −62 tools/run.js
  127. +1 −1  tools/server/boot.js
  128. +2 −1  tools/tests/test_bundler_assets.js
  129. +2 −2 tools/tests/test_bundler_npm.js
  130. +2 −1  tools/updater.js
  131. +5 −4 tools/warehouse.js
  132. +47 −40 tools/watch.js
View
35 .mailmap
@@ -0,0 +1,35 @@
+# This makes it easier to find GitHub usernames for History.md.
+#
+# This controls 'git shortlog'. eg, run:
+# git shortlog -s release/0.6.5.1..HEAD
+# to get a sorted list of all committers to revisions in HEAD but not
+# in 0.6.5.1.
+#
+# For any emails that show up in the shortlog that aren't in one of
+# these lists, figure out their GitHub username and add them.
+
+GITHUB: ansman <nicklas@ansman.se>
+GITHUB: awwx <andrew.wilcox@gmail.com>
+GITHUB: codeinthehole <david.winterbottom@gmail.com>
+GITHUB: jacott <geoffjacobsen@gmail.com>
+GITHUB: Maxhodges <Max@whiterabbitpress.com>
+GITHUB: meawoppl <meawoppl@gmail.com>
+GITHUB: mitar <mitar.git@tnode.com>
+GITHUB: mizzao <mizzao@gmail.com>
+GITHUB: mquandalle <maxime.quandalle@gmail.com>
+GITHUB: nathan-muir <ndmuir@gmail.com>
+GITHUB: RobertLowe <robert@iblargz.com>
+GITHUB: ryw <ry@rywalker.com>
+GITHUB: sdarnell <stephen@darnell.plus.com>
+GITHUB: timhaines <tmhaines@gmail.com>
+
+METEOR: avital <avital@thewe.net>
+METEOR: debergalis <matt@meteor.com>
+METEOR: dgreensp <dgreenspan@alum.mit.edu>
+METEOR: estark37 <emily@meteor.com>
+METEOR: estark37 <estark37@gmail.com>
+METEOR: glasser <glasser@meteor.com>
+METEOR: gschmidt <geoff@geoffschmidt.com>
+METEOR: n1mmy <nim@meteor.com>
+METEOR: sixolet <naomi@meteor.com>
+METEOR: Slava <slava@meteor.com>
View
177 History.md
@@ -1,22 +1,185 @@
## vNEXT
-* Better error when passing a string to {{#each}}. #722
+* Fail explicitly when publishing non-cursors.
-* Write dates to Mongo as ISODate rather than Integer; existing data can be
- converted by passing it through `new Date()`. #1228
+* Implement `$each`, `$sort`, and `$slice` options for minimongo's `$push`
+ modifier.
-* Login token deletion: Expire login tokens periodically. Add
- Meteor._logoutAllOthers() for logging out other connections logged in as the
- current user. Log out and close connections for deleted users and tokens.
+* Upgraded dependencies:
+ * SockJS server from 0.3.7 to 0.3.8
+
+
+## v0.6.6.1
+
+* Fix file watching on OSX. Work around Node issue #6251 by not using
+ fs.watch. #1483
+
+## v0.6.6
+
+
+#### Security
+
+* Add `browser-policy` package for configuring and sending
+ Content-Security-Policy and X-Frame-Options HTTP headers.
+ [See the docs](http://docs.meteor.com/#browserpolicy) for more.
+
+* Use cryptographically strong pseudorandom number generators when available.
+
+#### MongoDB
+
+* Add upsert support. `Collection.update` now supports the `{upsert:
+ true}` option. Additionally, add a `Collection.upsert` method which
+ returns the newly inserted object id if applicable.
+
+* `update` and `remove` now return the number of documents affected. #1046
+
+* `$near` operator for `2d` and `2dsphere` indices.
+
+* The `fields` option to the collection methods `find` and `findOne` now works
+ on the client as well. (Operators such as `$elemMatch` and `$` are not yet
+ supported in `fields` projections.) #1287
+
+* Pass an index and the cursor itself to the callbacks in `cursor.forEach` and
+ `cursor.map`, just like the corresponding `Array` methods. #63
+
+* Support `c.find(query, {limit: N}).count()` on the client. #654
+
+* Improve behavior of `$ne`, `$nin`, and `$not` selectors with objects containing
+ arrays. #1451
+
+* Fix various bugs if you had two documents with the same _id field in
+ String and ObjectID form.
+
+#### Accounts
+
+* [Behavior Change] Expire login tokens periodically. Defaults to 90
+ days. Use `Accounts.config({loginExpirationInDays: null})` to disable
+ token expiration.
+
+* [Behavior Change] Write dates generated by Meteor Accounts to Mongo as
+ Date instead of number; existing data can be converted by passing it
+ through `new Date()`. #1228
+
+* Log out and close connections for users if they are deleted from the
+ database.
+
+* Add Meteor.logoutOtherClients() for logging out other connections
+ logged in as the current user.
+
+* `restrictCreationByEmailDomain` option in `Accounts.config` to restrict new
+ users to emails of specific domain (eg. only users with @meteor.com emails) or
+ a custom validator. #1332
+
+* Support OAuth1 services that require request token secrets as well as
+ authentication token secrets. #1253
+
+* Warn if `Accounts.config` is only called on the client. #828
+
+* Fix bug where callbacks to login functions could be called multiple
+ times when the client reconnects.
+
+#### DDP
+
+* Fix infinite loop if a client disconnects while a long yielding method is
+ running.
+
+* Unfinished code to support DDP session resumption has been removed. Meteor
+ servers now stop processing messages from clients and reclaim memory
+ associated with them as soon as they are disconnected instead of a few minutes
+ later.
+
+#### Tools
* The pre-0.6.5 `Package.register_extension` API has been removed. Use
`Package._transitional_registerBuildPlugin` instead, which was introduced in
0.6.5. (A bug prevented the 0.6.5 reimplementation of `register_extension`
from working properly anyway.)
+* Support using an HTTP proxy in the `meteor` command line tool. This
+ allows the `update`, `deploy`, `logs`, and `mongo` commands to work
+ behind a proxy. Use the standard `http_proxy` environment variable to
+ specify your proxy endpoint. #429, #689, #1338
+
* Build Linux binaries on an older Linux machine. Meteor now supports
running on Linux machines with glibc 2.9 or newer (Ubuntu 10.04+, RHEL
- and CentOS 6+, Fedora 10+, Debian 6+).
+ and CentOS 6+, Fedora 10+, Debian 6+). Improve error message when running
+ on Linux with unsupported glibc, and include Mongo stderr if it fails
+ to start.
+
+* Install NPM modules with `--force` to avoid corrupted local caches.
+
+* Rebuild NPM modules in packages when upgrading to a version of Meteor that
+ uses a different version of Node.
+
+* Disable the Mongo http interface. This lets you run meteor on two ports
+ differing by 1000 at the same time.
+
+#### Misc
+
+* [Known issue] Breaks support for pre-release OSX 10.9 'Mavericks'.
+ Will be addressed shortly. See issues:
+ https://github.com/joyent/node/issues/6251
+ https://github.com/joyent/node/issues/6296
+
+* `EJSON.stringify` now takes options:
+ - `canonical` causes objects keys to be stringified in sorted order
+ - `indent` allows formatting control over the EJSON stringification
+
+* EJSON now supports `Infinity`, `-Infinity` and `NaN`.
+
+* Check that the argument to `EJSON.parse` is a string. #1401
+
+* Better error from functions that use `Meteor._wrapAsync` (eg collection write
+ methods and `HTTP` methods) and in DDP server message processing. #1387
+
+* Support `appcache` on Chrome for iOS.
+
+* Support literate CoffeeScript files with the extension `.coffee.md` (in
+ addition to the already-supported `.litcoffee` extension). #1407
+
+* Make `madewith` package work again (broken in 0.6.5). #1448
+
+* Better error when passing a string to `{{#each}}`. #722
+
+* Add support for JSESSIONID cookies for sticky sessions. Set the
+ `USE_JSESSIONID` environment variable to enable placing a JSESSIONID
+ cookie on sockjs requests.
+
+* Simplify the static analysis used to detect package-scope variables.
+
+* Upgraded dependencies:
+ * Node from 0.8.24 to 0.10.20
+ * MongoDB from 2.4.4 to 2.4.6
+ * MongoDB driver from 1.3.17 to 1.3.19
+ * http-proxy from 0.10.1 to a pre-release of 1.0.0
+ * stylus from 0.30.1 to 0.37.0
+ * nib from 0.8.2 to 1.0.0
+ * optimist from 0.3.5 to 0.6.0
+ * semver from 1.1.0 to 2.1.0
+ * request from 2.12.0 to 2.27.0
+ * keypress from 0.1.0 to 0.2.1
+ * underscore from 1.5.1 to 1.5.2
+ * fstream from 0.1.21 to 0.1.24
+ * tar from 0.1.14 to 0.1.18
+ * source-map from 0.1.26 to 0.1.30
+ * source-map-support from a fork of 0.1.8 to 0.2.3
+ * escope from a fork of 0.0.15 to 1.0.0
+ * estraverse from 1.1.2-1 to 1.3.1
+ * simplesmtp from 0.1.25 to 0.3.10
+ * stream-buffers from 0.2.3 to 0.2.5
+ * websocket from 1.0.7 to 1.0.8
+ * cli-color from 0.2.2 to 0.2.3
+ * clean-css from 1.0.11 to 1.1.2
+ * UglifyJS2 from a fork of 2.3.6 to a different fork of 2.4.0
+ * connect from 2.7.10 to 2.9.0
+ * send from 0.1.0 to 0.1.4
+ * useragent from 2.0.1 to 2.0.7
+ * replaced byline with eachline 2.3.3
+
+Patches contributed by GitHub users ansman, awwx, codeinthehole, jacott,
+Maxhodges, meawoppl, mitar, mizzao, mquandalle, nathan-muir, RobertLowe, ryw,
+sdarnell, and timhaines.
+
## v0.6.5.1
View
63 LICENSE.txt
@@ -90,6 +90,7 @@ github-url-from-git: https://github.com/visionmedia/node-github-url-from-git
pause: https://github.com/visionmedia/node-pause
range-parser: https://github.com/visionmedia/node-range-parser
send: https://github.com/visionmedia/send
+methods: https://github.com/visionmedia/node-methods
----------
Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca>
@@ -192,13 +193,6 @@ Copyright (c) 2010 Aleksander Williams
----------
-formidable: https://github.com/felixge/node-formidable
-----------
-
-By Felix Geisendörfer and Tim Koschuetzki, Debuggable, Ltd.
-
-
-----------
colors: https://github.com/Marak/colors.js
----------
@@ -245,9 +239,11 @@ Copyright (c) 2012 Nathan Rajlich <nathan@tootallnate.net>
----------
faye-websocket: https://github.com/faye/faye-websocket-node
+websocket-driver: https://github.com/faye/websocket-driver-node
----------
-Copyright (c) 2009-2012 James Coglan
+Copyright (c) 2009-2013 James Coglan
+Copyright (c) 2010-2013 James Coglan
----------
@@ -291,6 +287,7 @@ archy: https://github.com/substack/node-archy
shell-quote: https://github.com/substack/node-shell-quote
deep-equal: https://github.com/substack/node-deep-equal
editor: https://github.com/substack/node-editor
+minimist: https://github.com/substack/node-minimist
----------
Copyright 2010, 2011, 2012, 2013 James Halliday (mail@substack.net)
@@ -328,6 +325,7 @@ Felix Geisendörfer (felix@debuggable.com)
----------
form-data: https://github.com/felixge/node-form-data
+multiparty: https://github.com/superjoe30/node-multiparty
----------
Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
@@ -378,13 +376,6 @@ Copyright (c) 2013 Brian J. Brennan
----------
-byline: https://github.com/jahewson/node-byline
-----------
-
-node-byline (C) 2011-2013 John Hewson
-
-
-----------
child-process-close: https://github.com/piscisaureus/child-process-close
----------
@@ -475,6 +466,8 @@ Copyright (c) 2011 SDA Software Associates Inc.
----------
sha: https://github.com/ForbesLindesay/sha
+type-of: https://github.com/ForbesLindesay/type-of
+uglify-to-browserify: https://github.com/ForbesLindesay/uglify-to-browserify
----------
Copyright (c) 2013 Forbes Lindesay
@@ -516,6 +509,38 @@ Copyright 2011, Robert Mustacchi. All rights reserved.
Copyright 2011, Joyent, Inc. All rights reserved.
+----------
+eachline: https://github.com/williamwicks/node-eachline
+----------
+
+Copyright (c) 2013 William Wicks
+
+
+----------
+eventemitter2: https://github.com/hij1nx/EventEmitter2
+----------
+
+Copyright (c) 2011 hij1nx http://www.twitter.com/hij1nx
+
+
+----------
+stream-counter: https://github.com/superjoe30/node-stream-counter
+----------
+
+Copyright (c) 2013 Andrew Kelley
+
+----------
+uid2: https://github.com/coreh/uid2
+----------
+
+Copyright (c) 2013 Marco Aurelio
+
+----------
+geojson-utils: https://github.com/maxogden/geojson-js-utils
+----------
+
+Copyright (c) 2010 Max Ogden
+
==============
Apache License
==============
@@ -531,7 +556,8 @@ Unless required by applicable law or agreed to in writing, software distributed
"""
----------
-mongo-drivers: https://github.com/mongodb/node-mongodb-native
+mongodb: (Node driver) https://github.com/mongodb/node-mongodb-native
+kerberos: https://github.com/christkv/kerberos
----------
Copyright 2009 - 2010 Christian Amor Kvalheim.
@@ -1309,7 +1335,7 @@ Other
=====
----------
-mimelib: https://github.com/andris9/mimelib
+mimelib-noiconv: https://github.com/andris9/mimelib
mailcomposer: https://github.com/andris9/mailcomposer
simplesmtp: https://github.com/andris9/simplesmtp
rai: https://github.com/andris9/rai
@@ -1407,7 +1433,7 @@ For more information, please refer to <http://unlicense.org/>
----------
-mongodb: http://www.mongodb.org/
+MongoDB: http://www.mongodb.org/
----------
LICENSE
@@ -1711,6 +1737,7 @@ The externally maintained libraries used by libuv are:
----------
nodejs: http://nodejs.org/
+readable-stream: https://github.com/isaacs/readable-stream/
----------
View
4 README.md
@@ -56,8 +56,8 @@ From your checkout, you can read the docs locally. The `/docs` directory is a
meteor application, so simply change into the `/docs` directory and launch
the app:
- cd docs/
- meteor
+ cd docs/
+ ../meteor
You'll then be able to read the docs locally in your browser at
`http://localhost:3000/`
View
2  docs/.meteor/release
@@ -1 +1 @@
-galaxy-appconfig-2
+galaxy-follower-5
View
98 docs/client/api.html
@@ -62,9 +62,9 @@ <h2 id="publishandsubscribe"><span>Publish and subscribe</span></h2>
Publish functions can return a
[`Collection.Cursor`](#meteor_collection_cursor), in which case Meteor
-will publish that cursor's documents. You can also return an array of
-`Collection.Cursor`s, in which case Meteor will publish all of the
-cursors.
+will publish that cursor's documents to each subscribed client. You can
+also return an array of `Collection.Cursor`s, in which case Meteor will
+publish all of the cursors.
{{#warning}}
If you return multiple cursors in an array, they currently must all be from
@@ -92,16 +92,15 @@ <h2 id="publishandsubscribe"><span>Publish and subscribe</span></h2>
];
});
-Otherwise, the publish function should call the functions
-[`added`](#publish_added) (when a new document is added to the published record
-set), [`changed`](#publish_changed) (when some fields on a document in the
-record set are changed or cleared), and [`removed`](#publish_removed) (when
-documents are removed from the published record set) to inform subscribers about
-documents. These methods are provided by `this` in your publish function.
-
-
-
-<!-- TODO discuss ready -->
+Alternatively, a publish function can directly control its published
+record set by calling the functions [`added`](#publish_added) (to add a
+new document to the published record set), [`changed`](#publish_changed)
+(to change or clear some fields on a document already in the published
+record set), and [`removed`](#publish_removed) (to remove documents from
+the published record set). Publish functions that use these functions
+should also call [`ready`](#publish_ready) once the initial record set
+is complete. These methods are provided by `this` in your publish
+function.
Example:
@@ -634,7 +633,7 @@ <h2 id="collections"><span>Collections</span></h2>
of selectors.
* `$` to denote the matched array position is not
supported in modifier.
-* `findAndModify`, upsert, aggregate functions, and
+* `findAndModify`, aggregate functions, and
map/reduce aren't supported.
All of these will be addressed in a future release. For full
@@ -723,24 +722,27 @@ <h2 id="collections"><span>Collections</span></h2>
`multi` to true, and can use an arbitrary [Mongo
selector](#selectors) to find the documents to modify. It bypasses
any access control rules set up by [`allow`](#allow) and
- [`deny`](#deny).
+ [`deny`](#deny). The number of affected documents will be returned
+ from the `update` call if you don't pass a callback.
- Untrusted code can only modify a single document at once, specified
by its `_id`. The modification is allowed only after checking any
- applicable [`allow`](#allow) and [`deny`](#deny) rules.
+ applicable [`allow`](#allow) and [`deny`](#deny) rules. The number
+ of affected documents will be returned to the callback. Untrusted
+ code cannot perform upserts, except in insecure mode.
On the server, if you don't provide a callback, then `update` blocks
until the database acknowledges the write, or throws an exception if
something went wrong. If you do provide a callback, `update` returns
immediately. Once the update completes, the callback is called with a
-single error argument in the case of failure, or no arguments if the
-update was successful.
+single error argument in the case of failure, or a second argument
+indicating the number of affected documents if the update was successful.
On the client, `update` never blocks. If you do not provide a callback
and the update fails on the server, then Meteor will log a warning to
the console. If you provide a callback, Meteor will call that function
-with an error argument if there was an error, or no arguments if the
-update was successful.
+with an error argument if there was an error, or a second argument
+indicating the number of affected documents if the update was successful.
Client example:
@@ -766,9 +768,18 @@ <h2 id="collections"><span>Collections</span></h2>
}
});
-{{#warning}}
-The Mongo `upsert` feature is not implemented.
-{{/warning}}
+You can use `update` to perform a Mongo upsert by setting the `upsert`
+option to true. You can also use the [`upsert`](#upsert) method to perform an
+upsert that returns the _id of the document that was inserted (if there was one)
+in addition to the number of affected documents.
+
+{{> api_box upsert}}
+
+Modify documents that match `selector` according to `modifier`, or insert
+a document if no documents were modified. `upsert` is the same as calling
+`update` with the `upsert` option set to true, except that the return
+value of `upsert` is an object that contain the keys `numberAffected`
+and `insertedId`. (`update` returns only the number of affected documents.)
{{> api_box remove}}
@@ -784,7 +795,8 @@ <h2 id="collections"><span>Collections</span></h2>
find the documents to remove, and can remove more than one document
at once by passing a selector that matches multiple documents. It
bypasses any access control rules set up by [`allow`](#allow) and
- [`deny`](#deny).
+ [`deny`](#deny). The number of removed documents will be returned
+ from `remove` if you don't pass a callback.
As a safety measure, if `selector` is omitted (or is `undefined`),
no documents will be removed. Set `selector` to `{}` if you really
@@ -792,20 +804,22 @@ <h2 id="collections"><span>Collections</span></h2>
- Untrusted code can only remove a single document at a time,
specified by its `_id`. The document is removed only after checking
- any applicable [`allow`](#allow) and [`deny`](#deny) rules.
+ any applicable [`allow`](#allow) and [`deny`](#deny) rules. The
+ number of removed documents will be returned to the callback.
On the server, if you don't provide a callback, then `remove` blocks
-until the database acknowledges the write, or throws an exception if
+until the database acknowledges the write and then returns the number
+of removed documents, or throws an exception if
something went wrong. If you do provide a callback, `remove` returns
immediately. Once the remove completes, the callback is called with a
-single error argument in the case of failure, or no arguments if the
-remove was successful.
+single error argument in the case of failure, or a second argument
+indicating the number of removed documents if the remove was successful.
On the client, `remove` never blocks. If you do not provide a callback
-and the remove fails on the server, then Meteor will log a warning to
-the console. If you provide a callback, Meteor will call that function
-with an error argument if there was an error, or no arguments if the
-remove was successful.
+and the remove fails on the server, then Meteor will log a warning to the
+console. If you provide a callback, Meteor will call that function with an
+error argument if there was an error, or a second argument indicating the number
+of removed documents if the remove was successful.
Client example:
@@ -966,6 +980,8 @@ <h2 id="meteor_collection_cursor"><span>Cursors</span></h2>
{{> api_box cursor_foreach}}
+This interface is compatible with [Array.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach).
+
When called from a reactive computation, `forEach` registers dependencies on
the matching documents.
@@ -981,6 +997,8 @@ <h2 id="meteor_collection_cursor"><span>Cursors</span></h2>
{{> api_box cursor_map}}
+This interface is compatible with [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
+
When called from a reactive computation, `map` registers dependencies on
the matching documents.
@@ -1258,12 +1276,13 @@ <h2 id="meteor_collection_cursor"><span>Cursors</span></h2>
// Users.find({}, {fields: {password: 0, hash: 0}})
To return an object that only includes the specified field, use `1` as
-the value. The `_id` field is still included in the result.
+the value. The `_id` field is still included in the result.
// Users.find({}, {fields: {firstname: 1, lastname: 1}})
-It is not possible to mix inclusion and exclusion styles (exception for `_id`).
-Field operators such as `$` and `$elemMatch` are not available on client side yet.
+It is not possible to mix inclusion and exclusion styles (except for the cases
+when `_id` is included by default or explicitly excluded). Field operators such
+as `$` and `$elemMatch` are not available on the client side yet.
More advanced example:
@@ -1275,7 +1294,8 @@ <h2 id="meteor_collection_cursor"><span>Cursors</span></h2>
// returns { alterEgos: [{ name: "Kira" }, { name: "L" }] }
-Notice the nested field rule and array behavior, learn more in MongoDB docs.
+See <a href="http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/#projection">
+the MongoDB docs</a> for details of the nested field rules and array behavior.
{{/api_box_inline}}
@@ -1495,6 +1515,12 @@ <h2 id="accounts_api"><span>Accounts</span></h2>
{{> api_box logout}}
+{{> api_box logoutOtherClients}}
+
+For example, when called in a user's browser, connections in that browser
+remain logged in, but any other browsers or DDP clients logged in as that user
+will be logged out.
+
{{> api_box loginWithPassword}}
This function is provided by the `accounts-password` package. See the
View
95 docs/client/api.js
@@ -73,8 +73,7 @@ Template.api.release = {
descr: ["`Meteor.release` is a string containing the name of the " +
"[release](#meteorupdate) with which the project was built (for " +
"example, `\"" +
- // Put the current release in the docs as the example)
- (Meteor.release ? Meteor.release : '0.6.0') +
+ Meteor.release +
"\"`). It is `undefined` if the project was built using a git " +
"checkout of Meteor."]
};
@@ -89,9 +88,17 @@ Template.api.ejsonParse = {
Template.api.ejsonStringify = {
id: "ejson_stringify",
- name: "EJSON.stringify(val)",
+ name: "EJSON.stringify(val, [options])",
locus: "Anywhere",
args: [ {name: "val", type: "EJSON-compatible value", descr: "A value to stringify."} ],
+ options: [
+ {name: "indent",
+ type: "Boolean, Integer, or String",
+ descr: "Indents objects and arrays for easy readability. When `true`, indents by 2 spaces; when an integer, indents by that number of spaces; and when a string, uses the string as the indentation pattern."},
+ {name: "canonical",
+ type: "Boolean",
+ descr: "When `true`, stringifies keys in an object in sorted order."}
+ ],
descr: ["Serialize a value to a string.\n\nFor EJSON values, the serialization " +
"fully represents the value. For non-EJSON values, serializes the " +
"same way as `JSON.stringify`."]
@@ -116,10 +123,15 @@ Template.api.ejsonToJSONValue = {
Template.api.ejsonEquals = {
id: "ejson_equals",
- name: "EJSON.equals(a, b)", //doc options?
+ name: "EJSON.equals(a, b, [options])",
locus: "Anywhere",
args: [ {name: "a", type: "EJSON-compatible object"},
{name: "b", type: "EJSON-compatible object"} ],
+ options: [
+ {name: "keyOrderSensitive",
+ type: "Boolean",
+ descr: "Compare in key sensitive order, if supported by the JavaScript implementation. For example, `{a: 1, b: 2}` is equal to `{b: 2, a: 1}` only when `keyOrderSensitive` is `false`. The default is `false`."}
+ ],
descr: ["Return true if `a` and `b` are equal to each other. Return false otherwise." +
" Uses the `equals` method on `a` if present, otherwise performs a deep comparison."]
},
@@ -482,7 +494,7 @@ Template.api.meteor_collection = {
options: [
{name: "connection",
type: "Object",
- descr: "The Meteor connection that will manage this collection. Uses the default connection if not specified. Pass `null` to specify no connection. Unmanaged (`name` is null) collections cannot specify a connection."
+ descr: "The server connection that will manage this collection. Uses the default connection if not specified. Pass the return value of calling [`DDP.connect`](#ddp_connect) to specify a different server. Pass `null` to specify no connection. Unmanaged (`name` is null) collections cannot specify a connection."
},
{name: "idGeneration",
type: "String",
@@ -585,7 +597,7 @@ Template.api.update = {
id: "update",
name: "<em>collection</em>.update(selector, modifier, [options], [callback])",
locus: "Anywhere",
- descr: ["Modify one or more documents in the collection"],
+ descr: ["Modify one or more documents in the collection. Returns the number of affected documents."],
args: [
{name: "selector",
type: "Mongo selector, or object id",
@@ -597,7 +609,37 @@ Template.api.update = {
descr: "Specifies how to modify the documents"},
{name: "callback",
type: "Function",
- descr: "Optional. If present, called with an error object as its argument."}
+ descr: "Optional. If present, called with an error object as the first argument and, if no error, the number of affected documents as the second."}
+ ],
+ options: [
+ {name: "multi",
+ type: "Boolean",
+ descr: "True to modify all matching documents; false to only modify one of the matching documents (the default)."},
+ {name: "upsert",
+ type: "Boolean",
+ descr: "True to insert a document if no matching documents are found."}
+ ]
+};
+
+Template.api.upsert = {
+ id: "upsert",
+ name: "<em>collection</em>.upsert(selector, modifier, [options], [callback])",
+ locus: "Anywhere",
+ descr: ["Modify one or more documents in the collection, or insert one if no matching documents were found. " +
+ "Returns an object with keys `numberAffected` (the number of documents modified) " +
+ " and `insertedId` (the unique _id of the document that was inserted, if any)."],
+ args: [
+ {name: "selector",
+ type: "Mongo selector, or object id",
+ type_link: "selectors",
+ descr: "Specifies which documents to modify"},
+ {name: "modifier",
+ type: "Mongo modifier",
+ type_link: "modifiers",
+ descr: "Specifies how to modify the documents"},
+ {name: "callback",
+ type: "Function",
+ descr: "Optional. If present, called with an error object as the first argument and, if no error, the number of affected documents as the second."}
],
options: [
{name: "multi",
@@ -606,6 +648,7 @@ Template.api.update = {
]
};
+
Template.api.remove = {
id: "remove",
name: "<em>collection</em>.remove(selector, [callback])",
@@ -675,25 +718,31 @@ Template.api.cursor_fetch = {
Template.api.cursor_foreach = {
id: "foreach",
- name: "<em>cursor</em>.forEach(callback)",
+ name: "<em>cursor</em>.forEach(callback, [thisArg])",
locus: "Anywhere",
descr: ["Call `callback` once for each matching document, sequentially and synchronously."],
args: [
{name: "callback",
type: "Function",
- descr: "Function to call."}
+ descr: "Function to call. It will be called with three arguments: the document, a 0-based index, and <em>cursor</em> itself."},
+ {name: "thisArg",
+ type: "Any",
+ descr: "An object which will be the value of `this` inside `callback`."}
]
};
Template.api.cursor_map = {
id: "map",
- name: "<em>cursor</em>.map(callback)",
+ name: "<em>cursor</em>.map(callback, [thisArg])",
locus: "Anywhere",
descr: ["Map callback over all matching documents. Returns an Array."],
args: [
{name: "callback",
type: "Function",
- descr: "Function to call."}
+ descr: "Function to call. It will be called with three arguments: the document, a 0-based index, and <em>cursor</em> itself."},
+ {name: "thisArg",
+ type: "Any",
+ descr: "An object which will be the value of `this` inside `callback`."}
]
};
@@ -1026,6 +1075,20 @@ Template.api.logout = {
]
};
+Template.api.logoutOtherClients = {
+ id: "meteor_logoutotherclients",
+ name: "Meteor.logoutOtherClients([callback])",
+ locus: "Client",
+ descr: ["Log out other clients logged in as the current user, but does not log out the client that calls this function."],
+ args: [
+ {
+ name: "callback",
+ type: "Function",
+ descr: "Optional callback. Called with no arguments on success, or with a single `Error` argument on failure."
+ }
+ ]
+};
+
Template.api.loginWithPassword = {
id: "meteor_loginwithpassword",
@@ -1100,6 +1163,16 @@ Template.api.accounts_config = {
name: "forbidClientAccountCreation",
type: "Boolean",
descr: "Calls to [`createUser`](#accounts_createuser) from the client will be rejected. In addition, if you are using [accounts-ui](#accountsui), the \"Create account\" link will not be available."
+ },
+ {
+ name: "restrictCreationByEmailDomain",
+ type: "String Or Function",
+ descr: "If set, only allow new users with an email in the specified domain or if the predicate function returns true. Works with password-based sign-in and external services that expose email addresses (Google, Facebook, GitHub). All existing users still can log in after enabling this option. Example: `Accounts.config({ restrictCreationByEmailDomain: 'school.edu' })`."
+ },
+ {
+ name: "loginExpirationInDays",
+ type: "Number",
+ descr: "The number of days from when a user logs in until their token expires and they are logged out. Defaults to 90. Set to `null` to disable login expiration."
}
]
};
View
4 docs/client/concepts.html
@@ -824,9 +824,9 @@ <h3 class="nosection">Running on your own infrastructure</h3>
$ meteor bundle myapp.tgz
This command will generate a fully-contained Node.js application in the form of
-a tarball. To run this application, you need to provide Node.js 0.8 and a
+a tarball. To run this application, you need to provide Node.js 0.10 and a
MongoDB server. (The current release of Meteor has been tested with Node
-0.8.24.) You can then run the application by invoking node, specifying the HTTP
+0.10.20.) You can then run the application by invoking node, specifying the HTTP
port for the application to listen on, and the MongoDB endpoint. If you don't
already have a MongoDB server, we can recommend our friends at
[MongoHQ](http://mongohq.com).
View
8 docs/client/docs.js
@@ -1,8 +1,5 @@
Template.headline.release = function () {
- // XXX This is commented out because for now galaxy apps have to be on a
- // different Meteor release that has a bug fix.
- return "0.6.5.1";
- // return Meteor.release || "(checkout)";
+ return Meteor.release || "(checkout)";
};
@@ -155,6 +152,7 @@ var toc = [
{instance: "collection", name: "findOne"},
{instance: "collection", name: "insert"},
{instance: "collection", name: "update"},
+ {instance: "collection", name: "upsert"},
{instance: "collection", name: "remove"},
{instance: "collection", name: "allow"},
{instance: "collection", name: "deny"}
@@ -191,6 +189,7 @@ var toc = [
"Meteor.users",
"Meteor.loggingIn",
"Meteor.logout",
+ "Meteor.logoutOtherClients",
"Meteor.loginWithPassword",
{name: "Meteor.loginWithFacebook", id: "meteor_loginwithexternalservice"},
{name: "Meteor.loginWithGithub", id: "meteor_loginwithexternalservice"},
@@ -335,6 +334,7 @@ var toc = [
"audit-argument-checks",
"backbone",
"bootstrap",
+ "browser-policy",
"coffeescript",
"d3",
"force-ssl",
View
1  docs/client/packages.html
@@ -22,6 +22,7 @@ <h1 id="packages">Packages</h1>
{{> pkg_audit_argument_checks}}
{{> pkg_backbone}}
{{> pkg_bootstrap}}
+{{> pkg_browser_policy}}
{{> pkg_coffeescript}}
{{> pkg_d3}}
{{> pkg_force_ssl}}
View
165 docs/client/packages/browser-policy.html
@@ -0,0 +1,165 @@
+<template name="pkg_browser_policy">
+{{#better_markdown}}
+## `browser-policy`
+
+The `browser-policy` package lets you set security-related policies that will be
+enforced by newer browsers. These policies help you prevent and mitigate common
+attacks like cross-site scripting and clickjacking.
+
+When you add `browser-policy` to your app, you get default configurations for
+the HTTP headers X-Frame-Options and Content-Security-Policy. X-Frame-Options
+tells the browser which websites are allowed to frame your app. You should only
+let trusted websites frame your app, because malicious sites could harm your
+users with <a href="https://www.owasp.org/index.php/Clickjacking">clickjacking
+attacks</a>.
+<a href="https://developer.mozilla.org/en-US/docs/Security/CSP/Introducing_Content_Security_Policy">Content-Security-Policy</a>
+tells the browser where your app can load content from, which encourages safe
+practices and mitigates the damage of a cross-site-scripting attack.
+`browser-policy` also provides functions for you to configure these policies if
+the defaults are not suitable.
+
+If you only want to use Content-Security-Policy or X-Frame-Options but not both,
+you can add the individual packages `browser-policy-content` or
+`browser-policy-framing` instead of `browser-policy`.
+
+For most apps, we recommend that you take the following steps:
+
+* Add `browser-policy` to your app to enable a starter policy. With this starter
+policy, your app's client code will be able to load content (images, scripts,
+fonts, etc.) only from its own origin, except that XMLHttpRequests and WebSocket
+connections can go to any origin. Further, your app's client code will not be
+able to use functions such as `eval()` that convert strings to code. Users'
+browsers will only let your app be framed by web pages on the same origin as
+your app.
+* You can use the functions described below to customize the policies. If your
+app does not need any inline Javascript such as inline `<script>` tags, we
+recommend that you modify the policy by calling
+`BrowserPolicy.content.disallowInlineScripts()` in server code. This will result
+in one extra round trip when your app is loaded, but will help prevent
+cross-site scripting attacks by disabling all scripts except those loaded from a
+`script src` attribute.
+
+Meteor determines the browser policy when the server starts up, so you should
+call `BrowserPolicy` functions in top-level application code or in
+`Meteor.startup`.
+
+#### Frame options
+
+By default, if you add `browser-policy` or `browser-policy-framing`, only web
+pages on the same origin as your app are allowed to frame your app. You can use
+the following functions to modify this policy.
+<dl class="callbacks">
+{{#dtdd "BrowserPolicy.framing.disallow()"}}
+Your app will never render inside a frame or iframe.
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.framing.restrictToOrigin(origin)"}}
+Your app will only render inside frames loaded by `origin`. You can only call
+this function once with a single origin, and cannot use wildcards or specify
+multiple origins that are allowed to frame your app. (This is a limitation of
+the X-Frame-Options header.) Example values of `origin` include
+"http://example.com" and "https://foo.example.com".
+{{#warning}}
+This value of the X-Frame-Options header is not yet supported in Chrome or
+Safari and will be ignored in those browsers.
+{{/warning}}
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.framing.allowAll()"}}
+This unsets the X-Frame-Options header, so that your app can be framed by
+any webpage.
+{{/dtdd}}
+</dl>
+
+#### Content options
+
+You can use the functions in this section to control how different types of
+content can be loaded on your site.
+
+You can use the following functions to adjust policies on where Javascript and
+CSS can be run:
+
+<dl class="callbacks">
+{{#dtdd "BrowserPolicy.content.allowInlineScripts()"}}
+Allows inline `<script>` tags, `javascript:` URLs, and inline event handlers.
+The default policy already allows inline scripts.
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.content.disallowInlineScripts()"}}
+Disallows inline Javascript. Calling this function results in an extra
+round-trip on page load to retrieve Meteor runtime configuration that is usually
+part of an inline script tag.
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.content.allowEval()"}}
+Allows the creation of Javascript code from strings using function such as `eval()`.
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.content.disallowEval()"}}
+Disallows eval and related functions. The default policy already disallows eval.
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.content.allowInlineStyles()"}}
+Allows inline style tags and style attributes. The default policy already allows
+inline styles.
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.content.disallowInlineStyles()"}}
+Disallows inline CSS.
+{{/dtdd}}
+</dl>
+
+Finally, you can configure a whitelist of allowed requests that various types of
+content can make. The following functions are defined for the content types
+script, object, image, media, font, and connect.
+
+<dl class="callbacks">
+{{#dtdd "BrowserPolicy.content.allow&lt;ContentType&gt;Origin(origin)"}}
+Allows this type of content to be loaded from the given origin. `origin` is a
+string and can include an optional scheme (such as `http` or `https`), an
+optional wildcard at the beginning, and an optional port which can be a
+wildcard. Examples include `example.com`, `https://*.example.com`, and
+`example.com:*`. You can call these functions multiple times with different
+origins to specify a whitelist of allowed origins.
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.content.allow&lt;ContentType&gt;DataUrl()"}}
+Allows this type of content to be loaded from a `data:` URL.
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.content.allow&lt;ContentType&gt;SameOrigin()"}}
+Allows this type of content to be loaded from the same origin as your app.
+{{/dtdd}}
+
+{{#dtdd "BrowserPolicy.content.disallow&lt;ContentType&gt;()"}}
+Disallows this type of content on your app.
+{{/dtdd}}
+</dl>
+
+You can also set policies for all these types of content at once, using these
+functions:
+
+* `BrowserPolicy.content.allowSameOriginForAll()`,
+* `BrowserPolicy.content.allowDataUrlForAll()`,
+* `BrowserPolicy.content.allowOriginForAll(origin)`
+* `BrowserPolicy.content.disallowAll()`
+
+For example, if you want to allow the
+origin `https://foo.com` for all types of content but you want to disable
+`<object>` tags, you can call
+`BrowserPolicy.content.allowOriginForAll("https://foo.com")` followed by
+`BrowserPolicy.content.disallowObject()`.
+
+Other examples of using the `BrowserPolicy.content` API:
+
+* `BrowserPolicy.content.disallowFont()` causes the browser to disallow all
+`<font>` tags.
+* `BrowserPolicy.content.allowImageOrigin("https://example.com")`
+allows images to have their `src` attributes point to images served from
+`https://example.com`.
+* `BrowserPolicy.content.allowConnectOrigin("https://example.com")` allows XMLHttpRequest
+and WebSocket connections to `https://example.com`.
+
+
+{{/better_markdown}}
+</template>
View
12 docs/client/packages/random.html
@@ -3,8 +3,11 @@
## `random`
The `random` package provides several functions for generating random
-numbers. It uses a Meteor-provided random number generator that does not depend
-on the browser's facilities.
+numbers. It uses a cryptographically strong pseudorandom number generator when
+possible, but falls back to a weaker random number generator when
+cryptographically strong randomness is not available (on older browsers or on
+servers that don't have enough entropy to seed the cryptographically strong
+generator).
<dl class="callbacks">
{{#dtdd "Random.id()"}}
@@ -25,10 +28,5 @@
{{/dtdd}}
</dl>
-{{#note}}
-In the current implementation, random values do not come from a
-cryptographically strong pseudorandom number generator. Future releases will
-improve this, particularly on the server.
-{{/note}}
{{/better_markdown}}
</template>
View
5 docs/lib/release-override.js
@@ -0,0 +1,5 @@
+// While galaxy apps are on their own special meteor releases, override
+// Meteor.release here.
+if (Meteor.isClient) {
+ Meteor.release = Meteor.release ? "0.6.6.1" : undefined;
+}
View
1  examples/leaderboard/.meteor/packages
@@ -7,4 +7,3 @@ standard-app-packages
autopublish
insecure
preserve-inputs
-random
View
2  examples/leaderboard/.meteor/release
@@ -1 +1 @@
-0.6.5.1
+0.6.6.1
View
2  examples/parties/.meteor/release
@@ -1 +1 @@
-0.6.5.1
+0.6.6.1
View
19 examples/parties/client/client.js
@@ -3,13 +3,16 @@
Meteor.subscribe("directory");
Meteor.subscribe("parties");
-// If no party selected, select one.
+// If no party selected, or if the selected party was deleted, select one.
Meteor.startup(function () {
Deps.autorun(function () {
- if (! Session.get("selected")) {
+ var selected = Session.get("selected");
+ if (! selected || ! Parties.findOne(selected)) {
var party = Parties.findOne();
if (party)
Session.set("selected", party._id);
+ else
+ Session.set("selected", null);
}
});
});
@@ -213,19 +216,17 @@ Template.createDialog.events({
var coords = Session.get("createCoords");
if (title.length && description.length) {
- Meteor.call('createParty', {
+ var id = createParty({
title: title,
description: description,
x: coords.x,
y: coords.y,
public: public
- }, function (error, party) {
- if (! error) {
- Session.set("selected", party);
- if (! public && Meteor.users.find().count() > 1)
- openInviteDialog();
- }
});
+
+ Session.set("selected", id);
+ if (! public && Meteor.users.find().count() > 1)
+ openInviteDialog();
Session.set("showCreateDialog", false);
} else {
Session.set("createError",
View
14 examples/parties/model.js
@@ -52,6 +52,12 @@ var Coordinate = Match.Where(function (x) {
return x >= 0 && x <= 1;
});
+createParty = function (options) {
+ var id = Random.id();
+ Meteor.call('createParty', _.extend({ _id: id }, options));
+ return id;
+};
+
Meteor.methods({
// options should include: title, description, x, y, public
createParty: function (options) {
@@ -60,7 +66,8 @@ Meteor.methods({
description: NonEmptyString,
x: Coordinate,
y: Coordinate,
- public: Match.Optional(Boolean)
+ public: Match.Optional(Boolean),
+ _id: Match.Optional(NonEmptyString)
});
if (options.title.length > 100)
@@ -70,7 +77,9 @@ Meteor.methods({
if (! this.userId)
throw new Meteor.Error(403, "You must be logged in");
- return Parties.insert({
+ var id = options._id || Random.id();
+ Parties.insert({
+ _id: id,
owner: this.userId,
x: options.x,
y: options.y,
@@ -80,6 +89,7 @@ Meteor.methods({
invited: [],
rsvps: []
});
+ return id;
},
invite: function (partyId, userId) {
View
2  examples/todos/.meteor/release
@@ -1 +1 @@
-0.6.5.1
+0.6.6.1
View
1  examples/wordplay/.meteor/packages
@@ -7,4 +7,3 @@ standard-app-packages
insecure
jquery
preserve-inputs
-random
View
2  examples/wordplay/.meteor/release
@@ -1 +1 @@
-0.6.5.1
+0.6.6.1
View
5 examples/wordplay/client/wordplay.js
@@ -132,9 +132,8 @@ Template.scratchpad.events({
'click button, keyup input': function (evt) {
var textbox = $('#scratchpad input');
// if we clicked the button or hit enter
- if (evt.type === "click" ||
- (evt.type === "keyup" && evt.which === 13)) {
-
+ if ((evt.type === "click" || (evt.type === "keyup" && evt.which === 13))
+ && textbox.val()) {
var word_id = Words.insert({player_id: Session.get('player_id'),
game_id: game() && game()._id,
word: textbox.val().toUpperCase(),
View
13 examples/wordplay/model.js
@@ -102,9 +102,12 @@ Meteor.methods({
var word = Words.findOne(word_id);
var game = Games.findOne(word.game_id);
- // client and server can both check: must be at least three chars
- // long, not already used, and possible to make on the board.
- if (word.length < 3
+ // client and server can both check that the game has time remaining, and
+ // that the word is at least three chars, isn't already used, and is
+ // possible to make on the board.
+ if (game.clock === 0
+ || !word.word
+ || word.word.length < 3
|| Words.find({game_id: word.game_id, word: word.word}).count() > 1
|| paths_for_word(game.board, word.word).length === 0) {
Words.update(word._id, {$set: {score: 0, state: 'bad'}});
@@ -127,8 +130,8 @@ Meteor.methods({
if (Meteor.isServer) {
DICTIONARY = {};
_.each(Assets.getText("enable2k.txt").split("\n"), function (line) {
- // Skip comment lines
- if (line.indexOf("//") !== 0) {
+ // Skip blanks and comment lines
+ if (line && line.indexOf("//") !== 0) {
DICTIONARY[line] = true;
}
});
View
4 meteor
@@ -1,6 +1,8 @@
#!/bin/bash
-BUNDLE_VERSION=0.3.15
+BUNDLE_VERSION=0.3.19 # 0.3.20 is already built but was not used. next
+# time you bump this version number, go to 0.3.21 and remove this
+# comment.
# OS Check. Put here because here is where we download the precompiled
# bundles that are arch specific.
View
17 packages/accounts-base/accounts_client.js
@@ -70,6 +70,8 @@ Accounts.callLoginMethod = function (options) {
if (!options[f])
options[f] = function () {};
});
+ // make sure we only call the user's callback once.
+ var onceUserCallback = _.once(options.userCallback);
var reconnected = false;
@@ -116,7 +118,10 @@ Accounts.callLoginMethod = function (options) {
if (error) {
makeClientLoggedOut();
}
- options.userCallback(error);
+ // Possibly a weird callback to call, but better than nothing if
+ // there is a reconnect between "login result received" and "data
+ // ready".
+ onceUserCallback(error);
}});
}
};
@@ -142,19 +147,19 @@ Accounts.callLoginMethod = function (options) {
if (error || !result) {
error = error || new Error(
"No result from call to " + options.methodName);
- options.userCallback(error);
+ onceUserCallback(error);
return;
}
try {
options.validateResult(result);
} catch (e) {
- options.userCallback(e);
+ onceUserCallback(e);
return;
}
// Make the client logged in. (The user data should already be loaded!)
makeClientLoggedIn(result.id, result.token, result.tokenExpires);
- options.userCallback();
+ onceUserCallback();
};
if (!options._suppressLoggingIn)
@@ -188,7 +193,7 @@ Meteor.logout = function (callback) {
});
};
-Meteor._logoutAllOthers = function (callback) {
+Meteor.logoutOtherClients = function (callback) {
// Our connection is going to be closed, but we don't want to call the
// onReconnect handler until the result comes back for this method, because
// the token will have been deleted on the server. Instead, wait until we get
@@ -198,7 +203,7 @@ Meteor._logoutAllOthers = function (callback) {
var origOnReconnect = Meteor.connection.onReconnect;
var userId = Meteor.userId();
Meteor.connection.onReconnect = null;
- Meteor.apply('_logoutAllOthers', [], { wait: true },
+ Meteor.apply('logoutOtherClients', [], { wait: true },
function (error, result) {
Meteor.connection.onReconnect = origOnReconnect;
if (! error)
View
76 packages/accounts-base/accounts_common.js
@@ -4,6 +4,18 @@ Accounts = {};
// and accounts-ui-unstyled.
Accounts._options = {};
+// how long (in days) until a login token expires
+var DEFAULT_LOGIN_EXPIRATION_DAYS = 90;
+// Clients don't try to auto-login with a token that is going to expire within
+// .1 * DEFAULT_LOGIN_EXPIRATION_DAYS, capped at MIN_TOKEN_LIFETIME_CAP_SECS.
+// Tries to avoid abrupt disconnects from expiring tokens.
+var MIN_TOKEN_LIFETIME_CAP_SECS = 3600; // one hour
+// how often (in milliseconds) we check for expired tokens
+EXPIRE_TOKENS_INTERVAL_MS = 600 * 1000; // 10 minutes
+// how long we wait before logging out clients when Meteor.logoutOtherClients is
+// called
+CONNECTION_CLOSE_DELAY_MS = 10 * 1000;
+
// Set up config for the accounts system. Call this on both the client
// and the server.
//
@@ -19,23 +31,31 @@ Accounts._options = {};
// client signups.
// - forbidClientAccountCreation {Boolean}
// Do not allow clients to create accounts directly.
-// - _tokenLifetimeSecs {Number}
-// Seconds until a login token expires.
-// - _tokenExpirationIntervalSecs {Number}
-// How often (in seconds) to check for expired tokens
-// - _minTokenLifetimeSecs {Number}
-// The minimum number of seconds until a token expires in order for the
-// client to be willing to connect with that token.
-// - _connectionCloseDelaySecs {Number}
-// The number of seconds to wait before closing connections that when a user
-// is logged out by the server. Defaults to 10, to allow clients to store a
-// fresh token in localStorage when calling _logoutAllOthers.
+// - restrictCreationByEmailDomain {Function or String}
+// Require created users to have an email matching the function or
+// having the string as domain.
+// - loginExpirationInDays {Number}
+// Number of days since login until a user is logged out (login token
+// expires).
//
Accounts.config = function(options) {
+ // We don't want users to accidentally only call Accounts.config on the
+ // client, where some of the options will have partial effects (eg removing
+ // the "create account" button from accounts-ui if forbidClientAccountCreation
+ // is set, or redirecting Google login to a specific-domain page) without
+ // having their full effects.
+ if (Meteor.isServer) {
+ __meteor_runtime_config__.accountsConfigCalled = true;
+ } else if (!__meteor_runtime_config__.accountsConfigCalled) {
+ // XXX would be nice to "crash" the client and replace the UI with an error
+ // message, but there's no trivial way to do this.
+ Meteor._debug("Accounts.config was called on the client but not on the " +
+ "server; some configuration options may not take effect.");
+ }
+
// validate option keys
var VALID_KEYS = ["sendVerificationEmail", "forbidClientAccountCreation",
- "_tokenLifetimeSecs", "_tokenExpirationIntervalSecs",
- "_minTokenLifetimeSecs", "_connectionCloseDelaySecs"];
+ "restrictCreationByEmailDomain", "loginExpirationInDays"];
_.each(_.keys(options), function (key) {
if (!_.contains(VALID_KEYS, key)) {
throw new Error("Accounts.config: Invalid key: " + key);
@@ -49,11 +69,14 @@ Accounts.config = function(options) {
throw new Error("Can't set `" + key + "` more than once");
} else {
Accounts._options[key] = options[key];
- if (key === "_tokenExpirationInterval" && Meteor.isServer)
- initExpireTokenInterval();
}
}
});
+
+ // If the user set loginExpirationInDays to null, then we need to clear the
+ // timer that periodically expires tokens.
+ if (Meteor.isServer)
+ maybeStopExpireTokensInterval();
};
// Users table. Don't use the normal autopublish, since we want to hide
@@ -81,20 +104,21 @@ Accounts.LoginCancelledError.numericError = 0x8acdc2f;
Accounts.LoginCancelledError.prototype = new Error();
Accounts.LoginCancelledError.prototype.name = 'Accounts.LoginCancelledError';
-// how long (in seconds) until a login token expires
-DEFAULT_TOKEN_LIFETIME_SECS = 604800; // one week
-// We don't try to auto-login with a token that is going to expire within
-// MIN_TOKEN_LIFETIME seconds, to avoid abrupt disconnects from expiring tokens.
-var DEFAULT_MIN_TOKEN_LIFETIME_SECS = 3600; // one hour
+getTokenLifetimeMs = function () {
+ return (Accounts._options.loginExpirationInDays ||
+ DEFAULT_LOGIN_EXPIRATION_DAYS) * 24 * 60 * 60 * 1000;
+};
Accounts._tokenExpiration = function (when) {
- var tokenLifetimeSecs = Accounts._options._tokenLifetimeSecs ||
- DEFAULT_TOKEN_LIFETIME_SECS;
- return new Date(when.getTime() + tokenLifetimeSecs * 1000);
+ // We pass when through the Date constructor for backwards compatibility;
+ // `when` used to be a number.
+ return new Date((new Date(when)).getTime() + getTokenLifetimeMs());
};
Accounts._tokenExpiresSoon = function (when) {
- var minLifetimeSecs = Accounts._options._minTokenLifetimeSecs ||
- DEFAULT_MIN_TOKEN_LIFETIME_SECS;
- return new Date() > (new Date(when) - minLifetimeSecs * 1000);
+ var minLifetimeMs = .1 * getTokenLifetimeMs();
+ var minLifetimeCapMs = MIN_TOKEN_LIFETIME_CAP_SECS * 1000;
+ if (minLifetimeMs > minLifetimeCapMs)
+ minLifetimeMs = minLifetimeCapMs;
+ return new Date() > (new Date(when) - minLifetimeMs);
};
View
191 packages/accounts-base/accounts_server.js
@@ -91,10 +91,13 @@ Meteor.methods({
this.setUserId(null);
},
- // Nuke everything: delete all the current user's tokens and close all open
- // connections logged in as this user. Returns a fresh new login token that
- // this client can use.
- _logoutAllOthers: function () {
+ // Delete all the current user's tokens and close all open connections logged
+ // in as this user. Returns a fresh new login token that this client can
+ // use. Tests set Accounts._noConnectionCloseDelayForTest to delete tokens
+ // immediately instead of using a delay.
+ //
+ // @returns {Object} Object with token and tokenExpires keys.
+ logoutOtherClients: function () {
var self = this;
var user = Meteor.users.findOne(self.userId, {
fields: {
@@ -102,13 +105,27 @@ Meteor.methods({
}
});
if (user) {
+ // Save the current tokens in the database to be deleted in
+ // CONNECTION_CLOSE_DELAY_MS ms. This gives other connections in the
+ // caller's browser time to find the fresh token in localStorage. We save
+ // the tokens in the database in case we crash before actually deleting
+ // them.
var tokens = user.services.resume.loginTokens;
var newToken = Accounts._generateStampedLoginToken();
+ var userId = self.userId;
Meteor.users.update(self.userId, {
$set: {
- "services.resume.loginTokens": [newToken]
- }
+ "services.resume.loginTokensToDelete": tokens,
+ "services.resume.haveLoginTokensToDelete": true
+ },
+ $push: { "services.resume.loginTokens": newToken }
});
+ Meteor.setTimeout(function () {
+ // The observe on Meteor.users will take care of closing the connections
+ // associated with `tokens`.
+ deleteSavedTokens(userId, tokens);
+ }, Accounts._noConnectionCloseDelayForTest ? 0 :
+ CONNECTION_CLOSE_DELAY_MS);
// We do not set the login token on this connection, but instead the
// observe closes the connection and the client will reconnect with the
// new token.
@@ -127,9 +144,6 @@ Meteor.methods({
///
/// support reconnecting using a meteor login token
-// how often (in seconds) we check for expired tokens
-var DEFAULT_EXPIRE_TOKENS_INTERVAL_SECS = 600; // 10 minutes
-
// Login handler for resume tokens.
Accounts.registerLoginHandler(function(options) {
if (!options.resume)
@@ -183,19 +197,39 @@ var removeLoginToken = function (userId, loginToken) {
var expireTokenInterval;
// Deletes expired tokens from the database and closes all open connections
-// associated with these tokens. Exported for tests.
-var expireTokens = Accounts._expireTokens = function (oldestValidDate) {
- var tokenLifetimeSecs = Accounts._options._tokenLifetimeSecs ||
- DEFAULT_TOKEN_LIFETIME_SECS;
- oldestValidDate = oldestValidDate ||
- (new Date(new Date() - tokenLifetimeSecs * 1000));
+// associated with these tokens.
+//
+// Exported for tests. Also, the arguments are only used by
+// tests. oldestValidDate is simulate expiring tokens without waiting
+// for them to actually expire. userId is used by tests to only expire
+// tokens for the test user.
+var expireTokens = Accounts._expireTokens = function (oldestValidDate, userId) {
+ var tokenLifetimeMs = getTokenLifetimeMs();
+
+ // when calling from a test with extra arguments, you must specify both!
+ if ((oldestValidDate && !userId) || (!oldestValidDate && userId)) {
+ throw new Error("Bad test. Must specify both oldestValidDate and userId.");
+ }
- Meteor.users.update({
- "services.resume.loginTokens.when": { $lt: oldestValidDate }
- }, {
+ oldestValidDate = oldestValidDate ||
+ (new Date(new Date() - tokenLifetimeMs));
+ var userFilter = userId ? {_id: userId} : {};
+
+
+ // Backwards compatible with older versions of meteor that stored login token
+ // timestamps as numbers.
+ Meteor.users.update(_.extend(userFilter, {
+ $or: [
+ { "services.resume.loginTokens.when": { $lt: oldestValidDate } },
+ { "services.resume.loginTokens.when": { $lt: +oldestValidDate } }
+ ]
+ }), {
$pull: {
"services.resume.loginTokens": {
- when: { $lt: oldestValidDate }
+ $or: [
+ { when: { $lt: oldestValidDate } },
+ { when: { $lt: +oldestValidDate } }
+ ]
}
}
}, { multi: true });
@@ -203,16 +237,17 @@ var expireTokens = Accounts._expireTokens = function (oldestValidDate) {
// expired tokens.
};
-Meteor.users._ensureIndex("services.resume.loginTokens.when", { sparse: true });
-
-initExpireTokenInterval = function () {
- if (expireTokenInterval)
+maybeStopExpireTokensInterval = function () {
+ if (_.has(Accounts._options, "loginExpirationInDays") &&
+ Accounts._options.loginExpirationInDays === null &&
+ expireTokenInterval) {
Meteor.clearInterval(expireTokenInterval);
- var expirePeriodSecs = Accounts._options._tokenExpirationIntervalSecs ||
- DEFAULT_EXPIRE_TOKENS_INTERVAL_SECS;
- expireTokenInterval = Meteor.setInterval(expireTokens, expirePeriodSecs * 1000);
+ expireTokenInterval = null;
+ }
};
-initExpireTokenInterval();
+
+expireTokenInterval = Meteor.setInterval(expireTokens,
+ EXPIRE_TOKENS_INTERVAL_MS);
///
/// CREATE USER HOOKS
@@ -304,6 +339,49 @@ Accounts.validateNewUser = function (func) {
validateNewUserHooks.push(func);
};
+// XXX Find a better place for this utility function
+// Like Perl's quotemeta: quotes all regexp metacharacters. See
+// https://github.com/substack/quotemeta/blob/master/index.js
+var quotemeta = function (str) {
+ return String(str).replace(/(\W)/g, '\\$1');
+};
+
+// Helper function: returns false if email does not match company domain from
+// the configuration.
+var testEmailDomain = function (email) {
+ var domain = Accounts._options.restrictCreationByEmailDomain;
+ return !domain ||
+ (_.isFunction(domain) && domain(email)) ||
+ (_.isString(domain) &&
+ (new RegExp('@' + quotemeta(domain) + '$', 'i')).test(email));
+};
+
+// Validate new user's email or Google/Facebook/GitHub account's email
+Accounts.validateNewUser(function (user) {
+ var domain = Accounts._options.restrictCreationByEmailDomain;
+ if (!domain)
+ return true;
+
+ var emailIsGood = false;
+ if (!_.isEmpty(user.emails)) {
+ emailIsGood = _.any(user.emails, function (email) {
+ return testEmailDomain(email.address);
+ });
+ } else if (!_.isEmpty(user.services)) {
+ // Find any email of any service and check it
+ emailIsGood = _.any(user.services, function (service) {
+ return service.email && testEmailDomain(service.email);
+ });
+ }
+
+ if (emailIsGood)
+ return true;
+
+ if (_.isString(domain))
+ throw new Meteor.Error(403, "@" + domain + " email required");
+ else
+ throw new Meteor.Error(403, "Email doesn't match the criteria.");
+});
///
/// MANAGING USER OBJECTS
@@ -521,28 +599,59 @@ Meteor.users._ensureIndex('username', {unique: 1, sparse: 1});
Meteor.users._ensureIndex('emails.address', {unique: 1, sparse: 1});
Meteor.users._ensureIndex('services.resume.loginTokens.token',
{unique: 1, sparse: 1});
+// For taking care of logoutOtherClients calls that crashed before the tokens
+// were deleted.
+Meteor.users._ensureIndex('services.resume.haveLoginTokensToDelete',
+ { sparse: 1 });
+// For expiring login tokens
+Meteor.users._ensureIndex("services.resume.loginTokens.when", { sparse: 1 });
///
-/// LOGGING OUT DELETED USERS
+/// CLEAN UP FOR `logoutOtherClients`
///
-var DEFAULT_CONNECTION_CLOSE_DELAY_SECS = 10;
+var deleteSavedTokens = function (userId, tokensToDelete) {
+ if (tokensToDelete) {
+ Meteor.users.update(userId, {
+ $unset: {
+ "services.resume.haveLoginTokensToDelete": 1,
+ "services.resume.loginTokensToDelete": 1
+ },
+ $pullAll: {
+ "services.resume.loginTokens": tokensToDelete
+ }
+ });
+ }
+};
+
+Meteor.startup(function () {
+ // If we find users who have saved tokens to delete on startup, delete them
+ // now. It's possible that the server could have crashed and come back up
+ // before new tokens are found in localStorage, but this shouldn't happen very
+ // often. We shouldn't put a delay here because that would give a lot of power
+ // to an attacker with a stolen login token and the ability to crash the
+ // server.
+ var users = Meteor.users.find({
+ "services.resume.haveLoginTokensToDelete": true
+ }, {
+ "services.resume.loginTokensToDelete": 1
+ });
+ users.forEach(function (user) {
+ deleteSavedTokens(user._id, user.services.resume.loginTokensToDelete);
+ });
+});
+
+///
+/// LOGGING OUT DELETED USERS
+///
-// By default, connections are closed with a 10 second delay, to give other
-// clients a chance to find a new token in localStorage before
-// reconnecting. Delay can be configured with Accounts.config.
var closeTokensForUser = function (userTokens) {
- var delaySecs = DEFAULT_CONNECTION_CLOSE_DELAY_SECS;
- if (_.has(Accounts._options, "_connectionCloseDelaySecs"))
- delaySecs = Accounts._options._connectionCloseDelaySecs;
- Meteor.setTimeout(function () {
- Meteor.server._closeAllForTokens(_.map(userTokens, function (token) {
- return token.token;
- }));
- }, delaySecs * 1000);
+ Meteor.server._closeAllForTokens(_.map(userTokens, function (token) {
+ return token.token;
+ }));
};
-Meteor.users.find().observe({
+Meteor.users.find({}, { fields: { "services.resume": 1 }}).observe({
changed: function (newUser, oldUser) {
var removedTokens = [];
if (newUser.services && newUser.services.resume &&
View
30 packages/accounts-base/accounts_tests.js
@@ -178,3 +178,33 @@ Tinytest.add('accounts - insertUserDoc email', function (test) {
Meteor.users.remove(result.id);
Meteor.users.remove(result3.id);
});
+
+// More token expiration tests are in accounts-password
+Tinytest.addAsync('accounts - expire numeric token', function (test, onComplete) {
+ var userIn = { username: Random.id() };
+ var result = Accounts.insertUserDoc({ profile: {
+ name: 'Foo Bar'
+ } }, userIn);
+ var date = new Date(new Date() - 5000);
+ Meteor.users.update(result.id, {
+ $set: {
+ "services.resume.loginTokens": [{
+ token: Random.id(),
+ when: date
+ }, {
+ token: Random.id(),
+ when: +date
+ }]
+ }
+ });
+ var observe = Meteor.users.find(result.id).observe({
+ changed: function (newUser) {
+ if (newUser.services && newUser.services.resume &&
+ _.isEmpty(newUser.services.resume.loginTokens)) {
+ observe.stop();
+ onComplete();
+ }
+ }
+ });
+ Accounts._expireTokens(new Date(), result.id);
+});
View
2  packages/accounts-password/package.js
@@ -1,5 +1,5 @@
Package.describe({
- summary: "Password support for accounts."
+ summary: "Password support for accounts"
});
Package.on_use(function(api) {
View
43 packages/accounts-password/password_server.js
@@ -316,20 +316,35 @@ Meteor.methods({resetPassword: function (token, newVerifier) {
var stampedLoginToken = Accounts._generateStampedLoginToken();
- // Update the user record by:
- // - Changing the password verifier to the new one
- // - Replacing all valid login tokens with new ones (changing
- // password should invalidate existing sessions).
- // - Forgetting about the reset token that was just used
- // - Verifying their email, since they got the password reset via email.
- Meteor.users.update({_id: user._id, 'emails.address': email}, {
- $set: {'services.password.srp': newVerifier,
- 'services.resume.loginTokens': [stampedLoginToken],
- 'emails.$.verified': true},
- $unset: {'services.password.reset': 1}
- });
+ // NOTE: We're about to invalidate tokens on the user, who we might be
+ // logged in as. Make sure to avoid logging ourselves out if this
+ // happens. But also make sure not to leave the connection in a state
+ // of having a bad token set if things fail.
+ var oldToken = this._getLoginToken();
+ this._setLoginToken(null);
+
+ try {
+ // Update the user record by:
+ // - Changing the password verifier to the new one
+ // - Replacing all valid login tokens with new ones (changing
+ // password should invalidate existing sessions).
+ // - Forgetting about the reset token that was just used
+ // - Verifying their email, since they got the password reset via email.
+ Meteor.users.update({_id: user._id, 'emails.address': email}, {
+ $set: {'services.password.srp': newVerifier,
+ 'services.resume.loginTokens': [stampedLoginToken],
+ 'emails.$.verified': true},
+ $unset: {'services.password.reset': 1}
+ });
+ } catch (err) {
+ // update failed somehow. reset to old token.
+ this._setLoginToken(oldToken);
+ throw err;
+ }
+ this._setLoginToken(stampedLoginToken.token);
this.setUserId(user._id);
+
return {
token: stampedLoginToken.token,
tokenExpires: Accounts._tokenExpiration(stampedLoginToken.when),
@@ -421,6 +436,7 @@ Meteor.methods({verifyEmail: function (token) {
$push: {'services.resume.loginTokens': stampedLoginToken}});
this.setUserId(user._id);
+ this._setLoginToken(stampedLoginToken.token);
return {
token: stampedLoginToken.token,
tokenExpires: Accounts._tokenExpiration(stampedLoginToken.when),
@@ -499,6 +515,7 @@ Meteor.methods({createUser: function (options) {
// client gets logged in as the new user afterwards.
this.setUserId(result.id);
+ this._setLoginToken(result.token);
return result;
}});
@@ -533,5 +550,5 @@ Accounts.createUser = function (options, callback) {
///
Meteor.users._ensureIndex('emails.validationTokens.token',
{unique: 1, sparse: 1});
-Meteor.users._ensureIndex('emails.password.reset.token',
+Meteor.users._ensureIndex('services.password.reset.token',
{unique: 1, sparse: 1});
View
446 packages/accounts-password/password_tests.js
@@ -1,6 +1,4 @@
-Accounts.config({
- _connectionCloseDelaySecs: 0
-});
+Accounts._noConnectionCloseDelayForTest = true;
if (Meteor.isClient) (function () {
@@ -21,40 +19,30 @@ if (Meteor.isClient) (function () {
test.equal(Meteor.user().username, someUsername);
});
};
+ var waitForLoggedOutStep = function (test, expect) {
+ pollUntil(expect, function () {
+ return Meteor.userId() === null;
+ }, 10 * 1000, 100);
+ };
- // declare variable outside the testAsyncMulti, so we can refer to
- // them from multiple tests, but initialize them to new values inside
- // the test so when we use the 'debug' link in the tests, they get new
- // values and the tests don't fail.
- var username, username2, username3;
- var userId1, userId3;
- var email;
- var password, password2, password3;
-
- testAsyncMulti("passwords - long series", [
- function (test, expect) {
- username = Random.id();
- username2 = Random.id();
- username3 = Random.id();
- // use -intercept so that we don't print to the console
- email = Random.id() + '-intercept@example.com';
- password = 'password';
- password2 = 'password2';
- password3 = 'password3';
- },
+ testAsyncMulti("passwords - basic login with password", [
function (test, expect) {
+ // setup
+ this.username = Random.id();
+ this.email = Random.id() + '-intercept@example.com';
+ this.password = 'password';
+
Accounts.createUser(
- {username: username, email: email, password: password},
- loggedInAs(username, test, expect));
+ {username: this.username, email: this.email, password: this.password},
+ loggedInAs(this.username, test, expect));
},
function (test, expect) {
- userId1 = Meteor.userId();
- test.notEqual(userId1, null);
+ test.notEqual(Meteor.userId(), null);
},
logoutStep,
function (test, expect) {
- Meteor.loginWithPassword(username, password,
- loggedInAs(username, test, expect));
+ Meteor.loginWithPassword(this.username, this.password,
+ loggedInAs(this.username, test, expect));
},
logoutStep,
// This next step tests reactive contexts which are reactive on
@@ -69,7 +57,7 @@ if (Meteor.isClient) (function () {
});
// At the beginning, we're not logged in.
test.isFalse(loaded);
- Meteor.loginWithPassword(username, password, expect(function (error) {
+ Meteor.loginWithPassword(this.username, this.password, expect(function (error) {
test.equal(error, undefined);
test.notEqual(Meteor.userId(), null);
// By the time of the login callback, the user should be loaded.
@@ -82,18 +70,43 @@ if (Meteor.isClient) (function () {
},
logoutStep,
function (test, expect) {
- Meteor.loginWithPassword({username: username}, password,
- loggedInAs(username, test, expect));
+ Meteor.loginWithPassword({username: this.username}, this.password,
+ loggedInAs(this.username, test, expect));
},
logoutStep,
function (test, expect) {
- Meteor.loginWithPassword(email, password,
- loggedInAs(username, test, expect));
+ Meteor.loginWithPassword(this.email, this.password,
+ loggedInAs(this.username, test, expect));
},
logoutStep,
function (test, expect) {
- Meteor.loginWithPassword({email: email}, password,
- loggedInAs(username, test, expect));
+ Meteor.loginWithPassword({email: this.email}, this.password,
+ loggedInAs(this.username, test, expect));
+ },
+ logoutStep
+ ]);
+
+
+ testAsyncMulti("passwords - plain text passwords", [
+ function (test, expect) {
+ // setup
+ this.username = Random.id();
+ this.email = Random.id() + '-intercept@example.com';
+ this.password = 'password';
+
+ // create user with raw password (no API, need to invoke callLoginMethod
+ // directly)
+ Accounts.callLoginMethod({
+ methodName: 'createUser',
+ methodArguments: [{username: this.username, password: this.password}],
+ userCallback: loggedInAs(this.username, test, expect)
+ });
+ },
+ logoutStep,
+ // check can login normally with this password.
+ function(test, expect) {
+ Meteor.loginWithPassword({username: this.username}, this.password,
+ loggedInAs(this.username, test, expect));
},
logoutStep,
// plain text password. no API for this, have to invoke callLoginMethod
@@ -101,7 +114,7 @@ if (Meteor.isClient) (function () {
function (test, expect) {
Accounts.callLoginMethod({
// wrong password
- methodArguments: [{user: {email: email}, password: password2}],
+ methodArguments: [{user: {username: this.username}, password: 'wrong'}],
userCallback: expect(function (error) {
test.isTrue(error);
test.isFalse(Meteor.user());
@@ -110,105 +123,179 @@ if (Meteor.isClient) (function () {
function (test, expect) {
Accounts.callLoginMethod({
// right password
- methodArguments: [{user: {email: email}, password: password}],
- userCallback: loggedInAs(username, test, expect)
+ methodArguments: [{user: {username: this.username},
+ password: this.password}],
+ userCallback: loggedInAs(this