diff --git a/README.md b/README.md index 257ca53..685158e 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,43 @@ Following environmental variables are needed - VERSION : version number - DATE : build date -- IDP : Identification provider, e.g., "google" -- CLIENT_ID : IDP's client ID -- CLIENT_SECRET : IDP's client secret -- REDIRECT_URL : valid redirect URL set with IDP - COOKIE_SECRET : secret used to sign cookies - SESSION_TIMEOUT : session timeout in seconds, default is 30 minutes +- AUTHORIZATION_ENABLED : If not set to "true", then the authorization components will be disabled +- EMAILS_ENABLED : If not set to "true", then the email notifications will be disabled # Neo4j configuration - NEO4J_URI: Bolt URI of the Neo4j database - NEO4J_USER: Neo4j username - NEO4J_PASSWORD: Neo4j password # Test-data loading configuration - DATA_LOADING_MODE : (for testing only) set to "overwrite" to wipe the database before loading -- DATA_FILE : (for testing only) file containing data to load into the database for testing \ No newline at end of file +- DATA_FILE : (for testing only) file containing data to load into the database for testing +# Testing +- TEST_EMAIL : The email to be logged in if "test-idp" is specified as the IDP +# MYSQL configuration +- MYSQL_HOST : The host URL of the MYSQL database +- MYSQL_PORT : The port of the MYSQL database +- MYSQL_USER : The service user of the MYSQL database +- MYSQL_PASSWORD : The password for the service user of the MYSQL database +- MYSQL_DATABASE : The MYSQL database name +# Email notification configuration +- EMAIL_SMTP_HOST: email server hostname +- EMAIL_SMTP_PORT: email server port number +# Additional configuration for email server +- EMAIL_USER: email server's username as an additional parameter +- EMAIL_PASSWORD: email server's password as an additional parameter +# Google login configuration +- GOOGLE_CLIENT_ID: Google cloud client id +- GOOGLE_CLIENT_SECRET: Google cloud client secret +- GOOGLE_REDIRECT_URL: redirecting url after successful authentication +# NIH login configuration +- NIH_CLIENT_ID: NIH login server client id +- NIH_CLIENT_SECRET: NIH login client secret +- NIH_BASE_URL: NIH login server url +- NIH_REDIRECT_URL: redirecting url after successful authentication +- NIH_USERINFO_URL: NIH API address to search user information +- NIH_AUTHORIZE_URL: NIH API address to authenticate for login +- NIH_TOKEN_URL: NIH API address to create token for login +- NIH_LOGOUT_URL: NIH API address to invalidate token for logout +- NIH_SCOPE: space-separated lists of identifiers to specify access privileges +- NIH_PRO \ No newline at end of file diff --git a/config.env.example b/config.env.example index 8551410..167eaaf 100644 --- a/config.env.example +++ b/config.env.example @@ -2,19 +2,34 @@ COOKIE_SECRET=XXXXX SESSION_TIMEOUT=1200 VERSION=1.0 DATE=2022.05.19 +# NEO4J Configuration +NEO4J_URI=bolt://localhost:XXXX +NEO4J_USER=neo4j +NEO4J_PASSWORD=xxxxx # MySQL Configuration MY_SQL_HOST=127.0.0.1 MY_SQL_PORT=3306 -MY_SQL_PASSWORD=Dbtldud1 +MY_SQL_PASSWORD=XXXX MY_SQL_USER=root MY_SQL_DATABASE=session # Email Notification Config -EMAIL_SERVICE_EMAIL=XXXX@nih.gov -EMAIL_SMTP_HOST=mailfwd.nih.gov +EMAIL_SMTP_HOST=XXXX@XXXX EMAIL_SMTP_PORT=25 -# If Sent From AWS SMTP +# Additional Email Server Configuration #EMAIL_USER=XXXX #EMAIL_PASSWORD=XXXX -NEO4J_URI=bolt://localhost:XXXX -NEO4J_USER=neo4j -NEO4J_PASSWORD=xxxxx \ No newline at end of file +# GOOGLE LOGIN Config +GOOGLE_CLIENT_ID=XXXX +GOOGLE_CLIENT_SECRET=XXXX +GOOGLE_REDIRECT_URL=http://localhost:4010 +# NIH LOGIN Config +NIH_CLIENT_ID=XXXX +NIH_CLIENT_SECRET=XXXX +NIH_BASE_URL=https://stsstg.nih.gov +NIH_REDIRECT_URL=http://localhost:4010/profile +NIH_USERINFO_URL=https://stsstg.nih.gov/openid/connect/v1/userinfo +NIH_AUTHORIZE_URL=https://stsstg.nih.gov/auth/oauth/v2/authorize +NIH_TOKEN_URL=https://stsstg.nih.gov/auth/oauth/v2/token +NIH_LOGOUT_URL=https://stsstg.nih.gov/connect/session/logout +NIH_SCOPE=openid email profile +NIH_PROMPT=login \ No newline at end of file diff --git a/config.js b/config.js index a98dd75..f245e35 100644 --- a/config.js +++ b/config.js @@ -11,6 +11,7 @@ const config = { cookie_secret: process.env.COOKIE_SECRET, session_timeout: process.env.SESSION_TIMEOUT ? parseInt(process.env.SESSION_TIMEOUT) : 30 * 60, // 30 minutes authorization_enabled: process.env.AUTHORIZATION_ENABLED ? process.env.AUTHORIZATION_ENABLED.toLowerCase() === 'true' : true, + emails_enabled: process.env.EMAILS_ENABLED ? process.env.EMAILS_ENABLED.toLowerCase() === 'true' : true, //Neo4j connection NEO4J_URI: process.env.NEO4J_URI, @@ -22,13 +23,12 @@ const config = { //Testing TEST_EMAIL: process.env.TEST_EMAIL, // MySQL Session - mysql_host: process.env.MY_SQL_HOST, - mysql_port: process.env.MY_SQL_PORT, - mysql_user: process.env.MY_SQL_USER, - mysql_password: process.env.MY_SQL_PASSWORD, - mysql_database: process.env.MY_SQL_DATABASE, + mysql_host: process.env.MYSQL_HOST, + mysql_port: process.env.MYSQL_PORT, + mysql_user: process.env.MYSQL_USER, + mysql_password: process.env.MYSQL_PASSWORD, + mysql_database: process.env.MYSQL_DATABASE, // Email settings - email_service_email: process.env.EMAIL_SERVICE_EMAIL, email_transport: getTransportConfig() }; diff --git a/data-management/data-interface.js b/data-management/data-interface.js index e06e10a..7e4c68f 100644 --- a/data-management/data-interface.js +++ b/data-management/data-interface.js @@ -117,11 +117,20 @@ const registerUser = async (input, context) => { ...input.userInfo, ...generatedInfo }; - let result = await neo4j.registerUser(registrationInfo); - let adminEmails = await getAdminEmails(); - await sendAdminNotification(adminEmails); - await sendRegistrationConfirmation(input.userInfo.email) - return result; + let response = await neo4j.registerUser(registrationInfo); + if (response) { + let adminEmails = await getAdminEmails(); + let template_params = { + firstName: response.firstName, + lastName: response.lastName + } + await sendAdminNotification(adminEmails, template_params); + await sendRegistrationConfirmation(response.email, template_params) + return response; + } + else { + return new Error(errorName.UNABLE_TO_REGISTER_USER); + } } catch (err) { return err; } @@ -145,9 +154,11 @@ const approveUser = async (parameters, context) => { parameters.approvalDate = (new Date()).toString() let response = await neo4j.approveUser(parameters) if (response) { - if (response.email) { - await sendApprovalNotification(response.email); - } + let template_params = { + firstName: response.firstName, + lastName: response.lastName + }; + await sendApprovalNotification(response.email, template_params); return response; } else { return new Error(errorName.USER_NOT_FOUND); @@ -172,9 +183,12 @@ const rejectUser = async (parameters, context) => { parameters.rejectionDate = (new Date()).toString() let response = await neo4j.rejectUser(parameters) if (response) { - if (response.email) { - await sendRejectionNotification(response.email, response.comment); + let template_params = { + firstName: response.firstName, + lastName: response.lastName, + comment: response.comment } + await sendRejectionNotification(response.email, template_params); return response; } else { return new Error(errorName.USER_NOT_FOUND); @@ -230,9 +244,12 @@ const editUser = async (parameters, context) => { } let response = await neo4j.editUser(parameters) if (response) { - if (response.email) { - await sendEditNotification(response.email, response.comment); + let template_params = { + firstName: response.firstName, + lastName: response.lastName, + comment: response.comment } + await sendEditNotification(response.email, template_params); return response; } else { return new Error(errorName.USER_NOT_FOUND); diff --git a/data-management/email.js b/data-management/email.js deleted file mode 100644 index f8e78a0..0000000 --- a/data-management/email.js +++ /dev/null @@ -1,14 +0,0 @@ -//Placeholder email function -module.exports = { - sendEmail: (recipient, subject, content) =>{ - let email = { - email: { - from: "sender@email.com", - to: recipient, - subject: subject, - message: content - } - } - console.log(email) - } -} \ No newline at end of file diff --git a/data-management/graphql-api-constants.js b/data-management/graphql-api-constants.js index ea7adb6..d362e80 100644 --- a/data-management/graphql-api-constants.js +++ b/data-management/graphql-api-constants.js @@ -12,6 +12,7 @@ exports.errorName = { ALREADY_REJECTED: "ALREADY_REJECTED", INVALID_ROLE: "INVALID_ROLE", INVALID_STATUS: "INVALID_STATUS", + UNABLE_TO_REGISTER_USER: 'UNABLE_TO_REGISTER_USER' }; exports.errorType = { @@ -58,5 +59,9 @@ exports.errorType = { ALREADY_REJECTED: { message: "The specified user has already been rejected", statusCode: 409 + }, + UNABLE_TO_REGISTER_USER: { + message: "Something went wrong while registering the user", + statusCode: 409 } }; \ No newline at end of file diff --git a/data-management/notifications.js b/data-management/notifications.js index 4315f93..2a26000 100644 --- a/data-management/notifications.js +++ b/data-management/notifications.js @@ -1,83 +1,84 @@ -const {sendEmail} = require("./email"); const yaml = require('js-yaml'); -const fs = require('fs'); +const fs = require('fs'); +const {sendNotification} = require("../services/notify"); +const {createEmailTemplate} = require("../lib/create-email-template"); let email_constants = undefined -try{ +try { email_constants = yaml.load(fs.readFileSync('yaml/notification_email_values.yaml', 'utf8')); -} -catch (e) { +} catch (e) { console.error(e) } - module.exports = { - sendAdminNotification: async (admins) => { - if (email_constants){ - if (Array.isArray(admins)){ - admins.forEach((adminEmail) => { - sendEmail( - adminEmail, - email_constants.ADMIN_NOTIFICATION_SUBJECT, - email_constants.ADMIN_NOTIFICATION_CONTENT - ); - }) - } - else { - console.error('send email failed, admins parameter of sendAdminNotification is not an array'); - } - } - else{ + sendAdminNotification: async (admins, template_params) => { + if (email_constants) { + await sendNotification( + email_constants.NOTIFICATION_SENDER, + email_constants.ADMIN_NOTIFICATION_SUBJECT, + await createEmailTemplate("notification-template.html", { + message: email_constants.ADMIN_NOTIFICATION_CONTENT, ...template_params + }), + admins + ); + } else { console.error("Unable to load email constants from file, email not sent") } }, - sendRegistrationConfirmation: async (email) => { + sendRegistrationConfirmation: async (email, template_params) => { if (email_constants) { - sendEmail( - email, + await sendNotification( + email_constants.NOTIFICATION_SENDER, email_constants.CONFIRMATION_SUBJECT, - email_constants.CONFIRMATION_CONTENT + await createEmailTemplate("notification-template.html", { + message: email_constants.CONFIRMATION_CONTENT, ...template_params + }), + email ); - } - else{ + } else { console.error("Unable to load email constants from file, email not sent") } - }, - sendApprovalNotification: async (email) => { + sendApprovalNotification: async (email, template_params) => { if (email_constants) { - sendEmail( - email, + await sendNotification( + email_constants.NOTIFICATION_SENDER, email_constants.APPROVAL_SUBJECT, - email_constants.APPROVAL_CONTENT + await createEmailTemplate("notification-template.html", { + message: email_constants.APPROVAL_CONTENT, ...template_params + }), + email ); - } - else{ + } else { console.error("Unable to load email constants from file, email not sent") } }, - sendRejectionNotification: async (email, comment) => { + sendRejectionNotification: async (email, template_params) => { if (email_constants) { - sendEmail( - email, + await sendNotification( + email_constants.NOTIFICATION_SENDER, email_constants.REJECTION_SUBJECT, - email_constants.REJECTION_CONTENT_PRE_COMMENT + comment + email_constants.REJECTION_CONTENT_POST_COMMENT + await createEmailTemplate("notification-template.html", { + message: email_constants.REJECTION_CONTENT_PRE_COMMENT + template_params.comment + email_constants.REJECTION_CONTENT_POST_COMMENT, ...template_params + }), + email ); - } - else { + } else { console.error("Unable to load email constants from file, email not sent") } }, - sendEditNotification: async (email, comment) => { + sendEditNotification: async (email, template_params) => { if (email_constants) { - sendEmail( - email, + await sendNotification( + email_constants.NOTIFICATION_SENDER, email_constants.EDIT_SUBJECT, - email_constants.EDIT_CONTENT_PRE_COMMENT + comment + email_constants.EDIT_CONTENT_POST_COMMENT + await createEmailTemplate("notification-template.html", { + message: email_constants.EDIT_CONTENT_PRE_COMMENT + template_params.comment + email_constants.EDIT_CONTENT_POST_COMMENT, ...template_params + }), + email ); - } - else { + } else { console.error("Unable to load email constants from file, email not sent") } } diff --git a/mailer/index.js b/mailer/index.js deleted file mode 100644 index 0ba1d4f..0000000 --- a/mailer/index.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; -const nodemailer = require("nodemailer"); -const config = require("../config"); -const {sendNotification} = require("../services/notify"); - -module.exports = { - /* - - Sends an email to the provided recipient - - Arguments: - - recipient {array of strings} -- recipients of the email - - subject {string} -- The email's subject - - contents {string} -- The email's contents - - */ - sendEmail: async (recipient, subject, contents) => { - // create reusable transporter object using the default SMTP transport - let info = await sendNotification({ - from: config.email_service_email, - to: recipient, - // cc: [], - // bcc: [], - subject: subject, - html: contents, - }); - - console.log("Message sent: %s", info.messageId); - // Message sent: - - // Preview only available when sending through an Ethereal account - console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info)); - // Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou... - } -}; \ No newline at end of file diff --git a/middleware/middleware.js b/middleware/middleware.js deleted file mode 100644 index e837d9d..0000000 --- a/middleware/middleware.js +++ /dev/null @@ -1,13 +0,0 @@ -function withAsync(fn) { - return async (request, response, next) => { - try { - return await fn(request, response, next); - } catch (error) { - next(error); - } - }; -} - -module.exports = { - withAsync -}; \ No newline at end of file diff --git a/services/notify.js b/services/notify.js index 6f0c08e..487f072 100644 --- a/services/notify.js +++ b/services/notify.js @@ -1,7 +1,7 @@ const { createTransport } = require('nodemailer'); const config = require('../config'); -async function sendNotification({ from, to = [], cc = [], bcc = [], subject, html }) { +async function sendNotification(from, subject, html, to = [], cc = [], bcc = []) { if (!to?.length) { throw new Error('Missing recipient'); @@ -20,7 +20,22 @@ async function sendNotification({ from, to = [], cc = [], bcc = [], subject, htm async function sendMail(params) { const transport = createTransport(config.email_transport); - return await transport.sendMail(params); + console.log("Generating email to: "+params.to.join(', ')); + if (config.emails_enabled){ + try{ + let result = await transport.sendMail(params); + console.log("Email sent"); + return result; + } + catch (err){ + console.error("Email failed to send with ths following reason:" + err.message); + return err; + } + } + else { + console.log("Email not sent, email is disabled by configuration"); + return true; + } } function asArray(values = []) { diff --git a/templates/notification-template.html b/templates/notification-template.html index 2fc9e80..2ce00fb 100644 --- a/templates/notification-template.html +++ b/templates/notification-template.html @@ -3,7 +3,6 @@ - Bento TEST TEST