Using ValidatedMethod in 2020

Hi guys, is using ValidatedMethod still the best practice?
With https://github.com/aldeed/simpl-schema moved to npm package, is it still advisable to continue using ValidatedMethod? It hasn’t got an update for 2 years.

Thanks.

It doesn’t need updates, it does exactly what it should. I would never use a method without using ValidatedMethod personally

1 Like

Thanks for your input. So there is no better alternative for now, and it is future proof, right?

Nothing is ever really future-proof; ValidatedMethod is already the alternative of the Meteor method parameter checking using check, as initially suggested in the documentation. I’m not sure how others feel about SimpleSchema; I personally think that it is really good, I even use it outside of the context of Meteor methods. And ValidatedMethod is really just a thin wrapper.

Do you miss something from ValidatedMethod or SimpleSchema?

1 Like

ValidatedMethod is still good. It wraps normal methods, and simply creates a little structure. It also sets some better defaults. The validate function can be anything, it doesn’t have to be Simple Schema. I’ve been using it with JSON Schema with validators created through ajv. You could also just use check in the validator. Sometimes I use runtypes for quick and easy validation.

4 Likes

I’m personally using SimpleSchema, allow/deny, and client side updates. :smirk:

1 Like

Thanks very much for all the answers. I will use it with SimpleSchema as it seems fit best my project.

Also how do you secure Methods, I understand you can always do

    if (!this.userId) {
      throw new Meteor.Error("unauthorized");
    }

to avoid methods run by not authorised user, but even in this case one logged in user can access methods with data for another user. Apart from best practises documented on Meteor website, is there anything else I should be aware of?
Thanks.

1 Like

Not sure what you meant by this. this.userId cannot be changed from the client unless you have the login tokens of another user

@rjdavid
I think it may have been meant that if the app implements multi-tenancy, the mere check of whether the user is logged in is not sufficient.

@denysk
If that’s what you’re asking, in simple cases you could get away with carefully crafted CRUD statements (insert, find, update, remove). Some sort of business id would be stored in the user document, and in all of your mongo collections with tenancy would be part of your document schema. That’s what I do in my app, because the data model is really simple.

Note that every time you’re calling Meteor.user() on the server, Meteor will actually fetch the document, see Meteor.user() in the documentation:

On the server, this will fetch the record from the database. To improve the latency of a method that uses the user document multiple times, save the returned record to a variable instead of re-calling Meteor.user() .

If your multi-tenancy is anything but simple, consider using the npm package meteor-partitioner.

1 Like

ValidatedMethod is still good, but there has already been discussion and some work on replacing it. There is a PR for that, but there are still many disagreements on some details. Other more pressing things came and stalled the effort there, but hopefully we will be able to return to it soon.

2 Likes

There are even some problems with ValidatedMethods. For example, if you use SimpleSchema, you might have to run validation twice - once in the validate function, and again to produce auto-values. And since SimpleSchema mutates the original object you pass it, it’s kind of tricky to know what’s happening when you validate with it in the validate function (this is one of the reasons I moved to a different validation strategy). The regular method implementation doesn’t have this problem though.

3 Likes

ValidatedMethod is great as it is.
I’ve created my own class that extends it with some default mixins we always use.

If I was starting fresh, I probably wouldn’t use SimpleSchema for methods, as it is pretty heavy for validation and I don’t need auto-values or many of it’s other features.

I might still use SimpleSchema on the Server with Collection2 for db validation. The weight doesn’t matter there, and SimpleSchema knows how mongo works much better than many other validators

4 Likes

Thanks so much all for your inputs. Its all noted and will help!

1 Like

Yes, thanks exactly what I need!

1 Like

The power of ValidatedMethods comes actually through the mixins. You can write your own permission mixin, that checks for example, if the user is logged in and registered:

export const checkPermissions = function (options) {
  // optionally make methods public, if desired
  const exception = options.isPublic
  if (exception) return options

  const runFct = options.run
  options.run = function run (...args) {

    // does the client, that executes the current method
    // have a registered user currently being authenticated?
    const { userId } = this
    const user = Meteor.users.findOne(userId)

    if (!userId || !user) {
      throw new Meteor.Error('permissionDenied', 'userNotExists', {userId, name: options.name })
    }

    // only run the "run" function if the user check has passed
    return runFct.apply(this, args)
  }

  return options
}

with this you can create public/private methods:

new ValidatedMethod({
  name: 'publicMethod',
  isPublic: true,
  mixins: [ checkPermissions ],
  validate () {
    ...
  },
  run () {
    ...
  }
})

new ValidatedMethod({
  name: 'privateMethod',
  mixins: [ checkPermissions ],
  validate () {
    ...
  },
  run () {
    ...
  }
})
10 Likes

@jkuester Great, much appreciated!

That’s quite neat, I wasn’t aware of such usage! Thank you! I will now write a mixin to replace my frantic wild-west error wrapper around ValidatedMethod.

Mapping exceptions thrown in run() other than Meteor.Error to Meteor.Error is instrumental in a proper error handling that also includes two stack traces (client and server), client metadata and an optional context object only present on the client, where the Method is initially invoked. Meteor unfortunately has quite some shortcomings in this respect.

1 Like

@peterfkruger I actually do it like so:

export const logMethodRuntimeErrors = function (options) {
  const { run } = options

  options.run = function (...args) {
    const environment = this
   
    // for performace optimization put try most outside
    try {
      return run.apply(environment, args)
    } catch (methodRuntimeError) {
      // sanitize error here if you want
      // send error to logger here if you need
      throw methodRuntimeError
    }
  }

  return options
}

and then pass it to your mixins:

new ValidatedMethod({
  name: 'myMethod',
  validate() { ... },
  mixins: [ logMethodRuntimeErrors ],
  run: () { ... }
})

Note that order of the mixin entries is crucial, if you use this mixin with other runtime-mixins (that perform during the run method invokation) and want / want not capture their errors, too.

3 Likes

Thank you for the tips, they are very useful.

With respect to error logging what I prefer is far more complex. The default way of dealing with errors, i.e. if the error means an actual system error and no special handling is needed is as follows:

  • still on the server the error will be converted to a Meteor.Error if it wasn’t one already
  • it will be (re)thrown – Meteor propagates it to the client
  • on the client a wrapper around the method call catches the error
  • an augmented error object will be created out of:
    – the caught and sanitized error object incl. server stacktrace and, optionally, server context object if present in the 2nd parameter of Meteor.Error
    – client metadata such as IP, resolved IP location, navigator.userAgent, window.location
    – client stacktrace leading up to the Meteor method call
    – client call context object – whatever data items deemed characteristic for the error situation, including component’s name and method name as non-minified strings, if available

At this point a Meteor method is called with the augmented error object to specifically log it on the server (not on the console though). So it’s actually a back-and-forth process: client → Meteor method with business logic → client → Meteor method for error logging.

Ultimately on the server we get the sanitized error object with all of its properties, two stacktraces (one of which may be of minified code), one or two context objects, plus a client metadata object.

@jkuester, is there a need to query for the user record in this one?

1 Like