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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@
"dependencies": {
"@babel/runtime": "^7.16.7",
"@verida/did-client": "^2.0.0-rc3",
"@verida/did-document": "^2.0.0-rc3",
"@verida/encryption-utils": "^2.0.0-rc1",
"aws-serverless-express": "^3.4.0",
"cors": "^2.8.5",
"did-resolver": "^3.1.0",
"dotenv": "^8.2.0",
"ethers": "^5.7.2",
"express": "^4.17.1",
"express-basic-auth": "git+https://github.com/Mozzler/express-basic-auth.git",
"jsonwebtoken": "^8.5.1",
Expand Down
10 changes: 7 additions & 3 deletions sample.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
HASH_KEY=this_is_not_the_prod_hash_key
DID_RPC_URL=
DID_NETWORK=testnet
DID_CACHE_DURATION=3600
Expand All @@ -14,15 +13,20 @@ ACCESS_TOKEN_EXPIRY=300
REFRESH_JWT_SIGN_PK=insert-random-refresh-symmetric-key
# 30 Days
REFRESH_TOKEN_EXPIRY=2592000
DB_REFRESH_TOKENS="verida_refresh_tokens"
DB_DB_INFO="verida_db_info"
DB_REFRESH_TOKENS=verida_refresh_tokens
DB_DB_INFO=verida_db_info
# How often garbage collection runs (1=100%, 0.5 = 50%)
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
DB_PUBLIC_PASS=784c2n780c9cn0789
DB_DIDS=verida_dids

PORT=5151
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
2 changes: 2 additions & 0 deletions src/components/dbManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import Utils from './utils.js';
import _ from 'lodash';
import Db from "./db.js"
import EncryptionUtils from "@verida/encryption-utils"
import dotenv from 'dotenv';
dotenv.config();

class DbManager {

Expand Down
44 changes: 39 additions & 5 deletions src/components/userManager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import crypto from 'crypto';
//import jwt from 'jsonwebtoken';
import Db from './db.js'
//import AuthManager from './authManager.js';
import Utils from './utils.js'
import DbManager from './dbManager.js';

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

class UserManager {

Expand All @@ -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
File renamed without changes.
18 changes: 18 additions & 0 deletions src/routes/public.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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);
router.post('/auth/regenerateRefreshToken', AuthController.regenerateRefreshToken);
router.post('/auth/invalidateDeviceId', AuthController.invalidateDeviceId);
router.post('/auth/isTokenValid', AuthController.isTokenValid);

export default router;
24 changes: 10 additions & 14 deletions src/server-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import cors from 'cors';
import dotenv from 'dotenv';
import basicAuth from 'express-basic-auth';

import router from './routes/index.js';
import privateRoutes from './routes/private.js';
import publicRoutes from './routes/public.js';
import didStorageRoutes from './services/didStorage/routes'

import requestValidator from './middleware/requestValidator.js';
import userManager from './components/userManager.js';
import UserController from './controllers/user.js';
import AuthController from './controllers/auth.js';
import AuthManager from './components/authManager.js';
import didUtils from './services/didStorage/utils'

dotenv.config();

Expand All @@ -19,24 +21,18 @@ let corsConfig = {
//origin: process.env.CORS_HOST
};


// Parse incoming requests data
app.use(cors(corsConfig));
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

// Specify public endpoints
app.get('/auth/public', UserController.getPublic);
app.post('/auth/generateAuthJwt', AuthController.generateAuthJwt);
app.post('/auth/authenticate', AuthController.authenticate);
app.post('/auth/connect', AuthController.connect);
app.post('/auth/regenerateRefreshToken', AuthController.regenerateRefreshToken);
app.post('/auth/invalidateDeviceId', AuthController.invalidateDeviceId);
app.post('/auth/isTokenValid', AuthController.isTokenValid);

app.use(didStorageRoutes);
app.use(publicRoutes);
app.use(requestValidator);
app.use(router);
app.use(privateRoutes);

AuthManager.initDb();
userManager.ensureDefaultDatabases();
didUtils.createDb()

export default app;
Loading