diff --git a/chatto/src/main/javascript/package.json b/chatto/src/main/javascript/package.json index 00b02d8..3f1666b 100644 --- a/chatto/src/main/javascript/package.json +++ b/chatto/src/main/javascript/package.json @@ -1,15 +1,21 @@ { "dependencies": { + "@types/dompurify": "^2.0.0", "@types/markdown-it": "^0.0.9", + "@types/sjcl": "^1.0.28", + "@types/sprintf-js": "^1.1.2", "alertifyjs": "^1.12.0", "chart.js": "^2.9.3", "dompurify": "^2.0.7", "fuse.js": "^3.4.6", + "global": "^4.4.0", "handlebars": "^4.5.3", "loglevel": "^1.6.6", "markdown-it": "^10.0.0", "sjcl": "^1.0.8", - "tsify": "^4.0.1" + "sprintf-js": "^1.1.2", + "tsify": "^4.0.1", + "typescript": "^3.7.3" }, "devDependencies": { "browserify": "^16.5.0", @@ -28,6 +34,7 @@ "handlebars": "global:Handlebars", "dompurify": "global:DOMPurify", "fuse.js": "global:Fuse", - "sjcl": "global:sjcl" + "sjcl": "global:sjcl", + "sprintf-js": "global:sprintf" } } diff --git a/chatto/src/main/javascript/ts/src/controller/UserController.ts b/chatto/src/main/javascript/ts/src/controller/UserController.ts index 0aba4d4..e6f7631 100644 --- a/chatto/src/main/javascript/ts/src/controller/UserController.ts +++ b/chatto/src/main/javascript/ts/src/controller/UserController.ts @@ -7,13 +7,14 @@ import { Model } from "../model/AbstractModel"; import { View } from "../view/AbstractView"; import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; import { UserView } from "../view/UserView"; +import { UserModel } from "../model/UserModel"; export class UserController { - private _model: Model; + private _model: UserModel; private _view: UserView; - constructor(model: Model, view: UserView) { + constructor(model: UserModel, view: UserView) { this._model = model; this._view = view; } @@ -41,5 +42,9 @@ export class UserController { this.eventHandler(activeUsersMock); } + public getActiveUsers(): void { + this._model.getActiveUsers(); + } + } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/main.ts b/chatto/src/main/javascript/ts/src/main.ts index 010969f..8f27533 100644 --- a/chatto/src/main/javascript/ts/src/main.ts +++ b/chatto/src/main/javascript/ts/src/main.ts @@ -40,7 +40,7 @@ userModel.attach(userView); const userController = new UserController(userModel, userView); -userController.test(); +// userController.test(); @@ -50,6 +50,7 @@ log.info("hello"); const chatArea = document.getElementById('chat-area-new'); // @ts-ignore: Argument of type 'HTMLElement | null' is not assignable to parameter of type 'HTMLElement'. Type 'null' is not assignable to type 'HTMLElement'. const chatView = new ChatView(chatModel, chatArea); +chatModel.attach(chatView); const chatController = new ChatController(chatModel, chatView); @@ -58,6 +59,8 @@ function someFunc(vm: ActiveUserViewModel): void { // logger.info(vm) } +userController.getActiveUsers(); + log.info("test"); // someFunc(activeUserViewModelMock); @@ -70,7 +73,7 @@ JsonAPI.ACTIVE_USERS_GET + 'aef'; const encryptionService: EncryptionService = new SJCLEncryptionService(); let ct = encryptionService.encrypt("password","data"); -console.log(encryptionService.decrypt("password", ct)); +console.log(encryptionService.decrypt("password", JSON.parse(ct as string))); Handlebars.registerHelper('avatar', function() { return '
'; diff --git a/chatto/src/main/javascript/ts/src/model/ChatModel.ts b/chatto/src/main/javascript/ts/src/model/ChatModel.ts index 9dab5c4..6b44c68 100644 --- a/chatto/src/main/javascript/ts/src/model/ChatModel.ts +++ b/chatto/src/main/javascript/ts/src/model/ChatModel.ts @@ -46,10 +46,10 @@ export class ChatModel implements Subject { /** * Trigger an update in each subscriber. */ - public notify(): void { + public notify(userName: string): void { console.log('Subject: Notifying observers...'); for (const observer of this._observers) { - observer.update(this.state); + observer.update(this._messagesMap.get(userName)); } } @@ -58,20 +58,22 @@ export class ChatModel implements Subject { this.helperMethod(); console.log(`Subject: My state has just changed`); console.log(chatMessageList); - this.notify(); + this.notify("some user"); } - public getmessages(userName: string, passphrase: string, lastMessageTime: string | null): void { - const cVMs = ChatModelHelper.getMessages(userName, passphrase, lastMessageTime, this); + public async getmessages(userName: string, passphrase: string, lastMessageTime: string | null): Promise { + const cVMs = await ChatModelHelper.getMessages(userName, passphrase, lastMessageTime, this); if (cVMs != null) { log.info('Subject: My state has just changed') log.debug(cVMs); this._messagesMap.set(userName, cVMs); - this.notify(); + this.notify(userName); } else { log.error('Messages were null'); } + + return cVMs; } diff --git a/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts b/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts index a7c76bd..a71a2f8 100644 --- a/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts +++ b/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts @@ -10,39 +10,44 @@ import { fetchErrorHandler } from "./FetchErrorHandler"; import { EncryptionService } from "../service/EncryptionService"; import { SJCLEncryptionService } from "../service/SJCLEncryptionService"; -import { ChatModel } from "./ChatModel"; - +import { ChatModel } from "./ChatModel" export class ChatModelHelper { private static readonly encryptionService = new SJCLEncryptionService(); - public static getMessages(userName: string, passphrase: string, lastMessageTime: string | null, chatModel: ChatModel): ChatMessageViewModel[] | null { + public static async getMessages(userName: string, passphrase: string, lastMessageTime: string | null, chatModel: ChatModel): Promise { switch (lastMessageTime) { case null: { - this.getAllMessagesAjax(userName) - .then((data: ChatMessageDTO[]) => { - log.debug(`Subject: received all messages`); - // let userNames = data.map(ChatMessageViewModel => ChatMessageViewModel.fromUser) - // let sumt = data.map(chatMessageViewModel => { return this.encryptionService.decrypt(passphrase, chatMessageViewModel.messageCipher) }); - return data.map(vm => this.toChatMessageVM(vm, passphrase)); - // chatModel.setUserMessages(userName, chatMessageVMs); - // chatModel.notify(); - }) - break; + // this.getAllMessagesAjax(userName) + // .then((data: ChatMessageDTO[]) => { + // log.debug(`Subject: received all messages`); + // // let userNames = data.map(ChatMessageViewModel => ChatMessageViewModel.fromUser) + // // let sumt = data.map(chatMessageViewModel => { return this.encryptionService.decrypt(passphrase, chatMessageViewModel.messageCipher) }); + // return data.map(vm => this.toChatMessageVM(vm, passphrase)); + // // chatModel.setUserMessages(userName, chatMessageVMs); + // // chatModel.notify(); + // }) + // break; + + const data: ChatMessageDTO[] = await this.getAllMessagesAjax(userName); + return data.map(vm => this.toChatMessageVM(vm, passphrase)); + } default: { - this.getNewMessagesAjax(userName, lastMessageTime) - .then((data: ChatMessageDTO[]) => { - log.debug(`Subject: received new messages`); - return data.map(vm => this.toChatMessageVM(vm, passphrase)); - // chatModel.setUserMessages(userName, chatMessageVMs); - // this.state = data; - // chatModel.notify(); - }) - break; + // this.getNewMessagesAjax(userName, lastMessageTime) + // .then((data: ChatMessageDTO[]) => { + // log.debug(`Subject: received new messages`); + // return data.map(vm => this.toChatMessageVM(vm, passphrase)); + // // chatModel.setUserMessages(userName, chatMessageVMs); + // // this.state = data; + // // chatModel.notify(); + // }) + // break; + const data: ChatMessageDTO[] = await this.getNewMessagesAjax(userName, lastMessageTime); + return data.map(vm => this.toChatMessageVM(vm, passphrase)); } } - return null; + // return null; } private static toChatMessageVM(chatMessageDTO: ChatMessageDTO, passphrase: string): ChatMessageViewModel { @@ -55,13 +60,13 @@ export class ChatModelHelper { } private static async getAllMessagesAjax(toUser: string): Promise { - let headers = new Headers(); + const headers = new Headers(); if (JsonAPI.authToken == null) { log.error("authToken null"); return; }; headers.append('X-AUTH-TOKEN', JsonAPI.authToken); - let response = await fetch(`${JsonAPI.CHAT_MESSAGES_GET}/${toUser}`, { + const response = await fetch(`${JsonAPI.CHAT_MESSAGES_GET}/${toUser}`, { method: 'GET', headers: headers }); @@ -69,18 +74,18 @@ export class ChatModelHelper { if (fetchErrorHandler(response.clone())) { return null; } - let data: Promise = await response.json(); + const data: Promise = await response.json(); return data; } private static async getNewMessagesAjax(toUser: string, lastMessageTimeStamp: string): Promise { - let headers = new Headers(); + const headers = new Headers(); if (JsonAPI.authToken == null) { log.error("authToken null"); return; }; headers.append('X-AUTH-TOKEN', JsonAPI.authToken); - let response = await fetch(`${JsonAPI.CHAT_MESSAGES_GET}/${toUser}/${lastMessageTimeStamp}`, { + const response = await fetch(`${JsonAPI.CHAT_MESSAGES_GET}/${toUser}/${lastMessageTimeStamp}`, { method: 'GET', headers: headers }); @@ -88,7 +93,7 @@ export class ChatModelHelper { if (fetchErrorHandler(response.clone())) { return null; } - let data: Promise = await response.json(); + const data: Promise = await response.json(); return data; } } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/model/FetchErrorHandler.ts b/chatto/src/main/javascript/ts/src/model/FetchErrorHandler.ts index 09683b1..64f9cfb 100644 --- a/chatto/src/main/javascript/ts/src/model/FetchErrorHandler.ts +++ b/chatto/src/main/javascript/ts/src/model/FetchErrorHandler.ts @@ -1,3 +1,7 @@ +import log = require("loglevel"); +import { sprintf } from "sprintf-js"; +// import sprintf = require('sprintf-js').sprintf; + export function fetchErrorHandler(response: Response) { // alertify.success('Current position : ' + alertify.get('notifier', 'position')); if (!response.ok) { @@ -6,13 +10,16 @@ export function fetchErrorHandler(response: Response) { // throw new Error(response.statusText); // window.alert(sprintf('Some error occured. Http code is %s', response.status)); // alertify.error(sprintf('Some error occured. Http code is %s', response.status)); + log.error(sprintf('Some error occured. Http code is %s', response.status)); + log.error(); return true; }).then(json => { // the status was not ok but there is a json body // throw new Error(json.error.message); // example error message returned by a REST API // window.alert(sprintf('Error: %s (Http code %s)', json, response.status)); // alertify.error(sprintf('Some error occured. Http code is %s', response.status)); - console.log(json); + log.error(sprintf('Some error occured. Http code is %s', response.status)); + log.error(json); return true; }); } diff --git a/chatto/src/main/javascript/ts/src/model/UserModel.ts b/chatto/src/main/javascript/ts/src/model/UserModel.ts index c07021b..132d5d0 100644 --- a/chatto/src/main/javascript/ts/src/model/UserModel.ts +++ b/chatto/src/main/javascript/ts/src/model/UserModel.ts @@ -5,6 +5,7 @@ import { fetchErrorHandler } from "./FetchErrorHandler"; import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; import { JsonAPI } from "../singleton/JsonAPI"; import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; +import * as log from "loglevel"; export class UserModel implements Subject { /** @@ -53,7 +54,8 @@ export class UserModel implements Subject { * getActiveUsers */ public getActiveUsers(): void { - this.getActiveUsersAjax("", JsonAPI.ACTIVE_USERS_GET) + if(JsonAPI.authToken!= null){ + this.getActiveUsersAjax(JsonAPI.authToken, JsonAPI.ACTIVE_USERS_GET) .then(data => { // // activeUsers = data; // sessionStorage.setItem('activeUsers', JSON.stringify(data)); @@ -62,6 +64,10 @@ export class UserModel implements Subject { this.state = data; this.notify(); }) + } + else { + log.error('Auth token is null'); + } } async getActiveUsersAjax(authToken2: string, URL: string): Promise { diff --git a/chatto/src/main/javascript/ts/src/observe/Observable.ts b/chatto/src/main/javascript/ts/src/observe/Observable.ts index 44e9439..a6305b9 100644 --- a/chatto/src/main/javascript/ts/src/observe/Observable.ts +++ b/chatto/src/main/javascript/ts/src/observe/Observable.ts @@ -11,7 +11,7 @@ export interface Subject { detach(observer: Observer): void; // Notify all observers about an event. - notify(): void; + notify(param: any | null): void; } /** diff --git a/chatto/src/main/javascript/ts/src/service/SJCLEncryptionService.ts b/chatto/src/main/javascript/ts/src/service/SJCLEncryptionService.ts index 25c45df..82c22ad 100644 --- a/chatto/src/main/javascript/ts/src/service/SJCLEncryptionService.ts +++ b/chatto/src/main/javascript/ts/src/service/SJCLEncryptionService.ts @@ -8,6 +8,7 @@ export class SJCLEncryptionService implements EncryptionService { } public decrypt(passphrase: string, cipher: Object): Object { - return sjcl.decrypt(passphrase, cipher as sjcl.SjclCipherEncrypted, undefined, undefined); + // return sjcl.decrypt(passphrase, cipher as sjcl.SjclCipherEncrypted, undefined, undefined); + return sjcl.decrypt(passphrase, JSON.stringify(cipher), undefined, undefined); } } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/singleton/JsonAPI.ts b/chatto/src/main/javascript/ts/src/singleton/JsonAPI.ts index 38f5de2..4f0e120 100644 --- a/chatto/src/main/javascript/ts/src/singleton/JsonAPI.ts +++ b/chatto/src/main/javascript/ts/src/singleton/JsonAPI.ts @@ -3,6 +3,6 @@ export namespace JsonAPI { export let userName: string | null = localStorage.getItem('userName'); export let authToken: string | null = localStorage.getItem('authToken'); export const ACTIVE_USERS_GET = `/api/chat/get/active-users`; - export const CHAT_MESSAGES_GET = ``; + export const CHAT_MESSAGES_GET = `/api/chat/get/messages`; } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/view/ChatView.ts b/chatto/src/main/javascript/ts/src/view/ChatView.ts index 0eb0476..55acdbc 100644 --- a/chatto/src/main/javascript/ts/src/view/ChatView.ts +++ b/chatto/src/main/javascript/ts/src/view/ChatView.ts @@ -8,6 +8,9 @@ import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; import { ChatModel } from "../model/ChatModel"; import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; import * as log from 'loglevel'; +import * as DOMPurify from 'dompurify'; +import markdownit = require('markdown-it'); +var md = new markdownit(); export class ChatView implements Observer { private readonly _model: ChatModel; @@ -22,10 +25,16 @@ export class ChatView implements Observer { update(data: ChatMessageViewModel[]): void { + log.info('ChatView: updating view'); let html: string = ""; data.forEach((vm: ChatMessageViewModel) => { html += this._messageSendTemplate(vm); }); + + /** Very Important!!! + * Sanitizing HTML before displaying on webpage to prevent XSS attacks!! + */ + html = DOMPurify.sanitize(md.render(html)); this._element.innerHTML = html; log.debug(this._element.innerHTML); } diff --git a/chatto/src/main/javascript/yarn.lock b/chatto/src/main/javascript/yarn.lock index 2bd7948..d424635 100644 --- a/chatto/src/main/javascript/yarn.lock +++ b/chatto/src/main/javascript/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@types/dompurify@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.0.0.tgz#9616caa5bf2569aea2e4889d4f929d968c081b40" + integrity sha512-g/ilp+Bo6Ljy60i5LnjkGw00X7EIoFjoPGlxqZhV8TJ9fWEzXheioU1O+U/UzCzUA7pUDy/JNMytTQDJctpUHg== + dependencies: + "@types/trusted-types" "*" + "@types/linkify-it@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-2.1.0.tgz#ea3dd64c4805597311790b61e872cbd1ed2cd806" @@ -14,6 +21,21 @@ dependencies: "@types/linkify-it" "*" +"@types/sjcl@^1.0.28": + version "1.0.28" + resolved "https://registry.yarnpkg.com/@types/sjcl/-/sjcl-1.0.28.tgz#4693eb6943e385e844a70fb25b4699db286c7214" + integrity sha1-RpPraUPjhehEpw+yW0aZ2yhschQ= + +"@types/sprintf-js@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@types/sprintf-js/-/sprintf-js-1.1.2.tgz#a4fcb84c7344f39f70dc4eec0e1e7f10a48597a3" + integrity sha512-hkgzYF+qnIl8uTO8rmUSVSfQ8BIfMXC4yJAF4n8BE758YsKBZvFC4NumnAegj7KmylP0liEZNpb9RRGFMbFejA== + +"@types/trusted-types@*": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-1.0.4.tgz#922d092c84a776a59acb0bd6785fd82b59b9bad5" + integrity sha512-6jtHrHpmiXOXoJ31Cg9R+iEVwuEKPf0XHwFUI93eEPXx492/J2JHyafkleKE2EYzZprayk9FSjTyK1GDqcwDng== + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -1042,6 +1064,11 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dom-walk@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" + integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg= + domain-browser@^1.1.1, domain-browser@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -1462,6 +1489,14 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" +global@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + globo@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/globo/-/globo-1.1.0.tgz#0d26098955dea422eb2001b104898b0a101caaf3" @@ -2061,6 +2096,13 @@ mime@^1.3.6: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= + dependencies: + dom-walk "^0.1.0" + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -2980,6 +3022,11 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +sprintf-js@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -3302,6 +3349,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^3.7.3: + version "3.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" + integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw== + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"