Compare commits

..

7 Commits

Author SHA1 Message Date
de2ad81f8e Improve frontend api part 1 2020-08-09 10:55:44 +05:30
e4041f9e41 Updated websocket ts code
ws factory now creates new object on each invocation instead of reusing
2020-08-09 00:33:00 +05:30
00a01bc81b Organized imports 2020-08-07 12:31:07 +05:30
2b3d6009f1 First integration of websocket into chat
Added sockjs and stompjs via yarn
Added websocket code to chat code
New endpoint for chat
New endpoint for ping
2020-08-07 12:24:20 +05:30
21fa7b5e64 Added new websocket endpoint 2020-08-06 19:35:46 +05:30
5f01174764 Adjusted websocket deps
Added to bundle with yarn
Added cdn links to head fragment
2020-08-06 19:35:17 +05:30
56fd5e8c89 initial websocket commit 2020-08-06 13:52:57 +05:30
66 changed files with 1655 additions and 1478 deletions

1
.gitignore vendored
View File

@ -29,7 +29,6 @@ build/
### VS Code ###
.vscode/
.cache/
node_modules
bundle.js

View File

@ -1,51 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
### VS Code ###
.vscode/
.cache/
node_modules
bundle.js
bundle.min.js
adminBundle.js
adminBundle.min.js
worker.js
chatWorker.js
chatWorker.min.js
node
src/main/javascript/node/
dist
out
yarn-error.log
gen-password.txt
**/static/**
License.md

View File

@ -1,5 +1,6 @@
{
"dependencies": {
"@stomp/stompjs": "^5.4.4",
"@types/bootbox": "^5.2.0",
"@types/datatables.net": "^1.10.19",
"@types/datatables.net-buttons": "^1.4.3",
@ -8,6 +9,7 @@
"@types/jquery": "^3.3.31",
"@types/markdown-it": "^0.0.9",
"@types/sjcl": "^1.0.29",
"@types/sockjs-client": "^1.1.1",
"@types/sprintf-js": "^1.1.2",
"alertifyjs": "^1.12.0",
"bootbox": "^5.4.0",
@ -24,6 +26,7 @@
"moment": "^2.24.0",
"promise-worker": "^2.0.1",
"sjcl": "^1.0.8",
"sockjs-client": "^1.5.0",
"sprintf-js": "^1.1.2"
},
"devDependencies": {
@ -34,7 +37,6 @@
"grunt-banner": "^0.6.0",
"grunt-browserify": "^5.3.0",
"grunt-terser": "^1.0.0",
"prettier": "2.0.5",
"tsify": "^4.0.1",
"typescript": "^3.7.4",
"uglify-js": "^3.7.5"
@ -57,10 +59,11 @@
"depends": [
"@types/jquery"
]
}
},
"sockjs-client": "global:SockJS"
},
"scripts": {
"watch": "watchify src/main/frontend/chat/main.ts -p [ tsify --target ES6 --noImplicitAny ] --debug -o src/main/resources/static/js/bundle.js",
"watch:admin": "watchify src/main/frontend/admin/main.ts -p [ tsify --target ES6 --noImplicitAny ] --debug -o src/main/resources/static/js/adminBundle.js"
}
}
}

31
pom.xml
View File

@ -15,7 +15,7 @@
<description>A self hosted minimal E2E chat application</description>
<properties>
<java.version>11</java.version>
<gruntArg></gruntArg>
<gruntArg />
</properties>
<dependencies>
<dependency>
@ -60,11 +60,6 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency> -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -108,6 +103,11 @@
<artifactId>Either.java</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
@ -137,14 +137,6 @@
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>5.2.4</version>
<!-- <executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>migrate</goal>
</goals>
</execution>
</executions> -->
<configuration>
<driver>${spring.datasource.driverClassName}</driver>
<url>jdbc:mysql://${chatto.datasource.url}:${chatto.datasource.port}/${chatto.datasource.database-name}</url>
@ -223,17 +215,6 @@
</excludeProperties>
</configuration>
</plugin>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>2.12.0</version>
<configuration>
<configFile>${project.basedir}/eclipse-formatter.xml</configFile>
<compilerSource>11</compilerSource>
<compilerCompliance>11</compilerCompliance>
<compilerTargetPlatform>11</compilerTargetPlatform>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,56 @@
import { UserModel } from "../model/UserModel";
import { ChatModel } from "../model/ChatModel";
import { UserView } from "../view/UserView";
import { ChatView } from "../view/ChatView";
import { UserController } from "../controller/UserController";
import { ChatController } from "../controller/ChatController";
import { Client } from "@stomp/stompjs";
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
import log from "loglevel";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
export interface Props {
// userModel: UserModel;
// chatModel: ChatModel;
// userView: UserView;
// chatView: ChatView;
userController: UserController;
chatController: ChatController;
chatStompClient: Client;
}
export interface State {}
export class ChatComponent {
private readonly _props: Props;
private readonly _state: State;
constructor(props: Props, state: State) {
this._props = props;
this._state = state;
}
private async wsHandler() {
this._props.chatStompClient.onConnect = () => {
this._props.chatStompClient.subscribe("/user/queue/reply", (reply) => {
const data = JSON.parse(reply.body) as ChatMessageDTO;
const plainText = this._props.userController.decryptForUser(
data,
data.fromUser
);
this._props.chatController.store(data.fromUser, <ChatMessageViewModel>{
...data,
message: plainText,
messageTime: new Date(data.messageTime),
});
// log.debug(
// message,
// this._props.encryptionService.decrypt(
// "password",
// message.messageCipher
// )
// );
});
};
}
}

View File

@ -1,41 +1,42 @@
import { Controller } from "./AbstractController";
import "../model/AbstractModel"
import "../model/UserModel"
import "../view/AbstractView"
import "../view/UserView"
import "../model/AbstractModel";
import "../model/UserModel";
import "../view/AbstractView";
import "../view/UserView";
import { Model } from "../model/AbstractModel";
import { View } from "../view/AbstractView";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
import { ChatModel } from "../model/ChatModel";
import { ChatView } from "../view/ChatView";
import { Client } from "@stomp/stompjs";
import { EncryptionService } from "../../common/service/EncryptionService";
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
import log from "loglevel";
export interface Props {
model: ChatModel;
view: ChatView;
chatStompClient: Client;
encryptionService: EncryptionService;
}
export class ChatController {
private _model: ChatModel;
private _view: ChatView;
private readonly _props: Props;
constructor(props: Props) {
this._props = props;
}
public test(): void {
const chatMessageViewModels: ChatMessageViewModel[] = [];
let chatMessageViewModelMock = new ChatMessageViewModel();
chatMessageViewModelMock.fromUser = "user1";
chatMessageViewModelMock.toUser = "user2";
chatMessageViewModelMock.message = "";
chatMessageViewModelMock.messageTime = new Date();
chatMessageViewModels.push(chatMessageViewModelMock);
}
constructor(model: ChatModel, view: ChatView) {
this._model = model;
this._view = view;
}
/**
* eventHandler
*/
public eventHandler(vm: ChatMessageViewModel[]): void {
this._model.someBusinessMethod(vm);
}
public test(): void {
const chatMessageViewModels: ChatMessageViewModel[] = [];
let chatMessageViewModelMock = new ChatMessageViewModel();
chatMessageViewModelMock.fromUser = "user1";
chatMessageViewModelMock.toUser = "user2";
chatMessageViewModelMock.message = "";
chatMessageViewModelMock.messageTime = new Date();
chatMessageViewModels.push(chatMessageViewModelMock);
}
}
public store(userName: string, message: ChatMessageViewModel): void {
this._props.model.storeUserMessages(userName, Array(message), "new");
}
}

View File

@ -1,50 +1,30 @@
import { Controller } from "./AbstractController";
import "../model/AbstractModel"
import "../model/UserModel"
import "../view/AbstractView"
import "../view/UserView"
import "../model/AbstractModel";
import "../model/UserModel";
import "../view/AbstractView";
import "../view/UserView";
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";
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
export class UserController {
private _model: UserModel;
private _view: UserView;
private _model: UserModel;
private _view: UserView;
constructor(model: UserModel, view: UserView) {
this._model = model;
this._view = view;
}
constructor(model: UserModel, view: UserView) {
this._model = model;
this._view = view;
}
// public getActiveUsers(): void {
// this._model.getActiveUsers();
// }
/**
* eventHandler
*/
public eventHandler(vm: ActiveUserViewModel[]): void {
this._model.someBusinessMethod(vm);
}
public test(): void {
const activeUsersMock: ActiveUserViewModel[] = [];
let activeUserViewModelMock = new ActiveUserViewModel();
activeUserViewModelMock.userName = "some user";
// activeUserViewModelMock.lastActive = "3 hrs ago";
activeUserViewModelMock.online = true;
activeUsersMock.push(activeUserViewModelMock);
activeUserViewModelMock = new ActiveUserViewModel();
// activeUserViewModelMock.lastActive = "3 hrs ago";
activeUserViewModelMock.online = true;
activeUserViewModelMock.userName = "some user 2";
activeUsersMock.push(activeUserViewModelMock);
this.eventHandler(activeUsersMock);
}
public getActiveUsers(): void {
this._model.getActiveUsers();
}
}
decryptForUser(dto: ChatMessageDTO, userName: string): string {
return this._model.decryptForUser(dto, userName);
}
}

View File

@ -1,26 +1,46 @@
import { Client } from "@stomp/stompjs";
import { Builder } from "builder-pattern";
import * as Handlebars from "handlebars";
import * as log from "loglevel";
import { ChatController } from "./controller/ChatController";
import { UserController } from "./controller/UserController";
import { ChatModel } from "./model/ChatModel";
import { ChatModelHelper } from "./model/ChatModelHelper";
import { UserModel } from "./model/UserModel";
import log from "loglevel";
import moment from "moment";
import SockJS from "sockjs-client";
import { AlertifyNotificationService } from "../common/service/AlertifyNotificationService";
import { EncryptionServiceFactory } from "../common/service/EncryptionServiceFactory";
import { FuseSearchService } from "../common/service/FuseSearchService";
import { MarkDownItMarkDownService } from "../common/service/MarkDownItMarkDownService";
import { NotificationService } from "../common/service/NotificationService";
import { SearchService } from "../common/service/SearchService";
import { ChatController } from "./controller/ChatController";
import { UserController } from "./controller/UserController";
import { ChatModel } from "./model/ChatModel";
import { ChatModelHelper } from "./model/ChatModelHelper";
import { UserModel } from "./model/UserModel";
import { TemplateFactory } from "./template/TemplateFactory";
import { ChatView } from "./view/ChatView";
import { ChatViewDeps } from "./view/ChatViewDeps";
import { UserView } from "./view/UserView";
import { UserViewDeps } from "./view/UserViewDeps";
import { ActiveUserViewModel } from "./viewmodel/ActiveUserViewModel";
import moment = require("moment");
import { Credentials } from "../common/global/Credentials";
// log.setLevel("TRACE");
const authToken = Credentials.authToken;
const chatStompClient = new Client();
chatStompClient.webSocketFactory = () => new SockJS("/chat");
chatStompClient.activate();
const pingStompClient = new Client();
pingStompClient.webSocketFactory = () => new SockJS("/ping");
pingStompClient.activate();
pingStompClient.onConnect = () => {
pingStompClient.publish({ destination: "/app/ping" });
pingStompClient.subscribe("/user/queue/ping", (message) => {
log.debug(message);
log.debug(message.body);
});
};
const usersListElement = document.getElementById("contacts-box");
const userSearchButton = document.getElementById("user-search");
@ -38,7 +58,11 @@ const encryptionService = EncryptionServiceFactory.getEncryptionService();
const chatModelHelper = new ChatModelHelper(encryptionService, ns);
const chatModel = new ChatModel(chatModelHelper);
const userModel = new UserModel(ns);
const userModel = new UserModel({
notificationService: ns,
encryptionService,
authToken,
});
const cvDeps: ChatViewDeps = {
chatModel: chatModel,
// @ts-ignore: Argument of type 'HTMLElement | null' is not assignable to parameter of type 'HTMLElement'. Type 'null' is not assignable to type 'HTMLElement'.
@ -48,13 +72,19 @@ const cvDeps: ChatViewDeps = {
),
messageReceiveTemplate: TemplateFactory.getTemplate("msg_container_template"),
markdownService: new MarkDownItMarkDownService(),
encryptionService: encryptionService,
encryptionService,
notificationService: ns,
userModel: userModel,
chatStompClient,
};
const chatView = new ChatView(cvDeps);
chatModel.attach(chatView);
const chatController = new ChatController(chatModel, chatView);
const chatController = new ChatController({
model: chatModel,
view: chatView,
chatStompClient,
encryptionService,
});
const uvDeps: UserViewDeps = {
model: userModel,
@ -78,7 +108,7 @@ const uvDeps: UserViewDeps = {
const userView = new UserView(uvDeps);
userModel.attach(userView);
const userController = new UserController(userModel, userView);
userController.getActiveUsers();
// userController.getActiveUsers();
// @ts-ignore
Handlebars.registerHelper("moment", require("helper-moment"));

View File

@ -1,12 +1,12 @@
import { Subject } from "../observe/Observable";
import { Observer } from "../observe/Observer";
import { ObserverData } from "../observe/ObserverData";
import { JsonAPI } from "../singleton/JsonAPI";
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
import { ChatModelHelper } from "./ChatModelHelper";
import log = require("loglevel");
import { ObserverData } from "../observe/ObserverData";
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
import moment = require("moment");
import log from "loglevel";
import moment from "moment";
interface Params {
userName: string;
@ -44,7 +44,7 @@ export class ChatModel implements Subject<ChatMessageViewModel> {
log.info("Subject: Detached an observer.");
}
private storeUserMessages(
public storeUserMessages(
username: string,
messages: ChatMessageViewModel[],
op: string
@ -52,25 +52,28 @@ export class ChatModel implements Subject<ChatMessageViewModel> {
switch (op) {
case "clear":
this._messagesMap.set(username, []);
this.notify({ userName: username, data: [], op: op });
break;
case "page":
this._messagesMap.set(
username,
messages.concat(this.getStoredUserMessages(username))
);
this.notify({ userName: username, data: messages, op: op });
break;
// case "page": this._messagesMap.set(username, messages);
case "new":
this._messagesMap.set(
username,
this.getStoredUserMessages(username).concat(messages)
);
this.notify({ userName: username, data: messages, op: op });
break;
case "update":
this._messagesMap.set(
username,
this.getStoredUserMessages(username).concat(messages)
);
this.notify({ userName: username, data: messages, op: op });
break;
default:
new Error("Invalid option");
@ -102,47 +105,18 @@ export class ChatModel implements Subject<ChatMessageViewModel> {
}
}
break;
case "new":
{
const od: ObserverData<ChatMessageViewModel> = {
data: p.data,
op: p.op,
};
for (const observer of this._observers) {
observer.update(od);
}
}
break;
case "page":
{
const od: ObserverData<ChatMessageViewModel> = {
data: p.data,
op: p.op,
};
for (const observer of this._observers) {
observer.update(od);
}
}
break;
case "update":
{
const od: ObserverData<ChatMessageViewModel> = {
data: p.data,
op: p.op,
};
for (const observer of this._observers) {
observer.update(od);
}
}
break;
default: {
log.error("error");
const od: ObserverData<ChatMessageViewModel> = {
data: p.data,
op: p.op,
};
for (const observer of this._observers) {
observer.update(od);
}
}
}
}
public someBusinessMethod(chatMessageList: ChatMessageViewModel[]): void {}
public clear(): void {
log.info("Clearing model");
this._messagePageMap.set(JsonAPI.contactName!, 0);
@ -200,7 +174,7 @@ export class ChatModel implements Subject<ChatMessageViewModel> {
}
this.storeUserMessages(aVm.userName!, cVMsFiltered, op);
this.notify({ userName: aVm.userName!, data: cVMsFiltered, op: op });
// this.notify({ userName: aVm.userName!, data: cVMsFiltered, op: op });
}
return cVMsFiltered;

View File

@ -1,9 +1,9 @@
import * as log from "loglevel";
import log from "loglevel";
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
import { Sprintf } from "../../common/global/Sprintf";
import { EncryptionService } from "../../common/service/EncryptionService";
import { NotificationService } from "../../common/service/NotificationService";
import { JsonAPI } from "../singleton/JsonAPI";
import { Sprintf } from "../../common/global/Sprintf";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
import { fetchErrorHandler } from "./FetchErrorHandler";
@ -88,7 +88,7 @@ export class ChatModelHelper {
const vm = new ChatMessageViewModel();
vm.fromUser = chatMessageDTO.fromUser;
vm.toUser = chatMessageDTO.toUser;
vm.messageTime = chatMessageDTO.messageTime;
vm.messageTime = new Date(chatMessageDTO.messageTime);
vm.message = await this._encryptionService.decryptAsPromise(
passphrase,
chatMessageDTO.messageCipher
@ -106,7 +106,7 @@ export class ChatModelHelper {
// vm.messageTime = chatMessageDTO.messageTime;
chatMessageDTO.messageTime == null
? log.error("Message time somehow null")
: (vm.messageTime = chatMessageDTO.messageTime);
: (vm.messageTime = new Date(chatMessageDTO.messageTime));
vm.message = this._encryptionService.decrypt(
passphrase,
chatMessageDTO.messageCipher

View File

@ -1,11 +1,21 @@
import * as log from "loglevel";
import log from "loglevel";
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
import { NotificationService } from "../../common/service/NotificationService";
import { Subject } from "../observe/Observable";
import { Observer } from "../observe/Observer";
import { JsonAPI } from "../singleton/JsonAPI";
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
import { fetchErrorHandler } from "./FetchErrorHandler";
import { NotificationService } from "../../common/service/NotificationService";
import { EncryptionService } from "../../common/service/EncryptionService";
import { createApiHeaders } from "../../common/ajax/util";
import { getActiveUsers } from "../../common/ajax/Users";
export interface Props {
encryptionService: EncryptionService;
notificationService: NotificationService;
authToken: string;
}
export class UserModel implements Subject<ActiveUserViewModel> {
/**
* @type {Observer[]} List of subscribers. In real life, the list of
@ -14,12 +24,18 @@ export class UserModel implements Subject<ActiveUserViewModel> {
*/
private readonly observers: Observer<ActiveUserViewModel>[] = [];
private _activeUsersList: ActiveUserViewModel[] = Array();
private readonly _notificationService: NotificationService;
private readonly _activeUsersList: ActiveUserViewModel[] = Array();
private readonly _props: Props;
constructor(notificationService: NotificationService) {
constructor(props: Props) {
this._activeUsersList = [];
this._notificationService = notificationService;
this._props = props;
getActiveUsers(props.authToken).then((data) => {
log.debug(data);
this._activeUsersList.push(...data);
this.notify();
});
}
/**
@ -54,63 +70,16 @@ export class UserModel implements Subject<ActiveUserViewModel> {
}
}
public someBusinessMethod(activeuserList: ActiveUserViewModel[]): void {
this._activeUsersList = activeuserList;
this.helperMethod();
log.info(`Subject: My state has just changed`);
log.trace(activeuserList);
this.notify();
}
updateLastActive(username: String, lastActive: Date): void {
this._activeUsersList
.filter(u => u.userName == username)
.forEach(u => u.lastActive = lastActive)
.filter((u) => u.userName == username)
.forEach((u) => (u.lastActive = lastActive));
}
/**
* getActiveUsers
*/
public getActiveUsers(): void {
if (JsonAPI.authToken != null) {
this._getActiveUsersAjax(JsonAPI.authToken).then((data) => {
// // activeUsers = data;
// sessionStorage.setItem('activeUsers', JSON.stringify(data));
// log.trace(sessionStorage.getItem('activeUsers'));
log.info(`Subject: received ajax active users`);
data.map((d: any) => {
if (d.lastActive == null) return null;
d.lastActive = new Date(d.lastActive);
return d;
});
this._activeUsersList = data;
this.notify();
});
} else {
log.error("Auth token is null");
}
public decryptForUser(dto: ChatMessageDTO, userName: string): string {
const passphrase =
this._activeUsersList.find((u) => u.userName === userName)?.passphrase ||
"";
return this._props.encryptionService.decrypt(passphrase, dto.messageCipher);
}
private async _getActiveUsersAjax(authToken: string): Promise<any> {
let headers = new Headers();
headers.append("X-AUTH-TOKEN", authToken);
let response = await fetch(JsonAPI.ACTIVE_USERS_GET, {
method: "GET",
headers: headers,
});
log.debug(response.clone());
if (fetchErrorHandler(response.clone(), this._notificationService)) {
return null;
}
let data = await response.json();
// return data;
return new Promise((resolve, reject) => {
if (data != null) {
resolve(data);
} else reject("Response data null");
});
}
private helperMethod() { }
}

View File

@ -1,18 +1,18 @@
import * as DOMPurify from "dompurify";
import * as log from "loglevel";
import DOMPurify from "dompurify";
import log from "loglevel";
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
import { MessageCipherDTO } from "../../common/dto/MessageCipherDTO";
import { ChatModel } from "../model/ChatModel";
import { Observer } from "../observe/Observer";
import { EncryptionService } from "../../common/service/EncryptionService";
import { MarkDownService } from "../../common/service/MarkDownService";
import { NotificationService } from "../../common/service/NotificationService";
import { ChatModel } from "../model/ChatModel";
import { UserModel } from "../model/UserModel";
import { Observer } from "../observe/Observer";
import { ObserverData } from "../observe/ObserverData";
import { JsonAPI } from "../singleton/JsonAPI";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
import { ChatViewDeps } from "./ChatViewDeps";
import { fetchHandler } from "./FetchHandler";
import { ObserverData } from "../observe/ObserverData";
import { NotificationService } from "../../common/service/NotificationService";
import { UserModel } from "../model/UserModel";
export class ChatView implements Observer<ChatMessageViewModel> {
private readonly _chatModel: ChatModel;
@ -27,6 +27,7 @@ export class ChatView implements Observer<ChatMessageViewModel> {
private readonly _encryptionService: EncryptionService;
private readonly _notificationService: NotificationService;
private readonly _userModel: UserModel;
private readonly _deps: ChatViewDeps;
constructor(deps: ChatViewDeps) {
this._messageContainer = deps.messageContainer;
@ -37,6 +38,7 @@ export class ChatView implements Observer<ChatMessageViewModel> {
this._encryptionService = deps.encryptionService;
this._notificationService = deps.notificationService;
this._userModel = deps.userModel;
this._deps = deps;
this._initEventListeners();
$(document).ready(function () {
@ -181,7 +183,7 @@ export class ChatView implements Observer<ChatMessageViewModel> {
};
this.update({ data: new Array(context), op: "new" });
this._userModel.updateLastActive(contactName, msgTime)
this._userModel.updateLastActive(contactName, msgTime);
this._userModel.notify();
let messageCipher: MessageCipherDTO = this._encryptionService.encrypt(
@ -194,28 +196,17 @@ export class ChatView implements Observer<ChatMessageViewModel> {
messageCipher: messageCipher,
messageTime: msgTime.toISOString(),
};
this._sendMessageAJAX(chatMessageDTO);
// this._sendMessageAJAX(chatMessageDTO);
this._sendMessageWS(chatMessageDTO);
}
private _sendMessageAJAX(chatMessageDTO: any): void {
let headers = new Headers();
// console.log("Token = " + btoa("hmm" + ":" + "hmm"))
// headers.append('Accept','application/json')
headers.append("Content-Type", "application/json");
// headers.append('Authorization', basicAuthToken);
// @ts-ignore
headers.append("X-AUTH-TOKEN", JsonAPI.authToken);
fetch(JsonAPI.MESSAGE_POST, {
method: "POST",
headers: headers,
private _sendMessageWS(chatMessageDTO: ChatMessageDTO): void {
// if (this._deps.chatStompClinet.connected) {
this._deps.chatStompClient.publish({
body: JSON.stringify(chatMessageDTO),
})
.then((response) => {
log.debug(response);
return response.clone();
})
.then((response) => fetchHandler(response, this._notificationService));
destination: "/app/chat",
});
// }
}
private _chatMessagePageLoadAjax() {

View File

@ -4,14 +4,16 @@ import { MarkDownService } from "../../common/service/MarkDownService";
import { EncryptionService } from "../../common/service/EncryptionService";
import { NotificationService } from "../../common/service/NotificationService";
import { UserModel } from "../model/UserModel";
import { Client } from "@stomp/stompjs";
export interface ChatViewDeps {
chatModel: ChatModel;
messageContainer: HTMLElement;
messageSendTemplate: Handlebars.TemplateDelegate<ChatMessageViewModel>;
messageReceiveTemplate: Handlebars.TemplateDelegate<ChatMessageViewModel>;
markdownService: MarkDownService;
encryptionService: EncryptionService;
notificationService: NotificationService;
userModel: UserModel
}
chatModel: ChatModel;
messageContainer: HTMLElement;
messageSendTemplate: Handlebars.TemplateDelegate<ChatMessageViewModel>;
messageReceiveTemplate: Handlebars.TemplateDelegate<ChatMessageViewModel>;
markdownService: MarkDownService;
encryptionService: EncryptionService;
notificationService: NotificationService;
userModel: UserModel;
chatStompClient: Client;
}

View File

@ -1,15 +1,15 @@
import { Observer } from "../observe/Observer";
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
import { ChatModel } from "../model/ChatModel";
import log = require("loglevel");
import * as DOMPurify from "dompurify";
import DOMPurify from "dompurify";
import { NotificationService } from "../../common/service/NotificationService";
import { SearchService } from "../../common/service/SearchService";
import { ChatModel } from "../model/ChatModel";
import { UserModel } from "../model/UserModel";
import { UserViewDeps } from "./UserViewDeps";
import { Observer } from "../observe/Observer";
import { ObserverData } from "../observe/ObserverData";
import { JsonAPI } from "../singleton/JsonAPI";
import { NotificationService } from "../../common/service/NotificationService";
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
import { UserViewDeps } from "./UserViewDeps";
import log from "loglevel";
export class UserView implements Observer<ActiveUserViewModel> {
private readonly _model: UserModel;
@ -89,58 +89,63 @@ export class UserView implements Observer<ActiveUserViewModel> {
JsonAPI.contactName = userName;
// @ts-ignore: Object is possibly 'null'.
document.getElementById("user-name-span").innerText = userName;
let currentUser = this._model.activeUsersList.find((vm) => vm.userName === userName) || new ActiveUserViewModel();
let currentUser =
this._model.activeUsersList.find((vm) => vm.userName === userName) ||
new ActiveUserViewModel();
currentUser.userName = userName;
if (!currentUser?.passphrase) {
this._notificationService.passphrasePrompt(
async (result) => {
if (result) {
const valid = await this._chatModel.isPassphraseValid(result, currentUser.userName!);
if (!valid) {
bootbox.alert("Some error occured. Please check your password");
log.error("invalid password");
return;
}
currentUser.unlocked = true;
currentUser.passphrase = result;
const chatMessages: ChatMessageViewModel[] = await this._chatModel.getMessages(currentUser, "new");
this._model.activeUsersList
.filter((v) => v.userName == currentUser!.userName)
.forEach((v) => {
v.passphrase = result;
v.unlocked = true;
if (chatMessages.length > 0) {
v.lastMessageTime = new Date(
chatMessages[chatMessages.length - 1].messageTime
);
const lastMessageText = (v.lastMessageText =
chatMessages[chatMessages.length - 1].message);
if (lastMessageText.length > 15) {
v.lastMessageText =
chatMessages[chatMessages.length - 1].message.slice(
0,
15
) + "...";
}
}
});
this._promptHandler(currentUser);
this._notificationService.passphrasePrompt(async (result) => {
if (result) {
const valid = await this._chatModel.isPassphraseValid(
result,
currentUser.userName!
);
if (!valid) {
bootbox.alert("Some error occured. Please check your password");
log.error("invalid password");
return;
}
currentUser.unlocked = true;
currentUser.passphrase = result;
const chatMessages: ChatMessageViewModel[] = await this._chatModel.getMessages(
currentUser,
"new"
);
this._model.activeUsersList
.filter((v) => v.userName == currentUser!.userName)
.forEach((v) => {
v.passphrase = result;
v.unlocked = true;
if (chatMessages.length > 0) {
v.lastMessageTime = new Date(
chatMessages[chatMessages.length - 1].messageTime
);
const lastMessageText = (v.lastMessageText =
chatMessages[chatMessages.length - 1].message);
if (lastMessageText.length > 15) {
v.lastMessageText =
chatMessages[chatMessages.length - 1].message.slice(0, 15) +
"...";
}
}
});
this._promptHandler(currentUser);
}
);
}
else {
});
} else {
this._chatModel.getMessages(currentUser, "new");
}
el.className += " active";
if (currentUser.unlocked && currentUser.lastMessageTime) {
this._newMessagesLoop = setInterval(
this._chatModel.getMessages.bind(this._chatModel, currentUser, "update"),
this._chatModel.getMessages.bind(
this._chatModel,
currentUser,
"update"
),
10_000
);
this._model.notify();
@ -177,9 +182,8 @@ export class UserView implements Observer<ActiveUserViewModel> {
let searchResult = this._searchService.search(list, searchTerm);
this.update({ data: searchResult, op: "" });
log.debug(searchResult);
}
else {
this._notificationService.error("Please enter a name")
} else {
this._notificationService.error("Please enter a name");
}
});
}

View File

@ -28,17 +28,11 @@ export async function sendReencryptedMessages(
rencryptionDTOs: ReencryptionDTO[],
authToken: string
) {
let headers = new Headers();
// console.log("Token = " + btoa("hmm" + ":" + "hmm"))
// headers.append('Accept','application/json')
headers.append("Content-Type", "application/json");
headers.append("X-AUTH-TOKEN", authToken);
fetch(Routes.Admin.reencrypt, {
return fetch(Routes.Admin.reencrypt, {
method: "POST",
headers: headers,
headers: createApiHeaders(authToken),
body: JSON.stringify(rencryptionDTOs),
}).then((response) => console.log(response));
});
}
export async function getOneMessage(
@ -52,10 +46,6 @@ export async function getOneMessage(
method: "GET",
headers: createApiHeaders(authToken),
});
log.debug(response.clone());
// if (fetchErrorHandler(response.clone(), this._notificationService)) {
// return null;
// }
const data: Promise<any> = await response.json();
function func(data: any) {
const d1 = data.map((d: any) => {
@ -77,3 +67,15 @@ export async function getStats(authToken: string) {
});
return (await response.json()) as StatsDTO;
}
export async function sendMessageAJAX(
chatMessageDTO: ChatMessageDTO,
authToken: string
) {
const response = fetch(JsonAPI.MESSAGE_POST, {
method: "POST",
headers: createApiHeaders(authToken),
body: JSON.stringify(chatMessageDTO),
});
return response;
}

View File

@ -1,6 +1,8 @@
import { Routes } from "../routes/Routes";
import { createApiHeaders } from "./util";
import { AdminUserDTO } from "../dto/AdminUserDTO";
import { ActiveUserViewModel } from "../../chat/viewmodel/ActiveUserViewModel";
import { JsonAPI } from "../../chat/singleton/JsonAPI";
export async function getOtherUsers(
authToken: string
@ -13,3 +15,20 @@ export async function getOtherUsers(
let data = (await response.json()) as AdminUserDTO[];
return data;
}
export async function getActiveUsers(
authToken: string
): Promise<ActiveUserViewModel[]> {
const response = await fetch(JsonAPI.ACTIVE_USERS_GET, {
method: "GET",
headers: createApiHeaders(authToken),
});
const data = await response.json();
const data2 = data.map((d: any) => {
if (d.lastActive == null) return d;
d.lastActive = new Date(d.lastActive);
return d;
}) as ActiveUserViewModel[];
return data2;
}

View File

@ -1,8 +1,8 @@
import { MessageCipherDTO } from "./MessageCipherDTO";
export class ChatMessageDTO {
public toUser: string = "";
public fromUser: string = "";
public messageCipher!: MessageCipherDTO;
public messageTime: Date = new Date();
export interface ChatMessageDTO {
toUser: string;
fromUser: string;
messageCipher: MessageCipherDTO;
messageTime: string;
}

View File

@ -1,40 +1,12 @@
// export class MessageCipherDTO {
// // iv!: string;
// // v!: number;
// // iterations!: number;
// // keySize!: number;
// // tagSize!: number;
// // mode!: string;
// // adata!: string;
// // cipher!: string;
// // salt!: string;
// // cipherText!: string;
// iv!: string;
// v!: number;
// iter!: number;
// ks!: number;
// ts!: number;
// mode!: string;
// adata!: string;
// cipher!: string;
// salt!: string;
// ct!: string;
// // public toMessageCipherDTO {
// // }
// }
export interface MessageCipherDTO {
iv: string,
v: number,
iter: number,
ks: number,
ts: number,
mode: string,
adata: string,
cipher: string,
salt: string,
ct: string,
}
iv: string;
v: number;
iter: number;
ks: number;
ts: number;
mode: string;
adata: string;
cipher: string;
salt: string;
ct: string;
}

View File

@ -1,34 +1,34 @@
{
"properties" : [
{
"name" : "chatto.token.timeout-duration",
"type" : "java.lang.String",
"description" : "The duration for auth token validity. Token expires after this period of inactivity"
},
{
"name" : "chatto.frontend.log-level",
"type" : "java.lang.String",
"description" : "The log level for the frontend JS application"
},
{
"name" : "chatto.frontend.chat-page-size",
"type" : "java.lang.Integer",
"description" : "The pagination size for the chat area"
},
{
"name" : "chat-worker-bundle",
"type" : "java.lang.String",
"description" : "Name of the chat worker js bundle"
},
{
"name" : "chat-bundle",
"type" : "java.lang.String",
"description" : "Name of the chatjs bundle"
},
{
"name" : "admin-bundle",
"type" : "java.lang.String",
"description" : "Name of the admin js bundle"
}
]
"properties": [
{
"name": "chatto.token.timeout-duration",
"type": "java.lang.String",
"description": "The duration for auth token validity. Token expires after this period of inactivity"
},
{
"name": "chatto.frontend.log-level",
"type": "java.lang.String",
"description": "The log level for the frontend JS application"
},
{
"name": "chatto.frontend.chat-page-size",
"type": "java.lang.Integer",
"description": "The pagination size for the chat area"
},
{
"name": "chat-worker-bundle",
"type": "java.lang.String",
"description": "Name of the chat worker js bundle"
},
{
"name": "chat-bundle",
"type": "java.lang.String",
"description": "Name of the chatjs bundle"
},
{
"name": "admin-bundle",
"type": "java.lang.String",
"description": "Name of the admin js bundle"
}
]
}

View File

@ -2,9 +2,6 @@ package db.migration;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.security.SecureRandom;
import java.sql.PreparedStatement;
@ -15,75 +12,50 @@ import org.springframework.security.crypto.password.PasswordEncoder;
public class V3__add_default_admin extends BaseJavaMigration {
private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
private final SecureRandom random = new SecureRandom();
private final SecureRandom random = new SecureRandom();
/** different dictionaries used */
private final String ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private final String ALPHA = "abcdefghijklmnopqrstuvwxyz";
private final String NUMERIC = "0123456789";
private final String SPECIAL_CHARS = "!@#$%^&*_=+-/";
/** different dictionaries used */
private final String ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private final String ALPHA = "abcdefghijklmnopqrstuvwxyz";
private final String NUMERIC = "0123456789";
private final String SPECIAL_CHARS = "!@#$%^&*_=+-/";
/**
* Method will generate random string based on the parameters
*
* @param len
* the length of the random string
* @param dic
* the dictionary used to generate the password
* @return the random password
*/
public String generatePassword(int len, String dic) {
String result = "";
for (int i = 0; i < len; i++) {
int index = random.nextInt(dic.length());
result += dic.charAt(index);
}
return result;
}
/**
* Method will generate random string based on the parameters
*
* @param len the length of the random string
* @param dic the dictionary used to generate the password
* @return the random password
*/
public String generatePassword(int len, String dic) {
String result = "";
for (int i = 0; i < len; i++) {
int index = random.nextInt(dic.length());
result += dic.charAt(index);
}
return result;
}
@Override
public void migrate(final Context context) throws Exception {
try (final PreparedStatement ps = context.getConnection()
.prepareStatement(
"insert into users (user_id, name, password) values (0,?,?)")) {
final String generatedPassword = generatePassword(60,
ALPHA_CAPS + ALPHA + SPECIAL_CHARS);
final BufferedWriter bw = new BufferedWriter(
new FileWriter("gen-password.txt"));
@Override
public void migrate(final Context context) throws Exception {
try (final PreparedStatement ps = context.getConnection()
.prepareStatement("insert into users (user_id, name, password) values (0,?,?)")) {
final String generatedPassword = generatePassword(60, ALPHA_CAPS + ALPHA + SPECIAL_CHARS);
final BufferedWriter bw = new BufferedWriter(new FileWriter("gen-password.txt"));
bw.write(generatedPassword);
bw.write("\nPlease delete this file");
bw.close();
ps.setString(1, "admin");
ps.setString(2, passwordEncoder.encode(generatedPassword));
ps.execute();
}
bw.write(generatedPassword);
bw.write("\nPlease delete this file");
bw.close();
final var perms = Files
.getPosixFilePermissions(Paths.get("gen-password.txt"));
// add owners permission
perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
perms.remove(PosixFilePermission.OWNER_EXECUTE);
// add group permissions
perms.remove(PosixFilePermission.GROUP_READ);
perms.remove(PosixFilePermission.GROUP_WRITE);
perms.remove(PosixFilePermission.GROUP_EXECUTE);
// add others permissions
perms.remove(PosixFilePermission.OTHERS_READ);
perms.remove(PosixFilePermission.OTHERS_WRITE);
perms.remove(PosixFilePermission.OTHERS_EXECUTE);
Files.setPosixFilePermissions(Paths.get("gen-password.txt"), perms);
ps.setString(1, "admin");
ps.setString(2, passwordEncoder.encode(generatedPassword));
ps.execute();
}
try (final PreparedStatement ps = context.getConnection()
.prepareStatement(
"insert into users_roles (user_id, role_id) values (1,0)")) {
ps.execute();
}
}
try (final PreparedStatement ps = context.getConnection()
.prepareStatement("insert into users_roles (user_id, role_id) values (1,0)")) {
ps.execute();
}
}
}

View File

@ -17,8 +17,8 @@ public class ChattoApplication extends SpringBootServletInitializer {
}
@Configuration
@Profile("development")
@ComponentScan(lazyInit = true)
static class LocalConfig {
}
@Profile("development")
@ComponentScan(lazyInit = true)
static class LocalConfig {
}
}

View File

@ -12,21 +12,19 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationEn
import org.springframework.stereotype.Component;
@Component
public final class RESTAuthenticationEntryPoint
extends BasicAuthenticationEntryPoint {
public final class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP ApplicationStatus 401 - " + authEx.getMessage());
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP ApplicationStatus 401 - " + authEx.getMessage());
}
@Override
public void afterPropertiesSet() throws Exception {
setRealmName("Chatto");
super.afterPropertiesSet();
}
@Override
public void afterPropertiesSet() throws Exception {
setRealmName("Chatto");
super.afterPropertiesSet();
}
}

View File

@ -6,8 +6,7 @@ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder application) {
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ChattoApplication.class);
}

View File

@ -5,14 +5,16 @@ import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
// @EnableWebMvc
//@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**").allowedOrigins("*")
.allowedMethods("POST", "GET", "OPTIONS").allowedHeaders("*")
.allowCredentials(false).maxAge(3600);
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedMethods("POST","GET","OPTIONS")
.allowedHeaders("*")
.allowCredentials(false).maxAge(3600);
}
}
}

View File

@ -4,10 +4,7 @@ import java.awt.image.BufferedImage;
interface CaptchaBehaviour {
public BufferedImage generateCaptcha();
public BufferedImage generateCaptcha(String captchaText);
public String getRandomChars(int size);
public String getRandomChars();
}

View File

@ -6,10 +6,9 @@ import lombok.Builder;
/*Class for providing your own captcha generator*/
@Builder
public class ManualCaptchaBehaviour implements CaptchaBehaviour {
public class ManualCaptchaBehaviour implements CaptchaBehaviour{
private final int length;
private final String style;
@Override
public BufferedImage generateCaptcha() {
// TODO Auto-generated method stub
@ -34,4 +33,5 @@ public class ManualCaptchaBehaviour implements CaptchaBehaviour {
return null;
}
}

View File

@ -10,183 +10,174 @@ import java.io.IOException;
import java.util.Random;
/**
* This class represents a simple captcha consisting of an image {@code png} and
* its text value. Comic Neue Bold Font. Capital english letters {@code ONLY}.
* This class represents a simple captcha consisting
* of an image {@code png} and its text value.
* Comic Neue Bold Font.
* Capital english letters {@code ONLY}.
*
* @since 1.3
* @author Gennadiy Golovin
*/
public final class SimpleCaptcha {
private BufferedImage imagePng;
private char[] text;
private BufferedImage imagePng;
private char[] text;
/**
* Initializes a newly created default object consisting of 8 capital
* english letters.
*/
public SimpleCaptcha() {
this.text = getRandomChars();
/**
* Initializes a newly created default object
* consisting of 8 capital english letters.
*/
public SimpleCaptcha() {
this.text = getRandomChars();
try {
generateCaptcha();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
generateCaptcha();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Initializes a newly created object, which length depends on the passed
* {@code int} parameter, which {@code MUST} be greater than 0. If the
* condition is not met, initializes a newly created default object
* consisting of 8 symbols.
*
* @param length
* the quantity of symbols, that the captcha consists of, greater
* than 0.
*/
public SimpleCaptcha(int length) {
if (length < 1) {
this.text = getRandomChars();
} else {
this.text = getRandomChars(length);
}
/**
* Initializes a newly created object, which length
* depends on the passed {@code int} parameter,
* which {@code MUST} be greater than 0.
* If the condition is not met, initializes a newly
* created default object consisting of 8 symbols.
*
* @param length the quantity of symbols, that the
* captcha consists of, greater than 0.
*/
public SimpleCaptcha(int length) {
if (length < 1) {
this.text = getRandomChars();
} else {
this.text = getRandomChars(length);
}
try {
generateCaptcha();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
generateCaptcha();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Initializes a newly created object based on the passed {@link String}
* parameter, consisting of capital english letters. If the condition is not
* met, initializes a newly created default object consisting of 8 capital
* english letters.
*
* @param text
* the text string with the value of the captcha, length greater
* than 0.
*/
public SimpleCaptcha(String text) {
if (text == null || text.equals("")) {
this.text = getRandomChars();
} else {
this.text = text.toCharArray();
}
/**
* Initializes a newly created object based on the passed
* {@link String} parameter, consisting of capital english
* letters. If the condition is not met, initializes a newly
* created default object consisting of 8 capital english letters.
*
* @param text the text string with the value of the captcha,
* length greater than 0.
*/
public SimpleCaptcha(String text) {
if (text == null || text.equals("")) {
this.text = getRandomChars();
} else {
this.text = text.toCharArray();
}
try {
generateCaptcha();
} catch (IOException e) {
this.text = getRandomChars();
try {
generateCaptcha();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
try {
generateCaptcha();
} catch (IOException e) {
this.text = getRandomChars();
try {
generateCaptcha();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
/**
* Returns the picture with captcha
*
* @return {@link BufferedImage}
*/
public BufferedImage getImagePng() {
return imagePng;
}
/**
* Returns the picture with captcha
*
* @return {@link BufferedImage}
*/
public BufferedImage getImagePng() {
return imagePng;
}
/**
* Returns the text value of the captcha
*
* @return {@link String}
*/
public String getText() {
return String.valueOf(text);
}
/**
* Returns the text value of the captcha
*
* @return {@link String}
*/
public String getText() {
return String.valueOf(text);
}
//////// //////// //////// //////// //////// //////// //////// ////////
//////// //////// //////// //////// //////// //////// //////// ////////
private char[] getRandomChars() {
return getRandomChars(8);
}
private char[] getRandomChars() {
return getRandomChars(8);
}
private char[] getRandomChars(int quantity) {
private char[] getRandomChars(int quantity) {
char[] randomString = new char[quantity];
char[] randomString = new char[quantity];
Random random = new Random();
Random random = new Random();
int capitalLetter;
int capitalLetter;
for (int i = 0; i < quantity; i++) {
capitalLetter = 65 + random.nextInt(26);
randomString[i] = (char) capitalLetter;
}
for (int i = 0; i < quantity; i++) {
capitalLetter = 65 + random.nextInt(26);
randomString[i] = (char) capitalLetter;
}
return randomString;
}
return randomString;
}
private void generateCaptcha() throws IOException {
int charsQuantity = this.text.length;
BufferedImage[] images = new BufferedImage[charsQuantity];
private void generateCaptcha() throws IOException {
int charsQuantity = this.text.length;
BufferedImage[] images = new BufferedImage[charsQuantity];
for (int i = 0; i < charsQuantity; i++) {
images[i] = ImageIO.read(SimpleCaptcha.class
.getResourceAsStream("/pictures/" + this.text[i] + ".png"));
if (i % 2 == 0) {
images[i] = rotateImage(images[i], 25);
} else {
images[i] = rotateImage(images[i], -20);
}
}
for (int i = 0; i < charsQuantity; i++) {
images[i] = ImageIO.read(SimpleCaptcha.class.getResourceAsStream("/pictures/" + this.text[i] + ".png"));
if (i % 2 == 0) {
images[i] = rotateImage(images[i], 25);
} else {
images[i] = rotateImage(images[i], -20);
}
}
int imageSize = 30;
int rotatedImageSize = (int) Math.sqrt(imageSize * imageSize * 2);
int imageSize = 30;
int rotatedImageSize = (int) Math.sqrt(imageSize * imageSize * 2);
BufferedImage captchaImg = new BufferedImage(
rotatedImageSize * (charsQuantity - 1) / 10 * 6
+ rotatedImageSize,
rotatedImageSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2d = captchaImg.createGraphics();
graphics2d.setBackground(Color.WHITE);
graphics2d.clearRect(0, 0, captchaImg.getWidth(),
captchaImg.getHeight());
for (int i = 0; i < charsQuantity; i++) {
captchaImg.getGraphics().drawImage(images[i],
rotatedImageSize * i / 10 * 6, 0, null);
}
graphics2d.dispose();
this.imagePng = captchaImg;
}
BufferedImage captchaImg = new BufferedImage(rotatedImageSize * (charsQuantity - 1) / 10 * 6 + rotatedImageSize, rotatedImageSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2d = captchaImg.createGraphics();
graphics2d.setBackground(Color.WHITE);
graphics2d.clearRect(0, 0, captchaImg.getWidth(), captchaImg.getHeight());
for (int i = 0; i < charsQuantity; i++) {
captchaImg.getGraphics().drawImage(images[i], rotatedImageSize * i / 10 * 6, 0, null);
}
graphics2d.dispose();
this.imagePng = captchaImg;
}
private BufferedImage rotateImage(BufferedImage buffImage, double angle) {
private BufferedImage rotateImage(BufferedImage buffImage, double angle) {
double radian = Math.toRadians(angle);
double sin = Math.abs(Math.sin(radian));
double cos = Math.abs(Math.cos(radian));
double radian = Math.toRadians(angle);
double sin = Math.abs(Math.sin(radian));
double cos = Math.abs(Math.cos(radian));
int width = buffImage.getWidth();
int height = buffImage.getHeight();
int width = buffImage.getWidth();
int height = buffImage.getHeight();
int nWidth = (int) Math
.floor((double) width * cos + (double) height * sin);
int nHeight = (int) Math
.floor((double) height * cos + (double) width * sin);
int nWidth = (int) Math.floor((double) width * cos + (double) height * sin);
int nHeight = (int) Math.floor((double) height * cos + (double) width * sin);
BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight,
BufferedImage.TYPE_INT_ARGB);
BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = rotatedImage.createGraphics();
Graphics2D graphics = rotatedImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
graphics.drawImage(buffImage, 0, 0, null);
graphics.dispose();
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
graphics.drawImage(buffImage, 0, 0,null);
graphics.dispose();
return rotatedImage;
}
return rotatedImage;
}
}

View File

@ -9,30 +9,30 @@ public class SimpleCaptchaBehavior implements CaptchaBehaviour {
SimpleCaptcha simpleCaptcha = new SimpleCaptcha();
return simpleCaptcha.getImagePng();
}
@Override
public BufferedImage generateCaptcha(String captchaText) {
SimpleCaptcha simpleCaptcha = new SimpleCaptcha(captchaText);
return simpleCaptcha.getImagePng();
}
public String getRandomChars() {
return getRandomChars(8);
}
public String getRandomChars(int quantity) {
return getRandomChars(8);
}
public String getRandomChars(int quantity)
{
char[] randomString = new char[quantity];
Random random = new Random();
Random random = new Random();
int capitalLetter;
int capitalLetter;
for (int i = 0; i < quantity; i++) {
capitalLetter = 65 + random.nextInt(26);
randomString[i] = (char) capitalLetter;
}
for (int i = 0; i < quantity; i++) {
capitalLetter = 65 + random.nextInt(26);
randomString[i] = (char) capitalLetter;
}
return new String(randomString);
return new String(randomString);
}
}

View File

@ -7,35 +7,34 @@ import lombok.Builder;
@Builder
public class WebCaptcha {
private final CaptchaBehaviour captchaBehaviour;
public BufferedImage generateCaptcha() {
return captchaBehaviour.generateCaptcha();
}
}
public BufferedImage generateCaptcha(String captchaText) {
return captchaBehaviour.generateCaptcha(captchaText);
}
}
public String getRandomChars() {
return captchaBehaviour.getRandomChars();
}
return captchaBehaviour.getRandomChars();
}
public String getRandomChars(int quantity) {
return captchaBehaviour.getRandomChars(quantity);
}
public String getRandomChars(int quantity) {
return captchaBehaviour.getRandomChars(quantity);
}
}
// WebCaptcha webCaptcha = WebCaptcha.builder().captchaBehaviour(new
// SimpleCaptchaBehavior()).build();
// webCaptcha.generateCaptcha();
// WebCaptcha webCaptcha = WebCaptcha.builder().captchaBehaviour(new SimpleCaptchaBehavior()).build();
// webCaptcha.generateCaptcha();
//
// // @formatter:off
// webCaptcha = WebCaptcha.builder()
// .captchaBehaviour(
// ManualCaptchaBehaviour.builder()
// .length(8)
// .style("black")
// .build()
// ).build();
//
// // @formatter:on
// // @formatter:off
// webCaptcha = WebCaptcha.builder()
// .captchaBehaviour(
// ManualCaptchaBehaviour.builder()
// .length(8)
// .style("black")
// .build()
// ).build();
//
// // @formatter:on

View File

@ -10,13 +10,13 @@ import lombok.Getter;
@PropertySource(value = "classpath:git.properties")
@Getter
public class BuildInfo {
private final String buildVersion;
private final String branchName;
private final String buildVersion;
private final String branchName;
public BuildInfo(@Value("${git.build.version") String buildVersion,
@Value("${git.branch") String branchName) {
this.buildVersion = buildVersion;
this.branchName = branchName;
}
public BuildInfo(@Value("${git.build.version") String buildVersion,
@Value("${git.branch") String branchName) {
this.buildVersion = buildVersion;
this.branchName = branchName;
}
}

View File

@ -6,14 +6,11 @@ import org.ehcache.event.CacheEventListener;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomCacheEventLogger
implements CacheEventListener<Object, Object> {
public class CustomCacheEventLogger implements CacheEventListener<Object, Object> {
@Override
public void onEvent(
CacheEvent<? extends Object, ? extends Object> cacheEvent) {
log.debug("custom Caching event {} key = {} old {} new {} ",
cacheEvent.getType(), cacheEvent.getKey(),
public void onEvent(CacheEvent<? extends Object, ? extends Object> cacheEvent) {
log.debug("custom Caching event {} key = {} old {} new {} ", cacheEvent.getType(), cacheEvent.getKey(),
cacheEvent.getOldValue(), cacheEvent.getNewValue());
}
}

View File

@ -1,5 +1,6 @@
package org.ros.chatto.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

View File

@ -89,8 +89,8 @@ public class ChatMessageController {
@PathVariable final String userName,
@PathVariable final Instant lastMessageTime,
final Principal principal) {
final List<ChatMessageDTO> chatMessageDTOs = chatService
.getNewMessages(principal.getName(), userName, lastMessageTime);
final List<ChatMessageDTO> chatMessageDTOs = chatService.getNewMessages(
principal.getName(), userName, lastMessageTime);
return chatMessageDTOs;
}

View File

@ -1,14 +0,0 @@
package org.ros.chatto.controller;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CsrfController {
@RequestMapping("/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}

View File

@ -45,7 +45,7 @@ public class Home {
|| r.getAuthority().equals("ROLE_SUPER_USER"));
log.trace("Is admin? " + isAdmin);
// model.addAttribute("activeUsers",
// userService.getOtherActiveUsers(principal.getName()));
// userService.getOtherActiveUsers(principal.getName()));
return "chat";
}
}

View File

@ -14,16 +14,16 @@ import lombok.RequiredArgsConstructor;
@RequestMapping("/api/stats")
@RequiredArgsConstructor
public class StatisticsController {
private final StatisticsService statisticsService;
private final StatisticsService statisticsService;
@GetMapping
public StatsDTO rootStats() {
return StatsDTO.builder()
.totalMessages(statisticsService.totalMessage())
.totalOnlineUsers(statisticsService.totalUsersOnline())
.numMessagesToday(
statisticsService.messagesOnDay(Instant.now()))
.totalUsers(statisticsService.totalUsers()).build();
}
@GetMapping
public StatsDTO rootStats() {
return StatsDTO.builder()
.totalMessages(statisticsService.totalMessage())
.totalOnlineUsers(statisticsService.totalUsersOnline())
.numMessagesToday(
statisticsService.messagesOnDay(Instant.now()))
.totalUsers(statisticsService.totalUsers()).build();
}
}

View File

@ -6,9 +6,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping
public String viewUserProfile() {
return "user/home";
}
@RequestMapping
public String viewUserProfile() {
return "user/home";
}
}

View File

@ -1,5 +1,6 @@
package org.ros.chatto.dto;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
@ -11,10 +12,10 @@ import lombok.Data;
public class ChatMessageDTO {
@NotBlank(message = "Username should not be blank")
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "Username must be alphanumeric")
@Size(max = 15)
@Size(max=15)
private String toUser;
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "Username must be alphanumeric")
@Size(max = 15)
@Size(max=15)
private String fromUser;
private MessageCipherDTO messageCipher;
private Instant messageTime;

View File

@ -12,9 +12,7 @@ import lombok.Data;
@Data
public class MessageCipherDTO {
@Pattern(regexp = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") // regex
// for
// base64
@Pattern(regexp = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") // regex for base64
@NotBlank
private String iv;
@Max(1)

View File

@ -16,13 +16,11 @@ public class UserRegistrationDTO {
@Transient
@Size(min = 4, max = 75, message = "Password must be between 4 and 75 characters")
@NotBlank(message = "Password should not be blank")
// @Pattern(regexp =
// "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message
// = "Invalid password format")
// @Pattern(regexp = "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message = "Invalid password format")
private String password;
private Long captchaID;
private String captchaText;
private String captchaInput;
}

View File

@ -7,14 +7,15 @@ import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
public class ErrorModel {
public class ErrorModel{
@JsonProperty("field_name")
private String fieldName;
private String fieldName;
@JsonProperty("rejected_value")
private Object rejectedValue;
private Object rejectedValue;
@JsonProperty("error_message")
private String messageError;
private String messageError;
}

View File

@ -15,6 +15,6 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class ErrorResponse {
@JsonProperty("errors")
private List<ErrorModel> errorMessage;
private List<ErrorModel> errorMessage;
}

View File

@ -4,6 +4,5 @@ import org.springframework.cache.annotation.CacheEvict;
public class TokenCacheUtil {
@CacheEvict(value = "userTokenCache", key = "#cacheKey")
public static void evictSingleTokenValue(String cacheKey) {
}
public static void evictSingleTokenValue(String cacheKey) {}
}

View File

@ -26,8 +26,7 @@ public class UserLoggingSessionListener implements HttpSessionBindingListener {
@Override
public void valueBound(HttpSessionBindingEvent event) {
UserLoggingSessionListener user = (UserLoggingSessionListener) event
.getValue();
UserLoggingSessionListener user = (UserLoggingSessionListener) event.getValue();
log.debug("Incrementing session count for user {}", user.getUsername());
@ -39,8 +38,7 @@ public class UserLoggingSessionListener implements HttpSessionBindingListener {
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
UserLoggingSessionListener user = (UserLoggingSessionListener) event
.getValue();
UserLoggingSessionListener user = (UserLoggingSessionListener) event.getValue();
log.debug("Decrementing session count for user {}", user.getUsername());

View File

@ -11,19 +11,17 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
import org.springframework.stereotype.Component;
@Component("myAuthenticationSuccessHandler")
public class UserSessionLoggingLoginSuccessHandler
implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
HttpSession session = request.getSession(false);
if (session != null) {
UserLoggingSessionListener user = new UserLoggingSessionListener(
authentication.getName());
session.setAttribute("user", user);
}
response.sendRedirect("/chat");
}
public class UserSessionLoggingLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
HttpSession session = request.getSession(false);
if (session != null) {
UserLoggingSessionListener user = new UserLoggingSessionListener(authentication.getName());
session.setAttribute("user", user);
}
response.sendRedirect("/chat");
}
}

View File

@ -12,16 +12,15 @@ import org.springframework.security.web.authentication.logout.LogoutSuccessHandl
import org.springframework.stereotype.Component;
@Component("myLogoutSuccessHandler")
public class UserSessionLoggingLogoutSuccessHandler
implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
HttpSession session = request.getSession();
if (session != null) {
session.removeAttribute("user");
}
response.sendRedirect("/login?logout");
}
public class UserSessionLoggingLogoutSuccessHandler implements LogoutSuccessHandler{
@Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
HttpSession session = request.getSession();
if (session != null){
session.removeAttribute("user");
}
response.sendRedirect("/login?logout");
}
}

View File

@ -24,18 +24,18 @@ public class ChatMessage {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "m_id")
private Long messageID;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "from_user")
private ChatUser fromUser;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "to_user")
private ChatUser toUser;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "message")
private MessageCipher messageCipher;
private Instant messageTime;
private Instant messageTime;
}

View File

@ -19,7 +19,7 @@ this is what the json will look like*/
@Entity
@Table(name = "message_ciphers")
@EntityListeners(AuditingEntityListener.class)
// @JsonIgnoreProperties(value = { "id"}, allowGetters = false)
//@JsonIgnoreProperties(value = { "id"}, allowGetters = false)
public class MessageCipher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -16,6 +16,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Entity
@Table(name = "roles")
@Data
@ -23,14 +24,14 @@ public class Role {
@Id
@Column(name = "role_id")
private int roleID;
@Column(name = "role_name")
private String name;
private String name;
private String description;
@OneToMany(mappedBy = "role", cascade = { CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
@OneToMany(mappedBy = "role", cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH,
CascadeType.REFRESH })
@JsonBackReference
@ToString.Exclude
@EqualsAndHashCode.Exclude

View File

@ -20,13 +20,13 @@ public class UserSession {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private ChatUser user;
private boolean online;
private int numSessions;
private Instant timeStamp;
}

View File

@ -1,10 +1,9 @@
// package org.ros.chatto.repository;
//package org.ros.chatto.repository;
//
// import org.springframework.data.jpa.repository.JpaRepository;
// import org.springframework.stereotype.Repository;
//import org.springframework.data.jpa.repository.JpaRepository;
//import org.springframework.stereotype.Repository;
//
// @Repository
// public interface DBInitializerRepostory extends JpaRepository<Integer,
// Integer>{
//
// }
//@Repository
//public interface DBInitializerRepostory extends JpaRepository<Integer, Integer>{
//
//}

View File

@ -5,7 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MessageCipherRepository
extends JpaRepository<MessageCipher, Long> {
public interface MessageCipherRepository extends JpaRepository<MessageCipher, Long>{
}

View File

@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
public interface RoleRepository extends JpaRepository<Role, Long>{
@Query("select r from Role r where r.name = ?1")
public Role findByName(String roleName);
}

View File

@ -4,8 +4,8 @@ import java.util.List;
public interface UserRepositoryCustom {
// @Query("select s from Article s where s.author like ?1 and s.title = ?2")
// List<Article> findByAuthorAndTitle(String author, String title);
// @Query("select u from ChatUser u")
// @Query("select s from Article s where s.author like ?1 and s.title = ?2")
// List<Article> findByAuthorAndTitle(String author, String title);
// @Query("select u from ChatUser u")
public List<String> getAllUserNames(String s);
}

View File

@ -14,34 +14,30 @@ import org.ros.chatto.model.ChatUser;
import org.ros.chatto.repository.UserRepositoryCustom;
@Service
class UserRepositoryCustomImpl implements UserRepositoryCustom {
class UserRepositoryCustomImpl implements UserRepositoryCustom{
@PersistenceContext
private EntityManager entityManager;
@Override
public List<String> getAllUserNames(String userName) {
List<String> userNamesList = null;
// Session session = null;
// Session session = null;
try {
CriteriaBuilder criteriaBuilder = entityManager
.getCriteriaBuilder();
CriteriaQuery<String> criteriaQuery = criteriaBuilder
.createQuery(String.class);
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.class);
Root<ChatUser> root = criteriaQuery.from(ChatUser.class);
criteriaQuery.select(root.get("userName"));
criteriaQuery.where(
criteriaBuilder.notEqual(root.get("userName"), userName));
userNamesList = entityManager.createQuery(criteriaQuery)
.getResultList();
// for(String un: userNamesList)
// {
// System.out.println(un);
// }
criteriaQuery.where(criteriaBuilder.notEqual(root.get("userName"), userName));
userNamesList = entityManager.createQuery(criteriaQuery).getResultList();
// for(String un: userNamesList)
// {
// System.out.println(un);
// }
} catch (Exception e) {
e.printStackTrace();
}
return userNamesList;
}
}

View File

@ -16,29 +16,20 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
import org.springframework.stereotype.Component;
@Component
public class AuthenticationSuccessHandlerImpl
implements AuthenticationSuccessHandler {
private static final SimpleGrantedAuthority SUPER_USER_AUTHORITY = new SimpleGrantedAuthority(
"ROLE_SUPER_USER");
private static final SimpleGrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority(
"ROLE_ADMIN");
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
private static final SimpleGrantedAuthority SUPER_USER_AUTHORITY = new SimpleGrantedAuthority("ROLE_SUPER_USER");
private static final SimpleGrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority("ROLE_ADMIN");
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication)
throws IOException, ServletException {
Collection<? extends GrantedAuthority> authorities = authentication
.getAuthorities();
if (authorities.contains(ADMIN_AUTHORITY)
|| authorities.contains(SUPER_USER_AUTHORITY)) {
redirectStrategy.sendRedirect(httpServletRequest,
httpServletResponse, "/admin");
} else {
redirectStrategy.sendRedirect(httpServletRequest,
httpServletResponse, "/user");
}
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
if (authorities.contains(ADMIN_AUTHORITY) || authorities.contains(SUPER_USER_AUTHORITY)) {
redirectStrategy.sendRedirect(httpServletRequest, httpServletResponse, "/admin");
} else {
redirectStrategy.sendRedirect(httpServletRequest, httpServletResponse, "/user");
}
}
}

View File

@ -9,17 +9,18 @@ import org.springframework.stereotype.Service;
@Service
public class CaptchaService {
private final WebCaptcha webCaptcha;
public CaptchaService() {
webCaptcha = WebCaptcha.builder()
.captchaBehaviour(new SimpleCaptchaBehavior()).build();
webCaptcha = WebCaptcha.builder().captchaBehaviour(new SimpleCaptchaBehavior()).build();
}
public BufferedImage createCaptchaImage(final String captchaText) {
public BufferedImage createCaptchaImage(final String captchaText)
{
return webCaptcha.generateCaptcha(captchaText);
}
public String getRandomText() {
public String getRandomText()
{
return webCaptcha.getRandomChars();
}
}

View File

@ -46,15 +46,13 @@ public class DBInitializerService {
});
}
private void resetAllUserSessions(final Connection connection)
throws SQLException {
private void resetAllUserSessions(final Connection connection) throws SQLException {
final PreparedStatement preparedStatement = connection
.prepareStatement(dbInitializerConfig.getResetSessionsQuery());
preparedStatement.executeUpdate();
}
private void clearAllTokens(final Connection connection)
throws SQLException {
private void clearAllTokens(final Connection connection) throws SQLException {
final PreparedStatement preparedStatement = connection
.prepareStatement(dbInitializerConfig.getClearTokensQuery());
preparedStatement.executeUpdate();

View File

@ -0,0 +1,12 @@
package org.ros.chatto.websocket;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class Message {
private String from;
private String to;
private String text;
}

View File

@ -0,0 +1,26 @@
package org.ros.chatto.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ping");
registry.addEndpoint("/ping").withSockJS();
registry.addEndpoint("/chat");
registry.addEndpoint("/chat").withSockJS();
}
}

View File

@ -0,0 +1,39 @@
package org.ros.chatto.websocket;
import java.security.Principal;
import javax.validation.Valid;
import org.ros.chatto.dto.ChatMessageDTO;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.RequiredArgsConstructor;
@Controller
@RequiredArgsConstructor
public class WebSocketController {
private final SimpMessagingTemplate smt;
@MessageMapping("/ping")
public void send2(Principal principal) throws Exception {
smt.convertAndSendToUser(principal.getName(), "/queue/ping", "pong");
}
@MessageMapping("/chat")
public void send(@Valid @Payload final ChatMessageDTO message)
throws Exception {
smt.convertAndSendToUser(message.getFromUser(), "/queue/reply",
message);
smt.convertAndSendToUser(message.getToUser(), "/queue/reply", message);
}
@GetMapping("/ws")
public String wsPage() {
return "ws";
}
}

View File

@ -57,6 +57,13 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/loglevel/1.6.4/loglevel.min.js"
integrity="sha256-ACTlnmNCkOooSKkPCKYbiex8WLE82aeiN+Z9ElZag5Q=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"
integrity="sha512-5yJ548VSnLflcRxWNqVWYeQZnby8D8fJTmYRLyvs445j1XmzR8cnWi85lcHx3CUEeAX+GrK3TqTfzOO6LKDpdw=="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"
integrity="sha512-iKDtgDyTHjAitUDdLljGhenhPwrbBfqTKWO1mkhSFH3A7blITC9MhYon6SjnMhp4o0rADGw9yAC6EW4t5a4K3g=="
crossorigin="anonymous"></script>
<link th:href="@{/css/master.css}" href="../../resources/static/css/master.css" rel="stylesheet">
<link th:href="@{/css/colors.css}" href="../../resources/static/css/colors.css" rel="stylesheet">

View File

@ -0,0 +1,83 @@
<html>
<head>
<title>Chat WebSocket</title>
<script type="text/javascript">
var stompClient = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility
= connected ? 'visible' : 'hidden';
document.getElementById('response').innerHTML = '';
}
function connect() {
var socket = new SockJS('/chat2');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
// stompClient.subscribe('/topic/messages', function (messageOutput) {
// showMessageOutput(JSON.parse(messageOutput.body));
// });
stompClient.subscribe('/user/queue/reply', function (messageOutput) {
showMessageOutput(JSON.parse(messageOutput.body));
});
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendMessage() {
var from = document.getElementById('from').value;
var to = document.getElementById('to').value;
var text = document.getElementById('text').value;
stompClient.send("/app/chat", {},
JSON.stringify({ 'from': from, 'text': text, to: to }));
}
function showMessageOutput(messageOutput) {
var response = document.getElementById('response');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(messageOutput.from + ": "
+ messageOutput.text + " (" + messageOutput.time + ")"));
response.appendChild(p);
}
</script>
</head>
<body onload="disconnect()">
<div>
<div>
<input type="text" id="from" placeholder="Choose a nickname" />
</div>
<div>
<input type="text" id="to" placeholder="Send to User" />
</div>
<br />
<div>
<button id="connect" onclick="connect();">Connect</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">
Disconnect
</button>
</div>
<br />
<div id="conversationDiv">
<input type="text" id="text" placeholder="Write a message..." />
<button id="sendMessage" onclick="sendMessage();">Send</button>
<p id="response"></p>
</div>
</div>
</body>
</html>

View File

@ -15,68 +15,65 @@
// @RunWith(SpringRunner.class)
// @SpringBootTest
// public class ChattoApplicationTests {
// // @Autowired
// // ChatMessageRepository chatMessageRepository;
// // @Autowired
// // ChatMessageRepository chatMessageRepository;
// //
// // @Mock
// // ChatMessageRepository mockChatMessageRepository;
// //
// @Autowired
// private UserRepository userRepository;
// @Autowired
// private UserRoleRepository userRoleRepository;
// private final Logger logger = LoggerFactory.getLogger(ChattoApplicationTests.class);
// //
// // @Mock
// // ChatMessageRepository mockChatMessageRepository;
// @Test
// public void contextLoads() {
// }
// //
// @Autowired
// private UserRepository userRepository;
// @Autowired
// private UserRoleRepository userRoleRepository;
// private final Logger logger =
// LoggerFactory.getLogger(ChattoApplicationTests.class);
// //
// @Test
// public void contextLoads() {
// }
// //
// // @Test
// // public void testMessageRepo() {
// // chatMessageRepository.findAll().toString();
// // }
// @Test
// public void testRoleRepo() {
// List<String> list = userRoleRepository.getAllRegularUser();
// logger.info("List = {} ", list);
// }
// @Test
// public void findAllOtherUsers() {
// logger.info("Usernames =
// {}",userRepository.findAllOtherUserNames("hmm").toString());
// }
// // @Test
// // public void testSave() {
// // ChatUser fromUser = new ChatUser();
// // fromUser = userRepository.findByUserName("hmm");
// // ChatUser toUser = new ChatUser();
// // toUser = userRepository.findByUserName("user2");
// // ChatMessage chatMessage = new ChatMessage();
// // chatMessage.setMessage("Hello!");
// // chatMessage.setFromUser(fromUser);
// // chatMessage.setToUser(toUser);
// //
// // chatMessageRepository.save(chatMessage);
// // }
// /*
// * @Test public void testSave() { ChatUser fromUser = new ChatUser(); fromUser
// =
// * userRepository.findByUserName("hmm"); ChatUser toUser = new ChatUser();
// * toUser = userRepository.findByUserName("user2"); ChatMessage chatMessage =
// * new ChatMessage(); chatMessage.setMessage("Hello!");
// * chatMessage.setFromUser(fromUser); chatMessage.setToUser(toUser);
// *
// * // chatMessageRepository.save(chatMessage);
// * when(mockChatMessageRepository.save(any(ChatMessage.class))).thenReturn(
// * chatMessage); verify(mockChatMessageRepository,
// * times(1)).save(Mockito.any(ChatMessage.class)); }
// */
// // @Test
// // public void testMessageRepo() {
// // chatMessageRepository.findAll().toString();
// // }
// @Test
// public void testRoleRepo() {
// List<String> list = userRoleRepository.getAllRegularUser();
// logger.info("List = {} ", list);
// }
// @Test
// public void findAllOtherUsers() {
// logger.info("Usernames = {}",userRepository.findAllOtherUserNames("hmm").toString());
// }
// // @Test
// // public void testSave() {
// // ChatUser fromUser = new ChatUser();
// // fromUser = userRepository.findByUserName("hmm");
// // ChatUser toUser = new ChatUser();
// // toUser = userRepository.findByUserName("user2");
// // ChatMessage chatMessage = new ChatMessage();
// // chatMessage.setMessage("Hello!");
// // chatMessage.setFromUser(fromUser);
// // chatMessage.setToUser(toUser);
// //
// // chatMessageRepository.save(chatMessage);
// // }
// /*
// * @Test public void testSave() { ChatUser fromUser = new ChatUser(); fromUser =
// * userRepository.findByUserName("hmm"); ChatUser toUser = new ChatUser();
// * toUser = userRepository.findByUserName("user2"); ChatMessage chatMessage =
// * new ChatMessage(); chatMessage.setMessage("Hello!");
// * chatMessage.setFromUser(fromUser); chatMessage.setToUser(toUser);
// *
// * // chatMessageRepository.save(chatMessage);
// * when(mockChatMessageRepository.save(any(ChatMessage.class))).thenReturn(
// * chatMessage); verify(mockChatMessageRepository,
// * times(1)).save(Mockito.any(ChatMessage.class)); }
// */
// }

View File

@ -32,62 +32,61 @@
// @RunWith(MockitoJUnitRunner.class)
// @Slf4j
// public class UnitTest {
// @InjectMocks
// @InjectMocks
// // private RoleService roleService;
// // private UserTokenService userTokenService;
// // private RoleService roleService;
// // private UserTokenService userTokenService;
// @Mock
// private UserRoleRepository userRoleRepository;
// @Mock
// private UserRoleRepository userRoleRepository;
// @Mock
// private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// @Mock
// private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// @Mock
// private RoleRepository roleRepository;
// @Mock
// private RoleRepository roleRepository;
// @Mock
// private UserRepositoryCustom userRepositoryCustom;
// @Mock
// private UserRepositoryCustom userRepositoryCustom;
// @Mock
// private UserSessionRepository userSessionRepository;
// @Mock
// private UserSessionRepository userSessionRepository;
// @Mock
// private UserRepository userRepository;
// @Mock
// private UserRepository userRepository;
// private UserService userService = new UserServiceImpl(userRepository,
// userRoleRepository, passwordEncoder, roleRepository,
// userSessionRepository);
// // private ChatUser chatUser;
// private UserService userService = new UserServiceImpl(userRepository, userRoleRepository, passwordEncoder, roleRepository,
// userSessionRepository);
// // private ChatUser chatUser;
// @Before
// public void setupMock() {
// // userRepository = mock(UserRepository.class);
// // chatUser = mock(ChatUser.class);
// }
// @Before
// public void setupMock() {
// // userRepository = mock(UserRepository.class);
// // chatUser = mock(ChatUser.class);
// }
// @Test
// public void userRegistrationTest() {
// UserRegistrationDTO userRegistrationDTO = new UserRegistrationDTO();
// userRegistrationDTO.setUserName("mickey");
// userRegistrationDTO.setPassword("mouse");
// @Test
// public void userRegistrationTest() {
// UserRegistrationDTO userRegistrationDTO = new UserRegistrationDTO();
// userRegistrationDTO.setUserName("mickey");
// userRegistrationDTO.setPassword("mouse");
// ChatUser chatUser = new ChatUser();
// chatUser.setUserName("mickey");
// // chatUser.setPassword("mouse");
// ChatUser chatUser = new ChatUser();
// chatUser.setUserName("mickey");
// // chatUser.setPassword("mouse");
// Role role = new Role();
// role.setRoleID(2);
// role.setName("USER");
// when(roleRepository.findByName("USER")).thenReturn(role);
// when(userRepository.save(chatUser)).thenReturn(chatUser);
// Role role = new Role();
// role.setRoleID(2);
// role.setName("USER");
// when(roleRepository.findByName("USER")).thenReturn(role);
// when(userRepository.save(chatUser)).thenReturn(chatUser);
// UserRole userRole = userService.registerUser(userRegistrationDTO);
// assertArrayEquals(new Object[] { 2, "USER","mickey" },
// new Object[] { userRole.getRole().getRoleID(),
// userRole.getRole().getName(), userRole.getUser().getUserName() });
// verify(userRepository, times(1)).save(chatUser);
// verify(userRoleRepository,times(1)).save(userRole);
// }
// UserRole userRole = userService.registerUser(userRegistrationDTO);
// assertArrayEquals(new Object[] { 2, "USER","mickey" },
// new Object[] { userRole.getRole().getRoleID(),
// userRole.getRole().getName(), userRole.getUser().getUserName() });
// verify(userRepository, times(1)).save(chatUser);
// verify(userRoleRepository,times(1)).save(userRole);
// }
// }

1107
yarn.lock

File diff suppressed because it is too large Load Diff