From 3e081e6a0c14352ed70b1c60da996ac8d679c79a Mon Sep 17 00:00:00 2001 From: Alex Bogdanovski Date: Fri, 30 Jul 2021 22:18:17 +0300 Subject: [PATCH] added password strength checker and new option para.min_password_strength to enforce password strength --- README.md | 4 +- .../java/com/erudika/scoold/ScooldServer.java | 2 + .../scoold/controllers/SigninController.java | 46 ++++++++++++-- .../utils/ScooldRequestInterceptor.java | 1 + src/main/resources/lang_en.properties | 1 + src/main/resources/static/scripts/scoold.js | 63 ++++++++++++++++++- src/main/resources/static/styles/style.css | 6 ++ src/main/resources/templates/base.vm | 3 +- src/main/resources/templates/signin.vm | 8 ++- 9 files changed, 124 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 030483c8..37d587ce 100755 --- a/README.md +++ b/README.md @@ -211,9 +211,9 @@ para.admins = "admin@domain.com" # enable or disable email and password authentication para.password_auth_enabled = true # min. password length -para.min_password_length = 7 +para.min_password_length = 8 # min. password strength (1=Good, 2=Strong, 3=Very Strong) -para.min_password_strength = 1 +para.min_password_strength = 2 # Session cookie name para.auth_cookie = "scoold-auth" # Facebook - create your own Facebook app first! diff --git a/src/main/java/com/erudika/scoold/ScooldServer.java b/src/main/java/com/erudika/scoold/ScooldServer.java index f77894dc..2cffa495 100644 --- a/src/main/java/com/erudika/scoold/ScooldServer.java +++ b/src/main/java/com/erudika/scoold/ScooldServer.java @@ -83,6 +83,8 @@ public class ScooldServer extends SpringBootServletInitializer { public static final int MAX_TAGS_PER_POST = Config.getConfigInt("max_tags_per_post", 5); public static final int MAX_REPLIES_PER_POST = Config.getConfigInt("max_replies_per_post", 500); public static final int MAX_FAV_TAGS = Config.getConfigInt("max_fav_tags", 50); + public static final int MIN_PASS_LENGTH = Config.getConfigInt("min_password_length", 8); + public static final int MIN_PASS_STRENGTH = Config.getConfigInt("min_password_strength", 2); public static final int ANSWER_VOTEUP_REWARD_AUTHOR = Config.getConfigInt("answer_voteup_reward_author", 10); public static final int QUESTION_VOTEUP_REWARD_AUTHOR = Config.getConfigInt("question_voteup_reward author", 5); diff --git a/src/main/java/com/erudika/scoold/controllers/SigninController.java b/src/main/java/com/erudika/scoold/controllers/SigninController.java index 930039f0..b2fef085 100755 --- a/src/main/java/com/erudika/scoold/controllers/SigninController.java +++ b/src/main/java/com/erudika/scoold/controllers/SigninController.java @@ -24,6 +24,8 @@ import com.erudika.para.utils.Config; import com.erudika.para.utils.Utils; import static com.erudika.scoold.ScooldServer.HOMEPAGE; +import static com.erudika.scoold.ScooldServer.MIN_PASS_LENGTH; +import static com.erudika.scoold.ScooldServer.MIN_PASS_STRENGTH; import static com.erudika.scoold.ScooldServer.SIGNINLINK; import com.erudika.scoold.utils.HttpUtils; import com.erudika.scoold.utils.ScooldUtils; @@ -150,7 +152,8 @@ public String signup(@RequestParam String name, @RequestParam String email, @Req HttpServletRequest req, HttpServletResponse res, Model model) { boolean approvedDomain = utils.isEmailDomainApproved(email); if (!utils.isAuthenticated(req) && approvedDomain) { - if (!isEmailRegistered(email) && isSubmittedByHuman(req)) { + boolean goodPass = isPasswordStrongEnough(passw); + if (!isEmailRegistered(email) && isSubmittedByHuman(req) && goodPass) { User u = pc.signIn("password", email + ":" + name + ":" + passw, false); if (u != null && u.getActive()) { setAuthCookie(u.getPassword(), req, res); @@ -166,7 +169,11 @@ public String signup(@RequestParam String name, @RequestParam String email, @Req model.addAttribute("name", name); model.addAttribute("bademail", email); model.addAttribute("emailPattern", Email.EMAIL_PATTERN); - model.addAttribute("error", Collections.singletonMap("email", utils.getLang(req).get("msgcode.1"))); + if (!goodPass) { + model.addAttribute("error", Collections.singletonMap("passw", utils.getLang(req).get("msgcode.8"))); + } else { + model.addAttribute("error", Collections.singletonMap("email", utils.getLang(req).get("msgcode.1"))); + } return "base"; } } @@ -222,7 +229,11 @@ public String changePass(@RequestParam String email, model.addAttribute("token", ""); model.addAttribute("verified", !error); if (error) { - model.addAttribute("error", Collections.singletonMap("email", utils.getLang(req).get("msgcode.7"))); + if (!isPasswordStrongEnough(newpassword)) { + model.addAttribute("error", Collections.singletonMap("newpassword", utils.getLang(req).get("msgcode.8"))); + } else { + model.addAttribute("error", Collections.singletonMap("email", utils.getLang(req).get("msgcode.7"))); + } } return "base"; } @@ -317,6 +328,33 @@ private boolean isSubmittedByHuman(HttpServletRequest req) { return StringUtils.isBlank(req.getParameter("leaveblank")) && (System.currentTimeMillis() - time >= 7000); } + private boolean isPasswordStrongEnough(String password) { + if (StringUtils.length(password) >= MIN_PASS_LENGTH) { + int score = 0; + if (password.matches(".*[a-z].*")) { + score++; + } + if (password.matches(".*[A-Z].*")) { + score++; + } + if (password.matches(".*[0-9].*")) { + score++; + } + if (password.matches(".*[^\\w\\s\\n\\t].*")) { + score++; + } + // 1 = good strength, 2 = medium strength, 3 = high strength + if (MIN_PASS_STRENGTH <= 1) { + return score >= 2; + } else if (MIN_PASS_STRENGTH == 2) { + return score >= 3; + } else { + return score >= 4; + } + } + return false; + } + private String generatePasswordResetToken(String email, HttpServletRequest req) { if (StringUtils.isBlank(email)) { return ""; @@ -334,7 +372,7 @@ private String generatePasswordResetToken(String email, HttpServletRequest req) } private boolean resetPassword(String email, String newpass, String token) { - if (StringUtils.isBlank(newpass) || StringUtils.isBlank(token) || newpass.length() < Config.MIN_PASS_LENGTH) { + if (StringUtils.isBlank(newpass) || StringUtils.isBlank(token) || !isPasswordStrongEnough(newpass)) { return false; } Sysprop s = pc.read(email); diff --git a/src/main/java/com/erudika/scoold/utils/ScooldRequestInterceptor.java b/src/main/java/com/erudika/scoold/utils/ScooldRequestInterceptor.java index de569860..7f59c180 100755 --- a/src/main/java/com/erudika/scoold/utils/ScooldRequestInterceptor.java +++ b/src/main/java/com/erudika/scoold/utils/ScooldRequestInterceptor.java @@ -121,6 +121,7 @@ public void postHandle(HttpServletRequest request, HttpServletResponse response, modelAndView.addObject("MAX_TAGS_PER_POST", MAX_TAGS_PER_POST); modelAndView.addObject("MAX_REPLIES_PER_POST", MAX_REPLIES_PER_POST); modelAndView.addObject("MAX_FAV_TAGS", MAX_FAV_TAGS); + modelAndView.addObject("MIN_PASS_LENGTH", MIN_PASS_LENGTH); modelAndView.addObject("ANSWER_VOTEUP_REWARD_AUTHOR", ANSWER_VOTEUP_REWARD_AUTHOR); modelAndView.addObject("QUESTION_VOTEUP_REWARD_AUTHOR", QUESTION_VOTEUP_REWARD_AUTHOR); modelAndView.addObject("VOTEUP_REWARD_AUTHOR", VOTEUP_REWARD_AUTHOR); diff --git a/src/main/resources/lang_en.properties b/src/main/resources/lang_en.properties index 81d320c3..a063c377 100644 --- a/src/main/resources/lang_en.properties +++ b/src/main/resources/lang_en.properties @@ -325,6 +325,7 @@ msgcode.4 = Your account has been deleted. msgcode.5 = You have been signed out. msgcode.6 = Your email address has not been confirmed yet. msgcode.7 = Something went terribly wrong! +msgcode.8 = Password is not strong enough. msgcode.16 = This post was deleted. about.scoold.1 = Scoold is an open source forum platform for knowledge sharing and collaboration. It was inspired by StackOverflow.com the sites of the Stack Exchange family and we have huge respect towards the people behind them. diff --git a/src/main/resources/static/scripts/scoold.js b/src/main/resources/static/scripts/scoold.js index 862e00bc..d369d53c 100755 --- a/src/main/resources/static/scripts/scoold.js +++ b/src/main/resources/static/scripts/scoold.js @@ -15,7 +15,7 @@ * * For issues and patches go to: https://github.com/erudika */ -/*global window: false, jQuery: false, $: false, google, hljs, RTL_ENABLED, CONTEXT_PATH, M, CONFIRM_MSG, WELCOME_MESSAGE, WELCOME_MESSAGE_ONLOGIN, MAX_TAGS_PER_POST: false */ +/*global window: false, jQuery: false, $: false, google, hljs, RTL_ENABLED, CONTEXT_PATH, M, CONFIRM_MSG, WELCOME_MESSAGE, WELCOME_MESSAGE_ONLOGIN, MAX_TAGS_PER_POST, MIN_PASS_LENGTH: false */ "use strict"; $(function () { var mapCanvas = $("div#map-canvas"); @@ -1188,4 +1188,65 @@ $(function () { return true; }); } + + if (window.location.pathname.indexOf(CONTEXT_PATH + '/signin/') >= 0) { + var passwordInput = $('#passw, #newpassword'); + console.log(passwordInput); + var scoreMessage = $('#pass-meter-message'); + var messagesList = ['Too simple', 'Weak', 'Good', 'Strong', 'Very strong']; + passwordInput.on('keyup', function () { + var score = 0; + var val = passwordInput.val(); + if (val.length >= (MIN_PASS_LENGTH || 8)) { + console.log("len"); + ++score; + } + if (val.match(/(?=.*[a-z])/)) { + console.log("lower"); + ++score; + } + if (val.match(/(?=.*[A-Z])/)) { + console.log("upper"); + ++score; + } + if (val.match(/(?=.*[0-9])/)) { + console.log("num"); + ++score; + } + if (val.match(/(?=.*[^\w\s\n\t])/)) { + console.log("sym"); + ++score; + } + if (val.length === 0) { + score = -1; + } + switch (score) { + case 0: + case 1: + passwordInput.attr('class', 'pass-meter'); + scoreMessage.text(messagesList[0]); + break; + case 2: + passwordInput.attr('class', 'pass-meter psms-25'); + scoreMessage.text(messagesList[1]); + break; + case 3: + passwordInput.attr('class', 'pass-meter psms-50'); + scoreMessage.text(messagesList[2]); + break; + case 4: + passwordInput.attr('class', 'pass-meter psms-75'); + scoreMessage.text(messagesList[3]); + break; + case 5: + passwordInput.attr('class', 'pass-meter psms-100'); + scoreMessage.text(messagesList[4]); + break; + default: + passwordInput.attr('class', ''); + scoreMessage.text(''); + } + }); + } + }); diff --git a/src/main/resources/static/styles/style.css b/src/main/resources/static/styles/style.css index 7772c61e..1cab5549 100755 --- a/src/main/resources/static/styles/style.css +++ b/src/main/resources/static/styles/style.css @@ -392,6 +392,12 @@ img.profile-pic { display: none; } +#passw.pass-meter, #newpassword.pass-meter { border-bottom: 3px solid #F44336; box-shadow: none;} +#passw.pass-meter.psms-25, #newpassword.pass-meter.psms-25 { border-bottom: 3px solid #F44336; box-shadow: none; } +#passw.pass-meter.psms-50, #newpassword.pass-meter.psms-50 { border-bottom: 3px solid #FFC000; box-shadow: none; } +#passw.pass-meter.psms-75, #newpassword.pass-meter.psms-75 { border-bottom: 3px solid #b2ca7d; box-shadow: none; } +#passw.pass-meter.psms-100, #newpassword.pass-meter.psms-100 { border-bottom: 3px solid #88AF12; box-shadow: none; } + @media (max-width: 1400px) { .container {width: 90%;} } diff --git a/src/main/resources/templates/base.vm b/src/main/resources/templates/base.vm index 1f71343a..8144a22b 100755 --- a/src/main/resources/templates/base.vm +++ b/src/main/resources/templates/base.vm @@ -423,11 +423,12 @@ #set($isRTLJS = "RTL_ENABLED = $RTL_ENABLED") #set($isAdminJS = "IS_ADMIN = $isAdmin") #set($maxTagsJS = "MAX_TAGS_PER_POST = $MAX_TAGS_PER_POST") + #set($minPassLen = "MIN_PASS_LENGTH = $MIN_PASS_LENGTH") #set($_welcomeMsg = $scooldUtils.getWelcomeMessage($authUser)) #set($_welcomeMessageJS = "WELCOME_MESSAGE = '$_welcomeMsg'") #set($_welcomeMsgOnLogin = $scooldUtils.getWelcomeMessageOnLogin($authUser)) #set($_welcomeMessageOnLoginJS = "WELCOME_MESSAGE_ONLOGIN = '$_welcomeMsgOnLogin'") - + #if ($request.getServletPath() == $signinlink) diff --git a/src/main/resources/templates/signin.vm b/src/main/resources/templates/signin.vm index ada4e488..ce6e25eb 100755 --- a/src/main/resources/templates/signin.vm +++ b/src/main/resources/templates/signin.vm @@ -43,7 +43,9 @@ #if(!$resend)
- + +
+ #getmessagebox("red white-text" $error.get("passw"))
#end @@ -97,7 +99,9 @@ #if($token)
- + +
+ #getmessagebox("red white-text" $error.get("newpassword"))
#end #if($token)