old servers uses numbers instead of digits in the API. This patch intercept API requests to make it compatible with new and old servers.pull/763/head
@@ -22,6 +22,17 @@ function removePasswordProfile(profile) { | |||||
}; | }; | ||||
} | } | ||||
function replaceNumbersWithDigitsInProfile(profile) { | |||||
if ("numbers" in profile) { | |||||
const { numbers, ...profileWithoutNumbers } = profile; | |||||
return { | |||||
...profileWithoutNumbers, | |||||
digits: numbers, | |||||
}; | |||||
} | |||||
return profile; | |||||
} | |||||
export function getPasswordProfiles() { | export function getPasswordProfiles() { | ||||
return (dispatch, getState) => { | return (dispatch, getState) => { | ||||
const { settings, auth } = getState(); | const { settings, auth } = getState(); | ||||
@@ -30,21 +41,39 @@ export function getPasswordProfiles() { | |||||
headers: { Authorization: `Bearer ${auth.accessToken}` }, | headers: { Authorization: `Bearer ${auth.accessToken}` }, | ||||
}) | }) | ||||
.then((response) => { | .then((response) => { | ||||
dispatch(setPasswordProfiles(response.data.results)); | |||||
const profiles = response.data.results.map( | |||||
replaceNumbersWithDigitsInProfile | |||||
); | |||||
dispatch(setPasswordProfiles(profiles)); | |||||
return response; | return response; | ||||
}); | }); | ||||
}; | }; | ||||
} | } | ||||
export function addNumbersFieldInProfile(profile) { | |||||
return { | |||||
...profile, | |||||
numbers: profile.digits, | |||||
}; | |||||
} | |||||
export function savePasswordProfile(profile) { | export function savePasswordProfile(profile) { | ||||
return (dispatch, getState) => { | return (dispatch, getState) => { | ||||
const { settings, auth } = getState(); | const { settings, auth } = getState(); | ||||
return axios | return axios | ||||
.post(`${settings.baseURL}/passwords/`, profile, { | |||||
headers: { Authorization: `Bearer ${auth.accessToken}` }, | |||||
}) | |||||
.post( | |||||
`${settings.baseURL}/passwords/`, | |||||
addNumbersFieldInProfile(profile), | |||||
{ | |||||
headers: { Authorization: `Bearer ${auth.accessToken}` }, | |||||
} | |||||
) | |||||
.then((response) => { | .then((response) => { | ||||
dispatch(addPasswordProfile({ ...response.data })); | |||||
dispatch( | |||||
addPasswordProfile( | |||||
replaceNumbersWithDigitsInProfile({ ...response.data }) | |||||
) | |||||
); | |||||
return response; | return response; | ||||
}) | }) | ||||
.catch(() => | .catch(() => | ||||
@@ -61,11 +90,19 @@ export function updatePasswordProfile(profile) { | |||||
return (dispatch, getState) => { | return (dispatch, getState) => { | ||||
const { settings, auth } = getState(); | const { settings, auth } = getState(); | ||||
return axios | return axios | ||||
.put(`${settings.baseURL}/passwords/${profile.id}/`, profile, { | |||||
headers: { Authorization: `Bearer ${auth.accessToken}` }, | |||||
}) | |||||
.put( | |||||
`${settings.baseURL}/passwords/${profile.id}/`, | |||||
addNumbersFieldInProfile(profile), | |||||
{ | |||||
headers: { Authorization: `Bearer ${auth.accessToken}` }, | |||||
} | |||||
) | |||||
.then((response) => { | .then((response) => { | ||||
dispatch(addPasswordProfile({ ...response.data })); | |||||
dispatch( | |||||
addPasswordProfile( | |||||
replaceNumbersWithDigitsInProfile({ ...response.data }) | |||||
) | |||||
); | |||||
return response; | return response; | ||||
}) | }) | ||||
.catch(() => | .catch(() => | ||||
@@ -0,0 +1,26 @@ | |||||
import { | |||||
replaceNumbersWithDigitsInProfile, | |||||
addNumbersFieldInProfile, | |||||
} from "./profilesActions"; | |||||
it("replaceNumbersWithDigitsInProfile numbers become digits", () => { | |||||
expect( | |||||
replaceNumbersWithDigitsInProfile({ id: "p2", numbers: false }) | |||||
).toEqual({ id: "p2", digits: false }); | |||||
expect( | |||||
replaceNumbersWithDigitsInProfile([{ id: "p1", numbers: true }]) | |||||
).toEqual({ id: "p1", digits: true }); | |||||
}); | |||||
it("addNumbersFieldInProfile add numbers with digits", () => { | |||||
expect(addNumbersFieldInProfile({ id: "p1", digits: true })).toEqual({ | |||||
id: "p1", | |||||
digits: true, | |||||
numbers: true, | |||||
}); | |||||
expect(addNumbersFieldInProfile({ id: "p2", digits: false })).toEqual({ | |||||
id: "p2", | |||||
digits: false, | |||||
numbers: false, | |||||
}); | |||||
}); |
@@ -3,23 +3,12 @@ const initialState = {}; | |||||
export default function reduce(state = initialState, action) { | export default function reduce(state = initialState, action) { | ||||
switch (action.type) { | switch (action.type) { | ||||
case "SET_PASSWORD_PROFILES": | case "SET_PASSWORD_PROFILES": | ||||
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; | |||||
}, {}); | |||||
return action.profiles.reduce((acc, profile) => { | |||||
acc[profile.id] = { | |||||
...profile, | |||||
}; | |||||
return acc; | |||||
}, {}); | |||||
case "ADD_PASSWORD_PROFILE": | case "ADD_PASSWORD_PROFILE": | ||||
return { | return { | ||||
...state, | ...state, | ||||
@@ -18,23 +18,6 @@ describe("profiles reducer", () => { | |||||
p2: { id: "p2" }, | p2: { id: "p2" }, | ||||
}); | }); | ||||
}); | }); | ||||
it("SET_PASSWORD_PROFILES numbers become digits", () => { | |||||
expect( | |||||
reducer( | |||||
{}, | |||||
{ | |||||
type: "SET_PASSWORD_PROFILES", | |||||
profiles: [ | |||||
{ id: "p1", numbers: true }, | |||||
{ id: "p2", numbers: false }, | |||||
], | |||||
} | |||||
) | |||||
).toEqual({ | |||||
p1: { id: "p1", digits: true }, | |||||
p2: { id: "p2", digits: false }, | |||||
}); | |||||
}); | |||||
it("REMOVE_PASSWORD_PROFILE", () => { | it("REMOVE_PASSWORD_PROFILE", () => { | ||||
expect( | expect( | ||||
reducer( | reducer( | ||||
@@ -11,7 +11,7 @@ | |||||
"i18n:translate": "node src/i18n/translate.js", | "i18n:translate": "node src/i18n/translate.js", | ||||
"prettier": "prettier --write 'src/**/*'", | "prettier": "prettier --write 'src/**/*'", | ||||
"test": "yarn test:unit && yarn test:e2e", | "test": "yarn test:unit && yarn test:e2e", | ||||
"test:watch": "jest --watch", | |||||
"test:watch": "vue-cli-service test:unit --watch", | |||||
"test:unit": "vue-cli-service test:unit", | "test:unit": "vue-cli-service test:unit", | ||||
"test:e2e": "vue-cli-service test:e2e --headless", | "test:e2e": "vue-cli-service test:e2e --headless", | ||||
"lint": "vue-cli-service lint" | "lint": "vue-cli-service lint" | ||||
@@ -1,19 +1,55 @@ | |||||
import http from "./http"; | import http from "./http"; | ||||
function addNumbersFieldInProfile(profile) { | |||||
return { | |||||
...profile, | |||||
numbers: profile.digits, | |||||
}; | |||||
} | |||||
function replaceNumbersWithDigitsInProfile(profile) { | |||||
if ("numbers" in profile) { | |||||
const { numbers, ...profileWithoutNumbers } = profile; | |||||
return { | |||||
...profileWithoutNumbers, | |||||
digits: numbers, | |||||
}; | |||||
} | |||||
return profile; | |||||
} | |||||
export default { | export default { | ||||
all() { | all() { | ||||
return http.get("/passwords/"); | |||||
return http.get("/passwords/").then((response) => { | |||||
response.data.results = response.data.results.map( | |||||
replaceNumbersWithDigitsInProfile | |||||
); | |||||
return response; | |||||
}); | |||||
}, | }, | ||||
create(resource) { | create(resource) { | ||||
return http.post("/passwords/", resource); | |||||
return http | |||||
.post("/passwords/", addNumbersFieldInProfile(resource)) | |||||
.then((response) => { | |||||
response.data = replaceNumbersWithDigitsInProfile(response.data); | |||||
return response; | |||||
}); | |||||
}, | }, | ||||
read(resource) { | read(resource) { | ||||
return http.get(`/passwords/${resource.id}/`); | |||||
return http.get(`/passwords/${resource.id}/`).then((response) => { | |||||
response.data = replaceNumbersWithDigitsInProfile(response.data); | |||||
return response; | |||||
}); | |||||
}, | }, | ||||
update(resource) { | update(resource) { | ||||
return http.put(`/passwords/${resource.id}/`, resource); | |||||
return http | |||||
.put(`/passwords/${resource.id}/`, addNumbersFieldInProfile(resource)) | |||||
.then((response) => { | |||||
response.data = replaceNumbersWithDigitsInProfile(response.data); | |||||
return response; | |||||
}); | |||||
}, | }, | ||||
delete(resource) { | delete(resource) { | ||||
return http.delete(`/passwords/${resource.id}/`); | return http.delete(`/passwords/${resource.id}/`); | ||||
} | |||||
}, | |||||
}; | }; |
@@ -3,9 +3,11 @@ import defaultPasswordProfile from "../store/defaultPassword"; | |||||
export function encryptPassword(email, password) { | export function encryptPassword(email, password) { | ||||
return LessPass.generatePassword( | return LessPass.generatePassword( | ||||
"lesspass.com", | |||||
email, | |||||
password, | |||||
defaultPasswordProfile | |||||
{ | |||||
...defaultPasswordProfile, | |||||
site: "lesspass.com", | |||||
login: email, | |||||
}, | |||||
password | |||||
); | ); | ||||
} | } |
@@ -4,51 +4,159 @@ import Passwords from "@/api/password"; | |||||
const mock = new MockAdapter(axios); | const mock = new MockAdapter(axios); | ||||
test("Passwords.create", () => { | |||||
const password = { login: "text@example.org" }; | |||||
test("Passwords.create on old server replace numbers field after creation", () => { | |||||
mock | mock | ||||
.onPost("https://api.lesspass.com/passwords/", password) | |||||
.reply(201, { ...password, id: "1" }); | |||||
return Passwords.create(password).then(response => { | |||||
const passwordCreated = response.data; | |||||
expect(passwordCreated.id).toBe("1"); | |||||
expect(passwordCreated.login).toBe(password.login); | |||||
.onPost("https://api.lesspass.com/passwords/", { | |||||
login: "text@example.org", | |||||
digits: false, | |||||
numbers: false, | |||||
}) | |||||
.reply(201, { id: "p1", login: "text@example.org", numbers: false }); | |||||
const password = { login: "text@example.org", digits: false }; | |||||
return Passwords.create(password).then((response) => { | |||||
expect(response.status).toBe(201); | |||||
expect(response.data).toEqual({ | |||||
id: "p1", | |||||
login: "text@example.org", | |||||
digits: false, | |||||
}); | |||||
}); | }); | ||||
}); | }); | ||||
test("Passwords.all", () => { | |||||
mock.onGet("https://api.lesspass.com/passwords/").reply(200, {}); | |||||
return Passwords.all().then(response => { | |||||
test("Passwords.create on new server keeps digits", () => { | |||||
mock | |||||
.onPost("https://api.lesspass.com/passwords/", { | |||||
login: "text@example.org", | |||||
digits: false, | |||||
numbers: false, | |||||
}) | |||||
.reply(201, { id: "p1", login: "text@example.org", digits: false }); | |||||
const password = { login: "text@example.org", digits: false }; | |||||
return Passwords.create(password).then((response) => { | |||||
expect(response.status).toBe(201); | |||||
expect(response.data).toEqual({ | |||||
id: "p1", | |||||
login: "text@example.org", | |||||
digits: false, | |||||
}); | |||||
}); | |||||
}); | |||||
test("Passwords.all on old server transform numbers into digits", () => { | |||||
mock.onGet("https://api.lesspass.com/passwords/").reply(200, { | |||||
results: [{ id: "p1", numbers: false }], | |||||
}); | |||||
return Passwords.all().then((response) => { | |||||
expect(response.status).toBe(200); | |||||
expect(response.data).toEqual({ | |||||
results: [{ id: "p1", digits: false }], | |||||
}); | |||||
}); | |||||
}); | |||||
test("Passwords.all on new server keeps digits", () => { | |||||
mock.onGet("https://api.lesspass.com/passwords/").reply(200, { | |||||
results: [{ id: "p2", digits: false }], | |||||
}); | |||||
return Passwords.all().then((response) => { | |||||
expect(response.status).toBe(200); | expect(response.status).toBe(200); | ||||
expect(response.data).toEqual({ | |||||
results: [{ id: "p2", digits: false }], | |||||
}); | |||||
}); | }); | ||||
}); | }); | ||||
test("Passwords.get", () => { | |||||
test("Passwords.get on old server replace numbers with digits", () => { | |||||
mock | mock | ||||
.onGet( | .onGet( | ||||
"https://api.lesspass.com/passwords/c8e4f983-8ffe-b705-4064-d3b7aa4a4782/" | "https://api.lesspass.com/passwords/c8e4f983-8ffe-b705-4064-d3b7aa4a4782/" | ||||
) | ) | ||||
.reply(200, {}); | |||||
.reply(200, { id: "p1", numbers: false }); | |||||
return Passwords.read({ id: "c8e4f983-8ffe-b705-4064-d3b7aa4a4782" }).then( | return Passwords.read({ id: "c8e4f983-8ffe-b705-4064-d3b7aa4a4782" }).then( | ||||
response => { | |||||
(response) => { | |||||
expect(response.status).toBe(200); | expect(response.status).toBe(200); | ||||
expect(response.data).toEqual({ id: "p1", digits: false }); | |||||
} | } | ||||
); | ); | ||||
}); | }); | ||||
test("Passwords.update", () => { | |||||
test("Passwords.get on new server keeps digits", () => { | |||||
mock | |||||
.onGet( | |||||
"https://api.lesspass.com/passwords/c8e4f983-8ffe-b705-4064-d3b7aa4a4782/" | |||||
) | |||||
.reply(200, { id: "p1", digits: false }); | |||||
return Passwords.read({ id: "c8e4f983-8ffe-b705-4064-d3b7aa4a4782" }).then( | |||||
(response) => { | |||||
expect(response.status).toBe(200); | |||||
expect(response.data).toEqual({ id: "p1", digits: false }); | |||||
} | |||||
); | |||||
}); | |||||
test("Passwords.update on old server replace numbers field after update", () => { | |||||
mock | |||||
.onPut( | |||||
"https://api.lesspass.com/passwords/c8e4f983-4064-8ffe-b705-d3b7aa4a4782/", | |||||
{ | |||||
id: "c8e4f983-4064-8ffe-b705-d3b7aa4a4782", | |||||
login: "test@example.org", | |||||
digits: true, | |||||
numbers: true, | |||||
} | |||||
) | |||||
.reply(200, { | |||||
id: "c8e4f983-4064-8ffe-b705-d3b7aa4a4782", | |||||
login: "test@example.org", | |||||
numbers: true, | |||||
}); | |||||
const password = { | const password = { | ||||
id: "c8e4f983-4064-8ffe-b705-d3b7aa4a4782", | id: "c8e4f983-4064-8ffe-b705-d3b7aa4a4782", | ||||
login: "test@example.org" | |||||
login: "test@example.org", | |||||
digits: true, | |||||
}; | }; | ||||
return Passwords.update(password).then((response) => { | |||||
expect(response.status).toBe(200); | |||||
expect(response.data).toEqual({ | |||||
id: "c8e4f983-4064-8ffe-b705-d3b7aa4a4782", | |||||
login: "test@example.org", | |||||
digits: true, | |||||
}); | |||||
}); | |||||
}); | |||||
test("Passwords.update on new keeps digits", () => { | |||||
mock | mock | ||||
.onPut( | .onPut( | ||||
"https://api.lesspass.com/passwords/c8e4f983-4064-8ffe-b705-d3b7aa4a4782/", | "https://api.lesspass.com/passwords/c8e4f983-4064-8ffe-b705-d3b7aa4a4782/", | ||||
password | |||||
{ | |||||
id: "c8e4f983-4064-8ffe-b705-d3b7aa4a4782", | |||||
login: "test@example.org", | |||||
digits: true, | |||||
numbers: true, | |||||
} | |||||
) | ) | ||||
.reply(200, {}); | |||||
return Passwords.update(password).then(response => { | |||||
.reply(200, { | |||||
id: "c8e4f983-4064-8ffe-b705-d3b7aa4a4782", | |||||
login: "test@example.org", | |||||
digits: true, | |||||
}); | |||||
const password = { | |||||
id: "c8e4f983-4064-8ffe-b705-d3b7aa4a4782", | |||||
login: "test@example.org", | |||||
digits: true, | |||||
}; | |||||
return Passwords.update(password).then((response) => { | |||||
expect(response.status).toBe(200); | expect(response.status).toBe(200); | ||||
expect(response.data).toEqual({ | |||||
id: "c8e4f983-4064-8ffe-b705-d3b7aa4a4782", | |||||
login: "test@example.org", | |||||
digits: true, | |||||
}); | |||||
}); | }); | ||||
}); | }); | ||||
@@ -59,7 +167,7 @@ test("Passwords.delete", () => { | |||||
) | ) | ||||
.reply(204); | .reply(204); | ||||
return Passwords.delete({ id: "c8e4f983-8ffe-4064-b705-d3b7aa4a4782" }).then( | return Passwords.delete({ id: "c8e4f983-8ffe-4064-b705-d3b7aa4a4782" }).then( | ||||
response => { | |||||
(response) => { | |||||
expect(response.status).toBe(204); | expect(response.status).toBe(204); | ||||
} | } | ||||
); | ); | ||||