diff --git a/.gitignore b/.gitignore
index 959fcc6a6577499d9714f92bd17e6597496e3866..de70e3317e0561b13726a9388ac518b9ad7dc711 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,4 +70,4 @@ typings/
 
 # Ignore temporary backup files created by vim
 *~
-.swp
+*.swp
diff --git a/dylan-testing/common/config.js b/dylan-testing/common/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..f17ca7247f359214d82ce3ffbe57a2fdfcf159c2
--- /dev/null
+++ b/dylan-testing/common/config.js
@@ -0,0 +1,15 @@
+function getConfig(){
+    var config;
+
+    try {config = JSON.parse(process.env.UCCPORTAL_CONFIG);}
+    catch(e){
+        throw new Error(
+            "Failed to retrieve config from UCCPORTAL_CONFIG "
+            + "environment variable: " + e.message
+        );
+    }
+
+    return config;
+}
+
+module.exports = getConfig();
diff --git a/dylan-testing/common/constants.js b/dylan-testing/common/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..87d9c75b1078419f0ebda88c724555fbc0c98d09
--- /dev/null
+++ b/dylan-testing/common/constants.js
@@ -0,0 +1,3 @@
+module.exports = {
+    
+};
diff --git a/dylan-testing/common/socket-proxy.js b/dylan-testing/common/socket-proxy.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb91afa31a7b1eab0c350463d44cbf1277f9d352
--- /dev/null
+++ b/dylan-testing/common/socket-proxy.js
@@ -0,0 +1,44 @@
+var fs = require("fs");
+
+/**
+ * Creates a new middleware for express that proxies all requests to the
+ * specified UNIX domain socket.
+ *
+ * @param {string} socketPath - the path to the socket
+ * @return {function} - an express middleware function
+ */
+module.exports = function socketProxy(socketPath){
+    return function socketProxyMiddleware(req, res, next){
+        fs.stat(socketPath, function(err, stats){
+            // Check that we can actually open the socket,
+            // and that it's a socket
+            try {
+                if(err) throw err;
+                if(!stats.isSocket()) throw new Error("Not a socket");
+            }
+            catch(e){
+                res.status(500).end();
+                console.error(
+                    "Error proxying to socket \"" + socketPath + "\": "
+                    + e.message
+                );
+                return;
+            }
+
+            // Initiate HTTP request to proxy target
+            var reqOpts = {
+                socketPath: socketPath,
+                method: req.method,
+                path: req.path,
+                headers: req.headers
+            };
+            var target = http.request(reqOpts, function onResponse(targetRes){
+                res.writeHead(targetRes.statusCode, targetRes.headers);
+                targetRes.pipe(res);
+                // .pipe() will automatically call res.end()
+            });
+            req.pipe(target);
+        });
+    };
+}
+
diff --git a/dylan-testing/config.json b/dylan-testing/config.json
index f30002be68497d2e94e014fceb4e354c4a5766f7..f9a446381879969343bd2fd0ce5420b540fd8959 100644
--- a/dylan-testing/config.json
+++ b/dylan-testing/config.json
@@ -1,4 +1,7 @@
 {
-	"serviceUser":"uccportal",
-	"varPath":"/var/lib/uccportal"
+	"configPath":"/etc/uccportal/config.json",
+	"varPath":"/var/lib/uccportal",
+    "user":"uccportal",
+    "host":"0.0.0.0",
+    "port":8080
 }
diff --git a/dylan-testing/core/init.js b/dylan-testing/core/init.js
index 314219abc47dfd40e9682f110cea05cb85c47c6b..cfe5b85fc058d34abb1317ab7282a8d81d85c471 100644
--- a/dylan-testing/core/init.js
+++ b/dylan-testing/core/init.js
@@ -20,10 +20,11 @@ function getUserIds(username){
     return ids;
 }
 
-function initConfig(configPath){
+function initConfig(configPath, shadowConfig){
     var rawConfig, config;
 
     // Try to open config file
+    console.info("Loading config from \"" + configPath + "\"");
     try {rawConfig = fs.readFileSync(configPath);}
     catch(e){
         throw new Error(
@@ -32,7 +33,12 @@ function initConfig(configPath){
     }
     
     // Try to parse config file as JSON
-    try {config = JSON.parse(rawConfig);}
+    try {
+        // Make config inherit properties from shadowConfig, so it they're
+        // undefined in the current config file, their defaults take precedence
+        // from the default config file.
+        config = Object.assign({}, shadowConfig, JSON.parse(rawConfig));
+    }
     catch(e){
         throw new Error(
             "\"" + configPath + "\" is not in a valid JSON format: "
@@ -51,12 +57,23 @@ function initConfig(configPath){
             );
         }
 
-        // serviceUser: verify that a user and group of this name exist,
-        // then set serviceUid and serviceGid for later lookup
-        var ids = getUserIds(config.serviceUser);
-        config.serviceUid = ids.uid;
-        config.serviceGid = ids.gid;
+        // user: verify that a user and group of this name exist,
+        // then set uid and gid for later lookup
+        var ids = getUserIds(config.user);
+        config.uid = ids.uid;
+        config.gid = ids.gid;
 
+        // host: verify that host is in a (somewhat) valid format
+        // TODO: Improve this?
+        if(typeof config.host != "string" || !config.host.match(
+            /[a-z0-9:.-]+/i
+        )) throw new Error("host: not a valid hostname nor IP address");
+
+        // port: verify that a valid port was specified
+        if(
+            typeof config.port != "number"
+            || config.port%1 > 0 || config.port < 0 || config.port > 65536
+        ) throw new Error("port: invalid port number specified");
     }
     catch(e){
         throw new Error(
@@ -64,44 +81,41 @@ function initConfig(configPath){
         );
     }
 
+    // configPath: if the config file specifies a different path to a config
+    // file, then try to load this instead.
+    // Config files loaded later inherit from config files loaded earlier :)
+    if(config.configPath){
+        // Make sure that we don't keep re-loading the current config file
+        // recursively and overflow the stack.
+        configPath = config.configPath;
+        delete config.configPath
+        return initConfig(configPath, config);
+    }
+
     return config;
 }
 
 module.exports = function main(){
 	try {
-        // Verify that we're currently running as root
-        if(process.getuid() != 0){
-            throw new Error(
-                "UCCPortal must be run as root to function correctly.\n"
-                + "If you're worried about security, privileges will be dropped "
-                + "to that of the serviceUser listed in config.json, once "
-                + "initialisation is complete. An exception to this is "
-                + "auth-service.js, which requires it for PAM to function "
-                + "correctly."
-            );
-        }
-        
-        // Attempt to load config.json, or fail
+        // Attempt to load config.json
         var config = initConfig("config.json");
-        console.dir(config);
-        
+
+        // Set up environment for child processes
+        process.env.UCCPORTAL_CONFIG = JSON.stringify(config);
+        process.env.NODE_PATH = 
+            (process.env.NODE_PATH ? process.env.NODE_PATH + ":" : "")
+            + process.cwd()
+        ;
+
         // Launch services
         var nodejs = process.argv[0];
-        var spawnOpts = {
-            stdio:"inherit", uid:config.serviceUid, gid:config.serviceGid
-        };
-        var rootSpawnOpts = {stdio:"inherit", gid:config.serviceGid};
-        cproc.spawn(nodejs, ["core/pam-service.js"], rootSpawnOpts);
-        cproc.spawn(nodejs, ["core/proxy-service.js"], spawnOpts);
+        var spawnOpts = {stdio: "inherit"};
+        cproc.spawn(nodejs, ["core/web-service.js"], spawnOpts);
         cproc.spawn(nodejs, ["core/login-service.js"], spawnOpts);
-
-        // Drop privileges
-        process.setgid(config.serviceGid);
-        process.setuid(config.serviceUid);
     }
     catch(e){
         //console.error("Fatal Error: " + e.message);
-        throw e;
         process.exitCode = 1;
+        throw e;
     }
 }
diff --git a/dylan-testing/core/login-service.js b/dylan-testing/core/login-service.js
index 664ade79430d72f39f58816a0d3eb18f1b02979d..fcbc2410a70af0686e7b840e30447392357c6e71 100644
--- a/dylan-testing/core/login-service.js
+++ b/dylan-testing/core/login-service.js
@@ -1 +1,19 @@
-while(true);
+var express = require("express");
+var fs = require("fs");
+
+var config;
+var app;
+
+function main(){
+    // Read in config from environment
+    config = JSON.parse(process.env.UCCPORTAL_CONFIG);
+    
+    app = express();
+    app.get("/", function(req, res){
+        res.write("Hello World!");
+        res.end();
+    });
+
+    app.listen(config.varPath + "/login-service.sock");
+}
+main();
diff --git a/dylan-testing/core/pam-service.js b/dylan-testing/core/pam-service.js
deleted file mode 100644
index 664ade79430d72f39f58816a0d3eb18f1b02979d..0000000000000000000000000000000000000000
--- a/dylan-testing/core/pam-service.js
+++ /dev/null
@@ -1 +0,0 @@
-while(true);
diff --git a/dylan-testing/core/proxy-service.js b/dylan-testing/core/proxy-service.js
deleted file mode 100644
index 664ade79430d72f39f58816a0d3eb18f1b02979d..0000000000000000000000000000000000000000
--- a/dylan-testing/core/proxy-service.js
+++ /dev/null
@@ -1 +0,0 @@
-while(true);
diff --git a/dylan-testing/core/web-service.js b/dylan-testing/core/web-service.js
new file mode 100644
index 0000000000000000000000000000000000000000..cef2e70bf379de11e870921ca6f5e6b1e272baff
--- /dev/null
+++ b/dylan-testing/core/web-service.js
@@ -0,0 +1,25 @@
+var express = require("express");
+var http = require("http");
+var fs = require("fs");
+var config = require("common/config.js");
+var socketProxy = require("common/socket-proxy.js");
+
+var config;
+var app;
+
+function main(){
+    // Set up express app and routes
+    app = express();
+    app.use("/login", socketProxy(config.varPath + "/login-service.sock"));
+
+    // Start web server or crash
+    console.info(
+        "Starting web-service on host \"" + config.host
+        + "\" and port " + config.port
+    );
+    try {app.listen(config.port, config.host);}
+    catch(e){
+        throw new Error("Failed to start web-service: " + e.message);
+    }
+}
+main();
diff --git a/dylan-testing/package.json b/dylan-testing/package.json
index 94c7b195d3068badbe5b289991d577c4537ae6c3..74539c6bfd36d0612abeb968b236bada8e4b0bcb 100644
--- a/dylan-testing/package.json
+++ b/dylan-testing/package.json
@@ -11,5 +11,8 @@
     "url": "https://gitlab.ucc.asn.au/frekk/uccportal.git"
   },
   "author": "dylanh333; frekk",
-  "license": "GPL-3.0"
+  "license": "GPL-3.0",
+  "dependencies": {
+    "express": "^4.16.3"
+  }
 }