Skip to content

Commit

Permalink
Error handling & security updates (#744)
Browse files Browse the repository at this point in the history
* [Feature Request] New Features for Talawa-admin added (#725)

* check authentication feature added for talawa admin

* tests for check authentication added

* minor fixes

* typo fixes

* minor fixes

* npm err fixes

* google recaptcha configured

* new dependency added

* mailer, forgotPassword endpoint and otp endpoint is configured

* graphql endpoints updated

* constants updated

* Documentation updated

* tests for changes added

* hard coded value error fixes

* lint fixes

* lint fixes

* failed test cases fixes

* Failed tests fixes

* minor fixes

* marked-teminal dependency updated

* Tests fixes

* tests updated

* tests fixes

* tests updated

* new resolvers added and schema updated

* graphql mutations updated

* tests updated for changes

* tests updated

* tests updated

* tests updated

* minor fixes

* [Feature Request] : Plugin Architecture for Server (#730)

* Test : lib/resolvers/group_chat_query/groupChatMessages.js

* Add Test  : Added Valid JSON Check

* Create README.md

* Create README.md

* Update is-auth.js

* AddedDocs

Docs from withfilter pending

* Update index.js

* completed

* sample

* update/index.js

* update/index.js

* update/is-auth.js

* Update/readme.md

* update/image-readme

* Add/query-schema

* Update/Plugin-graphQL-schema

* Add/Plugin-MongoDB-Model

* Add/createPlugin-and-refractoring

* Add/getPlugins

* Add/Mutation/UpdatePluginStatus

* Add/Mutation/updateTempPluginInstalledOrgs

* Add/Mutation/Schema

* Add/import/mutations

* Add/plugins

* Fix/path-err

* Fix/Erros

* update/lockfile

* Add/plugin-model

* Update is-auth.js

* Removed extra queries

* Documentation added for plugin queries and models

* Fix/`delelte cr in prettier/prettier`

* updated : mutation

* Add/test `getPlugins`

* Fix/`lint error`

* test `updatePluginInstalledOrgs`

* Test/ `updateInstallStatus`

* Fix/Erros-1

* Fix/Erros-2

* Fix/Erros-3

* Test/`Queries.js`

* Test/`Mutation.js`

* remove extra `console.log`

* Delete admin-plugin-query.js

* Delete super-admin-plugin-query.js

* Fix/Package-lock

* added delimiter to helper functions

* error handling for connection manager

* added readme.md for connection manager

* error handling for Database module

Co-authored-by: Asmit Kumar Sirohi <[email protected]>
Co-authored-by: Siddhesh Bhupendra Kuakde <[email protected]>
  • Loading branch information
3 people authored and JamaicanFriedChicken committed Sep 6, 2022
1 parent 80318d3 commit 6b19d0b
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 53 deletions.
8 changes: 8 additions & 0 deletions constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const ORGANIZATION_NOT_FOUND_MESSAGE = 'organization.notFound';
const ORGANIZATION_NOT_FOUND_CODE = 'organization.notFound';
const ORGANIZATION_NOT_FOUND_PARAM = 'organization';

const CONNECTION_NOT_FOUND = 'Connection not found';

const ORGANIZATION_MEMBER_NOT_FOUND = "Organization's user is not a member";
const ORGANIZATION_MEMBER_NOT_FOUND_MESSAGE = 'organization.member.notFound';
const ORGANIZATION_MEMBER_NOT_FOUND_CODE = 'organization.member.notFound';
Expand Down Expand Up @@ -79,6 +81,8 @@ const POST_NOT_FOUND_MESSAGE = 'post.notFound';
const POST_NOT_FOUND_CODE = 'post.notFound';
const POST_NOT_FOUND_PARAM = 'post';

const DATABASE_CONNECTION_FAIL = 'Failed to connect to database';

const STATUS_ACTIVE = 'ACTIVE';

const IN_PRODUCTION = process.env.NODE_ENV === 'production';
Expand Down Expand Up @@ -108,6 +112,8 @@ module.exports = {
ORGANIZATION_NOT_FOUND_CODE,
ORGANIZATION_NOT_FOUND_PARAM,

CONNECTION_NOT_FOUND,

EVENT_PROJECT_NOT_FOUND,
EVENT_PROJECT_NOT_FOUND_CODE,
EVENT_PROJECT_NOT_FOUND_MESSAGE,
Expand Down Expand Up @@ -171,4 +177,6 @@ module.exports = {
POST_NOT_FOUND_MESSAGE,
POST_NOT_FOUND_CODE,
POST_NOT_FOUND_PARAM,

DATABASE_CONNECTION_FAIL,
};
48 changes: 48 additions & 0 deletions lib/ConnectionManager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Connection Manager Module

This is the module that is responsible for connecting/disconnecting to each tenant's database.

### Prototype :

`addTenantConnection(organizationId)`

`getTenantConnection(organizationId)`

`initTenants()`

`destroyConnections()`

`addTenantConnection` is responsible for connecting to the database of a specific organization `organizationId`
and returning a reference to the connection object if the organization is not found it will throw an organization not found error.

`getTenantConnection` is responsible for retrivnig a connection to `organizationId` which already is in memory if it's not the function will throw a connection not found error.

`initTenants` is usually used on the startup of the api which connects to all the available tenants that are stored.

`destoryConnections` is used to close the connections and deleting them from memory.

### Basic usage :

This is mostly how it's going to be used:

```javascript
const connection = getTenantConnection(organizationId);
const Post = connection.Post;

// posts is an array of the posts stored in that specific tenant.
const posts = await Post.find({});
// to close connections:
await destroyConnections();
// would throw an error:
const con = getTenantConnection(organizationId);
```

### This is the main way to manage the databases that are devided into 2 types:

##### main database:

which holds connections, organizations, users, files data (shared data).

##### tenant databases;

which holds organization specific data such as posts, events, comments...
36 changes: 23 additions & 13 deletions lib/ConnectionManager/addTenantConnection.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
const Tenant = require('../models/Tenant');
const Database = require('../Database/index');
const { setConnection, getConnection } = require('./connections');
const { setConnection } = require('./connections');
const { NotFoundError } = require('errors');
const requestContext = require('talawa-request-context');
const {
ORGANIZATION_NOT_FOUND,
ORGANIZATION_NOT_FOUND_MESSAGE,
ORGANIZATION_NOT_FOUND_PARAM,
ORGANIZATION_NOT_FOUND_CODE,
IN_PRODUCTION,
} = require('../../constants');

module.exports = async (organizationId) => {
try {
const alreadyConnected = getConnection(organizationId);
if (alreadyConnected) return alreadyConnected;
const [tenant] = await Tenant.find({ organization: organizationId });
if (!tenant || !tenant.url) throw new Error('Organization not found!');
console.log('tenant', tenant);
let connection = new Database(tenant.url);
await connection.connect();
setConnection(tenant.organization, connection);
return connection;
} catch (e) {
console.log('organization not found!');
const [tenant] = await Tenant.find({ organization: organizationId });
if (!tenant || !tenant.url) {
throw new NotFoundError(
!IN_PRODUCTION
? ORGANIZATION_NOT_FOUND
: requestContext.translate(ORGANIZATION_NOT_FOUND_MESSAGE),
ORGANIZATION_NOT_FOUND_CODE,
ORGANIZATION_NOT_FOUND_PARAM
);
}
let connection = new Database(tenant.url);
await connection.connect();
setConnection(tenant.organization, connection);
return connection;
};
7 changes: 6 additions & 1 deletion lib/ConnectionManager/connections.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
const { CONNECTION_NOT_FOUND } = require('../../constants');
const { NotFoundError } = require('errors');

const connections = {};

const setConnection = (orgId, connection) => {
connections[`${orgId}`] = connection;
};

const getConnection = (orgId) => {
if (!connections[orgId]) return null;
if (!connections[orgId]) {
throw new NotFoundError(CONNECTION_NOT_FOUND);
}
return connections[orgId];
};

Expand Down
6 changes: 1 addition & 5 deletions lib/ConnectionManager/destroyConnections.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
const connections = require('./connections');

module.exports = async () => {
try {
await connections.destroy();
} catch (e) {
console.log(e);
}
await connections.destroy();
};
6 changes: 1 addition & 5 deletions lib/ConnectionManager/getTenantConnection.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
const { getConnection } = require('./connections');

module.exports = (organizationId) => {
try {
return getConnection(organizationId);
} catch (e) {
console.log('organization not found!');
}
return getConnection(organizationId);
};
5 changes: 2 additions & 3 deletions lib/Database/MongoImplementation/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const MongooseInstance = require('mongoose').Mongoose;
const logger = require('logger');
const { DATABASE_CONNECTION_FAIL } = require('../../../constants');
const MessageChat = require('./schema/Chat');
const Comment = require('./schema/Comment');
const DirectChat = require('./schema/DirectChat');
Expand Down Expand Up @@ -60,8 +60,7 @@ function MongoDB(url, schemaObjects) {
});
this.Native = this.mongo.connection.db;
} catch (error) {
logger.error('Error while connecting to mongo database', error);
process.exit(1);
throw new Error(DATABASE_CONNECTION_FAIL);
}
};
this.disconnect = async () => {
Expand Down
4 changes: 2 additions & 2 deletions lib/helper_functions/addTenantId.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module.exports = (tenantId, id) => {
return tenantId + ' ' + id;
module.exports = (tenantId, id, del = ' ') => {
return tenantId + del + id;
};
4 changes: 2 additions & 2 deletions lib/helper_functions/getTenantFromId.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = (fullId) => {
const [tenantId, id] = fullId.split(' ');
module.exports = (fullId, del = ' ') => {
const [tenantId, id] = fullId.split(del);
return { tenantId, id };
};
27 changes: 9 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions tests/database/mongoImplementation.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const Database = require('../../lib/Database/index');
const User = require('../../lib/Database/MongoImplementation/schema/User');
const { DATABASE_CONNECTION_FAIL } = require('../../constants');

const firstUrl = 'mongodb://localhost:27017/db1?retryWrites=true&w=majority';
const secondUrl = 'mongodb://localhost:27017/db2?retryWrites=true&w=majority';
Expand Down Expand Up @@ -29,6 +30,13 @@ afterAll(async () => {
});

describe('testing multiple databases functionality', () => {
test('inavlid url throws an error', async () => {
await expect(async () => {
const db = new Database('some_invalid_url');
await db.connect();
}).rejects.toThrow(DATABASE_CONNECTION_FAIL);
});

test('first db is working', async () => {
const firstDbUser = new FirstUserObject({
firstName: 'test',
Expand Down
27 changes: 23 additions & 4 deletions tests/database/multi-tenancy.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
const shortid = require('shortid');
const Tenant = require('../../lib/models/Tenant');
const connectionManager = require('../../lib/ConnectionManager');
const {
Types: { ObjectId },
} = require('mongoose');
const {
CONNECTION_NOT_FOUND,
ORGANIZATION_NOT_FOUND,
} = require('../../constants');

const database = require('../../db');
const getUserIdFromSignUp = require('../functions/getUserIdFromSignup');
Expand Down Expand Up @@ -73,16 +80,20 @@ afterAll(async () => {

describe('tenant is working and transparent from main db', () => {
test('initTenants and destroyConnections', async () => {
let conn = connectionManager.getTenantConnection(organizationId);
expect(conn).toBe(null);
let conn;
expect(() => {
connectionManager.getTenantConnection(organizationId);
}).toThrow(CONNECTION_NOT_FOUND);
await connectionManager.initTenants();
conn = connectionManager.getTenantConnection(organizationId);
expect(conn).toBeTruthy();
await connectionManager.destroyConnections();
conn = connectionManager.getTenantConnection(organizationId);
expect(conn).toBe(null);
expect(() => {
connectionManager.getTenantConnection(organizationId);
}).toThrow(CONNECTION_NOT_FOUND);
await connectionManager.initTenants();
});

test('addConnection', async () => {
const organization = new Organization({
name: 'second tenant organization',
Expand Down Expand Up @@ -120,11 +131,19 @@ describe('tenant is working and transparent from main db', () => {
const conn = await connectionManager.addTenantConnection(
secondOrganizationId
);

expect(conn).toBeTruthy();
const posts = await conn.Post.find();
expect(posts).toEqual([]);
});

test('adding invalid org connection', async () => {
// throws the correct error on invalid org id.
await expect(async () => {
await connectionManager.addTenantConnection(new ObjectId());
}).rejects.toThrow(ORGANIZATION_NOT_FOUND);
});

test('getConnection', async () => {
const conn = connectionManager.getTenantConnection(organizationId);
const newPost = new conn.Post({
Expand Down

0 comments on commit 6b19d0b

Please sign in to comment.