From 530dcd2f04ec2b1cdbb9b63fd73a5380d0f12b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Thu, 7 Nov 2024 14:23:07 +0100 Subject: [PATCH] login, logout --- .../angular/src/app/api/User/UserPrivate.ts | 6 --- .../app/api/User/requests/UserLoginRequest.ts | 10 ++++ .../angular/src/app/api/User/user.service.ts | 27 +++++++++- src/main/angular/src/app/app.component.html | 8 +++ src/main/angular/src/app/app.routes.ts | 2 + .../email-confirmation.component.ts | 2 - .../src/app/pages/login/login.component.html | 24 +++++++++ .../src/app/pages/login/login.component.less | 7 +++ .../src/app/pages/login/login.component.ts | 48 +++++++++++++++++ .../app/pages/profile/profile.component.html | 15 ++++++ .../app/pages/profile/profile.component.ts | 40 +++++--------- src/main/angular/src/styles/button.less | 1 + src/main/java/de/ph87/tools/user/User.java | 4 ++ .../de/ph87/tools/user/UserController.java | 27 ++++++---- .../java/de/ph87/tools/user/UserService.java | 52 +++++++++++++------ .../tools/user/requests/UserLoginRequest.java | 2 +- 16 files changed, 211 insertions(+), 64 deletions(-) create mode 100644 src/main/angular/src/app/api/User/requests/UserLoginRequest.ts create mode 100644 src/main/angular/src/app/pages/login/login.component.html create mode 100644 src/main/angular/src/app/pages/login/login.component.less create mode 100644 src/main/angular/src/app/pages/login/login.component.ts diff --git a/src/main/angular/src/app/api/User/UserPrivate.ts b/src/main/angular/src/app/api/User/UserPrivate.ts index bece487..33cc9f7 100644 --- a/src/main/angular/src/app/api/User/UserPrivate.ts +++ b/src/main/angular/src/app/api/User/UserPrivate.ts @@ -1,12 +1,6 @@ import {validateBoolean, validateDate, validateString} from "../common/validators"; import {UserPublic} from "./UserPublic"; -export enum EmailStatus { - NOT_SET = 'NOT_SET', - CONFIRMATION_NEEDED = 'CONFIRMATION_NEEDED', - CONFIRMED = 'CONFIRMED', -} - export class UserPrivate extends UserPublic { constructor( diff --git a/src/main/angular/src/app/api/User/requests/UserLoginRequest.ts b/src/main/angular/src/app/api/User/requests/UserLoginRequest.ts new file mode 100644 index 0000000..a742eab --- /dev/null +++ b/src/main/angular/src/app/api/User/requests/UserLoginRequest.ts @@ -0,0 +1,10 @@ +export class UserLoginRequest { + + constructor( + readonly username: string, + readonly password: string, + ) { + // - + } + +} diff --git a/src/main/angular/src/app/api/User/user.service.ts b/src/main/angular/src/app/api/User/user.service.ts index a908bc5..7707d4f 100644 --- a/src/main/angular/src/app/api/User/user.service.ts +++ b/src/main/angular/src/app/api/User/user.service.ts @@ -12,6 +12,7 @@ import {GroupDeletedEvent} from "../group/events/GroupDeletedEvent"; import {GroupLeftEvent} from "../group/events/GroupLeftEvent"; import {UserLogoutEvent} from "./events/UserLogoutEvent"; import {validateBoolean} from "../common/validators"; +import {UserLoginRequest} from "./requests/UserLoginRequest"; function userPushMessageFromJson(json: any): object { const type = json['_type_']; @@ -60,7 +61,22 @@ export class UserService { } private fetchUser() { - this.api.getSingle(['User', 'whoAmI'], UserPrivate.fromJson, user => this.setUser(user)); + this.api.getSingle(['User', 'whoAmI'], UserPrivate.fromJsonOrNull, user => this.setUser(user)); + } + + login(username: string, password: string) { + const data = new UserLoginRequest(username, password); + this.api.postSingle(['User', 'login'], data, UserPrivate.fromJson, user => { + this.setUser(user); + this.gotoProfile(); + }); + } + + logout() { + this.api.getNone(['User', 'logout'], () => { + this.setUser(null); + this.gotoLogin(); + }); } private setUser(user: UserPrivate | null) { @@ -118,4 +134,13 @@ export class UserService { ) .subscribe(next); } + + gotoLogin() { + this.router.navigate(['Login']); + } + + gotoProfile() { + this.router.navigate(['Profile']); + } + } diff --git a/src/main/angular/src/app/app.component.html b/src/main/angular/src/app/app.component.html index 1b0d004..4d4c3af 100644 --- a/src/main/angular/src/app/app.component.html +++ b/src/main/angular/src/app/app.component.html @@ -2,6 +2,7 @@ + diff --git a/src/main/angular/src/app/app.routes.ts b/src/main/angular/src/app/app.routes.ts index 56129d6..16d5ff8 100644 --- a/src/main/angular/src/app/app.routes.ts +++ b/src/main/angular/src/app/app.routes.ts @@ -8,6 +8,7 @@ import {GroupsComponent} from "./pages/group/groups/groups.component"; import {GroupComponent} from "./pages/group/group/group.component"; import {NumbersComponent} from "./pages/tools/numbers/numbers.component"; import {EmailConfirmationComponent} from "./pages/email-confirmation/email-confirmation.component"; +import {LoginComponent} from "./pages/login/login.component"; export const routes: Routes = [ {path: 'SolarSystemPrintout', component: SolarSystemPrintoutComponent}, @@ -22,6 +23,7 @@ export const routes: Routes = [ {path: 'User/:publicUuid', component: UserComponent}, + {path: 'Login', component: LoginComponent}, {path: 'Profile', component: ProfileComponent}, {path: 'emailConfirmation/:emailConfirmation', component: EmailConfirmationComponent}, diff --git a/src/main/angular/src/app/pages/email-confirmation/email-confirmation.component.ts b/src/main/angular/src/app/pages/email-confirmation/email-confirmation.component.ts index 86847cc..f46e595 100644 --- a/src/main/angular/src/app/pages/email-confirmation/email-confirmation.component.ts +++ b/src/main/angular/src/app/pages/email-confirmation/email-confirmation.component.ts @@ -4,7 +4,6 @@ import {ActivatedRoute, Router} from "@angular/router"; import {GroupService} from "../../api/group/group.service"; import {UserService} from "../../api/User/user.service"; import {NgIf} from "@angular/common"; -import {EmailStatus} from "../../api/User/UserPrivate"; @Component({ selector: 'app-email-confirmation', @@ -46,5 +45,4 @@ export class EmailConfirmationComponent implements OnInit, OnDestroy { this.subs.length = 0; } - protected readonly EmailStatus = EmailStatus; } diff --git a/src/main/angular/src/app/pages/login/login.component.html b/src/main/angular/src/app/pages/login/login.component.html new file mode 100644 index 0000000..27b20ce --- /dev/null +++ b/src/main/angular/src/app/pages/login/login.component.html @@ -0,0 +1,24 @@ +
+
+
+
+ Login +
+
+ +
Benutzername
+ + +
Passwort
+ + +
+
+
+
Login
+
+ +
+
+
+
diff --git a/src/main/angular/src/app/pages/login/login.component.less b/src/main/angular/src/app/pages/login/login.component.less new file mode 100644 index 0000000..f7b675b --- /dev/null +++ b/src/main/angular/src/app/pages/login/login.component.less @@ -0,0 +1,7 @@ +input { + width: 100%; +} + +.button { + width: 100%; +} diff --git a/src/main/angular/src/app/pages/login/login.component.ts b/src/main/angular/src/app/pages/login/login.component.ts new file mode 100644 index 0000000..36f70cf --- /dev/null +++ b/src/main/angular/src/app/pages/login/login.component.ts @@ -0,0 +1,48 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {UserService} from "../../api/User/user.service"; +import {Subscription} from "rxjs"; +import {UserPrivate} from "../../api/User/UserPrivate"; +import {FormsModule} from "@angular/forms"; + +@Component({ + selector: 'app-login', + standalone: true, + imports: [ + FormsModule, + ], + templateUrl: './login.component.html', + styleUrl: './login.component.less' +}) +export class LoginComponent implements OnInit, OnDestroy { + + private readonly subs: Subscription[] = []; + + protected username: string = ''; + + protected password: string = ''; + + constructor( + protected readonly userService: UserService, + ) { + // - + } + + ngOnInit(): void { + this.subs.push(this.userService.subscribePush(UserPrivate, user => { + if (user !== null) { + this.userService.gotoProfile(); + } + })); + } + + ngOnDestroy(): void { + this.subs.forEach(sub => sub.unsubscribe()); + this.subs.length = 0; + } + + login() { + this.userService.login(this.username, this.password); + } + + protected readonly alert = alert; +} diff --git a/src/main/angular/src/app/pages/profile/profile.component.html b/src/main/angular/src/app/pages/profile/profile.component.html index af13952..2f94d16 100644 --- a/src/main/angular/src/app/pages/profile/profile.component.html +++ b/src/main/angular/src/app/pages/profile/profile.component.html @@ -47,6 +47,21 @@ +
+
+
+ Ausloggen +
+
+
+
+ Ausloggen +
+
+
+
+
+
diff --git a/src/main/angular/src/app/pages/profile/profile.component.ts b/src/main/angular/src/app/pages/profile/profile.component.ts index 5c92ee9..887f989 100644 --- a/src/main/angular/src/app/pages/profile/profile.component.ts +++ b/src/main/angular/src/app/pages/profile/profile.component.ts @@ -1,13 +1,10 @@ -import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; import {NgForOf, NgIf} from "@angular/common"; import {UserService} from "../../api/User/user.service"; import {FormsModule} from "@angular/forms"; import {TextComponent} from "../../shared/text/text.component"; import {GroupListComponent} from "../group/shared/group-list/group-list.component"; -import {Group} from "../../api/group/Group"; -import {Subscription} from "rxjs"; import {GroupService} from "../../api/group/group.service"; -import {EmailStatus, UserPrivate} from "../../api/User/UserPrivate"; const USER_NAME_MIN_LENGTH = 2; @@ -26,22 +23,18 @@ const USER_PASSWORD_MIN_LENGTH = 10; templateUrl: './profile.component.html', styleUrl: './profile.component.less' }) -export class ProfileComponent implements OnInit, OnDestroy { +export class ProfileComponent { protected readonly USER_NAME_MIN_LENGTH = USER_NAME_MIN_LENGTH; protected readonly USER_PASSWORD_MIN_LENGTH = USER_PASSWORD_MIN_LENGTH; - private readonly subs: Subscription[] = []; - protected password0: string = ""; protected password1: string = ""; protected email: string = ""; - protected groups: Group[] = []; - @ViewChild('p1') p1!: ElementRef; @@ -52,22 +45,6 @@ export class ProfileComponent implements OnInit, OnDestroy { // - } - ngOnInit(): void { - this.updateGroupList(); - this.subs.push(this.userService.subscribePush(UserPrivate, _ => { - this.updateGroupList(); - })); - } - - ngOnDestroy(): void { - this.subs.forEach(sub => sub.unsubscribe()); - this.subs.length = 0; - } - - private updateGroupList() { - this.groupService.findAllJoined(groups => this.groups = groups); - } - protected nameValidator(name: string): boolean { return name.length >= USER_NAME_MIN_LENGTH && !/\s+|^[^a-zA-Z0-9]+$/.test(name); } @@ -117,5 +94,16 @@ export class ProfileComponent implements OnInit, OnDestroy { }); } - protected readonly EmailStatus = EmailStatus; + logout() { + if (!this.userService.user?.password) { + if (!confirm("Du hast kein Passwort gesetzt. Du wirst Dich nicht wieder einloggen können. Trotzdem abmelden?")) { + return; + } + if (!confirm("Wirklich sicher?")) { + return; + } + } + this.userService.logout() + } + } diff --git a/src/main/angular/src/styles/button.less b/src/main/angular/src/styles/button.less index 341780b..dacbc67 100644 --- a/src/main/angular/src/styles/button.less +++ b/src/main/angular/src/styles/button.less @@ -5,6 +5,7 @@ .button { float: left; + text-align: center; margin-left: @quarterSpace; margin-right: @quarterSpace; padding: @halfSpace; diff --git a/src/main/java/de/ph87/tools/user/User.java b/src/main/java/de/ph87/tools/user/User.java index 82ebf59..c9a431f 100644 --- a/src/main/java/de/ph87/tools/user/User.java +++ b/src/main/java/de/ph87/tools/user/User.java @@ -98,6 +98,10 @@ public class User extends UserPublicAbstract { this.password = passwordEncoder.encode(password); } + public boolean verifyPassword(@NonNull final PasswordEncoder passwordEncoder, @NonNull final String password) { + return passwordEncoder.matches(password, this.password); + } + public void setEmail(@NonNull final String email) { if (this.email.equals(email)) { return; diff --git a/src/main/java/de/ph87/tools/user/UserController.java b/src/main/java/de/ph87/tools/user/UserController.java index 7dc20d0..020e74f 100644 --- a/src/main/java/de/ph87/tools/user/UserController.java +++ b/src/main/java/de/ph87/tools/user/UserController.java @@ -21,8 +21,8 @@ public class UserController { @Nullable @GetMapping("whoAmI") - public UserPrivateDto whoAmI(@Nullable final UserPrivateUuid userUuid, @NonNull final HttpServletResponse response) { - return userService.whoAmI(userUuid, response); + public UserPrivateDto whoAmI(@Nullable final UserPrivateUuid userPrivateUuid, @NonNull final HttpServletResponse response) { + return userService.whoAmI(userPrivateUuid, response); } @NonNull @@ -32,27 +32,32 @@ public class UserController { } @Nullable - @GetMapping("login") + @PostMapping("login") public UserPrivateDto login(@NonNull @RequestBody final UserLoginRequest loginRequest, @NonNull final HttpServletResponse response) { return userService.login(loginRequest, response); } + @GetMapping("logout") + public void logout(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final HttpServletResponse response) { + userService.logout(userPrivateUuid, response); + } + @NonNull @PostMapping("changeName") - public UserPrivateDto changeName(@NonNull final UserPrivateUuid userUuid, @NonNull @RequestBody final String name) { - return userService.changeName(userUuid, name); + public UserPrivateDto changeName(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull @RequestBody final String name) { + return userService.changeName(userPrivateUuid, name); } @NonNull @PostMapping("changePassword") - public UserPrivateDto changePassword(@NonNull final UserPrivateUuid userUuid, @NonNull @RequestBody final String password) { - return userService.changePassword(userUuid, password); + public UserPrivateDto changePassword(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull @RequestBody final String password) { + return userService.changePassword(userPrivateUuid, password); } @NonNull @PostMapping("changeEmail") - public UserPrivateDto changeEmail(@NonNull final UserPrivateUuid userUuid, @NonNull @RequestBody final String email) { - return userService.changeEmail(userUuid, email); + public UserPrivateDto changeEmail(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull @RequestBody final String email) { + return userService.changeEmail(userPrivateUuid, email); } @PostMapping("confirmEmail") @@ -61,8 +66,8 @@ public class UserController { } @GetMapping("delete") - public void delete(@NonNull final UserPrivateUuid userUuid, @NonNull final HttpServletResponse response) { - userService.delete(userUuid, response); + public void delete(@NonNull final UserPrivateUuid userPrivateUuid, @NonNull final HttpServletResponse response) { + userService.delete(userPrivateUuid, response); } } diff --git a/src/main/java/de/ph87/tools/user/UserService.java b/src/main/java/de/ph87/tools/user/UserService.java index ba68e7d..8c4708b 100644 --- a/src/main/java/de/ph87/tools/user/UserService.java +++ b/src/main/java/de/ph87/tools/user/UserService.java @@ -60,15 +60,14 @@ public class UserService { private final EmailService emailService; - @NonNull - public UserPrivateDto login(@NonNull final UserLoginRequest loginRequest, @NonNull final HttpServletResponse response) { - final User user = userRepository.findByName(loginRequest.name).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); - if (passwordEncoder.matches(loginRequest.password, user.getPassword())) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST); + @Nullable + public UserPrivateDto whoAmI(@Nullable final UserPrivateUuid privateUuid, @NonNull final HttpServletResponse response) { + if (privateUuid == null) { + return null; } - user.touch(); + final User user = userRepository.findByPrivateUuid(privateUuid.uuid).orElse(null); writeUserUuidCookie(response, user); - return new UserPrivateDto(user); + return UserPrivateDto.orNull(user); } @NonNull @@ -84,19 +83,38 @@ public class UserService { return user; } - @Nullable - public UserPrivateDto whoAmI(@Nullable final UserPrivateUuid privateUuid, @NonNull final HttpServletResponse response) { - if (privateUuid == null) { - return null; + @NonNull + public UserPrivateDto login(@NonNull final UserLoginRequest loginRequest, @NonNull final HttpServletResponse response) { + final Optional userOptional = userRepository.findByName(loginRequest.username); + if (userOptional.isEmpty()) { + log.warn("Login failed: Unknown user: username={}", loginRequest.username); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST); } - final User user = userRepository.findByPrivateUuid(privateUuid.uuid).orElse(null); + final User user = userOptional.get(); + if (user.getPassword().isEmpty()) { + log.warn("Login failed: No password set: user={}", user); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST); + } + if (!user.verifyPassword(passwordEncoder, loginRequest.password)) { + log.warn("Login failed: Wrong password: user={}", user); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST); + } + user.touch(); writeUserUuidCookie(response, user); - return UserPrivateDto.orNull(user); + log.info("Login successful: user={}", user); + return new UserPrivateDto(user); + } + + public void logout(@NonNull final UserPrivateUuid privateUuid, @NonNull final HttpServletResponse response) { + modify(privateUuid, user -> { + writeUserUuidCookie(response, null); + log.info("Logout successful: user={}", user); + }); } @NonNull public UserPrivateDto changeName(@NonNull final UserPrivateUuid privateUuid, @NonNull final String name) { - return modifyToDto(privateUuid, user -> { + return modify(privateUuid, user -> { if (user.getName().equals(name)) { return; } @@ -121,7 +139,7 @@ public class UserService { @NonNull public UserPrivateDto changePassword(@NonNull final UserPrivateUuid privateUuid, @NonNull final String password) { - return modifyToDto(privateUuid, user -> { + return modify(privateUuid, user -> { if (password.length() < PASSWORD_MIN_LENGTH) { log.warn("Cannot change User password: too short: length={}/{}, user={}", password.length(), PASSWORD_MIN_LENGTH, user); throw new ResponseStatusException(HttpStatus.BAD_REQUEST); @@ -137,7 +155,7 @@ public class UserService { @NonNull public UserPrivateDto changeEmail(@NonNull final UserPrivateUuid privateUuid, @NonNull final String email) { - return modifyToDto(privateUuid, user -> { + return modify(privateUuid, user -> { if (!isEmailValid(email)) { log.warn("Cannot change User email: not valid, user={}", user); throw new ResponseStatusException(HttpStatus.BAD_REQUEST); @@ -192,7 +210,7 @@ public class UserService { } @NonNull - private UserPrivateDto modifyToDto(@NonNull final UserPrivateUuid privateUuid, @NonNull Consumer modifier) { + private UserPrivateDto modify(@NonNull final UserPrivateUuid privateUuid, @NonNull Consumer modifier) { final User user = userAccessService.access(privateUuid); modifier.accept(user); return push(user); diff --git a/src/main/java/de/ph87/tools/user/requests/UserLoginRequest.java b/src/main/java/de/ph87/tools/user/requests/UserLoginRequest.java index 3cedd33..47a87f8 100644 --- a/src/main/java/de/ph87/tools/user/requests/UserLoginRequest.java +++ b/src/main/java/de/ph87/tools/user/requests/UserLoginRequest.java @@ -9,7 +9,7 @@ import lombok.ToString; @RequiredArgsConstructor public class UserLoginRequest { - public final String name; + public final String username; public final String password;