Bobby Circle Ciraldo
Bobby Circle Ciraldo

Reputation: 1353

Hide secret server code called from Meteor Methods

I'm trying to wrap my head around how to hide secret server code from a client in Meteor Methods. The docs seem to imply that the following is a general pattern of how it's done. https://guide.meteor.com/structure.html#using-require

(Note that, as per the docs, I'm using require instead of import, since import cannot be used in a conditional block.)

First, here's a Method defined in a file called methods.js and imported both on the client and the server:

/* methods.js */

let ServerCode
if (Meteor.isServer) {
  ServerCode = require('/imports/server-code.js')
}

Meteor.Methods({
  'myMethodName': function() {
    // ... common code
    if (Meteor.isServer) {
      ServerCode.secretFunction()
    }
    // ... common code
  }
})

Second, here's the secret server code in /imports/server-code.js which I am trying not to send to the client:

/* server-code.js */

class ServerCode {
  secretFunction() {
    console.log('TOP SECRET!!!!')
  }
}

const ServerCodeSingleton = new ServerCode()
module.exports = ServerCodeSingleton

But when I examine the source sent over to the client browser, I'm still seeing my secret server code being sent to the client:

Screenshot of the server code being sent to the client

Even when I do a production build, I can still search and find that 'TOP SECRET!!' string. I feel like I'm being too naive in my understanding of how require works, but the Meteor docs make it seem so simple. So what is the correct way to hide secret code that is called from Meteor Methods?

Upvotes: 3

Views: 685

Answers (2)

zim
zim

Reputation: 2386

EDIT: disregard answer, does not address OP's desire for DRY code or optimistic updates.

be default, Meteor implements an eager-loading scheme with regard to files. your methods.js is an example of that.

if a file is in a directory that's anywhere under a folder called "client", that file is served only to the client. likewise, any file under "server" is served only to the folder. so that's how you handle ensuring a file is served only to the server.

as you discovered, the "Meteor.isServer" construct only limits what's run in an environment, not what is served there.

i typically implement Meteor methods like this:

server/some/path/stuff.js:

// this is only on the server
Meteor.methods({
    'getStuff': function() {
        return "stuff";
    }
});

client/some/path/clientStuff.js:

// this is only on the client, calling the server method
Meteor.call('stuff', function(error, result) {
    if (error && error.reason) {
        alert(error.reason);
    }
    else {
        // use result
    }
});

regarding require, why are you using it?

i see that you have code in /imports, which Meteor treats as special and does not eagerly load. that tells me you're explicitly importing this stuff.

MDG has some strong recommendations for directory structure, ES15 modules and how to load code, which they detail here:

https://guide.meteor.com/structure.html

you'll note they don't use require, and are specifying modules differently from how you're doing it.

Upvotes: 0

Bobby Circle Ciraldo
Bobby Circle Ciraldo

Reputation: 1353

I finally figured this out I think.

The short version is, ignore what it says here; I believe it's incorrect or at least misleading:

https://guide.meteor.com/structure.html#using-require

And follow what it says here instead:

https://guide.meteor.com/security.html#secret-code

A longer explanation is: In a server-only file, import the secret code and assign it to a global variable. Then, in a common file, use isServer (or !isSimulation) to conditionally refer to that global variable.

So my original example might be re-written like this:

/*   /imports/methods.js   */

// Note: no conditional use of require this time

Meteor.Methods({
  'myMethodName': function() {
    // ... common code
    if (Meteor.isServer) {
      ServerCode.secret() // <-- Defined as a global outside of this file!
    }
    // ... common code
  }
})

And so the secret code file might look like this:

/*   /imports/server-code.js   */

class _ServerCode {
  secret() {
    console.log("Shhhhhh, I'm secret()!")
  }
}
// Here's the global variable:
SecretCode = new _SecretCode()

And then in a server-only file it might look like this:

/*   /server/server-main.js   */

import '/imports/secret-code' // <-- declare the global
import '/imports/methods' // <-- use the global in here

And then in a client-only file it might look like so:

/*   /client/client-main.js   */ 

import '/imports/methods'

//...

Meteor.call('myMethodName')

Now the client and server can both execute some of the same exact code in the Method body (DRY), while some secret code can be server-only and won't get sent to the client as well. It's a little annoying to have to resort to using a global variable, but perhaps that's the cleanest option until a fancier version of import comes along that supports built-in lazy-loading of modules.

Upvotes: 1

Related Questions