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 @@
Planeten
Kabel
Gruppen
+
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
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
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;