Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ DB_DB_INFO=verida_db_info
GC_PERCENT=0.1
# Verida Private Key as hex string (used to sign responses). Including leading 0x.
VDA_PRIVATE_KEY=
# Default maximum number of Megabytes for a storage context
DEFAULT_USER_CONTEXT_LIMIT_MB=10
# Maximum number of users supported by this node
MAX_USERS=10000

// alpha numeric only
DB_PUBLIC_USER=784c2n780c9cn0789
Expand Down
8 changes: 8 additions & 0 deletions src/components/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ class Db {
return env.DB_PROTOCOL + "://" + env.DB_HOST + ":" + env.DB_PORT;
}

// Total number of users in the system
async totalUsers() {
const couch = db.getCouch()
const usersDb = couch.db.use('_users')
const info = await usersDb.info()
return info.doc_count
}

}

const db = new Db()
Expand Down
40 changes: 37 additions & 3 deletions src/components/userManager.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import crypto from 'crypto';
import Db from './db.js'
import Utils from './utils.js'
import DbManager from './dbManager.js';

import dotenv from 'dotenv';
dotenv.config();

Expand Down Expand Up @@ -27,16 +30,26 @@ class UserManager {
}

async create(username, signature) {
const maxUsers = parseInt(process.env.MAX_USERS)
const currentUsers = await Db.totalUsers()

if (currentUsers >= maxUsers) {
throw new Error('Maximum user limit reached')
}

const couch = Db.getCouch()
const password = crypto.createHash('sha256').update(signature).digest("hex")

const storageLimit = process.env.DEFAULT_USER_CONTEXT_LIMIT_MB*1048576

// Create CouchDB database user matching username and password
let userData = {
_id: `org.couchdb.user:${username}`,
name: username,
password: password,
type: "user",
roles: []
roles: [],
storageLimit
};

let usersDb = couch.db.use('_users');
Expand All @@ -53,8 +66,6 @@ class UserManager {
}
}



/**
* Ensure we have a public user in the database for accessing public data
*/
Expand Down Expand Up @@ -102,6 +113,29 @@ class UserManager {
}
}

async getUsage(did, contextName) {
const username = Utils.generateUsername(did, contextName);
const user = await this.getByUsername(username);
const databases = await DbManager.getUserDatabases(did, contextName)

const result = {
databases: 0,
bytes: 0,
storageLimit: user.storageLimit
}

for (let d in databases) {
const database = databases[d]
const dbInfo = await DbManager.getUserDatabase(did, contextName, database.databaseName)
result.databases++
result.bytes += dbInfo.info.sizes.file
}

const usage = result.bytes / parseInt(result.storageLimit)
result.usagePercent = Number(usage.toFixed(4))
return result
}

}

let userManager = new UserManager();
Expand Down
29 changes: 22 additions & 7 deletions src/controllers/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,20 @@ class AuthController {

// Create the user if they don't exist
if (!user) {
const response = await UserManager.create(username, signature);
if (!response || !response.id) {
try {
const response = await UserManager.create(username, signature);
if (!response || !response.id) {
return res.status(500).send({
status: "fail",
data: {
"auth": "User does not exist and unable to create"
}
})
}
} catch (err) {
return res.status(500).send({
status: "fail",
data: {
"auth": "User does not exist and unable to create"
}
status: 'fail',
message: err.message
})
}
}
Expand All @@ -84,6 +91,15 @@ class AuthController {
async connect(req, res) {
const refreshToken = req.body.refreshToken;
const contextName = req.body.contextName;
const did = req.body.did

const userUsage = await UserManager.getUsage(did, contextName)
if (userUsage.usagePercent >= 100) {
return res.status(400).send({
status: "fail",
message: 'Storage limit reached'
});
}

const accessToken = await AuthManager.generateAccessToken(refreshToken, contextName);

Expand All @@ -92,7 +108,6 @@ class AuthController {
status: "success",
accessToken,
host: Db.buildHost() // required to know the CouchDB host
// username: removed, don't think it is needed
});
}
else {
Expand Down
28 changes: 28 additions & 0 deletions src/controllers/system.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import db from '../components/db'
import Utils from '../components/utils'
import packageJson from '../../package.json'

import dotenv from 'dotenv';
dotenv.config();

class SystemController {

async status(req, res) {
const currentUsers = await db.totalUsers()

const results = {
maxUsers: parseInt(process.env.MAX_USERS),
currentUsers,
version: packageJson.version
}

return Utils.signedResponse({
status: "success",
results
}, res);
}

}

const systemController = new SystemController();
export default systemController;
31 changes: 18 additions & 13 deletions src/controllers/user.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import DbManager from '../components/dbManager.js';
import UserManager from '../components/userManager.js';
import Utils from '../components/utils.js';
import Db from '../components/db.js'

Expand Down Expand Up @@ -37,6 +38,14 @@ class UserController {
});
}

const userUsage = await UserManager.getUsage(did, contextName)
if (userUsage.usagePercent >= 100) {
return res.status(400).send({
status: "fail",
message: 'Storage limit reached'
});
}

const databaseHash = Utils.generateDatabaseName(did, contextName, databaseName)

let success;
Expand Down Expand Up @@ -148,6 +157,14 @@ class UserController {
const databaseHash = Utils.generateDatabaseName(did, contextName, databaseName)

try {
const userUsage = await UserManager.getUsage(did, contextName)
if (userUsage.usagePercent >= 100) {
return res.status(400).send({
status: "fail",
message: 'Storage limit reached'
});
}

let success = await DbManager.updateDatabase(username, databaseHash, contextName, options);
if (success) {
await DbManager.saveUserDatabase(did, contextName, databaseName, databaseHash, options.permissions)
Expand Down Expand Up @@ -245,19 +262,7 @@ class UserController {
}

try {
const databases = await DbManager.getUserDatabases(did, contextName)

const result = {
databases: 0,
bytes: 0
}

for (let d in databases) {
const database = databases[d]
const dbInfo = await DbManager.getUserDatabase(did, contextName, database.databaseName)
result.databases++
result.bytes += dbInfo.info.sizes.file
}
const result = await UserManager.getUsage(did, contextName)

return Utils.signedResponse({
status: "success",
Expand Down
2 changes: 2 additions & 0 deletions src/routes/public.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import express from 'express';
import UserController from '../controllers/user.js';
import AuthController from '../controllers/auth.js';
import SystemController from '../controllers/system.js';

const router = express.Router();

// Specify public endpoints
router.get('/auth/public', UserController.getPublic);
router.get('/status', SystemController.status);
router.post('/auth/generateAuthJwt', AuthController.generateAuthJwt);
router.post('/auth/authenticate', AuthController.authenticate);
router.post('/auth/connect', AuthController.connect);
Expand Down
10 changes: 10 additions & 0 deletions test/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ describe("Server tests", function() {
const result = response.data.result
assert.equal(result.databases, 2, 'Expected number of databases')
assert.ok(result.bytes > 0, 'More than 0 bytes used')
assert.ok(result.usagePercent > 0, 'More than 0 percentage usage')
assert.equal(result.storageLimit, process.env.DEFAULT_USER_CONTEXT_LIMIT_MB*1048576, 'Storage limit is 100Mb')
})

// @todo: updates
Expand Down Expand Up @@ -312,7 +314,15 @@ describe("Server tests", function() {
assert.ok(response.data.results.indexOf('DeleteAll_3') >= 0, 'Deleted correct databases (DeleteAll_3)')
assert.ok(TestUtils.verifySignature(response), 'Have a valid signature in response')
})
})

describe("Server info", () => {
it("Status", async () => {
const response = await Axios.get(`${SERVER_URL}/status`);

assert.equal(response.data.results.maxUsers, process.env.MAX_USERS, 'Correct maximum number of users')
assert.ok(response.data.results.currentUsers > 2, 'At least two users')
assert.ok(response.data.results.version && response.data.results.version.length, 'Version specified')
})
})
})