-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: added frontend tests as well as updated the password strength meter
- Loading branch information
1 parent
45e4df4
commit a5803a6
Showing
7 changed files
with
542 additions
and
437 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,82 +1,130 @@ | ||
var TEACHER_PASSWORD_FIELD_ID = ''; | ||
var INDEP_STUDENT_PASSWORD_FIELD_ID = ''; | ||
let teacher_password_field = ''; | ||
let indep_student_password_field = ''; | ||
let most_used_passwords = ['Abcd1234', 'Password1', 'Qwerty123']; | ||
|
||
let password_strengths = [ | ||
{ name: 'No password!', colour: '#FF0000' }, | ||
{ name: 'Password too weak!', colour: '#DBA901' }, | ||
{ name: 'Strong password!', colour: '#088A08' }, | ||
{ name: 'Password too common!', colour: '#DBA901' } | ||
{ name: 'No password!', colour: '#FF0000' }, | ||
{ name: 'Password too weak!', colour: '#DBA901' }, | ||
{ name: 'Password too common!', colour: '#DBA901' }, | ||
{ name: 'Strong password!', colour: '#088A08' } | ||
]; | ||
|
||
$(function() { | ||
async function handlePasswordStrength() { | ||
const teacherPassword = $('#id_teacher_signup-teacher_password').val(); | ||
const independentStudentPassword = $( | ||
'#id_independent_student_signup-password' | ||
).val(); | ||
|
||
const isTeacherPasswordNonEmpty = teacherPassword.length > 0; | ||
const isIndependentStudentPasswordNonEmpty = independentStudentPassword.length > 0; | ||
|
||
const isTeacherPasswordComplex = isPasswordStrong(teacherPassword, true) && isTeacherPasswordNonEmpty; | ||
const isIndependentStudentPasswordComplex = isPasswordStrong(independentStudentPassword, false) && isIndependentStudentPasswordNonEmpty; | ||
|
||
const isTeacherPasswordNotPwned = await getPwnedStatus(teacherPassword) && isTeacherPasswordComplex; | ||
const isIndependentStudentPasswordNotPwned = await getPwnedStatus(independentStudentPassword) && isIndependentStudentPasswordComplex; | ||
|
||
const teacherPasswordStrength = [isTeacherPasswordNonEmpty, isTeacherPasswordComplex, isTeacherPasswordNotPwned].filter(value => value).length; | ||
const independentStudentPasswordStrength = [isIndependentStudentPasswordNonEmpty, isIndependentStudentPasswordComplex, isIndependentStudentPasswordNotPwned].filter(value => value).length; | ||
|
||
console.log(teacherPasswordStrength, independentStudentPasswordStrength) | ||
console.log(password_strengths) | ||
|
||
$('#teacher-password-sign').css( | ||
'background-color', | ||
password_strengths[teacherPasswordStrength].colour | ||
); | ||
$('#teacher-password-text').html(password_strengths[teacherPasswordStrength].name); | ||
$('#student-password-sign').css( | ||
'background-color', | ||
password_strengths[independentStudentPasswordStrength].colour | ||
); | ||
$('#student-password-text').html(password_strengths[independentStudentPasswordStrength].name); | ||
} | ||
|
||
teacher_password_field = $('#' + TEACHER_PASSWORD_FIELD_ID); | ||
indep_student_password_field = $('#' + INDEP_STUDENT_PASSWORD_FIELD_ID); | ||
|
||
setUpDynamicUpdates(teacher_password_field, true); | ||
setUpDynamicUpdates(indep_student_password_field, false); | ||
const getPwnedStatus = async (password) => { | ||
const computeSHA1Hash = (password) => | ||
CryptoJS.SHA1(password).toString().toUpperCase(); | ||
|
||
updatePasswordStrength(true); | ||
updatePasswordStrength(false); | ||
}); | ||
const doesSuffixExist = (data, suffix) => data.includes(suffix); | ||
|
||
function setUpDynamicUpdates(password_field, isTeacher) { | ||
password_field.on('keyup', function(){ | ||
updatePasswordStrength(isTeacher) | ||
}); | ||
password_field.on('paste', function(){ | ||
updatePasswordStrength(isTeacher) | ||
}); | ||
password_field.on('cut', function(){ | ||
updatePasswordStrength(isTeacher) | ||
}); | ||
} | ||
const calculateLevenshteinDistance = async (a, b) => { | ||
if (a.length === 0) return b.length; | ||
if (b.length === 0) return a.length; | ||
|
||
function updatePasswordStrength(isTeacher) { | ||
// The reason for the timeout is that if we just got $('#...').val() we'd get the | ||
// old value before the keypress / change. Apparently even jQuery itself implements | ||
// things this way, so maybe there is no better workaround. | ||
const matrix = Array.from({ length: a.length + 1 }, (_, i) => [i]); | ||
for (let j = 1; j <= b.length; j++) matrix[0][j] = j; | ||
|
||
setTimeout(function() { | ||
let password; | ||
for (let i = 1; i <= a.length; i++) { | ||
for (let j = 1; j <= b.length; j++) { | ||
const cost = a[i - 1] === b[j - 1] ? 0 : 1; | ||
matrix[i][j] = Math.min( | ||
matrix[i - 1][j] + 1, // deletion | ||
matrix[i][j - 1] + 1, // insertion | ||
matrix[i - 1][j - 1] + cost // substitution | ||
); | ||
} | ||
} | ||
|
||
if (isTeacher) { | ||
password = $('#' + TEACHER_PASSWORD_FIELD_ID).val(); | ||
} | ||
else { | ||
password = $('#' + INDEP_STUDENT_PASSWORD_FIELD_ID).val(); | ||
} | ||
return matrix[a.length][b.length]; | ||
}; | ||
|
||
let strength = 0; | ||
if (password.length > 0) { strength++; } | ||
if (isPasswordStrong(password, isTeacher)) { strength++; } | ||
try { | ||
const hashedPassword = computeSHA1Hash(password); | ||
const prefix = hashedPassword.substring(0, 5); | ||
const suffix = hashedPassword.substring(5); | ||
const apiUrl = `https://api.pwnedpasswords.com/range/${prefix}`; | ||
|
||
if ($.inArray(password, most_used_passwords) >= 0 && strength == 2) { strength = 3; } | ||
const response = await fetch(apiUrl); | ||
|
||
if (isTeacher) { | ||
updatePasswordCSS('#teacher-password-sign', '#teacher-password-text', strength); | ||
} | ||
else { | ||
updatePasswordCSS('#student-password-sign', '#student-password-text', strength); | ||
} | ||
if (!response.ok) { | ||
return true; // ignore the check if the server is down as the popup warns | ||
// the user that we cannot check the password | ||
} | ||
|
||
}); | ||
} | ||
const data = await response.text(); | ||
|
||
function isPasswordStrong(password, isTeacher) { | ||
if (isTeacher) { | ||
return password.length >= 10 && !(password.search(/[A-Z]/) === -1 || password.search(/[a-z]/) === -1 || password.search(/[0-9]/) === -1 || password.search(/[!@#$%^&*()_+\-=\[\]{};':\"\\|,.<>\/?]/) === -1) | ||
if (doesSuffixExist(data, suffix)) { | ||
return false; | ||
} | ||
else { | ||
return password.length >= 8 && !(password.search(/[A-Z]/) === -1 || password.search(/[a-z]/) === -1 || password.search(/[0-9]/) === -1) | ||
|
||
const similarPasswords = data | ||
.split('\n') | ||
.map((line) => line.split(':')) | ||
.filter( | ||
([hashSuffix]) => calculateLevenshteinDistance(suffix, hashSuffix) <= 2 | ||
); | ||
|
||
if (similarPasswords.length > 0) { | ||
similarPasswords.forEach(([hashSuffix, count]) => | ||
console.log(`Hash: ${hashSuffix}, Occurrences: ${count}`) | ||
); | ||
} else { | ||
return true; | ||
} | ||
} | ||
return true; | ||
} catch (error) { | ||
console.error(`Request failed with error: ${error.message}`); | ||
return false; | ||
} | ||
}; | ||
|
||
function updatePasswordCSS(passwordStrengthSign, passwordStrengthText, strength) { | ||
$(passwordStrengthSign).css('background-color', password_strengths[strength].colour); | ||
$(passwordStrengthText).html(password_strengths[strength].name); | ||
function isPasswordStrong(password, isTeacher) { | ||
if (isTeacher) { | ||
return ( | ||
password.length >= 10 && | ||
!( | ||
password.search(/[A-Z]/) === -1 || | ||
password.search(/[a-z]/) === -1 || | ||
password.search(/[0-9]/) === -1 || | ||
password.search(/[!@#$%^&*()_+\-=\[\]{};':\"\\|,.<>\/?]/) === -1 | ||
) | ||
); | ||
} else { | ||
return ( | ||
password.length >= 8 && | ||
!( | ||
password.search(/[A-Z]/) === -1 || | ||
password.search(/[a-z]/) === -1 || | ||
password.search(/[0-9]/) === -1 | ||
) | ||
); | ||
} | ||
} | ||
|
Oops, something went wrong.