]> PHS Git Server - phs-api.git/commitdiff
Adding new routes for git, vpn, and docker.
authorcharleswrayjr <charleswrayjr@gmail.com>
Fri, 5 Sep 2025 21:03:42 +0000 (16:03 -0500)
committercharleswrayjr <charleswrayjr@gmail.com>
Fri, 5 Sep 2025 21:03:42 +0000 (16:03 -0500)
13 files changed:
.idea/jsLibraryMappings.xml
.idea/phs-api.iml
app.js
package.json
src/controllers/auth.controller.js [new file with mode: 0644]
src/controllers/docker.controller.js [new file with mode: 0644]
src/controllers/git.controller.js
src/controllers/vpn.controller.js [new file with mode: 0644]
src/routes/auth.routes.js [new file with mode: 0644]
src/routes/docker.routes.js [new file with mode: 0644]
src/routes/git.routes.js
src/routes/index.js
src/routes/vpn.routes.js [new file with mode: 0644]

index d23208fbb7168875464dd7aaff4169c37be615ea..d76b16e26ce89c7b1e83a4f1f5537bfbee181217 100755 (executable)
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="JavaScriptLibraryMappings">
+    <file url="PROJECT" libraries="{@types/dockerode}" />
     <includedPredefinedLibrary name="Node.js Core" />
   </component>
 </project>
\ No newline at end of file
index d16c9d60b6d0fe45ef3839d912127c0f29449a45..3ef1604273da621a044df2d75165681919fc96d9 100755 (executable)
@@ -7,8 +7,10 @@
       <excludeFolder url="file://$MODULE_DIR$/tmp" />
       <excludeFolder url="file://$MODULE_DIR$/.zencoder" />
       <excludeFolder url="file://$MODULE_DIR$/bin" />
+      <excludeFolder url="file://$MODULE_DIR$/pm2logs" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="@types/dockerode" level="application" />
   </component>
 </module>
\ No newline at end of file
diff --git a/app.js b/app.js
index 9eb969ca018b366532fb3069d71c158444ebdc09..b7f29d0cf466f96abaab3d61efb5c0ca75c5e77f 100755 (executable)
--- a/app.js
+++ b/app.js
@@ -12,6 +12,9 @@ const runOnce = require ( './src/middleware/custom-startup' );
 const Routes = require( './src/routes' );
 const phsdb = require( './src/phsdb' );
 const fs = require('fs');
+const sshConfig = {
+  host:'192.168.1.62', port:22, username:'git', privateKey:fs.readFileSync( '/home/node/.ssh/git-ui' ),
+};
 
 const buffer = require('buffer');
 global.Blob = buffer.Blob
@@ -27,6 +30,8 @@ app.use((req, res, next) => {
 });
 
 global.phsdb = phsdb;
+global.fs = fs;
+global.sshConfig = sshConfig;
 
 // noinspection JSCheckFunctionSignatures
 app.use( bodyParser.json( {
index ed77ba239688e52117bb39d9f459169fe8e17428..4372ab0d2c2ddc9972a460a4e5b4937cd6be22be 100755 (executable)
@@ -10,6 +10,7 @@
     "cookie-parser": "~1.4.4",
     "cors": "^2.8.5",
     "debug": "~2.6.9",
+    "dockerode": "^4.0.7",
     "express": "5.0.0",
     "express-compression": "^1.0.2",
     "http-errors": "~1.6.3",
diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js
new file mode 100644 (file)
index 0000000..bf429b6
--- /dev/null
@@ -0,0 +1,5 @@
+module.exports = {
+  login: (req, res, next) => {
+    console.log('login');
+  },
+};
\ No newline at end of file
diff --git a/src/controllers/docker.controller.js b/src/controllers/docker.controller.js
new file mode 100644 (file)
index 0000000..475310e
--- /dev/null
@@ -0,0 +1,83 @@
+const { Client } = require( 'ssh2' );
+
+module.exports = {
+  stopContainer:async ( req, res, next ) => {
+    const { containerId } = req.body;
+    if (!containerId) {
+      return res.status( 400 ).json( { error:'Container ID required' } );
+    }
+
+    try {
+      const container = docker.getContainer( containerId );
+      await container.stop();
+      res.json( { message:`Container ${ containerId } stopped successfully` } );
+    } catch (error) {
+      res.status( 500 ).json( { error:`Failed to stop container: ${ error.message }` } );
+    }
+  }, startContainer:async ( req, res, next ) => {
+    const { containerId } = req.body;
+    if (!containerId) {
+      return res.status( 400 ).json( { error:'Container ID required' } );
+    }
+
+    try {
+      const container = docker.getContainer( containerId );
+      await container.start();
+      res.json( { message:`Container ${ containerId } started successfully` } );
+    } catch (error) {
+      res.status( 500 ).json( { error:`Failed to start container: ${ error.message }` } );
+    }
+  }, getContainers:async ( req, res, next ) => {
+    try {
+      const { all } = req.body;
+      const containers = await docker.listContainers( { all } );
+      res.json( {
+        containers:containers.map( c => ({
+          id:c.Id, name:c.Names[0], state:c.State, status:c.Status,
+        }) ),
+      } );
+    } catch (error) {
+      res.status( 500 ).json( { error:`Failed to list containers: ${ error.message }` } );
+    }
+  }, getImages:async ( req, res, next ) => {
+    try {
+      const images = await docker.listImages();
+      res.json( {
+        images:images.map( i => ({
+          id:i.Id, names:i.RepoTags, tags:i.RepoTags, size:i.Size, created:new Date( i.Created * 1000 ).toISOString(),
+        }) ),
+      } );
+    } catch (error) {
+      res.status( 500 ).json( { error:`Failed to list images: ${ error.message }` } );
+    }
+  }, composeDown:async ( req, res, next ) => {
+    const { composeFile } = req.body;
+    if (!composeFile) {
+      return res.status( 400 ).json( { error:'Compose file path required' } );
+    }
+
+    const conn = new Client();
+    conn.on( 'ready', () => {
+      const command = `docker compose -f ${ composeFile } down`;
+      conn.exec( command, ( err, stream ) => {
+        if (err) {
+          conn.end();
+          return res.status( 500 ).json( { error:'SSH command failed' } );
+        }
+        let output = '';
+        stream.on( 'data', ( data ) => (output += data) );
+        stream.stderr.on( 'data', ( data ) => (output += data) );
+        stream.on( 'close', ( code ) => {
+          conn.end();
+          if (code === 0) {
+            res.json( { message:`Compose services in ${ composeFile } stopped successfully` } );
+          } else {
+            res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+          }
+        } );
+      } );
+    } ).on( 'error', ( err ) => {
+      res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+    } ).connect( sshConfig );
+  }
+};
\ No newline at end of file
index a5953b951e6b8caff4a3a59829224fef0ee0c431..9d21f2adecfc60d152424fe8d6ec1f8839fe50eb 100755 (executable)
-const fs = require('fs');
-const { Client } = require('ssh2');
-const sshConfig = {
-  host: '192.168.1.62',
-  port: 22,
-  username: 'git',
-  privateKey: fs.readFileSync('/home/node/.ssh/git-ui'),
-};
+const { Client } = require( 'ssh2' );
 
 module.exports = {
-  createRepo: (req, res, next) => {
-      const { name, type, user } = req.body;
-      if (!name.match(/^[a-zA-Z0-9_-]+$/)) {
-        return res.status(400).json({ error: 'Invalid repository name' });
-      }
-      if (type === 'private' && !user) {
-        return res.status(400).json({ error: 'Username required for private repository' });
-      }
+  createRepo:( req, res, next ) => {
+    const { name, type, user } = req.body;
+    if (!name.match( /^[a-zA-Z0-9_-]+$/ )) {
+      return res.status( 400 ).json( { error:'Invalid repository name' } );
+    }
+    if (type === 'private' && !user) {
+      return res.status( 400 ).json( { error:'Username required for private repository' } );
+    }
+
+    const conn = new Client();
+    conn.on( 'ready', () => {
+      const commands = type === 'private' ? [`mkdir /opt/git/${ name }.git`, `cd /opt/git/${ name }.git`, `git init --bare`, `chown -R ${ user }:git /opt/git/${ name }.git`, `chmod -R u+rwX,go-rwx /opt/git/${ name }.git`,] : [`mkdir /opt/git/${ name }.git`, `cd /opt/git/${ name }.git`, `git init --bare --shared=group`, `chown -R git:git /opt/git/${ name }.git`, `chmod -R g+rwX /opt/git/${ name }.git`, `find /opt/git/${ name }.git -type d -exec chmod g+s {} \\;`, `git config core.sharedRepository group`,];
 
-      const conn = new Client();
-      conn.on('ready', () => {
-        const commands = type === 'private'
-          ? [
-            `mkdir /opt/git/${name}.git`,
-            `cd /opt/git/${name}.git`,
-            `git init --bare`,
-            `chown -R ${user}:git /opt/git/${name}.git`,
-            `chmod -R u+rwX,go-rwx /opt/git/${name}.git`,
-          ]
-          : [
-            `mkdir /opt/git/${name}.git`,
-            `cd /opt/git/${name}.git`,
-            `git init --bare --shared=group`,
-            `chown -R git:git /opt/git/${name}.git`,
-            `chmod -R g+rwX /opt/git/${name}.git`,
-            `find /opt/git/${name}.git -type d -exec chmod g+s {} \\;`,
-            `git config core.sharedRepository group`,
-          ];
+      conn.exec( `sudo -u git bash -c "${ commands.join( ' && ' ) }"`, ( err, stream ) => {
+        if (err) {
+          conn.end();
+          return res.status( 500 ).json( { error:'SSH command failed' } );
+        }
+        let output = '';
+        stream.on( 'data', ( data ) => (output += data) );
+        stream.stderr.on( 'data', ( data ) => (output += data) );
+        stream.on( 'close', ( code ) => {
+          conn.end();
+          if (code === 0) {
+            res.json( { message:`Repository ${ name }.git created successfully` } );
+          } else {
+            res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+          }
+        } );
+      } );
+    } ).on( 'error', ( err ) => {
+      res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+    } ).connect( sshConfig );
+  }, getRepos:async ( req, res, next ) => {
+    const conn = new Client();
+    conn.on( 'ready', () => {
+      const command = `ls -1 /opt/git | grep '\.git$' | sed 's/\.git$//'`;
+      conn.exec( command, ( err, stream ) => {
+        if (err) {
+          conn.end();
+          return res.status( 500 ).json( { error:'SSH command failed' } );
+        }
+        let output = '';
+        stream.on( 'data', ( data ) => (output += data) );
+        stream.stderr.on( 'data', ( data ) => (output += data) );
+        stream.on( 'close', ( code ) => {
+          conn.end();
+          if (code === 0) {
+            const repos = output.trim().split( '\n' ).map( name => ({
+              name, cloneUrl:`git@192.168.1.62:/opt/git/${ name }.git`,
+            }) );
+            res.json( { repos } );
+          } else {
+            res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+          }
+        } );
+      } );
+    } ).on( 'error', ( err ) => {
+      res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+    } ).connect( sshConfig );
+  }, deleteRepo:async ( req, res, next ) => {
+    const { name } = req.params;
+    if (!name.match( /^[a-zA-Z0-9_-]+$/ )) {
+      return res.status( 400 ).json( { error:'Invalid repository name' } );
+    }
 
-        conn.exec(`sudo -u git bash -c "${commands.join(' && ')}"`, (err, stream) => {
-          if (err) {
-            conn.end();
-            return res.status(500).json({ error: 'SSH command failed' });
+    const conn = new Client();
+    conn.on( 'ready', () => {
+      const command = `rm -rf /opt/git/${ name }.git`;
+      conn.exec( command, ( err, stream ) => {
+        if (err) {
+          conn.end();
+          return res.status( 500 ).json( { error:'SSH command failed' } );
+        }
+        let output = '';
+        stream.on( 'data', ( data ) => (output += data) );
+        stream.stderr.on( 'data', ( data ) => (output += data) );
+        stream.on( 'close', ( code ) => {
+          conn.end();
+          if (code === 0) {
+            res.json( { message:`Repository ${ name } deleted successfully` } );
+          } else {
+            res.status( 500 ).json( { error:`Command failed: ${ output }` } );
           }
-          let output = '';
-          stream.on('data', (data) => (output += data));
-          stream.stderr.on('data', (data) => (output += data));
-          stream.on('close', (code) => {
-            conn.end();
-            if (code === 0) {
-              res.json({ message: `Repository ${name}.git created successfully` });
-            } else {
-              res.status(500).json({ error: `Command failed: ${output}` });
-            }
-          });
-        });
-      }).on('error', (err) => {
-        res.status(500).json({ error: `SSH connection failed: ${err.message}` });
-      }).connect(sshConfig);
+        } );
+      } );
+    } ).on( 'error', ( err ) => {
+      res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+    } ).connect( sshConfig );
+  }, cloneRepo:async ( req, res, next ) => {
+    const { repoName, deployPath, user } = req.body;
+    if (!repoName || !deployPath || !user) {
+      return res.status( 400 ).json( { error:'Repository name, deployment path, and user are required' } );
+    }
+    if (!repoName.match( /^[a-zA-Z0-9_-]+$/ )) {
+      return res.status( 400 ).json( { error:'Invalid repository name' } );
     }
+
+    const conn = new Client();
+    conn.on( 'ready', () => {
+      const commands = [`mkdir -p ${ deployPath }`, `chown ${ user }:${ user } ${ deployPath }`, `su - ${ user } -c "cd ${ deployPath } && git clone git@localhost:/opt/git/${ repoName }.git ."`,];
+
+      conn.exec( commands.join( ' && ' ), ( err, stream ) => {
+        if (err) {
+          conn.end();
+          return res.status( 500 ).json( { error:'SSH command failed' } );
+        }
+        let output = '';
+        stream.on( 'data', ( data ) => (output += data) );
+        stream.stderr.on( 'data', ( data ) => (output += data) );
+        stream.on( 'close', ( code ) => {
+          conn.end();
+          if (code === 0) {
+            res.json( { message:`Repository ${ repoName } cloned to ${ deployPath }` } );
+          } else {
+            res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+          }
+        } );
+      } );
+    } ).on( 'error', ( err ) => {
+      res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+    } ).connect( sshConfig );
+  },
 };
\ No newline at end of file
diff --git a/src/controllers/vpn.controller.js b/src/controllers/vpn.controller.js
new file mode 100644 (file)
index 0000000..f0814d6
--- /dev/null
@@ -0,0 +1,99 @@
+const { Client } = require( 'ssh2' );
+
+module.exports = {
+  createClient:( req, res, next ) => {
+    const { clientName, staticIp } = req.body;
+    if (!clientName) {
+      return res.status( 400 ).json( { error:'Client name required' } );
+    }
+    if (staticIp && !staticIp.match( /^10\.8\.0\.\d{1,3}$/ )) {
+      return res.status( 400 ).json( { error:'Invalid static IP (must be 10.8.0.x)' } );
+    }
+
+    const conn = new Client();
+    conn.on( 'ready', () => {
+      const commands = [`cd /etc/openvpn/easy-rsa`, `./easyrsa build-client-full ${ clientName } nopass`, `mkdir -p /etc/openvpn/client-configs`, staticIp ? `echo "ifconfig-push ${ staticIp } 255.255.255.0" > /etc/openvpn/client-configs/${ clientName }.ccd` : 'true', `/usr/share/easy-rsa/pkitool ${ clientName }`, `openssl pkcs12 -export -in pki/issued/${ clientName }.crt -inkey pki/private/${ clientName }.key -certfile pki/ca.crt -out /etc/openvpn/client-configs/${ clientName }.p12 -passout pass:`, `openvpn --genkey --secret /etc/openvpn/client-configs/${ clientName }.tls-auth`, `cat /etc/openvpn/client-template.txt | sed "s/CLIENT_NAME/${ clientName }/" > /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "<ca>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat pki/ca.crt >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "</ca>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "<cert>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat pki/issued/${ clientName }.crt >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "</cert>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "<key>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat pki/private/${ clientName }.key >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "</key>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "<tls-auth>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat /etc/openvpn/client-configs/${ clientName }.tls-auth >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "</tls-auth>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat /etc/openvpn/client-configs/${ clientName }.ovpn`,];
+
+      conn.exec( commands.join( ' && ' ), ( err, stream ) => {
+        if (err) {
+          conn.end();
+          return res.status( 500 ).json( { error:'SSH command failed' } );
+        }
+        let output = '';
+        stream.on( 'data', ( data ) => (output += data) );
+        stream.stderr.on( 'data', ( data ) => (output += data) );
+        stream.on( 'close', ( code ) => {
+          conn.end();
+          if (code === 0) {
+            res.json( {
+              message:`Client ${ clientName } created${ staticIp ? ` with IP ${ staticIp }` : '' }`,
+              ovpn:output
+            } );
+          } else {
+            res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+          }
+        } );
+      } );
+    } ).on( 'error', ( err ) => {
+      res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+    } ).connect( sshConfig );
+  }, revokeClient:( req, res, next ) => {
+    const { clientName } = req.body;
+    if (!clientName) {
+      return res.status( 400 ).json( { error:'Client name required' } );
+    }
+
+    const conn = new Client();
+    conn.on( 'ready', () => {
+      const commands = [`cd /etc/openvpn/easy-rsa`, `./easyrsa revoke ${ clientName }`, `./easyrsa gen-crl`, `cp pki/crl.pem /etc/openvpn/crl.pem`, `rm -f /etc/openvpn/client-configs/${ clientName }.ccd`, `systemctl restart openvpn@server`,];
+
+      conn.exec( commands.join( ' && ' ), ( err, stream ) => {
+        if (err) {
+          conn.end();
+          return res.status( 500 ).json( { error:'SSH command failed' } );
+        }
+        let output = '';
+        stream.on( 'data', ( data ) => (output += data) );
+        stream.stderr.on( 'data', ( data ) => (output += data) );
+        stream.on( 'close', ( code ) => {
+          conn.end();
+          if (code === 0) {
+            res.json( { message:`Client ${ clientName } revoked` } );
+          } else {
+            res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+          }
+        } );
+      } );
+    } ).on( 'error', ( err ) => {
+      res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+    } ).connect( sshConfig );
+  }, getClients:async ( req, res, next ) => {
+    const conn = new Client();
+    conn.on( 'ready', () => {
+      const command = `cat /etc/openvpn/server/status`;
+      conn.exec( command, ( err, stream ) => {
+        if (err) {
+          conn.end();
+          return res.status( 500 ).json( { error:'SSH command failed' } );
+        }
+        let output = '';
+        stream.on( 'data', ( data ) => (output += data) );
+        stream.stderr.on( 'data', ( data ) => (output += data) );
+        stream.on( 'close', ( code ) => {
+          conn.end();
+          if (code === 0) {
+            const clients = output.split( '\n' ).filter( line => line.includes( 'CLIENT_LIST' ) ).map( line => {
+              const parts = line.split( ',' );
+              return { name:parts[1], ip:parts[2], connectedSince:parts[8] };
+            } );
+            res.json( { clients } );
+          } else {
+            res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+          }
+        } );
+      } );
+    } ).on( 'error', ( err ) => {
+      res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+    } ).connect( sshConfig );
+  }
+};
\ No newline at end of file
diff --git a/src/routes/auth.routes.js b/src/routes/auth.routes.js
new file mode 100644 (file)
index 0000000..805421b
--- /dev/null
@@ -0,0 +1,9 @@
+const express = require('express');
+const router = express.Router();
+const { validateAuth } = require('../middleware/routeHelpers');
+const authController = require('../controllers/auth.controller');
+
+module.exports = (passport) => {
+    router.post('/login', authController.login);
+    return router;
+};
\ No newline at end of file
diff --git a/src/routes/docker.routes.js b/src/routes/docker.routes.js
new file mode 100644 (file)
index 0000000..e9e9668
--- /dev/null
@@ -0,0 +1,13 @@
+const express = require( 'express' );
+const router = express.Router();
+const { validateAuth } = require( '../middleware/routeHelpers' );
+const dockerController = require( '../controllers/docker.controller' );
+
+module.exports = ( passport ) => {
+  router.post( '/start-container', dockerController.startContainer );
+  router.post( '/stop-container', dockerController.stopContainer );
+  router.get( '/images', dockerController.getImages );
+  router.get( '/containers', dockerController.getContainers );
+  router.post( '/compose-down', dockerController.composeDown );
+  return router;
+};
\ No newline at end of file
index 6c80c2cf9e05c2c53b4026e65948fbb38c690fb3..4f41df8102f3fdf0b8f0bd6fbebe5e488d7eebda 100755 (executable)
@@ -1,11 +1,14 @@
-const express = require('express');
+const express = require( 'express' );
 const router = express.Router();
-const { validateAuth } = require('../middleware/routeHelpers');
-const gitController = require('../controllers/git.controller');
+const { validateAuth } = require( '../middleware/routeHelpers' );
+const gitController = require( '../controllers/git.controller' );
 
-module.exports = (passport) => {
+module.exports = ( passport ) => {
   // router.use( validateAuth( passport ) );
-  router.post('/create-repo', gitController.createRepo);
+  router.post( '/create-repo', gitController.createRepo );
+  router.get( '/', gitController.getRepos );
+  router.delete( '/:name', gitController.deleteRepo );
+  router.get( '/clone-repo', gitController.cloneRepo );
 
   return router;
 };
\ No newline at end of file
index d1a1c0b66315c701dcb84c87615f50d432a5021f..ab3c644a9e7de1447a48c82d46be0fb0a22c50ee 100755 (executable)
@@ -1,12 +1,10 @@
-const express = require('express');
+const express = require( 'express' );
 const router = express.Router();
 
 module.exports.APIRoutes = ( passport ) => {
-  /* GET home page. */
-  router.get('/', function(req, res, next) {
-    res.render('index', { title: 'Express' });
-  });
 
-  router.use( '/git', require('./git.routes')(passport) );
+  router.use( '/git', require( './git.routes' )( passport ) );
+  router.use( '/docker', require( './docker.routes' )( passport ) );
+  router.use( '/vpn', require( './vpn.routes' )( passport ) );
   return router;
-}
+};
diff --git a/src/routes/vpn.routes.js b/src/routes/vpn.routes.js
new file mode 100644 (file)
index 0000000..48da868
--- /dev/null
@@ -0,0 +1,11 @@
+const express = require( 'express' );
+const router = express.Router();
+const { validateAuth } = require( '../middleware/routeHelpers' );
+const vpnController = require( '../controllers/vpn.controller' );
+
+module.exports = ( passport ) => {
+  router.post( '/create-client', validateAuth( passport ), vpnController.createClient );
+  router.delete( '/revoke-client/', validateAuth( passport ), vpnController.revokeClient );
+  router.get( '/clients', validateAuth( passport ), vpnController.getClients );
+  return router;
+};
\ No newline at end of file