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 <63240102+asmitsirohi@users.noreply.github.com>
Co-authored-by: Siddhesh Bhupendra Kuakde <65951872+SiddheshKukade@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 18, 2022
1 parent dbb8271 commit b4455c1
Show file tree
Hide file tree
Showing 58 changed files with 1,380 additions and 275 deletions.
25 changes: 24 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,29 @@ REFRESH_TOKEN_SECRET=

MONGO_DB_URL=

# Get the google recaptcha secret key from google recaptcha admin or https://www.google.com/recaptcha/admin/create
# from here for reCAPTCHA v2 and "I'm not a robot" Checkbox, and paste the key here.
# Note: In domains, fill localhost

RECAPTCHA_SECRET_KEY=

# Enter gmail credentials here for sending mails to the users. In MAIL_USERNAME enter email address and in MAIL_PASSWORD
# enter app password.
# To get the app password, follow these steps:
# 1. Go to your Google Account, https://myaccount.google.com/
# 2. Select Security.
# 3. Under "Signing in to Google," select App Passwords.
# 4. At the bottom, choose Select app and choose the app you using and then Select device and choose the device you’re using and then Generate.
# 5. The App Password is the 16-character code in the yellow bar on your device.
# 6. Paste that App Password in MAIL_PASSWORD.

# Note: You must setup two factor authentication in order to allow the app password.

# For more info refer, https://support.google.com/accounts/answer/185833

MAIL_USERNAME=
MAIL_PASSWORD=

# Replace these values with instructions from INSTALLATION.md
# androidFirebaseOptions
apiKey=AIzaSyBkqgTkg2yNRsl7jIx_EtCyF9YqiCJX7sz
Expand All @@ -32,4 +55,4 @@ iOSmessagingSenderId=803598513727
iOSprojectId=talawa-352607
iOSstorageBucket=talawa-352607.appspot.com
iosClientId=803598513727-2gt176dpe0ljn5ie967o4d0rm0vo8sm0.apps.googleusercontent.com
iosBundleId=com.talawa.app
iosBundleId=com.talawa.app
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"singleQuote": true
"singleQuote": true,
"endOfLine": "auto"
}
33 changes: 28 additions & 5 deletions INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,42 @@ Follow these steps to get the API running.
- `<DB_NAME>` is your cloud service database name.
* Your cloud service may call this URL a `connect` string. Search for the word `connect` in your online database dashboard to find it.
8. When finished, your `.env` file should have the following fields filled in.
8. Get the google recaptcha secret key from `google-recaptcha-admin` for reCAPTCHA v2 and "I'm not a robot" Checkbox, and copy the key to the `RECAPTCHA_SECRET_KEY` section of the `.env` file.
- Note: In domains, fill localhost
Google-recaptcha-admin: https://www.google.com/recaptcha/admin/create
9. Enter the gmail credentials for sending mails to the users. In `MAIL_USERNAME` enter email address and in `MAIL_PASSWORD` enter app password.
- To get the app password, follow these steps:
* Go to your Google Account, https://myaccount.google.com/
* Select Security.
* Under "Signing in to Google," select App Passwords.
* At the bottom, choose Select app and choose the app you using and then Select device and choose the device you’re using and then Generate.
* The App Password is the 16-character code in the yellow bar on your device.
* Paste that App Password in `MAIL_PASSWORD`.
- Note: You must setup two factor authentication in order to allow the app password.
- For more info refer, https://support.google.com/accounts/answer/185833
10. When finished, your `.env` file should have the following fields filled in.
- ACCESS_TOKEN_SECRET
- REFRESH_TOKEN_SECRET
- MONGO_DB_URL
- RECAPTCHA_SECRET_KEY
- MAIL_USERNAME
- MAIL_PASSWORD
Please review the contents of the `.env.sample` file for additional details.
9. Install required node packages.
11. Install required node packages.
npm install
10. To test the notification service create a new firebase project:
12. To test the notification service create a new firebase project:
- In the Firebase console, open Settings > [Service Accounts](https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk).
Expand All @@ -149,11 +172,11 @@ Follow these steps to get the API running.
7. Copy the keys to `.env` file, for how to set keys refer to `.env.sample` file.
8. Undo the changes made to the `firebase_options.dart` file by pasting the old content from step 2.
11. Start the `talawa-api` server using the below command in the same terminal.
13. Start the `talawa-api` server using the below command in the same terminal.
npm start
11. To stop the server after making changes. Press `CTRL + C` in the terminal where the above command is executed.
14. To stop the server after making changes. Press `CTRL + C` in the terminal where the above command is executed.
# Testing
You can run `talawa-api` tests using this command
Expand Down
16 changes: 16 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 All @@ -45,6 +47,10 @@ const EVENT_NOT_FOUND_MESSAGE = 'event.notFound';
const EVENT_NOT_FOUND_CODE = 'event.notFound';
const EVENT_NOT_FOUND_PARAM = 'event';

const ERROR_IN_SENDING_MAIL = 'Error in sending mail';

const INVALID_OTP = 'Invalid OTP';

const REGISTRANT_ALREADY_EXIST = 'Already registered for the event';
const REGISTRANT_ALREADY_EXIST_MESSAGE = 'registrant.alreadyExist';
const REGISTRANT_ALREADY_EXIST_CODE = 'registrant.alreadyExist';
Expand Down Expand Up @@ -75,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 @@ -104,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 All @@ -114,6 +124,10 @@ module.exports = {
EVENT_NOT_FOUND_CODE,
EVENT_NOT_FOUND_PARAM,

ERROR_IN_SENDING_MAIL,

INVALID_OTP,

USER_ALREADY_MEMBER,
USER_ALREADY_MEMBER_CODE,
USER_ALREADY_MEMBER_MESSAGE,
Expand Down Expand Up @@ -163,4 +177,6 @@ module.exports = {
POST_NOT_FOUND_MESSAGE,
POST_NOT_FOUND_CODE,
POST_NOT_FOUND_PARAM,

DATABASE_CONNECTION_FAIL,
};
Empty file added image/README.md
Empty file.
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 };
};
33 changes: 33 additions & 0 deletions lib/helper_functions/mailer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const nodemailer = require('nodemailer');

const { ERROR_IN_SENDING_MAIL } = require('../../constants');

const mailer = (email, subject, body) => {
//NODEMAILER SPECIFIC STUFF
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.MAIL_USERNAME,
pass: process.env.MAIL_PASSWORD,
},
});

let mailOptions = {
from: 'Talawa<>noreply@gmail.com',
to: email,
subject: subject,
html: body,
};

return new Promise((resolve, reject) => {
transporter.sendMail(mailOptions, function (err, info) {
if (err) {
reject(ERROR_IN_SENDING_MAIL);
} else {
resolve(info);
}
});
});
};

module.exports = mailer;
1 change: 0 additions & 1 deletion lib/middleware/is-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,4 @@ const isAuth = (req) => {
userId,
};
};

module.exports = isAuth;
1 change: 1 addition & 0 deletions lib/models/Organization.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const organizationSchema = new Schema({
},
],
visibleInSearch: Boolean,
tags: [],
createdAt: {
type: Date,
default: Date.now,
Expand Down
Loading

0 comments on commit b4455c1

Please sign in to comment.