Skip to content

Latest commit

 

History

History
1193 lines (1155 loc) · 32.9 KB

constructor.md

File metadata and controls

1193 lines (1155 loc) · 32.9 KB

new FilesCollection([config]) [Isomorphic]

Initialize FilesCollection collection.

Param/Type Locus Description Default Comment
config {Object} Isomorphic [Optional] {} See all options below
config.storagePath {String|Function} Server Storage path on file system function { return 'assets/app/uploads'; }
Always converted into the function since v1.7.4
Relative to running script
If Function is passed it must return String, arguments:
  • defaultPath - Default recommended path
Context is current FilesCollection instance.

Note: When running in development mode files stored at a relative path (within the Meteor project) are silently removed when Meteor is restarted.

To preserve files in development mode store them outside of the Meteor application, e.g. /data/Meteor/uploads/

The Meteor-Files package operates on the host filesystem, unlike Meteor Assets. When a relative path is specified for config.storagePath (path starts with ./ or no slash) files will be located relative to the assets folder.

When an absolute path is used (path starts with /) files will be located starting at the root of the filesystem.

If using MeteorUp, Docker volumes has to be created in mup.json, see Usage on MeteorUp
config.collection {Mongo.Collection} Isomorphic Mongo.Collection Instance You can pass your own Mongo Collection instance {collection: new Mongo.Collection('myFiles')}
config.collectionName {String} Isomorphic Collection name MeteorUploadFiles
config.continueUploadTTL {Number} Server Time in seconds, during upload may be continued, default 3 hours (10800 seconds) 10800 (3 hours) If upload is not continued during this time, memory used for this upload will be freed. And uploaded chunks is removed. Server will no longer wait for upload, all further upload attempts will result 408 Error (Can't continue upload, session expired. Start upload again.)
config.ddp {Object} Client Custom DDP connection for Collection. Object returned form DDP.connect() Meteor (The default DDP connection)
config.cacheControl {String} Server Set Cache-Control header public, max-age=31536000, s-maxage=31536000
config.responseHeaders {Object|Function} Server Allows to change default response headers Default Function We recommend to keep original function structure, with your modifications, see example altering default headers
DEPRECATED config.throttle {Number|false} Server DEPRECATED Throttle download speed in bps - TEMPORARILY DEPRECATED SINCE v1.9.0
config.downloadRoute {String} Isomorphic Server Route used to retrieve files /cdn/storage
config.schema {Object} Isomorphic Collection Schema Default Schema For more info read Schema docs
config.chunkSize {Number} Isomorphic Upload & Serve (for 206 response) chunk size 272144
config.namingFunction {Function} Isomorphic Function which returns String. Use it to create nested directories in the storage folder. Note: file extension appended to returned value false Primary sets file name on FS
if namingFunction is not set
FS-name is equal to file's record _id
config.permissions {Number} Server FS-permissions (access rights) in octal 0644 ex.: 0755, 0777
config.parentDirPermissions {Number} Server FS-permissions for parent directory (access rights) in octal 0755 ex.: 0777
config.integrityCheck {Boolean} Server CRC file check true
config.strict {Boolean} Server Strict mode for partial content false If is true server will return 416 response code, when range is not specified
config.downloadCallback {Function} Server Called right before initiate file download.
Arguments:
Context:
  • this.request
  • this.response
  • this.user()
  • this.userId
false Function should return {Boolean} value, return false to abort download, and true to continue
config.protected {Boolean|Function} Server Control download flow.
If function:
Arguments:
  • fileObj {Object|null} - If requested file exists - file object, otherwise - null

Context:
  • this.request
  • this.response
  • this.user()
  • this.userId
false If true - files will be served only to authorized users, if function() - you're able to check visitor's permissions in your own way.
  • return true to continue
  • return false to abort or {Number} to abort upload with specific response code, default response code is 401
config.public {Boolean} Isomorphic Allows to place files in public directory of your web-server. And let your web-server to serve uploaded files false Important notes:
  • Collection can not be public and protected at the same time!
  • downloadRoute must be explicitly provided. And pointed to root of web/proxy-server, like /uploads/
  • storagePath must point to absolute root path of web/proxy-server, like '/var/www/myapp/public/uploads/'
  • integrityCheck is not guaranteed!
  • play and force download features is not guaranteed!
  • Remember: NodeJS is not best solution for serving files
config.onBeforeUpload {Function} Isomorphic Callback, triggered right before upload is started on client and right after receiving a chunk on server
Arguments:
  • fileData {Object} - Current file metadata

Context:
  • this.file
  • this.user()
  • this.userId
  • this.chunkId {Number} - On server only
  • this.eof {Boolean} - On server only
false
  • return true to continue
  • return false to abort or {String} to abort upload with message

note: Because sending meta data as part of every chunk would hit the performance, meta is always empty ({}) except on the first chunk (chunkId=1 or chunkId=-1) and on eof (eof=true or chunkId=-1) (Fixed. Since v1.6.0 full file object is available in onBeforeUpload callback)

config.onInitiateUpload {Function} Server Function which executes on server right before upload is begin and right after onBeforeUpload hook returns true. This hook called only once per upload and fully asynchronous.
Arguments:
  • fileData {Object} - Current file metadata

Context:
  • this.file
  • this.user()
  • this.userId
  • this.chunkId {Number} - On server only
false See: #208
config.onBeforeRemove {Function} Server Callback, triggered right before remove file (from Client)
Arguments:
  • cursor {MongoCursor} - Current files to be removed on cursor, if has any

Context:
  • this.user()
  • this.userId
false Use with allowClientCode to control access to remove() method.
  • return true to continue
  • return false to abort
config.onAfterUpload {Function} Server Callback, triggered after file is written to FS
Arguments:
  • fileRef {Object} - Record from MongoDB
false Alternatively use: addListener('afterUpload', func)
config.onAfterRemove {Function} Server Callback, triggered after file(s) is removed from Collection
Arguments:
  • files {[Object]} - Array of removed documents
false
config.onbeforeunloadMessage {String|Function} Client Message shown to user when closing browser's window or tab, while upload in the progress Upload in a progress... Do you want to abort?
config.allowClientCode {Boolean} Isomorphic Allow use remove() method on client true
config.debug {Boolean} Isomorphic Turn on/of debugging and extra logging false
config.interceptRequest {Function} Server Intercept download request.
Arguments:
  • http {Object} - Middleware request instance
  • http.request {Object} - example: http.request.headers
  • http.response {Object} - example: http.response.end()
  • http.params {Object} - Data extracted from URI
  • http.params._id {String} - File's `_id`
  • http.params.query {String} - Request get query
  • http.params.name {String} - Request file name from URI
  • http.params.version {String} - File's version name from URI
false Usage example: Serve file from third-party resource.
  • return false from this function to continue standard behavior
  • return true to intercept incoming request
config.interceptDownload {Function} Server Intercept download request.
Arguments:
  • http {Object} - Middleware request instance
  • http.request {Object} - example: http.request.headers
  • http.response {Object} - example: http.response.end()
  • http.params {Object} - Data extracted from URI
  • http.params._id {String} - File's `_id`
  • http.params.query {String} - Request get query
  • http.params.name {String} - Request file name from URI
  • http.params.version {String} - File's version name from URI
  • fileRef {Object} - Current file record from MongoDB
  • version {String} - Requested file version
false Usage example: Serve file from third-party resource.
  • return false from this function to continue standard behavior
  • return true to intercept incoming request
config.disableUpload {Boolean} Both Disable upload from Client to Server (HTTP and DDP (WebSockets)) false Use for security reasons when only Server usage is needed
config.disableDownload {Boolean} Server Disable file download from Server to Client (HTTP) false Use for security reasons when only upload from Client to Server usage is needed, and files shouldn't be downloaded by any user.
config.allowedOrigins {Regex|Boolean} Server Regex of Origins that are allowed CORS access or `false` to disable completely. /^http:\/\/localhost:12[0-9]{3}$/ Defaults to `/^http:\/\/localhost:12[0-9]{3}$/` for allowing Meteor-Cordova builds access.
config.allowQueryStringCookies {Regex|Boolean} Isomorphic Allow passing Cookies in a query string (in URL). Primary should be used only in Cordova environment. Note: this option will be used only on Cordova. Directly passed to `ostrio:cookies` package false Highly recommended to use with Cordova
config.getUser {Function} Server Replace default way of recognizing user. Arguments:
  • http {Object} - Middleware request instance
  • http.request {Object} - example: http.request.headers
  • http.response {Object} - example: http.response.end()
Default function recognizing user based on x_mtok cookie. Usefull when you want to auth user based on custom cookie (or other way). Must return null or {userId: null || String, user: function=> null || user }
config.disableSetTokenCookie {Boolean} Client Disable automatic cookie setting Useful when you use multiple file collections or when you want to implement your own authorization.
config.sanitize {Function} Server (*accepted, but no used on the Client*) Sanitizer for sensitive Strings; Overrides default sanitize() method of FilesCollection instance. Primary used for FSName and fileId. Very low-level. Warning: use with caution! Default function Read more in #847, wekan/#4638, and wekan/#4640
config._preCollection {Mongo.Collection} Server Mongo.Collection Instance You can pass your own Mongo Collection instance {_preCollection: new Mongo.Collection('__pre_myFiles')}
config._preCollectionName {String} Server preCollection name __pre_MeteorUploadFiles

Event map:

Name Locus Description Comment
afterUpload Isomorphic Triggered right after file is written to FS.
Arguments:
  • fileRef {Object} - Record from MongoDB

Examples:

import { FilesCollection, helpers } from 'meteor/ostrio:files';

const imagesCollection = new FilesCollection({
  storagePath: 'assets/app/uploads/images',
  downloadRoute: '/files/images',
  collectionName: 'images',
  permissions: 0o755,
  allowClientCode: false,
  cacheControl: 'public, max-age=31536000',
  // Read more about cacheControl: https://devcenter.heroku.com/articles/increasing-application-performance-with-http-cache-headers
  onbeforeunloadMessage() {
    return 'Upload is still in progress! Upload will be aborted if you leave this page!';
  },
  onBeforeUpload(file) {
    // Allow upload files under 10MB, and only in png/jpg/jpeg formats
    // Note: You should never trust to extension and mime-type here
    // as this data comes from client and can be easily substitute
    // to check file's "magic-numbers" use `mmmagic` or `file-type` package
    // real extension and mime-type can be checked on client (untrusted side)
    // and on server at `onAfterUpload` hook (trusted side)
    if (file.size <= 10485760 && /png|jpe?g/i.test(file.ext)) {
      return true;
    }
    return 'Please upload image, with size equal or less than 10MB';
  },
  downloadCallback(fileObj) {
    if (this.params.query.download == 'true') {
      // Increment downloads counter
      imagesCollection.update(fileObj._id, {$inc: {'meta.downloads': 1}});
    }
    // Must return true to continue download
    return true;
  },
  protected(fileObj) {
    // Check if current user is owner of the file
    if (fileObj.meta.owner === this.userId) {
      return true;
    }
    return false;
  },
  namingFunction(file) {
    // MAKE SURE namingFunction IS SET ON Server
    // OVERWRITE Client's namingFunction FOR SECURITY REASONS AGAINST REVERSE-ENGINEERING ACTIONS
    return helpers.sanitize(file.fileId);
  },
});

// Export FilesCollection instance, so it can be imported in other files
export default imagesCollection;

Add extra security:

Attach SimpleSchema and .denyClient insecure calls to limit window for error

Attach schema [Isomorphic]:

To attach schema, use/install aldeed:collection2 and simple-schema packages.

import { FilesCollection } from 'meteor/ostrio:files';
const imagesCollection = new FilesCollection({/* ... */});
imagesCollection.collection.attachSchema(new SimpleSchema(imagesCollection.schema));

You're free to extend the schema to include your own properties. The default schema is stored under FilesCollection.schema object.

import { FilesCollection } from 'meteor/ostrio:files';
const mySchema = {
  ...FilesCollection.schema,
  myProp: String,
  myOtherProp: {
    type: Array
  }
};
const imagesCollection = new FilesCollection({
  /* ... */
  schema: mySchema
});
imagesCollection.collection.attachSchema(new SimpleSchema(mySchema));

Deny collection interaction on client [Server]:

Deny insert/update/remove from client

import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';

if (Meteor.isServer) {
  const imagesCollection = new FilesCollection({/* ... */});
  imagesCollection.deny({
    insert() {
      return true;
    },
    update() {
      return true;
    },
    remove() {
      return true;
    }
  });

  /* Equal shortcut: */
  imagesCollection.denyClient();
}

Allow collection interaction on client [Server]:

Allow insert/update/remove from client

import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';

if (Meteor.isServer) {
  const imagesCollection = new FilesCollection({/* ... */});
  imagesCollection.allow({
    insert() {
      return true;
    },
    update() {
      return true;
    },
    remove() {
      return true;
    }
  });

  /* Equal shortcut: */
  imagesCollection.allowClient();
}

Events listeners:

import { FilesCollection } from 'meteor/ostrio:files';
const imagesCollection = new FilesCollection({/* ... */});
// Alias addListener
imagesCollection.on('afterUpload', function (fileRef) {
  /* `this` context is the imagesCollection (FilesCollection) instance */
});

Use onBeforeUpload to avoid unauthorized upload:

import { FilesCollection } from 'meteor/ostrio:files';
const imagesCollection = new FilesCollection({
  collectionName: 'images',
  allowClientCode: true,
  onBeforeUpload() {
    if (this.userId) {
      const user = this.user();
      if (user.profile.role === 'admin') {
        // Allow upload only if
        // current user is signed-in
        // and has role is `admin`
        return true;
      }
    }

    return 'Not enough rights to upload a file!';
  }
});

Use onBeforeRemove to avoid unauthorized remove:

For more info see remove method.

import { FilesCollection } from 'meteor/ostrio:files';
const imagesCollection = new FilesCollection({
  collectionName: 'images',
  allowClientCode: true,
  onBeforeRemove() {
    if (this.userId) {
      const user = this.user();
      if (user.profile.role === 'admin') {
        // Allow removal only if
        // current user is signed-in
        // and has role is `admin`
        return true;
      }
    }

    return false;
  }
});

Use onAfterUpload to avoid mime-type and/or extension substitution:

For additional security, it's recommended to verify the mimetype by looking at the content of the file and delete it, if it looks malicious. E.g. you can use mmmagic package for this:

import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';

const imagesCollection = new FilesCollection({
  collectionName: 'images',
  onAfterUpload(file) {
    if (Meteor.isServer) {
      // check real mimetype
      const { Magic, MAGIC_MIME_TYPE } = require('mmmagic');
      const magic = new Magic(MAGIC_MIME_TYPE);
      magic.detectFile(file.path, Meteor.bindEnvironment((err, mimeType) => {
        if (err || !mimeType.includes('image')) {
          // is not a real image --> delete
          console.log('onAfterUpload, not an image: ', file.path);
          console.log('deleted', file.path);
          this.remove(file._id);
        }
      }));
    }
  }
});