add encryption
This commit is contained in:
Vendored
+3
@@ -438,6 +438,9 @@ body[showing-chat] #chat-page-wrapper {
|
|||||||
.message-bubble .sender-name {
|
.message-bubble .sender-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.message-bubble .body {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
.message-bubble[sentbyuser] {
|
.message-bubble[sentbyuser] {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+89
-9
@@ -205,9 +205,7 @@
|
|||||||
try {
|
try {
|
||||||
const [listState, toElement] = value;
|
const [listState, toElement] = value;
|
||||||
listState.handleAddition((newItem) => {
|
listState.handleAddition((newItem) => {
|
||||||
console.log(newItem, toElement);
|
|
||||||
const child = toElement(newItem);
|
const child = toElement(newItem);
|
||||||
console.log(child);
|
|
||||||
listState.handleRemoval(
|
listState.handleRemoval(
|
||||||
newItem,
|
newItem,
|
||||||
() => child.remove()
|
() => child.remove()
|
||||||
@@ -372,6 +370,79 @@
|
|||||||
chatObjects: (id) => [DATA_VERSION, "chat", id, "objects"]
|
chatObjects: (id) => [DATA_VERSION, "chat", id, "objects"]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// src/Model/Utility/crypto.ts
|
||||||
|
var IV_SIZE = 12;
|
||||||
|
var ENCRYPTION_ALG = "AES-GCM";
|
||||||
|
async function encryptString(plaintext, passphrase) {
|
||||||
|
if (!window.crypto.subtle) return plaintext;
|
||||||
|
const iv = generateIV();
|
||||||
|
const key = await importKey(passphrase, "encrypt");
|
||||||
|
const encryptedArray = await encrypt(iv, key, plaintext);
|
||||||
|
const encryptionData = {
|
||||||
|
iv: uInt8ToArray(iv),
|
||||||
|
encryptedArray: uInt8ToArray(encryptedArray)
|
||||||
|
};
|
||||||
|
return btoa(JSON.stringify(encryptionData));
|
||||||
|
}
|
||||||
|
async function decryptString(cyphertext, passphrase) {
|
||||||
|
try {
|
||||||
|
const encrypionData = JSON.parse(atob(cyphertext));
|
||||||
|
const iv = arrayToUint8(encrypionData.iv);
|
||||||
|
const encryptedArray = arrayToUint8(encrypionData.encryptedArray);
|
||||||
|
const key = await importKey(passphrase, "decrypt");
|
||||||
|
return await decrypt(iv, key, encryptedArray);
|
||||||
|
} catch {
|
||||||
|
return cyphertext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function encode(string) {
|
||||||
|
return new TextEncoder().encode(string);
|
||||||
|
}
|
||||||
|
function decode(array) {
|
||||||
|
return new TextDecoder("utf-8").decode(array);
|
||||||
|
}
|
||||||
|
async function encrypt(iv, key, message) {
|
||||||
|
const arrayBuffer = await window.crypto.subtle.encrypt(
|
||||||
|
{ name: ENCRYPTION_ALG, iv },
|
||||||
|
key,
|
||||||
|
encode(message)
|
||||||
|
);
|
||||||
|
return new Uint8Array(arrayBuffer);
|
||||||
|
}
|
||||||
|
async function decrypt(iv, key, cyphertext) {
|
||||||
|
const arrayBuffer = await crypto.subtle.decrypt(
|
||||||
|
{ name: ENCRYPTION_ALG, iv },
|
||||||
|
key,
|
||||||
|
cyphertext
|
||||||
|
);
|
||||||
|
return arrayBufferToString(arrayBuffer);
|
||||||
|
}
|
||||||
|
async function hash(encoded) {
|
||||||
|
return await crypto.subtle.digest("SHA-256", encoded);
|
||||||
|
}
|
||||||
|
function generateIV() {
|
||||||
|
return crypto.getRandomValues(new Uint8Array(IV_SIZE));
|
||||||
|
}
|
||||||
|
async function importKey(passphrase, purpose) {
|
||||||
|
return await crypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
await hash(encode(passphrase)),
|
||||||
|
{ name: ENCRYPTION_ALG },
|
||||||
|
false,
|
||||||
|
[purpose]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function arrayBufferToString(arrayBuffer) {
|
||||||
|
const uInt8Array = new Uint8Array(arrayBuffer);
|
||||||
|
return decode(uInt8Array);
|
||||||
|
}
|
||||||
|
function uInt8ToArray(uInt8Array) {
|
||||||
|
return Array.from(uInt8Array);
|
||||||
|
}
|
||||||
|
function arrayToUint8(array) {
|
||||||
|
return new Uint8Array(array);
|
||||||
|
}
|
||||||
|
|
||||||
// src/ViewModel/colors.ts
|
// src/ViewModel/colors.ts
|
||||||
var Color = /* @__PURE__ */ ((Color2) => {
|
var Color = /* @__PURE__ */ ((Color2) => {
|
||||||
Color2["Standard"] = "standard";
|
Color2["Standard"] = "standard";
|
||||||
@@ -509,7 +580,7 @@
|
|||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
// messaging
|
// messaging
|
||||||
sendMessage = (body) => {
|
sendMessage = async (body) => {
|
||||||
const senderName = this.settingsModel.username;
|
const senderName = this.settingsModel.username;
|
||||||
if (senderName == "") return false;
|
if (senderName == "") return false;
|
||||||
const allChannels = [this.info.primaryChannel];
|
const allChannels = [this.info.primaryChannel];
|
||||||
@@ -517,6 +588,9 @@
|
|||||||
allChannels.push(secondaryChannel);
|
allChannels.push(secondaryChannel);
|
||||||
}
|
}
|
||||||
const combinedChannel = allChannels.join("/");
|
const combinedChannel = allChannels.join("/");
|
||||||
|
if (this.info.encryptionKey != "") {
|
||||||
|
body = await encryptString(body, this.info.encryptionKey);
|
||||||
|
}
|
||||||
const chatMessage = _ChatModel.createChatMessage(
|
const chatMessage = _ChatModel.createChatMessage(
|
||||||
combinedChannel,
|
combinedChannel,
|
||||||
senderName,
|
senderName,
|
||||||
@@ -525,11 +599,19 @@
|
|||||||
this.connectionModel.sendMessageOrStore(chatMessage);
|
this.connectionModel.sendMessageOrStore(chatMessage);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
handleMessage = (channel, body) => {
|
handleMessage = async (body) => {
|
||||||
const chatMessage = _ChatModel.parseMessage(body);
|
const chatMessage = _ChatModel.parseMessage(body);
|
||||||
if (chatMessage == null) return;
|
if (chatMessage == null) return;
|
||||||
|
await this.decryptMessage(chatMessage);
|
||||||
this.addMessage(chatMessage);
|
this.addMessage(chatMessage);
|
||||||
};
|
};
|
||||||
|
decryptMessage = async (chatMessage) => {
|
||||||
|
const decryptedBody = await decryptString(
|
||||||
|
chatMessage.body,
|
||||||
|
this.info.encryptionKey
|
||||||
|
);
|
||||||
|
chatMessage.body = decryptedBody;
|
||||||
|
};
|
||||||
setMessageHandler = (handler) => {
|
setMessageHandler = (handler) => {
|
||||||
this.chatMessageHandler = handler;
|
this.chatMessageHandler = handler;
|
||||||
};
|
};
|
||||||
@@ -595,7 +677,7 @@
|
|||||||
const allChannels = channel.split("/");
|
const allChannels = channel.split("/");
|
||||||
for (const channel2 of allChannels) {
|
for (const channel2 of allChannels) {
|
||||||
if (channel2 != chatModel.info.primaryChannel) continue;
|
if (channel2 != chatModel.info.primaryChannel) continue;
|
||||||
chatModel.handleMessage(channel2, body);
|
chatModel.handleMessage(body);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -916,17 +998,16 @@
|
|||||||
class: "message-bubble",
|
class: "message-bubble",
|
||||||
"toggle:sentbyuser": chatMessageViewModel.sentByUser
|
"toggle:sentbyuser": chatMessageViewModel.sentByUser
|
||||||
},
|
},
|
||||||
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "sender-name" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement(
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement(
|
||||||
"span",
|
"span",
|
||||||
{
|
{
|
||||||
class: "body",
|
class: "body",
|
||||||
"subscribe:innerText": chatMessageViewModel.body
|
"subscribe:innerText": chatMessageViewModel.body
|
||||||
}
|
}
|
||||||
), /* @__PURE__ */ createElement("span", { class: "timestamp" }, chatMessageViewModel.dateSent))
|
), /* @__PURE__ */ createElement("span", { class: "timestamp ellipsis" }, chatMessageViewModel.dateSent))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
var ChatMessageViewModelToView = (chatMessageViewModel) => {
|
var ChatMessageViewModelToView = (chatMessageViewModel) => {
|
||||||
console.log(chatMessageViewModel);
|
|
||||||
return ChatMessage(chatMessageViewModel);
|
return ChatMessage(chatMessageViewModel);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1030,7 +1111,6 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
function scrollDown() {
|
function scrollDown() {
|
||||||
console.log("down");
|
|
||||||
messageContainer.scrollTop = messageContainer.scrollHeight;
|
messageContainer.scrollTop = messageContainer.scrollHeight;
|
||||||
}
|
}
|
||||||
function scrollDownIfApplicable() {
|
function scrollDownIfApplicable() {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default class ChatListModel {
|
|||||||
const allChannels = channel.split("/");
|
const allChannels = channel.split("/");
|
||||||
for (const channel of allChannels) {
|
for (const channel of allChannels) {
|
||||||
if (channel != chatModel.info.primaryChannel) continue;
|
if (channel != chatModel.info.primaryChannel) continue;
|
||||||
chatModel.handleMessage(channel, body);
|
chatModel.handleMessage(body);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-3
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import StorageModel, { storageKeys } from "./storageModel";
|
import StorageModel, { storageKeys } from "./storageModel";
|
||||||
import { createTimestamp, parse } from "./Utility/utility";
|
import { createTimestamp, parse } from "./Utility/utility";
|
||||||
|
import { decryptString, encryptString } from "./Utility/crypto";
|
||||||
|
|
||||||
import ChatListModel from "./chatListModel";
|
import ChatListModel from "./chatListModel";
|
||||||
import { Color } from "../ViewModel/colors";
|
import { Color } from "../ViewModel/colors";
|
||||||
@@ -166,7 +167,7 @@ export class ChatModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// messaging
|
// messaging
|
||||||
sendMessage = (body: string): boolean => {
|
sendMessage = async (body: string): Promise<boolean> => {
|
||||||
const senderName = this.settingsModel.username;
|
const senderName = this.settingsModel.username;
|
||||||
if (senderName == "") return false;
|
if (senderName == "") return false;
|
||||||
|
|
||||||
@@ -175,7 +176,10 @@ export class ChatModel {
|
|||||||
allChannels.push(secondaryChannel);
|
allChannels.push(secondaryChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const combinedChannel = allChannels.join("/");
|
const combinedChannel: string = allChannels.join("/");
|
||||||
|
if (this.info.encryptionKey != "") {
|
||||||
|
body = await encryptString(body, this.info.encryptionKey);
|
||||||
|
}
|
||||||
|
|
||||||
const chatMessage: ChatMessage = ChatModel.createChatMessage(
|
const chatMessage: ChatMessage = ChatModel.createChatMessage(
|
||||||
combinedChannel,
|
combinedChannel,
|
||||||
@@ -187,13 +191,22 @@ export class ChatModel {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMessage = (channel: string, body: string): void => {
|
handleMessage = async (body: string): Promise<void> => {
|
||||||
const chatMessage: ChatMessage | null = ChatModel.parseMessage(body);
|
const chatMessage: ChatMessage | null = ChatModel.parseMessage(body);
|
||||||
if (chatMessage == null) return;
|
if (chatMessage == null) return;
|
||||||
|
|
||||||
|
await this.decryptMessage(chatMessage);
|
||||||
this.addMessage(chatMessage);
|
this.addMessage(chatMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
decryptMessage = async (chatMessage: ChatMessage): Promise<void> => {
|
||||||
|
const decryptedBody: string = await decryptString(
|
||||||
|
chatMessage.body,
|
||||||
|
this.info.encryptionKey
|
||||||
|
);
|
||||||
|
chatMessage.body = decryptedBody;
|
||||||
|
};
|
||||||
|
|
||||||
setMessageHandler = (handler: (chatMessage: ChatMessage) => void): void => {
|
setMessageHandler = (handler: (chatMessage: ChatMessage) => void): void => {
|
||||||
this.chatMessageHandler = handler;
|
this.chatMessageHandler = handler;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -100,6 +100,10 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-bubble .body {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
.message-bubble[sentbyuser] {
|
.message-bubble[sentbyuser] {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export function MessagePage(chatViewModel: ChatViewModel) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
function scrollDown() {
|
function scrollDown() {
|
||||||
console.log("down");
|
|
||||||
messageContainer.scrollTop = messageContainer.scrollHeight;
|
messageContainer.scrollTop = messageContainer.scrollHeight;
|
||||||
}
|
}
|
||||||
function scrollDownIfApplicable() {
|
function scrollDownIfApplicable() {
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) {
|
|||||||
toggle:sentbyuser={chatMessageViewModel.sentByUser}
|
toggle:sentbyuser={chatMessageViewModel.sentByUser}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<span class="sender-name">{chatMessageViewModel.sender}</span>
|
<span class="sender-name ellipsis">{chatMessageViewModel.sender}</span>
|
||||||
<span
|
<span
|
||||||
class="body"
|
class="body"
|
||||||
subscribe:innerText={chatMessageViewModel.body}
|
subscribe:innerText={chatMessageViewModel.body}
|
||||||
></span>
|
></span>
|
||||||
<span class="timestamp">{chatMessageViewModel.dateSent}</span>
|
<span class="timestamp ellipsis">{chatMessageViewModel.dateSent}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -23,6 +23,5 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) {
|
|||||||
export const ChatMessageViewModelToView: React.StateItemConverter<
|
export const ChatMessageViewModelToView: React.StateItemConverter<
|
||||||
ChatMessageViewModel
|
ChatMessageViewModel
|
||||||
> = (chatMessageViewModel: ChatMessageViewModel) => {
|
> = (chatMessageViewModel: ChatMessageViewModel) => {
|
||||||
console.log(chatMessageViewModel);
|
|
||||||
return ChatMessage(chatMessageViewModel);
|
return ChatMessage(chatMessageViewModel);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user