Skip to content

Commit 15667ec

Browse files
authored
Require verifying password before chaning it (#1246)
* Verify current password before changing it. * split out verifyPassword into two functions * typo * change incorrect password message
1 parent fb8bc84 commit 15667ec

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed

_locales/en/messages.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{
1+
{
22
"extName": {
33
"message": "Authenticator",
44
"description": "Extension Name."
@@ -515,6 +515,9 @@
515515
"permission_unknown_permission": {
516516
"message": "Unknown permission. If see this message, please send a bug report."
517517
},
518+
"phrase_wrong": {
519+
"message": "Password incorrect"
520+
},
518521
"activate_auto_filter": {
519522
"message": "Warning: Smart filter loosely matches the domain name to an account. Always verify that you are on the correct website before entering a code!"
520523
}

src/components/Popup/SetPasswordPage.vue

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
<template>
22
<div>
33
<div class="text warning">{{ i18n.security_warning }}</div>
4+
<a-text-input
5+
:label="i18n.current_phrase"
6+
type="password"
7+
v-model="currentPhrase"
8+
v-show="!!defaultEncryption"
9+
/>
410
<a-text-input :label="i18n.phrase" type="password" v-model="phrase" />
511
<a-text-input
612
:label="i18n.confirm_phrase"
@@ -23,11 +29,13 @@
2329
</template>
2430
<script lang="ts">
2531
import Vue from "vue";
32+
import { verifyPasswordUsingKeyID } from "../../models/password";
2633
2734
export default Vue.extend({
2835
data: function () {
2936
return {
3037
phrase: "",
38+
currentPhrase: "",
3139
confirm: "",
3240
};
3341
},
@@ -53,10 +61,26 @@ export default Vue.extend({
5361
passwordPolicyHint: function () {
5462
return this.$store.state.menu.passwordPolicyHint;
5563
},
64+
defaultEncryption: function (): string | undefined {
65+
return this.$store.state.accounts.defaultEncryption;
66+
},
5667
},
5768
methods: {
5869
async removePassphrase() {
5970
this.$store.commit("currentView/changeView", "LoadingPage");
71+
72+
if (this.defaultEncryption) {
73+
const isCorrectPassword = await verifyPasswordUsingKeyID(
74+
this.defaultEncryption,
75+
this.currentPhrase
76+
);
77+
if (!isCorrectPassword) {
78+
this.$store.commit("notification/alert", this.i18n.phrase_not_match);
79+
this.$store.commit("currentView/changeView", "SetPasswordPage");
80+
return;
81+
}
82+
}
83+
6084
await this.$store.dispatch("accounts/changePassphrase", "");
6185
this.$store.commit("notification/alert", this.i18n.updateSuccess);
6286
this.$store.commit("style/hideInfo");
@@ -80,6 +104,19 @@ export default Vue.extend({
80104
}
81105
82106
this.$store.commit("currentView/changeView", "LoadingPage");
107+
108+
if (this.defaultEncryption) {
109+
const isCorrectPassword = await verifyPasswordUsingKeyID(
110+
this.defaultEncryption,
111+
this.currentPhrase
112+
);
113+
if (!isCorrectPassword) {
114+
this.$store.commit("notification/alert", this.i18n.phrase_wrong);
115+
this.$store.commit("currentView/changeView", "SetPasswordPage");
116+
return;
117+
}
118+
}
119+
83120
await this.$store.dispatch("accounts/changePassphrase", this.phrase);
84121
this.$store.commit("notification/alert", this.i18n.updateSuccess);
85122
this.$store.commit("style/hideInfo");

src/models/password.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { BrowserStorage, isOldKey } from "./storage";
2+
3+
export async function argonHash(
4+
value: string,
5+
salt: string
6+
): Promise<string | undefined> {
7+
const iframe = document.getElementById("argon-sandbox");
8+
const message = {
9+
action: "hash",
10+
value,
11+
salt,
12+
};
13+
14+
if (!iframe) {
15+
throw new Error("argon-sandbox missing!");
16+
}
17+
18+
const argonPromise: Promise<string | undefined> = new Promise((resolve) => {
19+
window.addEventListener("message", (response) => {
20+
resolve(response.data.response);
21+
});
22+
// @ts-expect-error bad typings
23+
iframe.contentWindow.postMessage(message, "*");
24+
});
25+
26+
return argonPromise;
27+
}
28+
29+
export async function argonVerify(
30+
value: string,
31+
hash: string
32+
): Promise<boolean> {
33+
const iframe = document.getElementById("argon-sandbox");
34+
const message = {
35+
action: "verify",
36+
value,
37+
hash,
38+
};
39+
40+
if (!iframe) {
41+
throw new Error("argon-sandbox missing!");
42+
}
43+
44+
const argonPromise: Promise<boolean> = new Promise((resolve) => {
45+
window.addEventListener("message", (response) => {
46+
resolve(response.data.response);
47+
});
48+
// @ts-expect-error bad typings
49+
iframe.contentWindow.postMessage(message, "*");
50+
});
51+
52+
return argonPromise;
53+
}
54+
55+
// Verify a password using keys in BrowserStorage
56+
export async function verifyPasswordUsingKeyID(
57+
keyId: string,
58+
password: string
59+
): Promise<boolean> {
60+
// Get key for current encryption
61+
const keys = await BrowserStorage.getKeys();
62+
if (isOldKey(keys)) {
63+
throw new Error(
64+
"v3 encryption not being used with verifyPassword. This should never happen!"
65+
);
66+
}
67+
68+
const key = keys.find((key) => key.id === keyId);
69+
if (!key) {
70+
throw new Error(`Key ${keyId} not in BrowserStorage`);
71+
}
72+
73+
return verifyPasswordUsingKey(key, password);
74+
}
75+
76+
export async function verifyPasswordUsingKey(
77+
key: Key,
78+
password: string
79+
): Promise<boolean> {
80+
// Hash password with argon
81+
const rawHash = await argonHash(password, key.salt);
82+
if (!rawHash) {
83+
throw new Error("argon2 did not return a hash!");
84+
}
85+
// https://passlib.readthedocs.io/en/stable/lib/passlib.hash.argon2.html#format-algorithm
86+
const possibleHash = rawHash.split("$")[5];
87+
88+
// verify user password by comparing their password hash with the
89+
// hash of their password's hash
90+
return await argonVerify(possibleHash, key.hash);
91+
}

0 commit comments

Comments
 (0)