diff --git a/README.md b/README.md index d694221..78df4c4 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # Pw-manager-nodejs + Pw-Manager-Nodejs-opensource This is my first project with nodejs! Please keep that in mind. # English + This Pw- Manager was created by me in NodeJs. This is my first project with NodeJs, Express and Crypto. A mysql db is also required. What can it do? + - Signup user - Login user - Store your data with site, username and pw @@ -23,6 +26,7 @@ Template link: Bootstrapdash.com/demo/corona-free/jquery/template/index.html The main focus of the project is the logic and function of the Pw Tresor. Updates that will follow are: + - Generate secure pw automatic function. - Pw share with other users on your system. - Automatic logout after 5 minutes @@ -32,10 +36,12 @@ im open to suggestions and happy to hear from you [message me](mailto:githubprojects@herrmannsven.de?subject=[GitHub]%20Source%20Han%20Sans) # German + Dieser Pw Tresor wurde von mir in NodeJs erstellt. Das ist mein erstes Projekt mit NodeJs, Express und Crypto. Außerdem wird eine mysql Db benötigt. Was kann der Password- Manager? + - Benutzer Registrieren - Benutzer einloggen - Passowrd, Benutzername und App/Seitenname speichern @@ -44,11 +50,12 @@ Was kann der Password- Manager? - Passwort ändern - Passwort löschen -Das Design Template stammt von Bootstrapdash einige Anpassungen wurden von mir durchgeführt. +Das Design Template stammt von Bootstrapdash einige Anpassungen wurden von mir durchgeführt. Template-Link: Bootstrapdash.com/demo/corona-free/jquery/template/index.html Der schwerpunkt des Projektes liegt mir aber an der Logik und funktion des Pw Tresores. Updates die folgen werden sind: + - generate secure pw automatic function. - Pw teilen mit anderen Nutzern auf deinem System. - Automatischer logout nach 5 Minuten @@ -57,18 +64,16 @@ Updates die folgen werden sind: schreibt mir gerne bei Fragen und Anregungen. [message me](mailto:githubprojects@herrmannsven.de?subject=[GitHub]%20Source%20Han%20Sans) - ![image](https://user-images.githubusercontent.com/19588101/156414735-43d82950-c763-4738-b53f-07c36b56606b.png) ![image](https://user-images.githubusercontent.com/19588101/156414766-1d074d1b-68c9-4150-b240-c27d91c56970.png) ![image](https://user-images.githubusercontent.com/19588101/156414790-b5d313c6-4b84-4b38-bb9b-102850d1563d.png) -
Credits -- frontend Design: Bootstrapdash -- Template used for project: Corona-Free -
-
-Special thanks to MyMartek for all the codereviews. +- frontend Design: Bootstrapdash +- Template used for project: Corona-Free +
+
+ Special thanks to MyMartek for all the codereviews. diff --git a/administration.js b/administration.js index 7fcc4ad..ba3f117 100644 --- a/administration.js +++ b/administration.js @@ -1,27 +1,28 @@ // This script runs on serversider -//dependencies required for the app -import connection from './Database/database.js'; -import escape from 'lodash.escape'; -import customer from './customer.js'; -import moment from 'moment'; -import { decrypt } from './crypto/crypto.js'; -import { promisify } from 'es6-promisify'; +// dependencies required for the app +import { promisify } from "es6-promisify"; +import escape from "lodash.escape"; +import moment from "moment"; + +import { decrypt } from "./crypto/crypto.js"; +import customer from "./customer.js"; +import connection from "./Database/database.js"; let encryptArray = []; // Darf nicht in die function rein. async function loadData(req, res) { try { if (!req.session.loggedIn) { - return;//res.render('login', { errormsg: '' }); + return; // res.render('login', { errormsg: '' }); } - - console.log('LoadDate Username= ' + req.session.username); + + console.log("LoadDate Username= " + req.session.username); let pwItemList = await connection.getAllPwFromUser(req); if (req.session.loggedIn) { - pwItemList.forEach(row => { + pwItemList.forEach((row) => { try { row.Name = decrypt(row.Name, req.session.pw); @@ -38,25 +39,31 @@ async function loadData(req, res) { let errormsg = req.session.errormsg; req.session.errormsg = undefined; - return res.render('index', { errormsg, pwDatas: pwItemList, userData: customer.getUserFromSession(req), moment: moment }); + return res.render("index", { + errormsg, + pwDatas: pwItemList, + userData: customer.getUserFromSession(req), + moment: moment, + }); } else { - return; //res.render('login', { errormsg: '' }); + return; // res.render('login', { errormsg: '' }); } } catch (err) { - console.log('Error on load: ' + err); + console.log("Error on load: " + err); } } -/** Returns customers list to ajax call - * - * @param {*} req - * @param {*} res +/** + * Returns customers list to ajax call + * + * @param {*} req + * @param {*} res */ -async function getCustomers(req,res) { +async function getCustomers(req, res) { try { res.send(await connection.getCustomers()); } catch (err) { - console.log('addnewpw err: ' + err); + console.log("addnewpw err: " + err); } } @@ -64,33 +71,33 @@ async function addNewPw(req, res) { try { await connection.insertPw(req, res); - res.redirect('/'); + res.redirect("/"); } catch (err) { - console.log('addnewpw err: ' + err); + console.log("addnewpw err: " + err); } } async function deletePw(req, res) { try { - if (req.body.confirmation === 'yes') { + if (req.body.confirmation === "yes") { await connection.deletePw(req.body.elementId); - res.redirect('/'); + res.redirect("/"); } } catch (err) { - console.log('deletePw err: ' + err); + console.log("deletePw err: " + err); } } async function showPw(req, res) { - try{ + try { if (encryptArray.includes(req.body.id)) { let index = encryptArray.indexOf(req.body.id); if (index > -1) { encryptArray.splice(index, 1); } - res.send(escape('*****')); + res.send(escape("*****")); } else { encryptArray.push(req.body.id); @@ -100,8 +107,8 @@ async function showPw(req, res) { return await getDecriptedPw(req, res); } } - }catch(err){ - console.log('Error in ShowPw: ' + err); + } catch (err) { + console.log("Error in ShowPw: " + err); } } @@ -110,16 +117,16 @@ async function copyPw(req, res) { } async function getDecriptedPw(req, res) { - try{ + try { if (req.session.loggedIn) { let decryptedPw = await connection.getDecriptedPw(req, res); - if(decryptedPw !== null){ + if (decryptedPw !== null) { res.send(escape(decryptedPw)); } } - }catch(err){ - console.log('Error in getDecriptedPw: ' + err); + } catch (err) { + console.log("Error in getDecriptedPw: " + err); } } @@ -132,35 +139,28 @@ async function getDecriptedPw(req, res) { async function changePw(req, res) { try { if (escape(req.body.newPw) !== escape(req.body.newPw1)) { - req.session.errormsg = req.t('pwMissmatch'); + req.session.errormsg = req.t("pwMissmatch"); } if (escape(req.body.changeelement) === null) { - req.session.errormsg = req.t('idIsNotDefined'); + req.session.errormsg = req.t("idIsNotDefined"); } if (req.session.loggedIn) { await connection.updatePwById(req); } } catch (err) { - console.log('Error in changePwApp: ' + err); + console.log("Error in changePwApp: " + err); } let save = promisify(req.session.save.bind(req.session)); await save(); - res.redirect('/'); + res.redirect("/"); } export default { loadData, routes: { - post: { - getCustomers, - addNewPw, - copyPw, - changePw, - deletePw, - showPw - } + post: { getCustomers, addNewPw, copyPw, changePw, deletePw, showPw }, }, }; diff --git a/customer.js b/customer.js index bf011e8..6965f93 100644 --- a/customer.js +++ b/customer.js @@ -1,65 +1,81 @@ // This script runs on serverside -//dependencies required for the app -import connection from './Database/database.js'; -import encrypt1 from './crypto/encrypt.js'; -import User from './user.js'; -import sessionHandler from './sessionHandler.js'; - -/** Sign up a new user - * - * @param {*} req - * @returns +// dependencies required for the app +import encrypt1 from "./crypto/encrypt.js"; +import connection from "./Database/database.js"; +import sessionHandler from "./sessionHandler.js"; +import User from "./user.js"; + +/** + * Sign up a new user + * + * @param {*} req + * @returns */ async function signUp(req) { let pw = req.body.pw; let pw1 = req.body.pw1; if (pw === null || pw1 === null) { - sessionHandler.setErrormsgToSession(req, 'signup.pwNotFound'); + sessionHandler.setErrormsgToSession(req, "signup.pwNotFound"); return false; } if (pw !== pw1) { - sessionHandler.setErrormsgToSession(req, 'signup.pwMissmage'); + sessionHandler.setErrormsgToSession(req, "signup.pwMissmage"); return false; } let hashedPw = encrypt1.hashPw(pw); if (hashedPw === null) { - sessionHandler.setErrormsgToSession(req, 'signup.pwHashProblem'); + sessionHandler.setErrormsgToSession(req, "signup.pwHashProblem"); return false; - } - - let user = new User(null, req.body.username, req.body.firstname, req.body.lastname, hashedPw, null); + } - if (user.username === null || user.firstname === null || user.lastname === null) { - sessionHandler.setErrormsgToSession(req, 'signup.pwHashProblem'); + let user = new User( + null, + req.body.username, + req.body.firstname, + req.body.lastname, + hashedPw, + null + ); + + if ( + user.username === null || + user.firstname === null || + user.lastname === null + ) { + sessionHandler.setErrormsgToSession(req, "signup.pwHashProblem"); return false; } - try{ + try { if (await connection.getUserExists(user.username)) { - sessionHandler.setErrormsgToSession(req, 'signup.userAlreadyExists'); + sessionHandler.setErrormsgToSession( + req, + "signup.userAlreadyExists" + ); return false; } - }catch(err){ - console.log('Error on signUp: ' + err); + } catch (err) { + console.log("Error on signUp: " + err); return err; } - - try{ + + try { await connection.insertUser(user); - }catch(err){ + } catch (err) { return err; } - await sessionHandler.updteUserPwFromSession(req.session.pw); + await sessionHandler.updteUserPwFromSession(req.session.pw); return true; } -/** Signin user and store to session +/** + * Signin user and store to session * * @param {*} req * @param {*} res @@ -67,65 +83,77 @@ async function signUp(req) { */ async function signIn(req, res) { if (req.session.loggedIn) { - res.redirect('/'); + res.redirect("/"); } try { let user = await connection.getUser(req, res); if (user === null) { - return res.render('login', { errormsg: req.t('login.generalError') }); + return res.render("login", { + errormsg: req.t("login.generalError"), + }); } - try{ - if(user.pw === encrypt1.hashPw(req.body.pw)){ - await sessionHandler.setUserToSession(req,res, user); + try { + if (user.pw === encrypt1.hashPw(req.body.pw)) { + await sessionHandler.setUserToSession(req, res, user); } - }catch(err){ - return res.render('login', { errormsg: req.t('loginError') }); + } catch (err) { + return res.render("login", { errormsg: req.t("loginError") }); } - res.redirect('/'); + res.redirect("/"); } catch (err) { - console.log('Error on singIn: ' + err); + console.log("Error on singIn: " + err); } } -/** Logout the current user from the session +/** + * Logout the current user from the session * * @param {*} req * @param {*} res * @returns */ async function logout(req, res) { - try{ + try { // Save default user data to session await sessionHandler.deleteUserFromSession(req); - }catch(err){ - console.log('Error on logout: ' + err); + } catch (err) { + console.log("Error on logout: " + err); } - try{ + try { // Regenerate new session await sessionHandler.regenerateSession(req); - }catch(err){ - console.log('Error on logout: ' + err); + } catch (err) { + console.log("Error on logout: " + err); } - - return res.redirect('/'); + + return res.redirect("/"); } -/** Gets the current user from the session +/** + * Gets the current user from the session * * @param {*} req * @param {*} res * @returns User object */ function getUserFromSession(req) { - return new User(req.session.userid, req.session.username, req.session.firstname, req.session.lastname, null, req.session.loggedIn); + return new User( + req.session.userid, + req.session.username, + req.session.firstname, + req.session.lastname, + null, + req.session.loggedIn + ); } -/** Change user pw(for login etc..) +/** + * Change user pw(for login etc..) * @param {*} req * @param {*} res * @returns @@ -135,32 +163,31 @@ async function changePw(req, res) { if (req.session.loggedIn) { if (pwList !== null) { - pwList.forEach(row => async function() { - try { - await connection.updatePwDatensatz(req, row); - } catch (err) { - console.log('ChangePW update sql: ' + err); - } - }); + pwList.forEach( + (row) => + async function () { + try { + await connection.updatePwDatensatz(req, row); + } catch (err) { + console.log("ChangePW update sql: " + err); + } + } + ); } } else { - return res.render('login', { errormsg: 'Nach Pw Änderung bitte erneut anmelden' }); + return res.render("login", { + errormsg: "Nach Pw Änderung bitte erneut anmelden", + }); } await sessionHandler.updteUserPwFromSession(req.body.newPw); connection.updateUserPw(req); - return res.render('login', { errormsg: req.t('loginErrorPwChange') }); + return res.render("login", { errormsg: req.t("loginErrorPwChange") }); } export default { signUp, - routes: { - post: { - logout, - signIn, - changePw - } - }, - getUserFromSession + routes: { post: { logout, signIn, changePw } }, + getUserFromSession, }; diff --git a/index.js b/index.js index c3470b1..4d41faf 100644 --- a/index.js +++ b/index.js @@ -1,19 +1,20 @@ // This script runs on serverside // dependencies required for the app -import 'dotenv/config'; - -import express from 'express'; -import helmet from 'helmet'; -import bodyParser from 'body-parser'; -import session from 'express-session'; -import rateLimit from 'express-rate-limit'; -import middleware from 'i18next-http-middleware'; -import i18next from 'i18next'; -import Backend from 'i18next-fs-backend'; -import FileStore from 'session-file-store'; -import routes from './routes.js'; -import cache from './cache.js'; +import "dotenv/config"; + +import bodyParser from "body-parser"; +import express from "express"; +import rateLimit from "express-rate-limit"; +import session from "express-session"; +import helmet from "helmet"; +import i18next from "i18next"; +import Backend from "i18next-fs-backend"; +import middleware from "i18next-http-middleware"; +import FileStore from "session-file-store"; + +import cache from "./cache.js"; +import routes from "./routes.js"; const SessionFileStore = FileStore(session); @@ -21,11 +22,9 @@ i18next .use(Backend) .use(middleware.LanguageDetector) .init({ - backend: { - loadPath: './locales/{{lng}}/translation.json' - }, - fallbackLng: 'en', - preload: ['en', 'de'] + backend: { loadPath: "./locales/{{lng}}/translation.json" }, + fallbackLng: "en", + preload: ["en", "de"], }); const app = express(); @@ -35,47 +34,52 @@ const limiter = rateLimit({ max: 900, // Limit each IP to 900 requests per `window` (here, per 15 minutes) standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers - message: 'Sorry, you reached the request limit! Please try again later!' + message: "Sorry, you reached the request limit! Please try again later!", }); // Apply the rate limiting middleware to all requests app.use(limiter); -app.use( - middleware.handle(i18next) -); +app.use(middleware.handle(i18next)); app.use(cache()); -app.use(helmet({ - contentSecurityPolicy: process.env.HTTPS === 'true' || process.env.HTTPS === true ? true : false, -})); +app.use( + helmet({ + contentSecurityPolicy: + process.env.HTTPS === "true" || process.env.HTTPS === true + ? true + : false, + }) +); // Why do i need extended false and not true? // https://stackoverflow.com/questions/35931135/cannot-post-error-using-express app.use(bodyParser.urlencoded({ extended: false })); -app.set('view engine', 'ejs'); +app.set("view engine", "ejs"); // render css files -app.use(express.static('public')); +app.use(express.static("public")); // Is only stored on server. -app.use(session({ - secret: process.env.SECRET, - resave: true, - saveUninitialized: true, - store: new SessionFileStore({ - path: './sessions', - ttl: 15 * 60, // 15 minutes - reapAsync: true +app.use( + session({ + secret: process.env.SECRET, + resave: true, + saveUninitialized: true, + store: new SessionFileStore({ + path: "./sessions", + ttl: 15 * 60, // 15 minutes + reapAsync: true, + }), }) -})); +); // routing routes(app); // set app to listen on port 3001 -app.listen(3001, function() { - console.log('server is running on port 3001'); +app.listen(3001, function () { + console.log("server is running on port 3001"); }); diff --git a/locales/de/translation.json b/locales/de/translation.json index eeb65da..9c26534 100644 --- a/locales/de/translation.json +++ b/locales/de/translation.json @@ -23,7 +23,7 @@ "login": "Anmelden", "generalError": "Registrierung fehlgeschlagen!", "userAlreadyExists": "Benutzer existiert bereits!", - "pwMissmatch":"Die beiden Passwörter stimmen nicht überein", + "pwMissmatch": "Die beiden Passwörter stimmen nicht überein", "pwNotFound": "Error: Pw wurde nicht gefunden!", "pwHashProblem": "Pw Hash Problem", "userDataNotFound": "Error: User Daten nicht gefunden!" diff --git a/locales/en/translation.json b/locales/en/translation.json index 48994a9..2cd93e6 100644 --- a/locales/en/translation.json +++ b/locales/en/translation.json @@ -23,7 +23,7 @@ "login": "Login", "generalError": "Sign up error!", "userAlreadyExists": "User already exists!", - "pwMissmatch":"Pw missmage", + "pwMissmatch": "Pw missmage", "pwNotFound": "Error: Pw not found!", "pwHashProblem": "pw hash problem", "userDataNotFound": "Error: User data not found!" diff --git a/routes.js b/routes.js index 6996475..51a398f 100644 --- a/routes.js +++ b/routes.js @@ -1,97 +1,115 @@ // This script runs on serversider -import user from './user.js'; -import customer from './customer.js'; -import administration from './administration.js'; // TODO: Change name !!! -import sessionHandler from './sessionHandler.js'; +import administration from "./administration.js"; // TODO: Change name !!! +import customer from "./customer.js"; +import sessionHandler from "./sessionHandler.js"; +import user from "./user.js"; export default function (app) { let routes = { customer, administration }; - app.get('/', async function(req, res) { - console.log('redirect to / (loadData)'); - + app.get("/", async function (req, res) { + console.log("redirect to / (loadData)"); + if (!req.session.loggedIn) { - console.log('user is null1 => login'); - return res.render('login', { errormsg: await sessionHandler.getErrorFromSession(req) }); + console.log("user is null1 => login"); + return res.render("login", { + errormsg: await sessionHandler.getErrorFromSession(req), + }); } - + await administration.loadData(req, res); }); - - app.get('/index', function(req, res) { - res.redirect('/'); + + app.get("/index", function (req, res) { + res.redirect("/"); }); - - app.post('/signup', async function(req, res) { + + app.post("/signup", async function (req, res) { try { if (await customer.signUp(req, res)) { - console.log('werde user einloggen'); + console.log("werde user einloggen"); await customer.signIn(req, res); } else { let msg = await sessionHandler.getErrorFromSession(req); - return res.render('signup', { userData: customer.getUserFromSession(req), errormsg: msg }); + return res.render("signup", { + userData: customer.getUserFromSession(req), + errormsg: msg, + }); } } catch (err) { - console.log('Error on Login: ' + err); - return res.render('signup', { userData: user, errormsg: req.t('signup.generalError') }); + console.log("Error on Login: " + err); + return res.render("signup", { + userData: user, + errormsg: req.t("signup.generalError"), + }); } }); - - app.get('/signup', function(req, res) { - return res.render('signup', { userData: customer.getUserFromSession(req), errormsg: '' }); + + app.get("/signup", function (req, res) { + return res.render("signup", { + userData: customer.getUserFromSession(req), + errormsg: "", + }); }); - - app.get('/documentation', function(req, res) { + + app.get("/documentation", function (req, res) { let user = customer.getUserFromSession(req); if (user.loggedIn == false) { customer.signIn(req, res); } else { - return res.render('documentation', { userData: user }); + return res.render("documentation", { userData: user }); } }); - - app.get('/changepw', async function(req, res) { + + app.get("/changepw", async function (req, res) { let user = customer.getUserFromSession(req); - - if (user.loggedIn === false || typeof user.loggedIn === 'undefined') { - res.redirect('/'); - //await customer.signIn(req, res); + + if (user.loggedIn === false || typeof user.loggedIn === "undefined") { + res.redirect("/"); + // await customer.signIn(req, res); } else { - return res.render('changepw', { userData: user }); + return res.render("changepw", { userData: user }); } }); - - // here you set all routes that would end in cannot get/... or cannot post/... to default page could also be you own error page + + // here you set all routes that would end in cannot get/... or cannot post/... + // to default page could also be you own error page // ---->cant use this or it will call loadData around 10 times for no reason. /*app.get('*', function(req, res) { - console.log("redirect to / (*1)"); - return res.redirect('/'); - });*/ + console.log("redirect to / (*1)"); + return res.redirect('/'); + });*/ Object.keys(routes).forEach(function (path) { if (routes[path]?.routes?.post) { Object.keys(routes[path].routes?.post).forEach(function (method) { - app.post(`/${path.toLowerCase()}/${method.toLowerCase()}`, routes[path].routes?.post[method]); + app.post( + `/${path.toLowerCase()}/${method.toLowerCase()}`, + routes[path].routes?.post[method] + ); }); } if (routes[path]?.routes?.get) { Object.keys(routes[path].routes?.get).forEach(function (method) { - app.get(`/${path.toLowerCase()}/${method.toLowerCase()}`, routes[path].routes?.get[method]); + app.get( + `/${path.toLowerCase()}/${method.toLowerCase()}`, + routes[path].routes?.get[method] + ); }); } }); - + // Catch for all other posts routes and redirect to default page - app.post('*', function(req, res) { - console.log('catch all posts and redirect to index'); - return res.redirect('/'); + app.post("*", function (req, res) { + console.log("catch all posts and redirect to index"); + return res.redirect("/"); }); // Catch for all other routes and redirect to default page - app.get('*', function(req, res) { - console.log('catch all and redirect to index'); - return res.redirect('/index'); + app.get("*", function (req, res) { + console.log("catch all and redirect to index"); + return res.redirect("/index"); }); } diff --git a/sessionHandler.js b/sessionHandler.js index 3378a6a..3638c14 100644 --- a/sessionHandler.js +++ b/sessionHandler.js @@ -1,14 +1,14 @@ // This script runs on serverside -import { promisify } from 'es6-promisify'; -import escape from 'lodash.escape'; +import { promisify } from "es6-promisify"; +import escape from "lodash.escape"; /** * Set user data to session - * @param {*} req - * @param {*} res - * @param {*} user - * @returns + * @param {*} req + * @param {*} res + * @param {*} user + * @returns */ async function setUserToSession(req, res, user) { req.session.loggedIn = true; @@ -21,34 +21,39 @@ async function setUserToSession(req, res, user) { await saveSession(req); } -/** Delte user data from session - * - * @param {*} req +/** + * Delte user data from session + * + * @param {*} req */ async function deleteUserFromSession(req) { req.session.loggedIn = false; req.session.userid = 0; - req.session.username = ''; - req.session.firstname = ''; - req.session.lastname = ''; - req.session.pw = ''; + req.session.username = ""; + req.session.firstname = ""; + req.session.lastname = ""; + req.session.pw = ""; await saveSession(req); } -/** Regenerate session - * - * @param {*} req +/** + * Regenerate session + * + * @param {*} req */ async function regenerateSession(req) { - let regeneratedSession = promisify(req.session.regenerate.bind(req.session)); + let regeneratedSession = promisify( + req.session.regenerate.bind(req.session) + ); await regeneratedSession(); await saveSession(req); } -/** Update pw from session - * - * @param {*} req +/** + * Update pw from session + * + * @param {*} req */ async function updteUserPwFromSession(req) { req.session.pw = escape(req.body.newPw); @@ -62,16 +67,17 @@ async function setErrormsgToSession(req, msg) { async function getErrorFromSession(req) { let errormsg = req.session.errormsg; - req.session.errormsg = ''; + req.session.errormsg = ""; await saveSession(req); - return errormsg ?? ''; + return errormsg ?? ""; } -/** Save session - * - * @param {*} req +/** + * Save session + * + * @param {*} req */ -async function saveSession(req){ +async function saveSession(req) { let save = promisify(req.session.save.bind(req.session)); await save(); } @@ -82,5 +88,5 @@ export default { updteUserPwFromSession, regenerateSession, setErrormsgToSession, - getErrorFromSession + getErrorFromSession, }; diff --git a/views/signup.ejs b/views/signup.ejs index 8a7816b..5dae22f 100644 --- a/views/signup.ejs +++ b/views/signup.ejs @@ -49,7 +49,7 @@ - +
<% if(errormsg.length > 0){ %> @@ -61,7 +61,7 @@ - <% } %> + <% } %>