diff --git a/History.md b/History.md index 587dbd676a..7201fe49d0 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,49 @@ ## vNEXT +* This release introduces Meteor Accounts, a full-featured auth system that supports + - fine-grained user-based control over database reads and writes + - federated login with Facebook, GitHub, Google, Twitter, and Weibo + - secure password login + - email validation and password recovery + - an optional set of UI widgets implementing standard login/signup/password + change/logout flows + + When you upgrade to Meteor 0.5.0, existing apps will lose the ability to write + to the database from the client. To restore this, either: + - configure each of your collections with + [`collection.allow`](http://docs.meteor.com/#allow) and + [`collection.deny`](http://docs.meteor.com/#deny) calls to specify which + users can perform which write operations, or + - add the `insecure` smart package (which is included in new apps by default) + to restore the old behavior where anyone can write to any collection which + has not been configured with `allow` or `deny` + + For more information on Meteor Accounts, see http://docs.meteor.com/#accounts + +* Arrays and objects can now be stored in the `Session`; mutating the value you + retrieve with `Session.get` does not affect the value in the session. + +* On the client, `Meteor.apply` takes a new `wait` option, which ensures that no + further method calls are sent to the server until this method is finished; it + is used for login and logout methods in order to keep the user ID + well-defined. You can also specifiy an `onReconnect` handler which is run when + re-establishing a connection; Meteor Accounts uses this to log back in on + reconnect. + +* Meteor now provides a compatible replacement for the DOM `localStorage` + facility that works in IE7, in the `localstorage-polyfill` smart package. + +* `Meteor.Collection` now takes its optional `manager` argument (used to + associate a collection with a server you've connected to with + `Meteor.connect`) as a named option. (The old call syntax continues to work + for now.) + +* Fix a bug where trying to immediately resubscribe to a record set after + unsubscribing could fail silently. + +* Better error handling for failed Mongo writes from inside methods; previously, + errors here could cause clients to stop processing data from the server. ## v0.4.2 diff --git a/examples/unfinished/accounts-ui-viewer/.meteor/.gitignore b/examples/unfinished/accounts-ui-viewer/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/examples/unfinished/accounts-ui-viewer/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/examples/unfinished/accounts-ui-viewer/.meteor/packages b/examples/unfinished/accounts-ui-viewer/.meteor/packages new file mode 100644 index 0000000000..634f653e55 --- /dev/null +++ b/examples/unfinished/accounts-ui-viewer/.meteor/packages @@ -0,0 +1,15 @@ +# Meteor packages used by this project, one per line. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +autopublish +insecure +preserve-inputs +accounts-ui +less +accounts-google +accounts-github +accounts-password +underscore +accounts-facebook diff --git a/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.html b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.html new file mode 100644 index 0000000000..9189a5972e --- /dev/null +++ b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.html @@ -0,0 +1,112 @@ + + accounts-ui-viewer + + + + {{> page}} + + + + + + + diff --git a/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.js b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.js new file mode 100644 index 0000000000..98ec13ea2c --- /dev/null +++ b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.js @@ -0,0 +1,215 @@ +Meteor.users.allow({update: function () { return true; }}); + +if (Meteor.isClient) { + + Accounts.STASH = _.extend({}, Accounts); + + var handleSetting = function (key, value) { + if (key === "numServices") { + _.each(['facebook', 'github', 'google'], + function (serv, i) { + if (i < value) + Accounts[serv] = Accounts.STASH[serv]; + else + Accounts[serv] = null; + }); + } else if (key === "hasPasswords") { + Accounts.password = value && Accounts.STASH.password || null; + var user = Meteor.user(); + if (user) { + if (! value) { + // make sure we have no username if "app" has no passwords + Meteor.users.update(Meteor.userId(), + { $unset: { username: 1 }}); + } else { + // make sure we have a username + Meteor.users.update(Meteor.userId(), + { $set: { username: Meteor.uuid() }}); + } + } + } else if (key === "signupFields") { + Accounts.ui._options.passwordSignupFields = value; + } + }; + + if (! Session.get('settings')) + Session.set('settings', { + openLeft: false, + positioning: "relative", + numServices: 3, + hasPasswords: true, + signupFields: 'EMAIL_ONLY' + }); + else + _.each(Session.get('settings'), function (v,k) { + handleSetting(k, v); + }); + + Template.page.settings = function () { + return Session.get('settings'); + }; + + Template.page.settingsClass = function () { + var settings = Session.get('settings'); + var classes = []; + if (settings.positioning) + classes.push('positioning-' + settings.positioning.toLowerCase()); + return classes.join(' '); + }; + + Template.page.outerClass = function () { + var settings = Session.get('settings'); + var classes = []; + if (settings.openLeft) + classes.push('login-buttons-dropdown-hangs-left'); + return classes.join(' '); + }; + + var keyValueFromId = function (id) { + var match; + if (id && (match = /^(.*?):(.*)$/.exec(id))) { + var key = match[1]; + var value = castValue(match[2]); + return [key, value]; + } + return null; + }; + + var castValue = function (value) { + if (value === "false") + value = false; + else if (value === "true") + value = true; + else if (/^[0-9]+$/.test(value)) + value = Number(value); + return value; + }; + + Template.radio.maybeChecked = function () { + var curValue = Session.get('settings')[this.key]; + if (castValue(this.value) === curValue) + return 'checked="checked"'; + return ''; + }; + + Template.page.radio = function (key, value, label) { + return new Handlebars.SafeString( + Template.radio({key: key, value: value, label: label})); + }; + + Template.page.button = function (key, value, label) { + return new Handlebars.SafeString( + Template.button({key: key, value: value, label: label})); + }; + + Template.page.match = function (kv) { + kv = keyValueFromId(kv); + if (! kv) + return false; + + return Session.get('settings')[kv[0]] === kv[1]; + }; + + var fakeLogin = function () { + Accounts.createUser( + {username: Meteor.uuid(), + password: "password", + profile: { name: "Joe Schmoe" }}, + function () { + var user = Meteor.user(); + if (! user) + return; + // delete our username if we are in a mode + // where there aren't usernames/emails/passwords + // (only third-party auth) so that there is no + // "Change Password" button when signed in + if (! Session.get('settings').hasPasswords) + Meteor.users.update(Meteor.userId(), + { $unset: { username: 1 }}); + }); + }; + + var exitFlows = function () { + Accounts._loginButtonsSession.set('inSignupFlow', false); + Accounts._loginButtonsSession.set('inForgotPasswordFlow', false); + Accounts._loginButtonsSession.set('inChangePasswordFlow', false); + Accounts._loginButtonsSession.set('inMessageOnlyFlow', false); + }; + + Template.page.events({ + 'change #controlpane input[type=radio]': function (event) { + var input = event.currentTarget; + var keyValue; + if (input && input.id && (keyValue = keyValueFromId(input.id))) { + var key = keyValue[0]; + var value = keyValue[1]; + if (value === "false") + value = false; + else if (value === "true") + value = true; + var settings = Session.get('settings'); + settings[key] = value; + Session.set('settings', settings); + + handleSetting(key, value); + } + }, + 'click #controlpane button': function (event) { + if (this.key === "fakeConfig") { + var service = this.value; + if (! Accounts.loginServiceConfiguration.findOne({service: service})) + Accounts.loginServiceConfiguration.insert( + {service: service, fake: true}); + } else if (this.key === "unconfig") { + var service = this.value; + Accounts.loginServiceConfiguration.remove({service: service}); + } else if (this.key === "messages") { + if (this.value === "error") { + Accounts._loginButtonsSession.set('errorMessage', 'An error occurred! Gee golly gosh.'); + } else if (this.value === "info") { + Accounts._loginButtonsSession.set('infoMessage', 'Here is some information that is crucial.'); + } else if (this.value === "clear") { + Accounts._loginButtonsSession.resetMessages(); + } + } else if (this.key === "sign") { + if (this.value === 'in') { + // create a random new user + Accounts._loginButtonsSession.closeDropdown(); + fakeLogin(); + } else if (this.value === 'out') { + Meteor.logout(); + } + } else if (this.key === "showConfig") { + Accounts._loginButtonsSession.configureService(this.value); + } else if (this.key === "lov") { + exitFlows(); + Accounts._loginButtonsSession.set("dropdownVisible", true); + if (Meteor.userId()) + Meteor.logout(); + if (this.value === "createAccount") + Accounts._loginButtonsSession.set("inSignupFlow", true); + else if (this.value === "forgotPassword") + Accounts._loginButtonsSession.set("inForgotPasswordFlow", true); + } else if (this.key === "liv") { + exitFlows(); + Accounts._loginButtonsSession.set("dropdownVisible", true); + if (! Meteor.userId()) + fakeLogin(); + if (this.value === "changePassword") + Accounts._loginButtonsSession.set("inChangePasswordFlow", true); + else if (this.value === "messageOnly") + Accounts._loginButtonsSession.set("inMessageOnlyFlow", true); + } else if (this.key === "modals") { + var value = this.value; + _.each([ + 'resetPasswordToken', + 'enrollAccountToken', + 'justVerifiedEmail'], function (k) { + Accounts._loginButtonsSession.set( + k, k.indexOf(value) >= 0 ? 'foo' : null); + }); + } + } + }); + +} \ No newline at end of file diff --git a/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.less b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.less new file mode 100644 index 0000000000..1e5a017cc7 --- /dev/null +++ b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.less @@ -0,0 +1,110 @@ + +* { padding: 0; margin: 0; } +html, body { height: 100%; } + +#controlpane { + position: absolute; + left: 0; + width: 299px; + top: 0; + bottom: 0; + + background: #eee; + border-right: 1px solid #999; + + overflow: auto; + + h3 { + border-top: 1px solid #999; + font-size: 85%; + margin-bottom: 5px; + } + + .group { + margin: 10px; + } + + input[type=radio] { + margin-left: 5px; + vertical-align: middle; + } + + label { + padding-left: 3px; + } +} + +#previewpane { + position: absolute; + left: 300px; + right: 0; + top: 0; + bottom: 0; + + #preview-wrapper { + margin: 20px; + } +} + +.radio { + white-space: nowrap; +} + +.positioning-floatright { + #login-buttons { + float: right; + margin-right: 180px; + } + + #pos-indicator { + display: block; + top: 0; + right: 0; + width: 200px; + height: 20px; + } +} + +.positioning-relative { + #login-buttons { + position: relative; + left: 120px; + top: 20px; + } + + #pos-indicator { + display: block; + top: 0; + left: 0; + width: 140px; + height: 40px; + } +} + +.positioning-absolute { + #login-buttons { + position: absolute; + left: 140px; + top: 40px; + } + + #pos-indicator { + display: block; + top: 0; + left: 0; + width: 140px; + height: 40px; + } +} + +#pos-indicator { + position: absolute; + background: #eec; + display: none; +} + +a { color: blue; } + +button { padding: 4px; + margin-bottom: 4px; // for when buttons wrap + } \ No newline at end of file diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index 0a50eba3d8..796efc1b2f 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -17,9 +17,13 @@ var userId = Meteor.userId(); if (!userId) return null; - if (Meteor.userLoaded()) - return Meteor.users.findOne(userId); - // Not yet loaded: return a minimal object. + if (Meteor.userLoaded()) { + var user = Meteor.users.findOne(userId); + if (user) return user; + } + // Either the subscription isn't done yet, or for some reason this user has + // no published fields (and thus is considered to not exist in + // minimongo). Return a minimal object. return {_id: userId}; }; diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 9a4b112466..10d77fa56f 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -115,21 +115,13 @@ }; // XXX see comment on Accounts.createUser in passwords_server about adding a - // third "server options" argument. - var defaultCreateUserHook = function (options, extra, user) { - // This hook gets 'extra' directly from the createUser method, so make sure - // we don't allow users to set any fields at creation time that they won't - // later be able to set according to the default Meteor.users.allow. Set - // your own onCreateUser if you want users to be able to specify other - // fields at creation time. - if (_.any(extra, function(value, key) {return key != 'profile';})) { - console.log(JSON.stringify(extra)); - throw new Meteor.Error(400, "Disallowed fields in extra"); - } - - return _.extend(user, extra); + // second "server options" argument. + var defaultCreateUserHook = function (options, user) { + if (options.profile) + user.profile = options.profile; + return user; }; - Accounts.insertUserDoc = function (options, extra, user) { + Accounts.insertUserDoc = function (options, user) { // add created at timestamp (and protect passed in user object from // modification) user = _.extend({createdAt: +(new Date)}, user); @@ -137,15 +129,15 @@ var fullUser; if (onCreateUserHook) { - fullUser = onCreateUserHook(options, extra, user); + fullUser = onCreateUserHook(options, user); // This is *not* part of the API. We need this because we can't isolate // the global server environment between tests, meaning we can't test // both having a create user hook set and not having one set. if (fullUser === 'TEST DEFAULT HOOK') - fullUser = defaultCreateUserHook(options, extra, user); + fullUser = defaultCreateUserHook(options, user); } else { - fullUser = defaultCreateUserHook(options, extra, user); + fullUser = defaultCreateUserHook(options, user); } _.each(validateNewUserHooks, function (hook) { @@ -199,13 +191,13 @@ // @param serviceData {Object} Data to store in the user's record // under services[serviceName]. Must include an "id" field // which is a unique identifier for the user in the service. - // @param extra {Object, optional} Any additional fields to place on the user - // object + // @param options {Object, optional} Other options to pass to insertUserDoc + // (eg, profile) // @returns {Object} Object with token and id keys, like the result // of the "login" method. Accounts.updateOrCreateUserFromExternalService = function( - serviceName, serviceData, extra) { - extra = extra || {}; + serviceName, serviceData, options) { + options = _.clone(options || {}); if (serviceName === "password" || serviceName === "resume") throw new Error( @@ -221,26 +213,28 @@ var user = Meteor.users.findOne(selector); if (user) { - // don't overwrite existing fields - // XXX subobjects (aka 'profile', 'services')? - var newKeys = _.difference(_.keys(extra), _.keys(user)); - var newAttrs = _.pick(extra, newKeys); + // We *don't* process options (eg, profile) for update, but we do replace + // the serviceData (eg, so that we keep an unexpired access token and + // don't cache old email addresses in serviceData.email). + // XXX provide an onUpdateUser hook which would let apps update + // the profile too var stampedToken = Accounts._generateStampedLoginToken(); - var result = {token: stampedToken.token}; + var setAttrs = {}; + setAttrs["services." + serviceName] = serviceData; + // XXX Maybe we should re-use the selector above and notice if the update + // touches nothing? Meteor.users.update( user._id, - {$set: newAttrs, $push: {'services.resume.loginTokens': stampedToken}}); - result.id = user._id; - return result; + {$set: setAttrs, + $push: {'services.resume.loginTokens': stampedToken}}); + return {token: stampedToken.token, id: user._id}; } else { - // Create a new user. - var servicesClause = {}; - servicesClause[serviceName] = serviceData; - var insertOptions = {services: servicesClause, generateLoginToken: true}; - // Build a user doc; clone to make sure sure mutating - // insertOptions.services doesn't affect user.services or vice versa. - user = {services: JSON.parse(JSON.stringify(servicesClause))}; - return Accounts.insertUserDoc(insertOptions, extra, user); + // Create a new user with the service data. Pass other options through to + // insertUserDoc. + user = {services: {}}; + user.services[serviceName] = serviceData; + options.generateLoginToken = true; + return Accounts.insertUserDoc(options, user); } }; diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index d350d811d6..b771e17de1 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -6,20 +6,24 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService', function (test) // create an account with facebook var uid1 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', {id: facebookId}, {profile: {foo: 1}}).id; - test.equal(Meteor.users.find({"services.facebook.id": facebookId}).count(), 1); - test.equal(Meteor.users.findOne({"services.facebook.id": facebookId}).profile.foo, 1); + 'facebook', {id: facebookId, monkey: 42}, {profile: {foo: 1}}).id; + var users = Meteor.users.find({"services.facebook.id": facebookId}).fetch(); + test.length(users, 1); + test.equal(users[0].profile.foo, 1); + test.equal(users[0].services.facebook.monkey, 42); - // create again with the same id, see that we get the same user. profile - // doesn't get overwritten in this implementation (though we should do - // something better with merging later). + // create again with the same id, see that we get the same user. + // it should update services.facebook but not profile. var uid2 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', {id: facebookId}, {profile: {foo: 1000, bar: 2}}).id; + 'facebook', {id: facebookId, llama: 50}, + {profile: {foo: 1000, bar: 2}}).id; test.equal(uid1, uid2); - test.equal(Meteor.users.find({"services.facebook.id": facebookId}).count(), 1); - test.equal(Meteor.users.findOne(uid1).profile.foo, 1); - test.equal(Meteor.users.findOne(uid1).profile.bar, undefined); - + users = Meteor.users.find({"services.facebook.id": facebookId}).fetch(); + test.length(users, 1); + test.equal(users[0].profile.foo, 1); + test.equal(users[0].profile.bar, undefined); + test.equal(users[0].services.facebook.llama, 50); + test.equal(users[0].services.facebook.monkey, undefined); // cleanup Meteor.users.remove(uid1); @@ -48,7 +52,6 @@ Tinytest.add('accounts - insertUserDoc username', function (test) { // user does not already exist. create a user object with fields set. var result = Accounts.insertUserDoc( - userIn, {profile: {name: 'Foo Bar'}}, userIn ); @@ -61,7 +64,6 @@ Tinytest.add('accounts - insertUserDoc username', function (test) { // run the hook again. now the user exists, so it throws an error. test.throws(function () { Accounts.insertUserDoc( - userIn, {profile: {name: 'Foo Bar'}}, userIn ); @@ -83,7 +85,6 @@ Tinytest.add('accounts - insertUserDoc email', function (test) { // user does not already exist. create a user object with fields set. var result = Accounts.insertUserDoc( - userIn, {profile: {name: 'Foo Bar'}}, userIn ); @@ -97,7 +98,6 @@ Tinytest.add('accounts - insertUserDoc email', function (test) { // run the hook again. now the user exists, so it throws an error. test.throws(function () { Accounts.insertUserDoc( - userIn, {profile: {name: 'Foo Bar'}}, userIn ); @@ -106,20 +106,20 @@ Tinytest.add('accounts - insertUserDoc email', function (test) { // now with only one of them. test.throws(function () { Accounts.insertUserDoc( - {}, {}, {emails: [{address: email1}]} + {}, {emails: [{address: email1}]} ); }); test.throws(function () { Accounts.insertUserDoc( - {}, {}, {emails: [{address: email2}]} + {}, {emails: [{address: email2}]} ); }); // a third email works. var result3 = Accounts.insertUserDoc( - {}, {}, {emails: [{address: email3}]} + {}, {emails: [{address: email3}]} ); var user3 = Meteor.users.findOne(result3.id); test.equal(typeof user3.createdAt, 'number'); diff --git a/packages/accounts-facebook/facebook_server.js b/packages/accounts-facebook/facebook_server.js index 2184bb1f36..2a003f9003 100644 --- a/packages/accounts-facebook/facebook_server.js +++ b/packages/accounts-facebook/facebook_server.js @@ -11,7 +11,7 @@ accessToken: accessToken, email: identity.email }, - extra: {profile: {name: identity.name}} + options: {profile: {name: identity.name}} }; }); diff --git a/packages/accounts-github/github_server.js b/packages/accounts-github/github_server.js index ae695e3230..f9dc9147fb 100644 --- a/packages/accounts-github/github_server.js +++ b/packages/accounts-github/github_server.js @@ -11,7 +11,7 @@ email: identity.email, username: identity.login }, - extra: {profile: {name: identity.name}} + options: {profile: {name: identity.name}} }; }); diff --git a/packages/accounts-google/google_server.js b/packages/accounts-google/google_server.js index d0e8726c71..167e5f1b75 100644 --- a/packages/accounts-google/google_server.js +++ b/packages/accounts-google/google_server.js @@ -11,7 +11,7 @@ accessToken: accessToken, email: identity.email }, - extra: {profile: {name: identity.name}} + options: {profile: {name: identity.name}} }; }); diff --git a/packages/accounts-oauth-helper/oauth_server.js b/packages/accounts-oauth-helper/oauth_server.js index 5e49755a54..c94e540a42 100644 --- a/packages/accounts-oauth-helper/oauth_server.js +++ b/packages/accounts-oauth-helper/oauth_server.js @@ -14,7 +14,7 @@ // - (For OAuth1 only) oauthBinding {OAuth1Binding} bound to the appropriate provider // - (For OAuth2 only) query {Object} parameters passed in query string // - return value is: - // - {serviceData, (optional extra)} where serviceData should end + // - {serviceData:, (optional options:)} where serviceData should end // up in the user's services[name] field // - `null` if the user declined to give permissions Accounts.oauth.registerService = function (name, version, handleOauthRequest) { diff --git a/packages/accounts-oauth1-helper/oauth1_server.js b/packages/accounts-oauth1-helper/oauth1_server.js index 9733417d73..cd2e934871 100644 --- a/packages/accounts-oauth1-helper/oauth1_server.js +++ b/packages/accounts-oauth1-helper/oauth1_server.js @@ -55,7 +55,7 @@ // Get or create user doc and login token for reconnect. Accounts.oauth._loginResultForState[query.state] = Accounts.updateOrCreateUserFromExternalService( - service.serviceName, oauthResult.serviceData, oauthResult.extra); + service.serviceName, oauthResult.serviceData, oauthResult.options); } } diff --git a/packages/accounts-oauth1-helper/oauth1_tests.js b/packages/accounts-oauth1-helper/oauth1_tests.js index b6610e892a..78c0318088 100644 --- a/packages/accounts-oauth1-helper/oauth1_tests.js +++ b/packages/accounts-oauth1-helper/oauth1_tests.js @@ -92,7 +92,7 @@ Tinytest.add("oauth1 - error in user creation", function (test) { accessToken: twitterfailAccessToken, accessTokenSecret: twitterfailAccessTokenSecret }, - extra: { + options: { profile: {invalid: true} } }; diff --git a/packages/accounts-oauth2-helper/oauth2_server.js b/packages/accounts-oauth2-helper/oauth2_server.js index f0104418ed..696a59afe5 100644 --- a/packages/accounts-oauth2-helper/oauth2_server.js +++ b/packages/accounts-oauth2-helper/oauth2_server.js @@ -14,7 +14,7 @@ // Get or create user doc and login token for reconnect. Accounts.oauth._loginResultForState[query.state] = Accounts.updateOrCreateUserFromExternalService( - service.serviceName, oauthResult.serviceData, oauthResult.extra); + service.serviceName, oauthResult.serviceData, oauthResult.options); } // Either close the window, redirect, or render nothing diff --git a/packages/accounts-oauth2-helper/oauth2_tests.js b/packages/accounts-oauth2-helper/oauth2_tests.js index b10b183c9f..2331f691fb 100644 --- a/packages/accounts-oauth2-helper/oauth2_tests.js +++ b/packages/accounts-oauth2-helper/oauth2_tests.js @@ -55,7 +55,7 @@ Tinytest.add("oauth2 - error in user creation", function (test) { serviceData: { id: failbookId }, - extra: { + options: { profile: {invalid: true} } }; diff --git a/packages/accounts-password/email_tests.js b/packages/accounts-password/email_tests.js index 843e98c261..fc7da6b80e 100644 --- a/packages/accounts-password/email_tests.js +++ b/packages/accounts-password/email_tests.js @@ -17,9 +17,9 @@ function (test, expect) { email1 = Meteor.uuid() + "-intercept@example.com"; Accounts.createUser({email: email1, password: 'foobar'}, - expect(function (error) { - test.equal(error, undefined); - })); + expect(function (error) { + test.equal(error, undefined); + })); }, function (test, expect) { Accounts.forgotPassword({email: email1}, expect(function (error) { diff --git a/packages/accounts-password/passwords_client.js b/packages/accounts-password/passwords_client.js index 00e21e0498..2b877c36ea 100644 --- a/packages/accounts-password/passwords_client.js +++ b/packages/accounts-password/passwords_client.js @@ -1,12 +1,7 @@ (function () { - Accounts.createUser = function (options, extra, callback) { + Accounts.createUser = function (options, callback) { options = _.clone(options); // we'll be modifying options - if (typeof extra === "function") { - callback = extra; - extra = {}; - } - if (!options.password) throw new Error("Must set options.password"); var verifier = Meteor._srp.generateVerifier(options.password); @@ -14,7 +9,7 @@ delete options.password; options.srp = verifier; - Meteor.apply('createUser', [options, extra], {wait: true}, + Meteor.apply('createUser', [options], {wait: true}, function (error, result) { if (error || !result) { error = error || new Error("No result"); diff --git a/packages/accounts-password/passwords_server.js b/packages/accounts-password/passwords_server.js index 5ce4b5bf06..7fb474a773 100644 --- a/packages/accounts-password/passwords_server.js +++ b/packages/accounts-password/passwords_server.js @@ -355,8 +355,7 @@ // // returns an object with id: userId, and (if options.generateLoginToken is // set) token: loginToken. - var createUser = function (options, extra) { - extra = extra || {}; + var createUser = function (options) { var username = options.username; var email = options.email; if (!username && !email) @@ -379,19 +378,19 @@ if (email) user.emails = [{address: email, verified: false}]; - return Accounts.insertUserDoc(options, extra, user); + return Accounts.insertUserDoc(options, user); }; // method for create user. Requests come from the client. Meteor.methods({ - createUser: function (options, extra) { + createUser: function (options) { options = _.clone(options); options.generateLoginToken = true; if (Accounts._options.forbidClientAccountCreation) throw new Meteor.Error(403, "Signups forbidden"); // Create user. result contains id and token. - var result = createUser(options, extra); + var result = createUser(options); // safety belt. createUser is supposed to throw on error. send 500 error // instead of sending a verification email with empty userid. if (!result.id) @@ -420,20 +419,16 @@ // which is always empty when called from the createUser method? eg, "admin: // true", which we want to prevent the client from setting, but which a custom // method calling Accounts.createUser could set? - Accounts.createUser = function (options, extra, callback) { + Accounts.createUser = function (options, callback) { options = _.clone(options); options.generateLoginToken = false; - if (typeof extra === "function") { - callback = extra; - extra = {}; - } // XXX allow an optional callback? if (callback) { throw new Error("Meteor.createUser with callback not supported on the server yet."); } - var userId = createUser(options, extra).id; + var userId = createUser(options).id; return userId; }; diff --git a/packages/accounts-password/passwords_tests.js b/packages/accounts-password/passwords_tests.js index 79058da652..99177c2474 100644 --- a/packages/accounts-password/passwords_tests.js +++ b/packages/accounts-password/passwords_tests.js @@ -199,9 +199,9 @@ if (Meteor.isClient) (function () { logoutStep, // test Accounts.validateNewUser function(test, expect) { - Accounts.createUser({username: username3, password: password3}, - // should fail the new user validators - {profile: {invalid: true}}, + Accounts.createUser({username: username3, password: password3, + // should fail the new user validators + profile: {invalid: true}}, expect(function (error) { test.equal(error.error, 403); test.equal( @@ -211,10 +211,10 @@ if (Meteor.isClient) (function () { }, logoutStep, function(test, expect) { - Accounts.createUser({username: username3, password: password3}, + Accounts.createUser({username: username3, password: password3, // should fail the new user validator with a special // exception - {profile: {invalidAndThrowException: true}}, + profile: {invalidAndThrowException: true}}, expect(function (error) { test.equal( error.reason, @@ -224,14 +224,13 @@ if (Meteor.isClient) (function () { // test Accounts.onCreateUser function(test, expect) { Accounts.createUser( - {username: username3, password: password3}, - {testOnCreateUserHook: true}, + {username: username3, password: password3, + testOnCreateUserHook: true}, loggedInAs(username3, test, expect)); }, function(test, expect) { test.equal(Meteor.user().profile.touchedByOnCreateUser, true); }, - // test Meteor.user(). This test properly belongs in // accounts-base/accounts_tests.js, but this is where the tests that // actually log in are. @@ -243,6 +242,14 @@ if (Meteor.isClient) (function () { test.equal(err, undefined); })); }, + function(test, expect) { + Meteor.call('clearUsernameAndProfile'); + Meteor.default_connection.onQuiesce(expect(function() { + test.isTrue(Meteor.userId()); + var user = Meteor.user(); + test.equal(user, {_id: Meteor.userId()}); + })); + }, logoutStep, function(test, expect) { var clientUser = Meteor.user(); @@ -275,14 +282,14 @@ if (Meteor.isServer) (function () { var email = Meteor.uuid() + '@example.com'; test.throws(function () { // should fail the new user validators - Accounts.createUser({email: email}, {profile: {invalid: true}}); + Accounts.createUser({email: email, profile: {invalid: true}}); }); // disable sending emails var oldEmailSend = Email.send; Email.send = function() {}; - var userId = Accounts.createUser({email: email}, - {testOnCreateUserHook: true}); + var userId = Accounts.createUser({email: email, + testOnCreateUserHook: true}); Email.send = oldEmailSend; test.isTrue(userId); @@ -296,7 +303,7 @@ if (Meteor.isServer) (function () { function (test) { var username = Meteor.uuid(); - var userId = Accounts.createUser({username: username}, {}); + var userId = Accounts.createUser({username: username}); var user = Meteor.users.findOne(userId); // no services yet. diff --git a/packages/accounts-password/passwords_tests_setup.js b/packages/accounts-password/passwords_tests_setup.js index bd5aa463e6..e1142de742 100644 --- a/packages/accounts-password/passwords_tests_setup.js +++ b/packages/accounts-password/passwords_tests_setup.js @@ -4,9 +4,9 @@ Accounts.validateNewUser(function (user) { return !(user.profile && user.profile.invalid); }); -Accounts.onCreateUser(function (options, extra, user) { - if (extra.testOnCreateUserHook) { - user.profile = (user.profile || {}); +Accounts.onCreateUser(function (options, user) { + if (options.testOnCreateUserHook) { + user.profile = user.profile || {}; user.profile.touchedByOnCreateUser = true; return user; } else { @@ -35,5 +35,11 @@ Accounts.config({ // This test properly belongs in accounts-base/accounts_tests.js, but // this is where the tests that actually log in are. Meteor.methods({ - testMeteorUser: function () { return Meteor.user(); } + testMeteorUser: function () { return Meteor.user(); }, + clearUsernameAndProfile: function () { + if (!this.userId) + throw new Error("Not logged in!"); + Meteor.users.update(this.userId, + {$unset: {profile: 1, username: 1}}); + } }); diff --git a/packages/accounts-twitter/twitter_server.js b/packages/accounts-twitter/twitter_server.js index 5414dae9d9..4008224b3d 100644 --- a/packages/accounts-twitter/twitter_server.js +++ b/packages/accounts-twitter/twitter_server.js @@ -10,7 +10,7 @@ accessToken: oauthBinding.accessToken, accessTokenSecret: oauthBinding.accessTokenSecret }, - extra: { + options: { profile: { name: identity.name } diff --git a/packages/accounts-weibo/weibo_server.js b/packages/accounts-weibo/weibo_server.js index fe5e4bb4e6..a061d5054d 100644 --- a/packages/accounts-weibo/weibo_server.js +++ b/packages/accounts-weibo/weibo_server.js @@ -11,7 +11,7 @@ accessToken: accessToken.access_token, screenName: identity.screen_name }, - extra: {profile: {name: identity.screen_name}} + options: {profile: {name: identity.screen_name}} }; }); diff --git a/packages/amplify/package.js b/packages/amplify/package.js index b70c41913a..c808eda438 100644 --- a/packages/amplify/package.js +++ b/packages/amplify/package.js @@ -1,5 +1,5 @@ Package.describe({ - summary: "Cross browser API for Persistant Storage, PubSub and Request." + summary: "API for Persistant Storage, PubSub and Request" }); Package.on_use(function (api) { diff --git a/packages/autopublish/package.js b/packages/autopublish/package.js index 7833caae42..ae8c12f22a 100644 --- a/packages/autopublish/package.js +++ b/packages/autopublish/package.js @@ -1,5 +1,5 @@ Package.describe({ - summary: "Automatically publish all data in the database to every client" + summary: "Automatically publish the entire database to all clients" }); Package.on_use(function (api, where) { diff --git a/packages/force-ssl/package.js b/packages/force-ssl/package.js index bc413ad820..e397ec0eaa 100644 --- a/packages/force-ssl/package.js +++ b/packages/force-ssl/package.js @@ -1,5 +1,5 @@ Package.describe({ - summary: "Require this application always use transport layer encryption" + summary: "Require this application to use secure transport (HTTPS)" }); Package.on_use(function (api) { diff --git a/packages/preserve-inputs/package.js b/packages/preserve-inputs/package.js index 3a9d4f0a9d..f8eca26f9b 100644 --- a/packages/preserve-inputs/package.js +++ b/packages/preserve-inputs/package.js @@ -1,5 +1,5 @@ Package.describe({ - summary: "Automatically preserve all form fields that have a unique id" + summary: "Automatically preserve all form fields with a unique id" }); Package.on_use(function (api, where) { diff --git a/packages/underscore/package.js b/packages/underscore/package.js index 71b0a526d7..00b473b891 100644 --- a/packages/underscore/package.js +++ b/packages/underscore/package.js @@ -1,5 +1,5 @@ Package.describe({ - summary: "Collection of small helper functions (map, each, bind, ...)" + summary: "Collection of small helper functions: _.map, _.each, ..." }); Package.on_use(function (api, where) {