diff --git a/package-lock.json b/package-lock.json
index d863d4643a20fa314c26d1652e8751b34bd74ce0..2adb6e0d1d7bdb5c3676ca357aad9e595bea52ae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -366,6 +366,15 @@
         "minimist": "^1.2.0"
       }
     },
+    "@commander-lol/vault-client": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/@commander-lol/vault-client/-/vault-client-0.1.1.tgz",
+      "integrity": "sha512-fZDlKNIX2vF/JLOqV5zUI7QsOSnmjX8huEIt65yWDk3hMTk6Kyd2lDdcvwdjqOlwmNTRRv9OijDfOayQ0ObDEw==",
+      "requires": {
+        "date-fns": "^2.17.0",
+        "node-fetch": "^2.6.1"
+      }
+    },
     "@google-cloud/common": {
       "version": "3.5.0",
       "resolved": "https://npm.lcr.gr/@google-cloud%2fcommon/-/common-3.5.0.tgz",
@@ -2603,6 +2612,11 @@
       "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.1.tgz",
       "integrity": "sha512-M4RggEH5OF2ZuCOxgOU67R6Z9ohjKbxGvAQz48vj53wLmL0bAgumkBvycR32f30pK+Og9pIR+RFDyChbaE4oLA=="
     },
+    "date-fns": {
+      "version": "2.17.0",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.17.0.tgz",
+      "integrity": "sha512-ZEhqxUtEZeGgg9eHNSOAJ8O9xqSgiJdrL0lzSSfMF54x6KXWJiOH/xntSJ9YomJPrYH/p08t6gWjGWq1SDJlSA=="
+    },
     "deasync": {
       "version": "0.1.21",
       "resolved": "https://npm.lcr.gr/deasync/-/deasync-0.1.21.tgz",
diff --git a/package.json b/package.json
index 7dd8c9be4650d8d89b58b88411883b172061e868..13d1e659bf5e0630f870966c5bc88af921f894e6 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
   "author": "Louis Capitanchik <louis@microhacks.co.uk>",
   "license": "GPL-3.0+",
   "dependencies": {
+    "@commander-lol/vault-client": "^0.1.1",
     "@google-cloud/storage": "^5.5.0",
     "@koa/cors": "^3.1.0",
     "@koa/multer": "^3.0.0",
diff --git a/src/config/vault.js b/src/config/vault.js
index 6a1853fd7cb3f8ddfe8e6a6f748eb8d5f8e62567..9454c3ac646fa6e4910860aebbdb15926364d39d 100644
--- a/src/config/vault.js
+++ b/src/config/vault.js
@@ -3,7 +3,7 @@ const { env } = require('bootstrap')
 module.exports = {
 	base_url: env('VAULT_BASE_URL'),
 	totp_path: env('VAULT_TOTP_PATH', '/v1/totp'),
-	kv_path: env('VAULT_KV_PATH', '/v1/kv/data'),
+	kv_path: env('VAULT_KV_PATH', '/v1/kv'),
 	auth_path: env('VAULT_AUTH_PATH'),
 	credentials: {
 		role_id: env('VAULT_ROLE_ID'),
diff --git a/src/services/totp/vault.js b/src/services/totp/vault.js
index 2651d792967de96d872e97ba588e9d96191c5f03..eb2fac397f3d1391df100dd2f0b9ad8ac6869fcd 100644
--- a/src/services/totp/vault.js
+++ b/src/services/totp/vault.js
@@ -1,72 +1,31 @@
 const TotpProvider = require('./interface')
 const { config } = require('bootstrap')
-const fetch = require('node-fetch')
 const { URL } = require('url')
-const pathUtil = require('path')
-const moment = require('moment')
 const threadContext = require('core/injection/ThreadContext')
+const { VaultClient, VaultSimpleAuth, VaultKVStore, VaultTOTPStore } = require('@commander-lol/vault-client')
 
 class VaultTotpProvider extends TotpProvider {
 	constructor() {
 		super()
 		this.config = config('totp')
-		this.baseUrl = config('vault.base_url')
-		this.authPath = config('vault.auth_path')
-		this.totpPath = config('vault.totp_path')
-		this.kvPath = config('vault.kv_path')
-		this.credentials = config('vault.credentials')
-
-		this.token = null
-	}
-
-	createUrl(...path) {
-		const fullPath = pathUtil.join(...path)
-		return (new URL(fullPath, this.baseUrl)).toString()
-	}
-
-	async refreshToken() {
-		return threadContext.profile('totp.refreshToken', undefined, async () => {
-			const path = this.createUrl(this.authPath)
-			const result = await fetch(path, {
-				method: 'post',
-				body: JSON.stringify(this.credentials),
-				headers: {
-					'Content-Type': 'application/json',
+		this.client = new VaultClient(config('vault.base_url'), {
+			auth: VaultSimpleAuth,
+			stores: {
+				kv: VaultKVStore,
+				totp: VaultTOTPStore,
+			},
+			options: {
+				auth: {
+					path: config('vault.auth_path'),
+					credentials: config('vault.credentials')
 				},
-			})
-
-			if (result.ok) {
-				const payload = await result.json()
-				const token = payload?.auth?.client_token
-
-				if (token == null) {
-					this.token = null
-				} else {
-					const expires = moment.utc().add(payload?.auth?.lease_duration ?? 5 * 60, 'seconds')
-					this.token = {
-						token,
-						expires,
-					}
-				}
-			} else {
-				this.token = null
-			}
-		})
-	}
-
-	async preflight() {
-		return threadContext.profile('totp.preflight', undefined, async () => {
-			if (this.token == null) {
-				await this.refreshToken()
-			} else if (this.token.expires.clone().subtract(30, 'seconds').isSameOrBefore(moment.utc())) { // Less than 30 seconds from expiry
-				await this.refreshToken()
-			}
-
-			if (this.token == null) {
-				throw new TypeError('Unable to get vault lease')
-			}
-
-			return this.token
+				kv: {
+					path: config('vault.kv_path'),
+				},
+				totp: {
+					path: config('vault.totp_path'),
+				},
+			},
 		})
 	}
 
@@ -78,28 +37,8 @@ class VaultTotpProvider extends TotpProvider {
 			const id = user.id
 			const email = user.email
 
-			const payload = {
-				account_name: email,
-				issuer: 'Jetsam',
-				generate: true,
-			}
-			const path = this.createUrl(this.totpPath, 'keys', id)
-			const { token } = await this.preflight()
-
-			const response = await fetch(path, {
-				method: 'post',
-				body: JSON.stringify(payload),
-				headers: {
-					'Content-Type': 'application/json',
-					'X-Vault-Token': token,
-				},
-			})
-
-			if (response.ok) {
-				const { data } = await response.json()
-				const barcode = data?.barcode
-				const url = data?.url
-
+			try {
+				const { barcode, url } = await this.client.stores.totp.createProvider(id, 'Jetsam', email)
 				const parsed = new URL(url)
 				const secret = parsed.searchParams.get('secret')
 
@@ -108,9 +47,10 @@ class VaultTotpProvider extends TotpProvider {
 					url,
 					secret,
 				}
+			} catch(e) {
+				console.error(e)
+				return null
 			}
-
-			return null
 		})
 	}
 
@@ -122,71 +62,42 @@ class VaultTotpProvider extends TotpProvider {
 			throw new TypeError('Must provide a TOTP code to verify')
 		}
 
-		const path = this.createUrl(this.totpPath, 'code', id)
-		const payload = { code }
-		const { token } = await this.preflight()
-
-		const response = await fetch(path, {
-			method: 'post',
-			body: JSON.stringify(payload),
-			headers: {
-				'Content-Type': 'application/json',
-				'X-Vault-Token': token,
-			},
-		})
-
-		if (response.ok) {
-			const { data } = await response.json()
-			return data?.valid ?? false
+		try {
+			const { valid } = await this.client.stores.totp.verify(id, code)
+			return valid
+		} catch (e) {
+			return false
 		}
-
-		return false
-	}
-
-	async _saveRecoveryCodes(id, codes) {
-		const path = this.createUrl(this.kvPath, 'totp_recovery', id)
-		const payload = { data: { codes } }
-		const { token } = await this.preflight()
-
-		return await fetch(path, {
-			method: 'post',
-			body: JSON.stringify(payload),
-			headers: {
-				'Content-Type': 'application/json',
-				'X-Vault-Token': token,
-			},
-		})
 	}
 
 	async createRecoveryCodes(userid) {
-		await this.preflight()
 		const crypto = require('core/utils/crypto')
 		let codes = [null, null, null, null, null]
 		codes = await Promise.all(codes.map(c => crypto.secureHexString(16)))
 		const hashes = await Promise.all(codes.map(c => crypto.hash(c)))
 
-		const response = await this._saveRecoveryCodes(userid, hashes)
-		if (response.ok) {
-			return { codes }
+		try {
+			await this.client.stores.kv.write(`totp_recovery/${ userid }`, { codes: hashes })
+		} catch (e) {
+			console.log(e)
+			return null
 		}
-		return null
+		return { codes }
 	}
 
 	async verifyRecoveryCode(userid, code) {
 		const crypto = require('core/utils/crypto')
-		const { token } = await this.preflight()
-		const path = this.createUrl(this.kvPath, 'totp_recovery', userid)
+		let data = null
 
-		const response = await fetch(path, {
-			headers: {
-				'X-Vault-Token': token,
-			},
-		})
+		try {
+			;({ data } = await this.client.stores.kv.read(`totp_recovery/${ userid }`))
+		} catch (e) {
+			console.log(e)
+		}
 
-		if (response.ok) {
-			const { data } = await response.json()
-			const { codes } = data.data
+		const codes = data?.codes
 
+		if (codes) {
 			let found = null
 			search: for (const hash of codes) {
 				if (await crypto.verify(hash, code)) {
@@ -197,7 +108,7 @@ class VaultTotpProvider extends TotpProvider {
 
 			if (found != null) {
 				const newCodes = codes.filter(c => c !== found)
-				await this._saveRecoveryCodes(userid, newCodes)
+				await this.client.stores.kv.write(`totp_recovery/${ userid }`, { codes: newCodes })
 				return true
 			}
 		}