Kaynağa Gözat

Add function to calculate password strength

Danilo Bargen 5 yıl önce
ebeveyn
işleme
1019e6baa5

+ 74 - 0
src/helpers/password_strength.ts

@@ -0,0 +1,74 @@
+/**
+ * This file is part of Threema Web.
+ *
+ * Threema Web is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+export const enum Strength {
+    BAD = 'bad',
+    WEAK = 'weak',
+    GOOD = 'good',
+    STRONG = 'strong',
+}
+
+/**
+ * A very simple (rather naive) password strength indicator.
+ */
+export function scorePassword(password: string): {score: number, strength: Strength} {
+    let score = 0;
+
+    // Detect empty password
+    if (password.length === 0) {
+        return {score: score, strength: Strength.BAD};
+    }
+
+    // Award letter count, but less points for repeated letters
+    const letterCount = {};
+    for (const character of password) {
+        letterCount[character] = (letterCount[character] || 0) + 1;
+        score += 5.0 / letterCount[character];
+    }
+
+    // Bonus points for multiple character categories
+    const categories = {
+        digits: /\d/.test(password),
+        lower: /[a-z]/.test(password),
+        upper: /[A-Z]/.test(password),
+        nonWords: /\W/.test(password),
+    };
+    const categoryCount = Object
+        .values(categories)
+        .reduce((total, x) => x ? total + 1 : total, 0);
+    score += (categoryCount - 1) * 10;
+
+    // Truncate to an integer
+    score = Math.trunc(score);
+
+    // Score strength
+    let strength;
+    if (score > 80) {
+        strength = Strength.STRONG;
+    } else if (score > 60) {
+        strength = Strength.GOOD;
+    } else if (score > 40) {
+        strength = Strength.WEAK;
+    } else {
+        strength = Strength.BAD;
+    }
+
+    return {
+        score: score,
+        strength: strength,
+    };
+}

+ 2 - 1
tests/ts/main.ts

@@ -24,7 +24,8 @@ import './confidential_helpers';
 import './containers';
 import './crypto_helpers';
 import './emoji_helpers';
-import './logger_helpers';
 import './helpers';
+import './logger_helpers';
 import './markup_parser';
+import './password_strength_helpers';
 import './receiver_helpers';

+ 47 - 0
tests/ts/password_strength_helpers.ts

@@ -0,0 +1,47 @@
+/**
+ * Copyright © 2016-2019 Threema GmbH (https://threema.ch/).
+ *
+ * This file is part of Threema Web.
+ *
+ * Threema Web is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import {scorePassword, Strength} from '../../src/helpers/password_strength';
+
+describe('Password Strength Helpers', () => {
+    describe('scorePassword', () => {
+        it('returns 0 for empty passwords', function() {
+            expect(scorePassword('')).toEqual({score: 0, strength: Strength.BAD});
+        });
+
+        it('increases points depending on character count', function() {
+            expect(scorePassword('a')).toEqual({score: 5, strength: Strength.BAD});
+            expect(scorePassword('abcd')).toEqual({score: 20, strength: Strength.BAD});
+            expect(scorePassword('aaaa')).toEqual({score: 10, strength: Strength.BAD});
+        });
+
+        it('awards points for multiple character classes', function() {
+            expect(scorePassword('abc')).toEqual({score: 15, strength: Strength.BAD});
+            expect(scorePassword('aBc')).toEqual({score: 25, strength: Strength.BAD});
+            expect(scorePassword('aB3')).toEqual({score: 35, strength: Strength.BAD});
+        });
+
+        it('assigns strength based on score', function() {
+            expect(scorePassword('aB3')).toEqual({score: 35, strength: Strength.BAD});
+            expect(scorePassword('aB3cde')).toEqual({score: 50, strength: Strength.WEAK});
+            expect(scorePassword('aB3cdefgh')).toEqual({score: 65, strength: Strength.GOOD});
+            expect(scorePassword('aB3cdefghijkl')).toEqual({score: 85, strength: Strength.STRONG});
+        });
+    });
+});