Browse Source

Migrate numbers to digits everywhere

pull/763/head
Guillaume Vincent 1 year ago
parent
commit
8c19c8d25b
20 changed files with 172 additions and 92 deletions
  1. +1
    -1
      cli/lesspass/__init__.py
  2. +16
    -0
      containers/backend/api/migrations/0011_rename_numbers_password_digits.py
  3. +1
    -1
      containers/backend/api/models.py
  4. +11
    -1
      containers/backend/api/serializers.py
  5. +63
    -18
      containers/backend/api/tests/tests_passwords.py
  6. +3
    -3
      doc/openapi.yaml
  7. +0
    -1
      mobile/src/password/passwordGenerator.test.js
  8. +17
    -7
      mobile/src/password/profilesReducer.js
  9. +5
    -5
      mobile/src/password/profilesReducer.test.js
  10. +1
    -1
      packages/lesspass-pure/src/components/Options.vue
  11. +1
    -1
      packages/lesspass-pure/src/i18n/en.json
  12. +12
    -10
      packages/lesspass-pure/src/services/url-parser.js
  13. +1
    -1
      packages/lesspass-pure/src/store/defaultPassword.js
  14. +0
    -1
      packages/lesspass-pure/src/views/ExportYourPasswords.vue
  15. +26
    -27
      packages/lesspass-pure/src/views/PasswordGenerator.vue
  16. +4
    -4
      packages/lesspass-pure/tests/unit/services/url-parser.spec.js
  17. +2
    -2
      packages/lesspass-pure/tests/unit/store/getters.spec.js
  18. +6
    -6
      packages/lesspass-pure/tests/unit/store/mutations.spec.js
  19. +1
    -1
      packages/lesspass-site/index.html
  20. +1
    -1
      packages/lesspass/test.js

+ 1
- 1
cli/lesspass/__init__.py View File

@@ -31,7 +31,7 @@ Options:
--no-symbols remove symbols from password
-c, --clipboard copy generated password to clipboard rather than displaying it.
Need pbcopy (OSX), xsel or xclip (Linux) or clip (Windows).
-v, --version lesspass version number
-v, --version lesspass version

Examples:



+ 16
- 0
containers/backend/api/migrations/0011_rename_numbers_password_digits.py View File

@@ -0,0 +1,16 @@
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("api", "0010_alter_password_site_and_login"),
]

operations = [
migrations.RenameField(
model_name="password",
old_name="numbers",
new_name="digits",
),
]

+ 1
- 1
containers/backend/api/models.py View File

@@ -78,7 +78,7 @@ class Password(DateMixin):
lowercase = models.BooleanField(default=True)
uppercase = models.BooleanField(default=True)
symbols = models.BooleanField(default=True)
numbers = models.BooleanField(default=True)
digits = models.BooleanField(default=True)

length = models.IntegerField(default=16)
counter = models.IntegerField(default=1)


+ 11
- 1
containers/backend/api/serializers.py View File

@@ -13,7 +13,7 @@ class PasswordSerializer(serializers.ModelSerializer):
"lowercase",
"uppercase",
"symbols",
"numbers",
"digits",
"counter",
"length",
"version",
@@ -26,6 +26,16 @@ class PasswordSerializer(serializers.ModelSerializer):
user = self.context["request"].user
return models.Password.objects.create(user=user, **validated_data)

def to_internal_value(self, data):
if "number" in data and "digits" not in data:
data["digits"] = data["number"]
if "numbers" in data and "digits" not in data:
data["digits"] = data["numbers"]
if "symbol" in data and "symbols" not in data:
data["symbols"] = data["symbol"]
data = super().to_internal_value(data)
return data


class EncryptedPasswordProfileSerializer(serializers.ModelSerializer):
class Meta:


+ 63
- 18
containers/backend/api/tests/tests_passwords.py View File

@@ -45,53 +45,98 @@ class LoginPasswordsTestCase(APITestCase):
self.assertEqual(404, request.status_code)
self.assertEqual(1, models.Password.objects.all().count())

def test_create_password(self):
def test_create_password_old_api(self):
password = {
"site": "lesspass.com",
"login": "test@oslab.fr",
"lowercase": True,
"login": "test@lesspass.com",
"lowercase": False,
"uppercase": True,
"number": True,
"symbol": True,
"counter": 1,
"numbers": False,
"symbols": False,
"counter": 2,
"length": 12,
}
self.assertEqual(0, models.Password.objects.count())
self.client.post("/api/passwords/", password)
self.assertEqual(1, models.Password.objects.count())
profile = models.Password.objects.first()
self.assertEqual(profile.site, "lesspass.com")
self.assertEqual(profile.login, "test@lesspass.com")
self.assertFalse(profile.lowercase)
self.assertTrue(profile.uppercase)
self.assertFalse(profile.digits)
self.assertFalse(profile.symbols)
self.assertEqual(profile.counter, 2)
self.assertEqual(profile.length, 12)
self.assertEqual(profile.version, 2)

def test_create_password_v2(self):
def test_create_password_with_missing_s_old_api(self):
password = {
"site": "lesspass.com",
"login": "test@oslab.fr",
"login": "test@lesspass.com",
"lowercase": True,
"uppercase": True,
"number": True,
"symbol": True,
"number": False,
"symbol": False,
"counter": 1,
"length": 12,
"length": 16,
"version": 2,
}
self.client.post("/api/passwords/", password)
self.assertEqual(2, models.Password.objects.first().version)
profile = models.Password.objects.first()
self.assertFalse(profile.digits)
self.assertFalse(profile.symbols)

def test_create_password_v2(self):
password = {
"site": "lesspass.com",
"login": "testv2@lesspass.com",
"lowercase": True,
"uppercase": False,
"digits": False,
"symbols": False,
"counter": 3,
"length": 16,
"version": 2,
}
self.client.post("/api/passwords/", password)
profile = models.Password.objects.first()
self.assertEqual(profile.site, "lesspass.com")
self.assertEqual(profile.login, "testv2@lesspass.com")
self.assertTrue(profile.lowercase)
self.assertFalse(profile.uppercase)
self.assertFalse(profile.digits)
self.assertFalse(profile.symbols)
self.assertEqual(profile.counter, 3)
self.assertEqual(profile.length, 16)
self.assertEqual(profile.version, 2)

def test_update_password(self):
password = factories.PasswordFactory(user=self.user)
self.assertNotEqual("facebook.com", password.site)
new_password = {
"site": "facebook.com",
"login": "test@oslab.fr",
"login": "test@lesspass.com",
"lowercase": True,
"uppercase": True,
"number": True,
"symbol": True,
"counter": 1,
"length": 12,
"digits": False,
"symbols": False,
"counter": 2,
"length": 20,
"version": 2,
}
request = self.client.put("/api/passwords/%s/" % password.id, new_password)
self.assertEqual(200, request.status_code, request.content.decode("utf-8"))
password_updated = models.Password.objects.get(id=password.id)
self.assertEqual("facebook.com", password_updated.site)
self.assertEqual(password_updated.site, "facebook.com")
self.assertEqual(password_updated.login, "test@lesspass.com")
self.assertTrue(password_updated.lowercase)
self.assertTrue(password_updated.uppercase)
self.assertFalse(password_updated.digits)
self.assertFalse(password_updated.symbols)
self.assertEqual(password_updated.counter, 2)
self.assertEqual(password_updated.length, 20)
self.assertEqual(password_updated.version, 2)

def test_cant_update_other_password(self):
not_my_password = factories.PasswordFactory(user=factories.UserFactory())


+ 3
- 3
doc/openapi.yaml View File

@@ -356,7 +356,7 @@ components:
- site
- uppercase
- lowercase
- numbers
- digits
- symbols
- length
- counter
@@ -378,8 +378,8 @@ components:
description: Generated password has lowercase characters
type: boolean
default: true
numbers:
description: Generated password has numbers
digits:
description: Generated password has digits
type: boolean
default: true
symbols:


+ 0
- 1
mobile/src/password/passwordGenerator.test.js View File

@@ -51,7 +51,6 @@ describe("generatePassword should not care about the extra number field used for
lowercase: true,
uppercase: true,
digits: true,
number: true,
symbols: true,
};
return generatePassword("password", passwordProfile).then(


+ 17
- 7
mobile/src/password/profilesReducer.js View File

@@ -3,13 +3,23 @@ const initialState = {};
export default function reduce(state = initialState, action) {
switch (action.type) {
case "SET_PASSWORD_PROFILES":
return action.profiles.reduce((acc, profile) => {
acc[profile.id] = {
...profile,
["digits"]: profile.numbers,
};
return acc;
}, {});
return action.profiles
.map((profile) => {
if ("numbers" in profile) {
const { numbers, ...profileWithoutNumbers } = profile;
return {
...profileWithoutNumbers,
digits: numbers,
};
}
return profile;
})
.reduce((acc, p) => {
acc[p.id] = {
...p,
};
return acc;
}, {});
case "ADD_PASSWORD_PROFILE":
return {
...state,


+ 5
- 5
mobile/src/password/profilesReducer.test.js View File

@@ -31,16 +31,16 @@ describe("profiles reducer", () => {
}
)
).toEqual({
p1: { id: "p1", numbers: true, digits: true },
p2: { id: "p2", numbers: false, digits: false },
p1: { id: "p1", digits: true },
p2: { id: "p2", digits: false },
});
});
it("REMOVE_PASSWORD_PROFILE", () => {
expect(
reducer(
{
p1: { id: "p1", numbers: true, digits: true },
p2: { id: "p2", numbers: false, digits: false },
p1: { id: "p1", digits: true },
p2: { id: "p2", digits: false },
},
{
type: "REMOVE_PASSWORD_PROFILE",
@@ -48,7 +48,7 @@ describe("profiles reducer", () => {
}
)
).toEqual({
p2: { id: "p2", numbers: false, digits: false },
p2: { id: "p2", digits: false },
});
});
it("ADD_PASSWORD_PROFILE", () => {


+ 1
- 1
packages/lesspass-pure/src/components/Options.vue View File

@@ -54,7 +54,7 @@
type="checkbox"
tabindex="1"
class="form-check-input"
v-model="options.numbers"
v-model="options.digits"
/>
<label class="form-check-label" for="numbers__btn">
0-9


+ 1
- 1
packages/lesspass-pure/src/i18n/en.json View File

@@ -1,6 +1,6 @@
{
"AlreadyOnLessPass": "Already on LessPass? Sign In",
"AtLeastOneOptionShouldBeSelected": "You must select at least one option among lowercase, uppercase, numbers or symbols.",
"AtLeastOneOptionShouldBeSelected": "You must select at least one option among lowercase, uppercase, digits or symbols.",
"Change my password": "Change my password",
"ChangePasswordError": "We cannot change your password with the information provided.",
"ChangePasswordSuccessful": "Your password was changed successfully.",


+ 12
- 10
packages/lesspass-pure/src/services/url-parser.js View File

@@ -22,10 +22,7 @@ export function removeSiteSubdomain(url) {
const tld = mostUsedTlds[i];
const tldWithDot = `.${tld}`;
if (hostname.endsWith(tldWithDot)) {
const domain = hostname
.replace(tldWithDot, "")
.split(".")
.pop();
const domain = hostname.replace(tldWithDot, "").split(".").pop();
if (domain) {
return domain + tldWithDot;
}
@@ -44,7 +41,7 @@ export function getSuggestions(url) {
const urlElements = cleanedUrl
.toLowerCase()
.split(".")
.filter(element => element.length >= 2);
.filter((element) => element.length >= 2);
if (urlElements.length < 2) return [];
const baseName = urlElements[urlElements.length - 2];
const tld = urlElements[urlElements.length - 1];
@@ -62,13 +59,13 @@ export function getSuggestions(url) {
}

export function getSite() {
return new Promise(resolve => {
return new Promise((resolve) => {
if (
typeof chrome !== "undefined" &&
typeof chrome.tabs !== "undefined" &&
typeof chrome.tabs.query !== "undefined"
) {
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
resolve(tabs[0].url);
});
} else {
@@ -79,19 +76,24 @@ export function getSite() {

function passwordProfileFromRawQuery(query) {
const password = {};
["uppercase", "lowercase", "numbers", "symbols"].forEach(booleanishQuery => {
if ("numbers" in query) {
password["digits"] =
query["numbers"].toLowerCase() === "true" ||
query["numbers"].toLowerCase() === "1";
}
["uppercase", "lowercase", "digits", "symbols"].forEach((booleanishQuery) => {
if (booleanishQuery in query) {
password[booleanishQuery] =
query[booleanishQuery].toLowerCase() === "true" ||
query[booleanishQuery].toLowerCase() === "1";
}
});
["site", "login"].forEach(stringQuery => {
["site", "login"].forEach((stringQuery) => {
if (stringQuery in query) {
password[stringQuery] = query[stringQuery];
}
});
["length", "counter", "version"].forEach(intQuery => {
["length", "counter", "version"].forEach((intQuery) => {
if (intQuery in query) {
password[intQuery] = parseInt(query[intQuery], 10);
}


+ 1
- 1
packages/lesspass-pure/src/store/defaultPassword.js View File

@@ -3,7 +3,7 @@ export default {
site: "",
uppercase: true,
lowercase: true,
numbers: true,
digits: true,
symbols: true,
length: 16,
counter: 1,


+ 0
- 1
packages/lesspass-pure/src/views/ExportYourPasswords.vue View File

@@ -63,7 +63,6 @@ export default {
let lines = [["name", "url", "username", "password"]];
for (let i = 0; i < this.passwords.length; i++) {
const passwordProfile = this.passwords[i];
passwordProfile["digits"] = passwordProfile["numbers"];
const generatedPassword = await LessPass.generatePassword(
passwordProfile,
this.masterPassword


+ 26
- 27
packages/lesspass-pure/src/views/PasswordGenerator.vue View File

@@ -128,19 +128,19 @@ export default {
RemoveAutoComplete,
InputSite,
MasterPassword,
Options
Options,
},
computed: {
...mapState(["password", "passwords"]),
...mapGetters([
"passwordURL",
"shouldAutoFillSite",
"shouldRemoveSubdomain"
])
"shouldRemoveSubdomain",
]),
},
beforeMount() {
if (this.shouldAutoFillSite) {
getSite().then(site => {
getSite().then((site) => {
const cleanedSite = this.shouldRemoveSubdomain
? removeSiteSubdomain(site)
: cleanUrl(site);
@@ -148,7 +148,7 @@ export default {
});
}
this.$store.dispatch("getPasswordFromUrlQuery", {
query: this.$route.query
query: this.$route.query,
});
},
mounted() {
@@ -160,20 +160,20 @@ export default {
return {
masterPassword: "",
passwordGenerated: "",
cleanTimeout: null
cleanTimeout: null,
};
},
watch: {
password: {
handler: function() {
handler: function () {
this.cleanErrors();
},
deep: true
deep: true,
},
masterPassword: function(newMasterPassword) {
masterPassword: function (newMasterPassword) {
this.masterPassword = newMasterPassword;
this.cleanErrors();
}
},
},
methods: {
togglePasswordType(element) {
@@ -211,13 +211,13 @@ export default {
}
const lowercase = this.password.lowercase;
const uppercase = this.password.uppercase;
const numbers = this.password.numbers;
const digits = this.password.digits;
const symbols = this.password.symbols;
if (!lowercase && !uppercase && !numbers && !symbols) {
if (!lowercase && !uppercase && !digits && !symbols) {
message.error(
this.$t(
"AtLeastOneOptionShouldBeSelected",
"You must select at least one option among lowercase, uppercase, numbers or symbols."
"You must select at least one option among lowercase, uppercase, digits or symbols."
)
);
return;
@@ -233,24 +233,23 @@ export default {
}
this.cleanErrors();
const passwordProfile = {
site,
login,
lowercase,
uppercase,
numbers,
digits,
symbols,
length: this.password.length,
counter: this.password.counter,
version: this.password.version
version: this.password.version,
};
return LessPass.generatePassword(
site,
login,
masterPassword,
passwordProfile
).then(passwordGenerated => {
this.passwordGenerated = passwordGenerated;
this.copyPassword();
this.cleanFormIn30Seconds();
});
return LessPass.generatePassword(passwordProfile, masterPassword).then(
(passwordGenerated) => {
this.passwordGenerated = passwordGenerated;
this.copyPassword();
this.cleanFormIn30Seconds();
}
);
},
focusBestInputField() {
try {
@@ -311,7 +310,7 @@ export default {
.then(() => {
this.focusBestInputField();
});
}
}
},
},
};
</script>

+ 4
- 4
packages/lesspass-pure/tests/unit/services/url-parser.spec.js View File

@@ -144,7 +144,7 @@ test("getPasswordFromUrlQuery", () => {
site: "example.org",
uppercase: true,
lowercase: true,
numbers: true,
digits: true,
symbols: false,
length: 16,
counter: 1,
@@ -156,14 +156,14 @@ test("getPasswordFromUrlQuery", () => {
test("getPasswordFromUrlQuery with base 64 encoded password profile", () => {
const query = {
passwordProfileEncoded:
"eyJsb2dpbiI6InRlc3RAZXhhbXBsZS5vcmciLCJzaXRlIjoiZXhhbXBsZS5vcmciLCJ1cHBlcmNhc2UiOnRydWUsImxvd2VyY2FzZSI6dHJ1ZSwibnVtYmVycyI6dHJ1ZSwic3ltYm9scyI6ZmFsc2UsImxlbmd0aCI6MTYsImNvdW50ZXIiOjEsInZlcnNpb24iOjJ9",
"eyJsb2dpbiI6InRlc3RAZXhhbXBsZS5vcmciLCJzaXRlIjoiZXhhbXBsZS5vcmciLCJ1cHBlcmNhc2UiOnRydWUsImxvd2VyY2FzZSI6dHJ1ZSwiZGlnaXRzIjp0cnVlLCJzeW1ib2xzIjpmYWxzZSwibGVuZ3RoIjoxNiwiY291bnRlciI6MSwidmVyc2lvbiI6Mn0",
};
const expectedPassword = {
login: "test@example.org",
site: "example.org",
uppercase: true,
lowercase: true,
numbers: true,
digits: true,
symbols: false,
length: 16,
counter: 1,
@@ -182,7 +182,7 @@ test("getPasswordFromUrlQuery booleanish", () => {
const expectedPassword = {
uppercase: true,
lowercase: true,
numbers: true,
digits: true,
symbols: false,
};
expect(urlParser.getPasswordFromUrlQuery(query)).toEqual(expectedPassword);


+ 2
- 2
packages/lesspass-pure/tests/unit/store/getters.spec.js View File

@@ -7,7 +7,7 @@ test("passwordURL", () => {
site: "example.org",
uppercase: true,
lowercase: true,
numbers: true,
digits: true,
symbols: false,
length: 16,
counter: 1,
@@ -19,7 +19,7 @@ test("passwordURL", () => {
};

expect(getters.passwordURL(state)).toBe(
"https://www.lesspass.com/#/?passwordProfileEncoded=eyJsb2dpbiI6InRlc3RAZXhhbXBsZS5vcmciLCJzaXRlIjoiZXhhbXBsZS5vcmciLCJ1cHBlcmNhc2UiOnRydWUsImxvd2VyY2FzZSI6dHJ1ZSwibnVtYmVycyI6dHJ1ZSwic3ltYm9scyI6ZmFsc2UsImxlbmd0aCI6MTYsImNvdW50ZXIiOjEsInZlcnNpb24iOjJ9"
"https://www.lesspass.com/#/?passwordProfileEncoded=eyJsb2dpbiI6InRlc3RAZXhhbXBsZS5vcmciLCJzaXRlIjoiZXhhbXBsZS5vcmciLCJ1cHBlcmNhc2UiOnRydWUsImxvd2VyY2FzZSI6dHJ1ZSwiZGlnaXRzIjp0cnVlLCJzeW1ib2xzIjpmYWxzZSwibGVuZ3RoIjoxNiwiY291bnRlciI6MSwidmVyc2lvbiI6Mn0%3D"
);
});



+ 6
- 6
packages/lesspass-pure/tests/unit/store/mutations.spec.js View File

@@ -65,7 +65,7 @@ test("SET_DEFAULT_OPTIONS", () => {
login: "",
uppercase: true,
lowercase: true,
numbers: true,
digits: true,
symbols: true,
length: 16,
counter: 1,
@@ -122,7 +122,7 @@ describe("SET_PASSWORDS", () => {
lowercase: true,
uppercase: true,
symbols: true,
numbers: true,
digits: true,
counter: 1,
length: 16,
version: 2
@@ -134,7 +134,7 @@ describe("SET_PASSWORDS", () => {
lowercase: true,
uppercase: false,
symbols: false,
numbers: true,
digits: true,
counter: 1,
length: 8,
version: 2
@@ -146,7 +146,7 @@ describe("SET_PASSWORDS", () => {
lowercase: true,
uppercase: true,
symbols: true,
numbers: true,
digits: true,
counter: 1,
length: 12,
version: 1
@@ -160,7 +160,7 @@ describe("SET_PASSWORDS", () => {
site: "www.example.org",
uppercase: true,
lowercase: true,
numbers: true,
digits: true,
symbols: true,
length: 16,
counter: 1,
@@ -172,7 +172,7 @@ describe("SET_PASSWORDS", () => {
site: "",
uppercase: true,
lowercase: true,
numbers: true,
digits: true,
symbols: true,
length: 16,
counter: 1,


+ 1
- 1
packages/lesspass-site/index.html View File

@@ -271,7 +271,7 @@
<div class="row air">
<div class="col-12 col-sm-4 py-5 feature">
<p class="lead">
Manage complex passwords with LessPass options (numbers only,
Manage complex passwords with LessPass options (digits only,
adjust length, etc...)
</p>
<img


+ 1
- 1
packages/lesspass/test.js View File

@@ -39,7 +39,7 @@ test("createFingerprint", () => {
});
});

test("generatePassword simpler API", () => {
test("generatePassword api v2", () => {
const passwordProfile = {
site: "example.org",
login: "contact@example.org",


Loading…
Cancel
Save