Breaking changes to auth branch

710 views
Skip to first unread message

Nick Martin

unread,
Sep 21, 2012, 6:53:01 p.m.2012-09-21
to meteo...@googlegroups.com
Hi everybody!

We're in the final stages of Auth, cleaning up the API and getting set to make it ready for prime time. In consideration of everyone using the branch, we've been batching up breaking API changes so that you don't have to be continually re-working your app. Today we've pushed a new batch of breaking changes to the auth branch:

* New login service config API
  - config in mongo instead of code.
  - first-run wizard to configure oauth services in accounts-ui.
* User document restructure
  - autopublish only 'profile' and 'username' fields.
  - moved 'name' into 'profile'
  - change 'emails.email' to 'emails.address'
* Remove email based merging
  - oauth users with the same email get separate accounts
  - email stored in 'services' object, not top level emails field
* Add callbacks to oauth login functions. Display errors in account-ui

===

First and formost, we've re-jiggered the API for configuring oauth providers. Instead of hard-coding secrets into javascript, we now use a mongo collection to store the configuration. This means you can have different configuration for different deployed versions of you app, and is generally easier to manage. Additionally, we've made accounts-ui provide a first-run wizard to help you set up oauth login services. This should drastically improve the first-run experience for people using oauth.

-> You will need to remove all calls to Meteor.accounts.*.setSecret, and either remove the first two arguments to Meteor.accounts.*.config or remove it entirely. Meteor.accounts.*.config is now optional and only needed if you wish to configure the 'scope' oauth parameter.

-> If you are not using accounts-ui, you'll need to populate the 'Meteor.accounts.configuration' collection. The easiest way may be to add accounts-ui and go through the wizard, though you can also update the db from the command line 'meteor mongo' shell, or in a Meteor.startup block.

We've changed the structure of the user record in mongo. Previously, we automatically published the whole user object, minus 'services' and 'private'. Now we've flipped things around and new top level fields on user are not published by default. There is a new 'profile' sub-object, which is both automatically published and editable by the user.

-> If you have any additional fields on your user records that you want visible to the client, you must either explicitly publish them (if you do not want them directly editable by the user) or move them to the 'profile' sub-object (if you do want them editable).

We've also changed the structure of the 'emails' field to be more clear: 'emails.email' is now 'emails.address'.

OAuth accounts no longer get merged based on email addresses. That is, if you login with both facebook and google you will get two separate accounts, even if you use the same email address for both services. This also changes what happens if you login with email and password, then login with facebook. This change eliminates a lot of complexity and potential security issues around ownership of email addresses. At some point, we'll revisit this and have some way to merge accounts that belong to the same user, but for now this change brings Meteor more in line with other sites and what users expect. If you want to send email to a user who has logged in via facebook or google, you'll find their email address in 'services.facebook.email', not the top level 'emails' field.

Finally, we've added callbacks to the loginWithFacebook/Twitter/etc functions to bring them in line with the other loginWith functions. If you use these functions directly, you should add a callback to check for errors. If error is an instance of Meteor.accounts.ConfigError, you're missing the appropriate configuration document in mongo. If error is an instance of Meteor.accounts.LoginCancelledError, the user closed the login pop-up or didn't agree to give permissions to your app. Otherwise, it could be either an expected server error (e.g., if you used Meteor.accounts.validateNewUser and the proposed user document doesn't validate), or an unexpected server error.


That's it for this batch, but we've got a bunch more API changes coming down the pipe. As always, feedback is welcome and encouraged!

-- Avi and Nick

Tom Coleman

unread,
Sep 24, 2012, 9:13:33 a.m.2012-09-24
to meteo...@googlegroups.com
Good stuff! One little gotcha that burnt me: 

I was accidentally not adding the `extra` fields to the `user` in `Meteor.accounts.onCreateUser` (dumb, I know). But as a result, my facebook users were not getting _any_ published fields set, and thus were not appearing on the client.

Could be worth either adding a note to the docs, or putting in a work around in case users exist that have no `profile`, `username` or `emails` set.

Cheers,
Tom
--
You received this message because you are subscribed to the Google Groups "meteor-core" group.
To post to this group, send email to meteo...@googlegroups.com.
To unsubscribe from this group, send email to meteor-core...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/meteor-core?hl=en.

Lloyd B

unread,
Sep 24, 2012, 10:29:01 a.m.2012-09-24
to meteo...@googlegroups.com
just finished migrating.. two things: 
  1. I can't be bothered to copy/paste FB AppId and Secrets into the Accounts-ui configuration wizard after every `meteor reset` during development, so i still end up hardcoding config/secrets server-side for insertion on first launch.
  2. When navigating to my app on a local network address (e.g. http://192.168.1.64:3000), the oAuth popup (loginWithFacebook) assumes my `redirect_uri` should be "localhost". This is normally fine but it has broken my ability to test from the Android Emulator where "localhost" resolves to the emulator IP. In the emulator, I must navigate to my app using the host machine IP.

David Glasser

unread,
Sep 24, 2012, 11:24:21 a.m.2012-09-24
to meteo...@googlegroups.com

Yeah, we've talked about a flag to meteor reset that preserves internal tables.

Nick Martin

unread,
Sep 24, 2012, 4:23:45 p.m.2012-09-24
to meteo...@googlegroups.com
Good feedback, Lloyd. Thanks!

Re #1 Hrm, yeah. Makes sense. Like Dave mentioned, we've talked about making meteor reset keep config/secrets. The problem is right now it works by just deleting the whole database directory. More fine-grain resetting probably requires running mongo. Another option would be to allow specifying appId and secret in the options hash to Meteor.accounts.facebook.config. This would override the data in mongo, and would mean people could set their values either in the database or in the code.

For #2, you can override the ROOT_URL environment variable to control where facebook requests are redirected back to. Looking at the code though, it looks like we don't handle this correctly in the development mode runner, we override ROOT_URL with localhost even if you already have it set. I've made a note and will fix it so that you can override from the command line. In the mean time, you can override the ROOT_URL with '__meteor_runtime_config__.ROOT_URL = "http://192.168.1.64:3000"' in your code.

Cheers,
-- Nick

--
You received this message because you are subscribed to the Google Groups "meteor-core" group.
To view this discussion on the web visit https://groups.google.com/d/msg/meteor-core/-/PWAf32iom1AJ.

Nick Martin

unread,
Sep 24, 2012, 4:32:22 p.m.2012-09-24
to meteo...@googlegroups.com
Thanks, Tom.

This is a good point. We should definitely handle this better.

One change that we're planning on making that should help is moving from 'is the user record loaded' to 'is the user data subscription ready' to determine when the user record is ready. If I understand correctly, right now you'll end up with the user record saying it is still loading indefinitely. Whereas if we use the subscription, we can make Meteor.user() return an object with just _id.

Cheers,
-- Nick

Tom Coleman

unread,
Sep 24, 2012, 6:43:38 p.m.2012-09-24
to meteo...@googlegroups.com
Hi Nick,

How do you delete users then?

Nick Martin

unread,
Sep 24, 2012, 9:22:30 p.m.2012-09-24
to meteo...@googlegroups.com
Hi Tom,

Not sure I understand. Is this related to the question about user record loading?

You can currently delete users with Meteor.users.remove on the server. We don't yet have a way to terminate all sessions held open by a user, though.

Cheers,
-- Nick

Tom Coleman

unread,
Sep 24, 2012, 9:40:52 p.m.2012-09-24
to meteo...@googlegroups.com
Hi Nick,

Sorry to be so brief. Yeah, dealing with open sessions is what I'm referring to.

If client side code assumes a missing user object means that it just hasn't been published properly (rather than that it's been deleted), then there's really no way to tell the client that the user has been deleted. Although, perhaps this can be dealt with another way (through the loginTokens perhaps?)

PS -  Maybe there's a larger issue of hiding data by using a `fields` specifier in a published cursor -- users would no doubt expect empty objects to appear client side rather than not at all if they happen to not have the relevant fields. 

Not saying the behaviour is _incorrect_, just that it makes for quite a head-scratcher (it's burnt me twice now and I've only worked it out after digging deep into livedata etc). I'm not sure what the answer is; logging in the console is probably overkill..

Cheers,
Tom

Nick Martin

unread,
Sep 24, 2012, 9:55:57 p.m.2012-09-24
to meteo...@googlegroups.com
Ahh, I see what you're getting at. A head-scratcher indeed.

I suspect we'll need some sort of explicit mechanism for deletion, looking for a missing user object probably isn't enough info. Another option would be to provide a method that removes all loginTokens for a user and disconnects all sessions belonging to the user. The user would then try to reconnect, get a failure, and revert to not logged in.

Will continue to think on this. Any suggestions welcome =).

Cheers,
-- Nick

Tom Coleman

unread,
Sep 24, 2012, 10:22:27 p.m.2012-09-24
to meteo...@googlegroups.com
I think `Meteor.removeUser(..)` makes a lot of sense.

As for the more general problem; apart from adding:

_.isEmpty(obj) && console.log('Not publishing empty document to ' + collection + '; this is probably not what you want.');

to livedata_server:555 (or 452?).

(which I think could be really annoying for people who actually want this functionality), I don't really see what to do. I guess there could be something in the Meteor.publish section of the docs about it, alternatively.

Tom

Tom Coleman

unread,
Sep 24, 2012, 10:33:46 p.m.2012-09-24
to meteo...@googlegroups.com
Hi Nick.

When you say:

On Saturday, 22 September 2012 at 8:53 AM, Nick Martin wrote:

-> If you have any additional fields on your user records that you want visible to the client, you must either explicitly publish them (if you do not want them directly editable by the user) or move them to the 'profile' sub-object (if you do want them editable).
Do you mean to setup another publication on the users collection? Something like:

  Meteor.publish(null, function() {
    if (this.userId())
      return Meteor.users.find({_id: this.userId()},
                               {fields: {specialField: true}});
    else
      return null;
  }, {is_auto: true});

Cheers,
Tom.

Nick Martin

unread,
Sep 24, 2012, 10:43:48 p.m.2012-09-24
to meteo...@googlegroups.com
Yup! That's exactly what I mean.

The 'is_auto' there isn't really needed, it just turns off a warning when used with autopublish, which you are presumably not using.

-- Nick

--

Nick Martin

unread,
Sep 25, 2012, 1:42:09 a.m.2012-09-25
to meteo...@googlegroups.com
Hi Lloyd,

I've fixed things so that the ROOT_URL environment variable is now respected when running in development mode. So now you can do "env ROOT_URL='http://192.168.1.64:3000' ..checkout../meteor" and test on your emulator.

Cheers,
-- Nick

Lloyd B

unread,
Sep 25, 2012, 11:14:54 a.m.2012-09-25
to meteo...@googlegroups.com
thanks Nick, that solved my problem (setting ROOT_ENV).

my app being primarily aimed at mobile, my next priority is Meteor working on iOS6 (long-polling broken) and Windows Phone (`windows.open` prohibited).. Android and iOS<6 are largely ok.. 

steeve

unread,
Sep 30, 2012, 4:56:55 p.m.2012-09-30
to meteo...@googlegroups.com
Nick;

Per this statement...

We've changed the structure of the user record in mongo. Previously, we automatically published the whole user object, minus 'services' and 'private'. Now we've flipped things around and new top level fields on user are not published by default. There is a new 'profile' sub-object, which is both automatically published and editable by the user.

-> If you have any additional fields on your user records that you want visible to the client, you must either explicitly publish them (if you do not want them directly editable by the user) or move them to the 'profile' sub-object (if you do want them editable).

Does that mean that all fields in the profile field are editable and updateable by the user regardless of how the Meteor.users.allow({ update: function (userId, docs, fields, modifier) { } }) is handled?

Suppose I have update locked down.  

Can the user still edit and update their own profile sub-fields even though I have the update locked down?

I am just trying to understand where I might want to move my sub-fields like Groups and stuff that I don't want the user to be able to edit and update, but must be published to the user, in order to determine what views to display (or not display).

Thanks
Steeve

Nick Martin

unread,
Sep 30, 2012, 10:15:58 p.m.2012-09-30
to meteo...@googlegroups.com
Hi Steeve,

On Sun, Sep 30, 2012 at 1:56 PM, steeve <stephen...@gmail.com> wrote:
Nick;

Per this statement...

We've changed the structure of the user record in mongo. Previously, we automatically published the whole user object, minus 'services' and 'private'. Now we've flipped things around and new top level fields on user are not published by default. There is a new 'profile' sub-object, which is both automatically published and editable by the user.

-> If you have any additional fields on your user records that you want visible to the client, you must either explicitly publish them (if you do not want them directly editable by the user) or move them to the 'profile' sub-object (if you do want them editable).

Does that mean that all fields in the profile field are editable and updateable by the user regardless of how the Meteor.users.allow({ update: function (userId, docs, fields, modifier) { } }) is handled?

Suppose I have update locked down.  

Can the user still edit and update their own profile sub-fields even though I have the update locked down?

On the current auth branch, you can lock things down with `Meteor.users.allow({insert/update/remove: function () { return false; }})`. If you've done that, users will not be able to edit their profile.

However, in an upcoming change the new way will be `Meteor.users.deny({insert/update/remove: function () { return true; }})`. That will do the same thing, prevent any user modifications from the client regardless of other 'allow()' statements.

I am just trying to understand where I might want to move my sub-fields like Groups and stuff that I don't want the user to be able to edit and update, but must be published to the user, in order to determine what views to display (or not display).


I would think the top level of the user object is a good place for things like that. It's not really part of the user's profile as I imagine it, more just a general property of a user. You'll need to publish the things you want on the user explicitly, with something like `Meteor.publish(function () { return Meteor.users.find(this.userId(), {fields: {groups: 1}}))`. 
 
Cheers,
-- Nick

Stephen Cannon

unread,
Oct 1, 2012, 6:13:41 a.m.2012-10-01
to meteo...@googlegroups.com
Nick;

Thanks for clarifying. Helps me out a lot!

Steeve
> --
> You received this message because you are subscribed to the Google Groups
> "meteor-core" group.
> To post to this group, send email to meteo...@googlegroups.com.
> To unsubscribe from this group, send email to
> meteor-core...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/meteor-core?hl=en.



--
Thanks
Stephen Cannon
Mobile: 727.386.6298
stephen...@gmail.com

steeve

unread,
Oct 3, 2012, 8:39:25 a.m.2012-10-03
to meteo...@googlegroups.com
Nick and Tom;

I was just reading through this sub-thread about a user being removed server side and potential client side issues if there is an active session.  I just ran into something that seems to be in the vein of what is being discussed.

I have a Meteor.users.active boolean field on my user collection items.  I use that to activate/deactive users because my app is a business app with an audit log and deleting users isn't always the optimal use case.

What I have done is if the user is not active or Meteor.users.active == false then the user is auto-logged out.  Which works good for an Admin disabling accounts.  After this, when I check the Meteor.user() client side it is null.

But, my app also has a remove user function.  

For removing users prior to Meteor.users.remove() server side I set the active = false which logs the user out.  

Then server side I call Meteor.users.remove.

What is interesting is that client side the Meteor.user() object remains as 
_id: "75282dcd-0cca-45bb-91db-98da01b2e949"
loading: true
__proto__: Object 

I also tested not setting the user active=false to see what happens when someone is logged in on the client side and server side the Meteor.users.remove() is called for that id and the same results are produced.  The Meteor.user() object exists client side with an id and loading.

So, if someone is implementing something like this in their templates

{{#if currentUser.loading}}
    {{> loading}}
  {{else}}
    {{#if currentUser}}
      {{> navbar}}
      {{> home}}
    {{else}}
      {{> login}}
    {{/if}}
  {{/if}}

the user sees a Loading screen after their account has been deleted/removed via server side Meteor.users.remove().

The Meteor.users() object with id, loading doesn't seem like correct or expected behavior to me.  

Should I log a GH issue for this?

Thanks
Steeve

Avital Oliver

unread,
Oct 5, 2012, 1:05:34 a.m.2012-10-05
to meteo...@googlegroups.com
Steeve,

Thanks for this detailed description. Yes, I think this is a bug and you are welcome to file it. It's also worth pointing out that we plan to change the API for knowing whether the user document is loaded.

I'm curious though how you forced users with `active` set to true to get logged out...?

Thanks,
Avital.

To view this discussion on the web visit https://groups.google.com/d/msg/meteor-core/-/Q6E0gdpPRs0J.

Stephen Cannon

unread,
Oct 5, 2012, 5:42:05 a.m.2012-10-05
to meteo...@googlegroups.com
Avital;

If you are going to change the API for user document loading
identification then I will wait and when the change comes down the
pipeline I can test this again. For now I can live with it.

The way I log out the user is not very advanced and probably not the
most secure option, but rather very simple and basic at best.

I have a field 'active' as a boolean on my user collection items.

It is created when a user account is created.

It is not a sub-field in the profile sub-field.

I publish it like this and the user cannot edit it.

Meteor.publish("myuser",
function () {
return Meteor.users.find(this.userId(), {fields: {active:1,
groups: 1, locations: 1, clockedin: 1}});
}
);

Client side I have this.

if(Meteor.user().active == false){
Meteor.logout();
}

Not the most advanced or secure method but it works for now. It could
be implemented further down in meteor auth core. For example, in the
following cases, which I built on top of the PR270 I had submitted in
the past.

1. If user.active = false prevent login
Right now I implement this through a postLogin hook

2. Or if user.active = false prevent session resume by checking
options.resume, which I implement with a preLogin hook to prevent
session resume after setting user.active = false.

3. Lastly, the part I didn't look at in depth, somehow invalidating
the session and logging the user out further down in the meteor auth
core when active = false server side. Which as I pointed out I am
doing it in a very basic manner for now.

Thanks
Steeve

Avital Oliver

unread,
Oct 5, 2012, 2:52:31 p.m.2012-10-05
to meteo...@googlegroups.com
Thanks a lot for sharing this, Stephen. We will also have better support for forcing users to be logged out, and deleting users. But for now it's good to know how people are working around this.

David Glasser

unread,
Oct 8, 2012, 12:42:04 p.m.2012-10-08
to meteo...@googlegroups.com
Hi folks! We're seeing the light at the end of the tunnel for the auth
branch: we're hoping to get it merged to devel and a "release
candidate" built pretty soon! This is our last chance to do a bunch of
refactorings before it affects people who are using release builds, so
there'll be a few more batches of breaking changes in the next couple
of days. Here are the ones I'm about to push:

- The internal Mongo collections used by auth have been renamed to
start with meteor_accounts_. This means that when you take this
update, you'll need to re-configure your login services (since your
stored login service data is in the "accounts._loginTokens" collection
and will need to be in "meteor_accounts_loginTokens").

- When you write your own OAuth service implemention, we've simplified
the nested data structure returned by the callback you pass to
handleOauthRequest. We've updated this on all the handlers in meteor
core, but you'll need to update your Atmosphere packages. It now
should look something like:

return {
serviceData: {
id: identity.id,
accessToken: accessToken,
email: identity.email
},
extra: {profile: {name: identity.name}}
};

- We've renamed Accounts.onCreateUserHook to Accounts.insertUserDoc,
and now it actually inserts the doc instead of just augmenting and
returning it. Hopefully the purpose of this function (which is really
only used if you're implementing login services without using
something like the oauth helper) is now more clear. (It also is now
optionally responsible for generating a login token.)

- We've renamed and refactor Accounts.updateOrCreateUser: it's now
called Accounts.updateOrCreateUserFromExternalService. This should
make it more clear that it's an internal function only used by OAuth
implementations and the equivalent, not a convenience wrapper around
Accounts.createUser. It also takes different arguments now.

- Non-breaking change: If you're specifying a "manager" to the
Meteor.Collection constructor (to associate it with a server you've
connected to with Meteor.connect) you should now specify it in the
options hash instead of as a bare argument; but the old version still
works for now.



Expect to see more messages on this thread in the next few days. Exciting times!

--dave

Avital Oliver

unread,
Oct 8, 2012, 10:44:35 p.m.2012-10-08
to meteo...@googlegroups.com
Another small breaking change, only relevant to people building additional login service packages:

- We renamed the templates named "configureLoginServicesDialogForFoo" (Foo being the name of the service) to "configureLoginServiceDialogForFoo"

David Glasser

unread,
Oct 9, 2012, 12:43:24 a.m.2012-10-09
to meteo...@googlegroups.com
And another:

- In method bodies and publish handlers, this.userId is now just a
value instead of a function returning a value.

David Glasser

unread,
Oct 9, 2012, 1:37:34 a.m.2012-10-09
to meteo...@googlegroups.com
And some more!

- New reactive function Meteor.userLoaded() and Handlebars helper
{{currentUserLoaded}} which is true when the user is logged in *and*
their data has been loaded into Meteor.user(). Replaces the old
behavior where Meteor.user() returned {_id: id, loading: true} while
it was loading and there was no reactivity. (Meteor.user() now
returns {_id: id} in this case, but you should check
Meteor.userLoading() if you really care, eg to show a spinner.)

- The "resume" tokens (used so that opening new tabs or reloading
preserves your login state) and email validation tokens are now part
of the user doc. This means that deleting the user doc cleans them up.
This also means that when you upgrade to this version, all existing
users will be logged out.

--dave

Nick Martin

unread,
Oct 9, 2012, 7:20:05 p.m.2012-10-09
to meteo...@googlegroups.com
And another. We're almost done, I swear =)

I've changed the verb for the process where we send an email to new users to confirm their email address from 'validate' to 'confirm'. This is reduce confusion with validateNewUser and other validation tasks.

If you're using accounts-ui, the only relevant change is going from `Accounts.config({validateEmails: true})` to `Accounts.config({sendConfirmationEmail: true})`.

If you write your own UI and deal with confirmation emails, you'll probably have to change more things.
- validateEmail method changed to confirmEmail
- the fields in the database have changed from 'emails.validated' to 'emails.confirmed', and 'validationToken' to 'confirmationToken'
- email templates function name changed
- accounts-url Accounts._validateEmailToken changed to Accounts._confirmEmailToken

Wiki will be updated shortly.

-- Nick

David Glasser

unread,
Oct 9, 2012, 10:19:36 p.m.2012-10-09
to meteo...@googlegroups.com
And some more:

- Accounts.configuration (not to be confused with Accounts.config) is
now Accounts.loginServiceConfiguration

- The default onCreateUser hook does not permit you to set any fields
(through "extra") other than profile, to match the default
Meteor.users.allow.

Avital Oliver

unread,
Oct 10, 2012, 12:24:57 a.m.2012-10-10
to meteo...@googlegroups.com
...and more:

- In the options to Accounts.config, forbidSignups was renamed to forbidClientAccountCreation
- We eliminated Accounts.config: if you want to validate certain fields on new user documents, use Meteor.validateNewUser
- We eliminated Accounts.{facebook,github,google}.config. Instead, use the new Accounts.ui.config (if you are using accounts-ui), or use the new options argument to Meteor.loginWith{Facebook,Google,Github}.
- We introduced Accounts.ui.config with the following options:
  - passwordSignupFields: one of the following options: "USERNAME_ONLY", "USERNAME_AND_EMAIL", "EMAIL_ONLY", "USERNAME_AND_OPTIONAL_EMAIL"
  - requestPermissions: an object with permissions to request, e.g. {facebook: ["user_about_me", "user_activity"]}
- Meteor.loginWith{Facebook,Google,Github} now take an options argument where you can specify requestPermissions as in Accounts.ui.config

Avital Oliver

unread,
Oct 10, 2012, 12:54:29 a.m.2012-10-10
to meteo...@googlegroups.com
A change to accounts-ui: dropdown opens by default to right, unless wrapped in an element with class "login-buttons-dropdown-hangs-left"

Nick Martin

unread,
Oct 10, 2012, 2:07:02 a.m.2012-10-10
to meteo...@googlegroups.com
Haha, just kidding. We came up with a much better word: 'verify'. All the 'confirm's have been changed to 'verify'.

Sorry for the whiplash =)

-- Nick

On Tue, Oct 9, 2012 at 4:20 PM, Nick Martin <n...@meteor.com> wrote:

Kasper Souren

unread,
Oct 14, 2012, 4:58:21 p.m.2012-10-14
to meteo...@googlegroups.com
On Wednesday, October 10, 2012 6:25:18 AM UTC+2, Avital Oliver wrote:
  - requestPermissions: an object with permissions to request, e.g. {facebook: ["user_about_me", "user_activity"]} 

It took me a while before I figured it out, even when looking at http://auth-docs.meteor.com/#accounts_ui_config
Would be good to add a little bit more verbosity in the docs.

Instead of {facebook: ['user_likes'], github: ['user', 'repo']} put
Accounts.ui.config({requestPermissions: {facebook: ['user_likes'], github: ['user', 'repo']}})

Thanks!


Avital Oliver

unread,
Oct 15, 2012, 12:21:13 p.m.2012-10-15
to meteo...@googlegroups.com
Thanks, that's a good idea.

--
You received this message because you are subscribed to the Google Groups "meteor-core" group.
To view this discussion on the web visit https://groups.google.com/d/msg/meteor-core/-/FQ39T4hb5YIJ.
Reply all
Reply to author
Forward
0 new messages