host.js

Generated by CoffeeScript 1.6.1

GOAL: A simple to setup and run, multi-tenant Git Server written in NodeJS.

This was initially created to be used as a multi-tenant git server with powerful event triggers.

var GitServer, async, fs, http, https, pushover,
  _this = this,
  __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

pushover = require('pushover');

http = require('http');

https = require('https');

async = require('async');

fs = require('fs');

GitServer = (function() {

Constructor function for each instance of GitServer

Params
repos Array List of repositories
repoLocation String Location where the repo's are/will be stored
port Int Port on which to run this server.
certs Object Object of 'key' and 'cert' with the location of the certs (only used for HTTPS)

  function GitServer(repos, logging, repoLocation, port, certs) {
    var _this = this;
    this.repos = repos != null ? repos : [];
    this.logging = logging != null ? logging : false;
    this.repoLocation = repoLocation != null ? repoLocation : '/tmp/repos';
    this.port = port != null ? port : 7000;
    this.certs = certs;
    this.getRepo = function(repoName) {
      return GitServer.prototype.getRepo.apply(_this, arguments);
    };
    this.getUser = function(username, password, repo) {
      return GitServer.prototype.getUser.apply(_this, arguments);
    };
    this.checkTriggers = function(method, repo) {
      return GitServer.prototype.checkTriggers.apply(_this, arguments);
    };
    this.onPush = function(push) {
      return GitServer.prototype.onPush.apply(_this, arguments);
    };
    this.onFetch = function(fetch) {
      return GitServer.prototype.onFetch.apply(_this, arguments);
    };
    this.makeReposIfNull = function(callback) {
      return GitServer.prototype.makeReposIfNull.apply(_this, arguments);
    };
    this.gitListeners = function() {
      return GitServer.prototype.gitListeners.apply(_this, arguments);
    };
    this.permissableMethod = function(username, password, method, repo, gitObject) {
      return GitServer.prototype.permissableMethod.apply(_this, arguments);
    };
    this.processSecurity = function(gitObject, method, repo) {
      return GitServer.prototype.processSecurity.apply(_this, arguments);
    };
    this.log = function() {
      return GitServer.prototype.log.apply(_this, arguments);
    };
    this.createRepo = function(repo, callback) {
      return GitServer.prototype.createRepo.apply(_this, arguments);
    };
    this.git = pushover(this.repoLocation, {
      autoCreate: false
    });
    this.permMap = {
      fetch: 'R',
      push: 'W'
    };
    this.gitListeners();
    this.makeReposIfNull(function() {
      var message, red, reset;
      if (_this.certs != null) {
        _this.server = https.createServer(_this.certs, _this.git.handle.bind(_this.git));
      } else {
        red = '\033[31m';
        reset = '\033[0m';
        message = "WARNING: No SSL certs passed in. Running as HTTP and not HTTPS.\nBe careful, without HTTPS your user/pass will not be encrypted";
        console.log(red + message + reset);
        _this.server = http.createServer(_this.git.handle.bind(_this.git));
      }
      return _this.server.listen(_this.port, function() {
        return _this.log('Server listening on ', _this.port, '\r');
      });
    });
  }


Create a repo on the fly

Params
repoName Object Name of the repo we are creating.


  GitServer.prototype.createRepo = function(repo, callback) {
    if ((repo.name == null) || (repo.anonRead == null)) {
      this.log('Not enough details, need atleast .name and .anonRead');
      false;
    }
    if (!this.getRepo(repo.name)) {
      this.log('Creating repo', repo.name);
      this.repos.push(repo);
      return this.git.create(repo.name, callback);
    } else {
      return this.log('This repo already exists');
    }
  };

  GitServer.prototype.log = function() {
    var args, key, value;
    args = (function() {
      var _results;
      _results = [];
      for (key in arguments) {
        value = arguments[key];
        _results.push("" + value);
      }
      return _results;
    }).apply(this, arguments);
    if (this.logging) {
      return console.log("LOG: ", args.join(' '));
    }
  };


Process the request and check for basic authentication.

Params
gitObject Object Git object from the pushover module
method String Method we are getting security for ['fetch','push']
repo Object Repo object that we are doing this method on


  GitServer.prototype.processSecurity = function(gitObject, method, repo) {
    var auth, creds, plain_auth, req, res;
    req = gitObject.request;
    res = gitObject.response;
    auth = req.headers['authorization'];
    if (auth === void 0) {
      res.statusCode = 401;
      res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"');
      return res.end('<html><body>Need some creds son</body></html>');
    } else {
      plain_auth = (new Buffer(auth.split(' ')[1], 'base64')).toString();
      creds = plain_auth.split(':');
      return this.permissableMethod(creds[0], creds[1], method, repo, gitObject);
    }
  };


Check to see if: return Username and password match return This user has permission to do this method on this repo

Params
username String Username of the requesting user
password String Password of the requesting user
method String Method we are checking against ['fetch','push']
gitObject Object Git object from pushover module


  GitServer.prototype.permissableMethod = function(username, password, method, repo, gitObject) {
    var user, _ref;
    this.log(username, 'is trying to', method, 'on repo:', repo.name, '...');
    user = this.getUser(username, password, repo);
    if (user === false) {
      this.log(username, 'was rejected as this user doesnt exist, or password is wrong');
      return gitObject.reject(500, 'Wrong username or password');
    } else {
      if (_ref = this.permMap[method], __indexOf.call(user.permissions, _ref) >= 0) {
        this.log(username, 'Successfully did a', method, 'on', repo.name);
        this.checkTriggers(method, repo);
        return gitObject.accept();
      } else {
        this.log(username, 'was rejected, no permission to', method, 'on', repo.name);
        return gitObject.reject(500, "You dont have these permissions");
      }
    }
  };

  GitServer.prototype.gitListeners = function() {
    this.git.on('push', this.onPush);
    this.git.on('fetch', this.onFetch);
    return this.git.on('info', this.onFetch);
  };


Checks all the passed in repo's to make sure they all have a real .git directory.



  GitServer.prototype.makeReposIfNull = function(callback) {
    var repo, repoNames, _i, _len, _ref,
      _this = this;
    this.log('Making repos if they dont exist');
    repoNames = [];
    _ref = this.repos;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      repo = _ref[_i];
      if ((repo.name != null) && (repo.anonRead != null) && (repo.users != null)) {
        repoNames.push("" + repo.name + ".git");
      } else {
        console.log('Bad Repo', repo.name, 'is missing an attribute..');
      }
    }
    return async.reject(repoNames, this.git.exists.bind(this.git), function(results) {
      var _j, _len1;
      if (results.length > 0) {
        for (_j = 0, _len1 = results.length; _j < _len1; _j++) {
          repo = results[_j];
          console.log('Creating repo directory: ', repo);
        }
        return async.map(results, _this.git.create.bind(_this.git), callback);
      } else {
        return callback();
      }
    });
  };


When the git fetch command is triggered, this is fired.

Params
fetch Object Git object from pushover module.


  GitServer.prototype.onFetch = function(fetch) {
    var repo;
    this.log('Got a FETCH call for', fetch.repo);
    repo = this.getRepo(fetch.repo);
    if (repo !== false) {
      if (repo.anonRead === true) {
        this.checkTriggers('fetch', repo);
        return fetch.accept();
      } else {
        return this.processSecurity(fetch, 'fetch', repo);
      }
    } else {
      this.log('Rejected - Repo', fetch.repo, 'doesnt exist');
      return fetch.reject(500, 'This repo doesnt exist');
    }
  };


When the git push command is triggered, this is fired.

Params
push Object Git object from pushover module.


  GitServer.prototype.onPush = function(push) {
    var repo;
    this.log('Got a PUSH call for', push.repo);
    repo = this.getRepo(push.repo);
    if (repo !== false) {
      return this.processSecurity(push, 'push', repo);
    } else {
      this.log('Rejected - Repo', push.repo, 'doesnt exist');
      return push.reject(500, 'This repo doesnt exist');
    }
  };


Check if this repo has onSuccessful triggers

Params
method String fetch|push
repo Object Repo object we are checking


  GitServer.prototype.checkTriggers = function(method, repo) {
    var _base;
    if (repo.onSuccessful != null) {
      if (repo.onSuccessful[method] != null) {
        this.log('On successful triggered: ', method, 'on', repo.name);
        return typeof (_base = repo.onSuccessful)[method] === "function" ? _base[method](repo, method) : void 0;
      }
    }
  };


Get the user object, check user/pass is correct and it exists in this repo.

Params
username String Username to find
password String Password of the Username
repo Object Repo object this user should be in.


  GitServer.prototype.getUser = function(username, password, repo) {
    var userObject, _i, _len, _ref;
    _ref = repo.users;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      userObject = _ref[_i];
      if (userObject.user.username === username && userObject.user.password === password) {
        return userObject;
      }
    }
    return false;
  };


Get the repo from the array of repos

Params
repoName String Name of the repo we are trying to find


  GitServer.prototype.getRepo = function(repoName) {
    var repo, _i, _len, _ref;
    _ref = this.repos;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      repo = _ref[_i];
      if (repo.name + '.git' === repoName) {
        return repo;
      }
    }
    return false;
  };

  return GitServer;

})();

module.exports = GitServer;