I am working with apostrophe cms right now and have put the entire application behind a SAML IDP using Apostrophe-Saml. however, I have noticed that files that users upload, are placed into a 'public' directory and can be viewed without logging in. Is there a way that uploaded images/videos/files can be secured behind authentication?
The apostrophe-express module has a middleware option, but this does not appear to be used when accessing static content.
My next attempt was to override an apostrophe-assets method
self.servePublicAssets = function() {
var middleware = [];
if (self.lessMiddleware) {
// bc: only if the new implementation of enableLessMiddleware is in place.
// If it's an old override, it'll already be added to Express and
// this property won't be set
middleware.push(self.lessMiddleware);
}
//THIS NEXT LINE IS THE LINE OF INTEREST
middleware.push(self.apos.express.static(self.apos.rootDir + '/public'));
//SEE THE LINE ABOVE
self.expressMiddleware = {
// Run really early, before all of the stuff apostrophe-express normally
// puts in, for performance reasons. Preempts expensive
// queries related to `apostrophe-global` on every static file
when: 'beforeRequired',
middleware: middleware
};
};
I essentially tried to swap the 'line of interest' out for something that looks like this:
middleware.push(self.apos.app.use(self.apos.rootDir + '/public', authMethod(), self.apos.express.static(self.apos.rootDir + '/public')));
But this did not appear to work either. Does anyone have any ideas or know exactly how to do this? Thanks.
While difficult at the time the question was asked, this is now straightforward: install and configure the apostrophe-secure-attachments module.
Related
I'm new to node, blah blah
I'm looking through some code I found, and encountered the lines
var app = express();
var glob = require('glob');
var controllers = glob.sync(config.root + '/app/controllers/*.js');
controllers.forEach(function (controller) {
require(controller)(app);
});
I understand that this goes and gets all the filenames of every file in /app/controllers/ that ends with .js, and sticks them in an array, then iterates over each one and calls a require on it. Makes sense, and looks like a nice way of not requiring each one individually. My question is what's that last (app) for? I tried looking at node documentation, but there is no require()() function. editing out the (app) leaves the code working fine with no errors, but doesn't load the controllers. If I had to take a guess, is it 'multiplying' the app by the found controller? Why would app.require(controller) not be a suitable option?
Thanks in advance
require is a part of node and is how modules are loaded. When you edit out the (app), it is probably still loading the controllers but because you haven't passed the app object over to each controller, they fail silently or return a controller object that doesn't work.
So something in the controller (probably a route being created or other similar stuff) needs to know about the app object and it has to be passed into the controller because app isn't in the global scope. You may want to review the node docs for module and globals as those will probably clear up WAY more than just this one question.
In my estimation we will have:
/* some-controller-file.js */
module.exports = function (app) {
/* do things with `app` */
}
So this allows you to use the created app inside of the controllers probably so you can attach routes.
I'm new to Node but I figured I'd jump right in and start converting a PHP app into Node/Express. It's a bilingual app that uses gettext with PO/MO files. I found a Node module called node-gettext. I'd rather not convert the PO files into another format right now, so it seems this library is my only option.
So my concern is that right now, before every page render, I'm doing something like this:
exports.home_index = function(req, res)
{
var gettext = require('node-gettext'),
gt = new gettext();
var fs = require('fs');
gt.textdomain('de');
var fileContents = fs.readFileSync('./locale/de.mo');
gt.addTextdomain('de', fileContents);
res.render(
'home/index.ejs',
{ gt: gt }
);
};
I'll also be using the translations in classes, so with how it's set up now I'd have to load the entire translation file again every time I want to translate something in another place.
The translation file is about 50k and I really don't like having to do file operations like this on every page load. In Node/Express, what would be the most efficient way to handle this (aside from a database)? Usually a user won't even be changing their language after the first time (if they're changing it from English).
EDIT:
Ok, I have no idea if this is a good approach, but it at least lets me reuse the translation file in other parts of the app without reloading it everywhere I need to get translated text.
In app.js:
var express = require('express'),
app = express(),
...
gettext = require('node-gettext'),
gt = new gettext();
Then, also in app.js, I create the variable app.locals.gt to contain the gettext/translation object, and I include my middleware function:
app.locals.gt = gt;
app.use(locale());
In my middleware file I have this:
mod
module.exports = function locale()
{
return function(req, res, next)
{
// do stuff here to populate lang variable
var fs = require('fs');
req.app.locals.gt.textdomain(lang);
var fileContents = fs.readFileSync('./locales/' + lang + '.mo');
req.app.locals.gt.addTextdomain(lang, fileContents);
next();
};
};
It doesn't seem like a good idea to assign the loaded translation file to app, since depending on the current request that file will be one of two languages. If I assigned the loaded translation file to app instead of a request variable, can that mix up users' languages?
Anyway, I know there's got to be a better way of doing this.
The simplest option would be to do the following:
Add this in app.js:
var languageDomains = {};
Then modify your Middleware:
module.exports = function locale()
{
return function(req, res, next)
{
// do stuff here to populate lang variable
if ( !req.app.locals.languageDomains[lang] ) {
var fs = require('fs');
var fileContents = fs.readFileSync('./locales/' + lang + '.mo');
req.app.locals.languageDomains[lang] = true;
req.app.locals.gt.addTextdomain(lang, fileContents);
}
req.textdomain = req.app.locals.gt.textdomain(lang);
next();
};
};
By checking if the file has already been loaded you are preventing the action from happening multiple times, and the domain data will stay resident in the server's memory. The downside to the simplicity of this solution is that if you ever change the contents of your .mo files whilst the server is running, the changes wont be taken into account. However, this code could be extended to keep an eye on the mtime of the files, and reload accordingly, or make use of fs.watchFile — if required:
if ( !req.app.locals.languageDomains[lang] ) {
var fs = require('fs'), filename = './locales/' + lang + '.mo';
var fileContents = fs.readFileSync(filename);
fs.watchFile(filename, function (curr, prev) {
req.app.locals.gt.addTextdomain(lang, fs.readFileSync(filename));
});
req.app.locals.languageDomains[lang] = true;
req.app.locals.gt.addTextdomain(lang, fileContents);
}
Warning: It should also be noted that using sync versions of functions outside of server initialisation is not a good idea because it can freeze the thread. You'd be better off changing your sync loading to the async equivalent.
After the above changes, rather than passing gt to your template, you should be able to use req.textdomain instead. It seems that the gettext library supports a number of requests directly on each domain object, which means you hopefully don't need to refer to the global gt object on a per request basis (which will be changing it's default domain on each request):
Each domain supports:
getTranslation
getComment
setComment
setTranslation
deleteTranslation
compilePO
compileMO
Taken from here:
https://github.com/andris9/node-gettext/blob/e193c67fdee439ab9710441ffd9dd96d027317b9/lib/domain.js
update
A little bit of further clarity.
Once the server has loaded the file into memory the first time, it should remain there for all subsequent connections it receives (for any visitor/request) because it is stored globally and wont be garbage collected — unless you remove all references to the data, which would mean gettext would need to have some kind of unload/forget domain method.
Node is different to PHP in that its environment is shared and wraps its own HTTP server (if you are using something like Express), which means it is very easy to remember data globally as it has a constant environment that all the code is executed within. PHP is always executed after the HTTP server has received and dealt with the request (e.g. Apache). Each PHP response is then executed in its own separate run-time, which means you have to rely on databases, sessions and cache stores to share even simple information and most resources.
further optimisations
Obviously with the above you are constantly running translations on each page load. Which means the gettext library will still be using the translation data resident in memory, which will take up processing time. To get around this, it would be best to make sure your URLs have something that makes them unique for each different language i.e. my-page/en/ or my.page.fr or even jp.domain.co.uk/my-page and then enable some kind of full page caching using something like memcached or express-view-cache. However, once you start caching pages you need to make certain there aren't any regions that are user specific, if so, you need to start implement more complicated systems that are sensitive to these areas.
Remember: The golden rule of optimisation, don't do so before you need to... basically meaning I wouldn't worry about page caching until you know it's going to be an issue, but it is always worth bearing in mind what your options are, as it should shape your code design.
update 2
Just to illustrate a bit further on the behaviour of a server running in JavaScript, and how the global behaviour is not just a property of req.app, but in fact any object that is further up the scope chain.
So, as an example, instead of adding var languageDomains = {}; to your app.js, you could instantiate it further up the scope of wherever your middleware is placed. It's best to keep your global entities in one place however, so app.js is the better place, but this is just for illustration.
var languageDomains = {};
module.exports = function locale()
{
/// you can still access languageDomains here, and it will behave
/// globally for the entire server.
languageDomains[lang]
}
So basically, where-as with PHP, the entire code-base is re-executed on each request — so the languageDomains would be instantiated a-new each time — in Node the only part of the code to be re-executed is the code within locale() (because it is triggered as part of a new request). This function will still have a reference to the already existing and defined languageDomains via the scope chain. Because languageDomains is never reset (on a per request basis) it will behave globally.
Concurrent users
Node.js is single threaded. This means that in order for it to be concurrent i.e. handle multiple requests at the "same" time, you have to code your app in such a way that each little part can be executed very quickly and then slip into a waiting state, whilst another part of another request is dealt with.
This is the reason for the asynchronous and callback nature of Node, and the reason to avoid Sync calls whilst your app is running. Any one Sync request could halt or freeze execution of the thread and delay handling for all other requests. The reason why I state this is to give you a better idea of how multiple users might interact with your code (and global objects).
Basically once a request is being dealt with by your server, it is it's only focus, until that particular execution cycle ends i.e. your request handler stops calling other code that needs to run synchronously. Once that happens the next queued item is dealt with (a callback or something), this could be part of another request, or it could be the next part in the current request.
I'm trying out node and some frameworks for node atm, specifically locomotive. However, i seem to be stuck on routing using locomotive. A couple questions i can't find the answer to, so here goes:
why does the locomotive out-of-box install use index.html.ejs as a
filename? Why not just index.ejs? What's the benefit?
i'm trying to add a route to a view: searchName.html.ejs which i
added in the views folder. To achieve this i made a toolController
like this:
var locomotive = require('locomotive').Controller,
toolController = new Controller();
toolController.searchName = function() {
this.render();
}
module.exports = toolController;
I also added a route in routes.js like so:
this.match('searchName', 'tool#searchName');
However, that doesn't work (and yet it's what the documentation says ought to work). The result is a 404 error. So how do i make that route work?
Suppose i want to make a route to eg, anExample.html? How do i go
about that? I notice that in the out-of-the-box app from
locomotive, you cannot enter localhost:3000/index.html . Nor even
localhost:3000/index This seems highly impractical to me, as there
are plenty of users who'll add the specific page they want to go to.
So how can i make that work?
PS: I went through all questions regarding this on stackoverflow and searched the web, but i still can't figure this out.enter code here
The benefit is that this naming scheme allows you to specify several different formats for a single route. So you could have search_name.html.ejs and search_name.xml.ejs, then respond with either view depending on what your client is expecting.
There are a couple issues with the example code you posted. You should be seeing a more descriptive error than a 404, so I'm not sure what's happening there, but here are the fixes to your code that work in my environment.
In the controller:
//tool_controller.js
var locomotive = require('locomotive');
var toolController = new locomotive.Controller();
toolController.searchName = function() {
this.render();
};
module.exports = toolController;
In routes.js:
//routes.js
module.exports = function routes()
{
this.match('searchName', 'tool#searchName');
}
Then, you'll need to change the view to this: views/tool/search_name.html.ejs. It's not clear from the documentation, but locomotive automatically lowercases and underscores actions that are camel-cased, like searchName.
Now start the app and browse to http://localhost:3000/searchName
If you just want to serve a static html file, the easiest way is to just drop it in the public folder. This folder is specifically for serving up static content like client-side js, css, etc. And it works just fine for serving static HTML as well.
I would like to be able to get Express to treat several directories (not just one) as "static" -- that is, if the file is there, then serve it.
Connect's static() module seems to be geared up for people who want to make files in a specific directory available in the server's root. However, that's not what I want. What I am after, is end up with something like this:
GET /modules/MODULE1 -> Return files in modules/MODULE1/public
GET /modules/MODULE2 -> Return files in modules/MODULE2/public
GET /modules/MODULE3 -> Return files in modules/MODULE3/public
I am looking at the source of static, which in turns uses send, which in turns defines SendStream, which takes the file path straight from the request (which is not what I want).
Are there easy ways to do this?
Merc.
what's wrong with
app.use('/modules/MODULE1', express.static('modules/MODULE1/public'))
for each module?
The answer is here:
https://groups.google.com/forum/?fromgroups=#!topic/express-js/kK9muR0mjR4
Basically:
var st = express.static(
__dirname + app.set('path.static'),
{ maxAge : app.set('static.expiry') }
);
app.get(/^\/static\/(v.+?\/)?(.+$)/, function (req, res, next) {
req.url = req.params[1];
st(req, res, next);
});
Basically, since Static consider req.url, it's a matter of hacking it so that it "looks right" when/if the path matches.
I asked TJ if he would add it as an option, he (rightly) answered:
this isn't something send() should do, you can already do this easily with connect/express with several static() middleware, you would just have to do the same but more manual with send()
https://github.com/visionmedia/send/issues/10#issuecomment-8225096
During login, I save the userId in a jQuery cookie, so that I can access it by $.cookie('userId').
Now that I am trying to access it from Jade, as,
- if ($.cookie('userId') === userId)
I get an error
$ is not defined
How can I access the cookie ?
My code:
- if (($.cookie('userId')) === userId)
input(type='submit', value='Move')
else
input(type='submit', disabled, value='Move'')
You seem to misunderstand concepts. Let me give you a lecture. ;)
Basics. Cookie is stored on the client-side. So using jQuery $.cookie('userId') will retrieve it when this script is run on the client-side.
But here you use JADE templating engine on server-side. So you didn't even send the page to the client-side yet. So what cookie do you want to retrieve? There is no cookie, because you are at the server.
There's more. Running $.cookie('userId') throws the error, because you are on the server-side. There is no $ object. And there cannot be, because you cannot use jQuery on server-side (well, actually you can, but this is irrelevant at the moment). Did you define $ object in your route system? Or did you define helper $? I don't think so.
Express+JADE tutorial. Now let me give you a short tutorial. Say you have a view (in app.js)
app.get('/', function(req, res){
res.render('index.jade', { code: 500 });
});
Now in index.jade you can use code as a variable. So you can write your index.jade for example like this:
// some code
- if (code == 500)
input(type='submit', disabledm, value='Move')
- else
input(type='submit', value='Move')
// some code
and now it will work! You may also use JavaScript functions in template if you predefine them. For example (in your app.js)
app.helpers({
positive: function(no) { return no > 0; }
});
then in index.jade template you can use
- if (positive(-13))
input(type="submit")
and even combine these two concepts
- if (positive(code))
// do some stuff
To cookie or not to cookie. If you really need to use cookies (and you shouldn't except for sessions or CSRF for example) then you need to use client-side JavaScript. This means that you need to add (after jQuery)
script(src='my-path/my-script.js')
to the index.jade file and write my-script.js like this
$(document).ready(function() {
if ($.cookie('userId') == userId) {
// do something like append input to some holder
}
});
But there are several problems with that solution. First: what is userId (second part of equality)? You need to predefine it in your index.jade. For example using
script
var userId = #{ user.id };
and adding user object in your route. Second: cookies are local variables. Setting them requires additional server code which I do not thing you would like. This makes maintaining the app harder. And finally: storing a user's id in a cookie seems to be pointless. The final user don't even have to know it's own id. The server needs to know! Sessions system comes handy here.
Final note. The code you wrote shows us that you cannot distinguish between server-side and client-side JavaScript. The solution to your problem is to learn, learn and learn. Read tutorials and everything about JADE, Express and Node.js.
What #freakish said is right. You're trying server side vs. client side.
If the cookie is set you should be able to access it on server like so
app.use(express.cookieParser());
app.get('/', function(req, res){
// index is jade template
// local variable on jade template holds cookie value
// use req.cookies.userid
res.render('index', { userid: req.cookies.userid });
});