Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show error messages on UI #74

Merged
merged 2 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
🍌
</span>
</h1>
<Alert />
<GeneralInfo />
<div class="measure">
<div v-if="secure">
<router-link class="button-nav" to="/share">
Expand All @@ -21,7 +23,6 @@
</router-link>
</div>
</div>
<GeneralInfo />
</nav>

<router-view v-if="secure" />
Expand All @@ -36,6 +37,7 @@
</template>

<script lang="ts">
import Alert from "./components/Alert.vue";
import GeneralInfo from "./components/GeneralInfo.vue";
import GoOfflineInfo from "./components/GoOfflineInfo.vue";
import SavePageInfo from "./components/SavePageInfo.vue";
Expand All @@ -46,7 +48,7 @@ import Vue from "vue";

export default Vue.extend({
name: "App",
components: { GeneralInfo, GoOfflineInfo, SavePageInfo, ForkMe },
components: { Alert, GeneralInfo, GoOfflineInfo, SavePageInfo, ForkMe },
computed: {
localFile(): boolean {
return window.location.protocol === "file:";
Expand Down Expand Up @@ -130,6 +132,7 @@ body {
nav {
display: flex;
align-items: flex-end;
flex-direction: column;
}

.card,
Expand Down
72 changes: 72 additions & 0 deletions src/components/Alert.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<template>
<div v-if="message" class="alert measure" :class="level">
<p class="alert__text">
{{ message }}
</p>
</div>
</template>

<script lang="ts">
import Vue from "vue";

// This is a false positive. Suppress eslint error.
/* eslint-disable no-unused-vars */
enum AlertLevel {
Error = "error",
Warn = "warn"
}
/* eslint-enable no-unused-vars */

type AlertData = {
message: string | null;
level: AlertLevel;
};

export default Vue.extend({
name: "Alert",
data: function(): AlertData {
prybalko marked this conversation as resolved.
Show resolved Hide resolved
return {
message: null,
level: AlertLevel.Error
};
},
created: function() {
this.$eventHub.$on("showError", (text: string) => {
this.level = AlertLevel.Error;
this.message = text;
});
this.$eventHub.$on("showWarn", (text: string) => {
this.level = AlertLevel.Warn;
this.message = text;
});
this.$eventHub.$on("clearAlerts", () => {
this.message = null;
});
}
});
</script>

<style>
.alert {
display: flex;
flex-direction: column;
align-items: flex-end;
border-radius: 1rem;
padding: 1.5rem;
border: 1px solid;
}
.alert.warn {
border-color: #e8b173;
background-color: #ffe7b3;
color: #8b6400;
}
.alert.error {
border-color: #e87f73;
background-color: #ffbbb3;
color: darkred;
}
.alert__text {
margin: 0;
width: 100%;
}
</style>
2 changes: 1 addition & 1 deletion src/components/GeneralInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default Vue.extend({
}
.fold input:checked ~ .fold-content {
max-height: 999px;
margin-bottom: 60px;
margin-bottom: 30px;
opacity: 1;
}

Expand Down
14 changes: 11 additions & 3 deletions src/util/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function decrypt(
salt: Uint8Array,
passphrase: string,
nonce: Uint8Array
): Uint8Array {
): Uint8Array | null {
const key = SCRYPT(passphrase, Buffer.from(salt.buffer), 1 << 15, 8, 1, 32);
// This is a false positive, `secretbox.open` is unrelated to `fs.open`
// eslint-disable-next-line security/detect-non-literal-fs-filename
Expand Down Expand Up @@ -166,14 +166,17 @@ function reconstruct(shardObjects: Shard[], passphrase: string): string {
throw "Versions mismatch along shards!";
}

let decryptedMsg: Uint8Array | null;

switch (versions[0]) {
case 0: {
const shardData = shardObjects.map(shard => shard.data);
const encryptedSecret = SECRETS.combine(shardData);
const secret = dehexify(encryptedSecret);
const nonce = dehexify(nonces[0]);
const salt = hashString(titles[0]);
return uint8ArrayToStr(decrypt(secret, salt, passphrase, nonce));
decryptedMsg = decrypt(secret, salt, passphrase, nonce);
break;
}
case 1: {
const shardDataV1 = shardObjects.map(
Expand All @@ -183,11 +186,16 @@ function reconstruct(shardObjects: Shard[], passphrase: string): string {
const secretV1 = dehexify(encryptedSecretV1);
const nonceV1 = BASE64.toByteArray(nonces[0]);
const saltV1 = hashString(titles[0]);
return uint8ArrayToStr(decrypt(secretV1, saltV1, passphrase, nonceV1));
decryptedMsg = decrypt(secretV1, saltV1, passphrase, nonceV1);
break;
}
default:
throw "Version is not supported!";
}
if (!decryptedMsg) {
throw "Unable to decrypt the secret";
}
return uint8ArrayToStr(decryptedMsg);
}

export default {
Expand Down
34 changes: 24 additions & 10 deletions src/views/Combine.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@
</template>

<script lang="ts">
/*eslint no-console: ["error", { allow: ["warn", "error"] }] */

import crypto, { Shard } from "../util/crypto";
import Vue from "vue";

Expand Down Expand Up @@ -98,22 +96,34 @@ export default Vue.extend({
},
mounted: function() {
this.$eventHub.$emit("foldGeneralInfo");
this.$eventHub.$emit("clearAlerts");
},
methods: {
onDecode: function(result: string) {
onDecode: function(result: string): string | void {
this.$eventHub.$emit("clearAlerts");
if (result === "") {
return;
}
if (this.qrCodes.has(result)) {
console.warn("Shard already seen");
this.$eventHub.$emit("showWarn", "Shard already seen");
return;
}
const parsed = crypto.parse(result);
let parsed;
try {
parsed = crypto.parse(result);
} catch (error) {
this.$eventHub.$emit("showError", error);
return;
}

if (this.title && this.title !== parsed.title) {
console.error("title mismatch!");
this.$eventHub.$emit("showError", "title mismatch!");
return;
} else {
this.title = parsed.title;
}
if (this.nonce && this.nonce !== parsed.nonce) {
console.error("nonce mismatch!");
this.$eventHub.$emit("showError", "nonce mismatch!");
return;
} else {
this.nonce = parsed.nonce;
Expand All @@ -122,15 +132,15 @@ export default Vue.extend({
this.requiredShards &&
this.requiredShards !== parsed.requiredShards
) {
console.error("requiredShards mismatch");
this.$eventHub.$emit("showError", "requiredShards mismatch");
return;
} else {
this.requiredShards = parsed.requiredShards;
}
this.qrCodes.add(result);
this.shards.push(parsed);
},
reconstruct: function() {
reconstruct: function(): void {
if (!this.passphrase) {
return;
}
Expand All @@ -139,7 +149,11 @@ export default Vue.extend({
.filter(el => el)
.join("-");
const shards = Array.from(this.shards);
this.recoveredSecret = crypto.reconstruct(shards, this.passphrase);
try {
this.recoveredSecret = crypto.reconstruct(shards, this.passphrase);
} catch (error) {
this.$eventHub.$emit("showError", error);
}
}
}
});
Expand Down
34 changes: 24 additions & 10 deletions src/views/Share.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@
<label>3. Shards</label>
<br />
Will require any {{ requiredShards }} shards out of
<input v-model.number="totalShards" type="number" min="3" /> to
reconstruct
<input
v-model.number="totalShards"
:disabled="encryptionMode"
type="number"
min="3"
max="255"
/>
to reconstruct
</p>
<button
class="button-card"
Expand Down Expand Up @@ -105,20 +111,28 @@ export default Vue.extend({
return Math.floor(this.totalShards / 2) + 1;
},
shards(): string[] {
this.$eventHub.$emit("clearAlerts");
if (!this.encryptionMode) {
return [];
}
return crypto.share(
this.secret,
this.title,
this.recoveryPassphrase,
this.totalShards,
this.requiredShards
);
try {
return crypto.share(
this.secret,
this.title,
this.recoveryPassphrase,
this.totalShards,
this.requiredShards
);
} catch (error) {
this.$eventHub.$emit("showError", error);
this.toggleMode(); // back to editing
}
return [];
}
},
mounted: function() {
this.$eventHub.$emit("foldGeneralInfo");
this.$eventHub.$emit("clearAlerts");
},
methods: {
regenPassphrase: function() {
Expand All @@ -139,7 +153,7 @@ textarea.tooLong {
border: 5px solid red;
}
input[type="number"] {
width: 48px;
width: 64px;
text-align: center;
}
.error-text {
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ test("fails with incorrect password", () => {
3
);
var parsed = shards.map(s => crypto.parse(s));
var reconstructed = crypto.reconstruct(parsed, "");
expect(reconstructed).not.toBe("Secret message");
expect(reconstructed).toBe("");
expect(() => crypto.reconstruct(parsed, "")).toThrow(
"Unable to decrypt the secret"
);
});

test("works with unicode strings", () => {
Expand Down