diff --git a/.idea/.idea.WebDesktop 2.0/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.WebDesktop 2.0/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..8590396 --- /dev/null +++ b/.idea/.idea.WebDesktop 2.0/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.WebDesktop 2.0/.idea/watcherTasks.xml b/.idea/.idea.WebDesktop 2.0/.idea/watcherTasks.xml new file mode 100644 index 0000000..fb0d65a --- /dev/null +++ b/.idea/.idea.WebDesktop 2.0/.idea/watcherTasks.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml b/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml index 5a154ff..c3c5ee6 100644 --- a/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml +++ b/.idea/.idea.WebDesktop 2.0/.idea/workspace.xml @@ -6,10 +6,30 @@ - + + + + + + + - + + + + + + + + + + + + + + + @@ -48,6 +69,7 @@ "RunOnceActivity.ShowReadmeOnStart": "true", "SHARE_PROJECT_CONFIGURATION_FILES": "true", "WebServerToolWindowFactoryState": "false", + "list.type.of.created.stylesheet": "SCSS", "nodejs_package_manager_path": "npm", "settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings", "ts.external.directory.path": "D:\\Programmierstuff\\Projekte\\WebDesktop 2.0\\Frontend\\node_modules\\typescript\\lib", @@ -130,6 +152,7 @@ + diff --git a/Backend/Controllers/UserController.cs b/Backend/Controllers/UserController.cs index 3ef2297..aee8c6b 100644 --- a/Backend/Controllers/UserController.cs +++ b/Backend/Controllers/UserController.cs @@ -36,7 +36,6 @@ public class UserController : ControllerBase { } [HttpGet("token")] - [Authorized] public ActionResult GetToken() { return this.FromLogicResult(_logic.GenerateToken(_logic.GetCurrentUserRefreshToken())); } diff --git a/Backend/appsettings.json b/Backend/appsettings.json index cf43216..9a5d7af 100644 --- a/Backend/appsettings.json +++ b/Backend/appsettings.json @@ -33,12 +33,12 @@ ], "Messages": { "Users": { - "NotFound": "This user does not exist", - "InvalidEditData": "Userdata does not match security rules", - "InvalidRegisterData": "Userdata does not match security rules", - "WrongPassword": "Wrong password", - "UsernameOrEmailExist": "This username or email already exist", - "InvalidRefreshToken": "Invalid RefreshToken" + "NotFound": "Dieser Benutzer existiert nicht!", + "InvalidEditData": "Benutzerdaten entsprechen nicht den Sicherheitsvorschritfen!", + "InvalidRegisterData": "Benutzerdaten entsprechen nicht den Sicherheitsvorschritfen!", + "WrongPassword": "Falsches Passwort!", + "UsernameOrEmailExist": "Der Benutzername oder die E-Mail existieren bereits", + "InvalidRefreshToken": "Dein RefreshToken ist abgelaufen!" } } } diff --git a/Frontend/angular.json b/Frontend/angular.json index 196db0b..d5e372d 100644 --- a/Frontend/angular.json +++ b/Frontend/angular.json @@ -31,6 +31,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.scss" ], "scripts": [] @@ -99,6 +100,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.scss" ], "scripts": [] diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 84cb63b..88c479d 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -9,10 +9,12 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "~13.1.0", + "@angular/cdk": "^13.3.9", "@angular/common": "~13.1.0", "@angular/compiler": "~13.1.0", "@angular/core": "~13.1.0", "@angular/forms": "~13.1.0", + "@angular/material": "^13.3.9", "@angular/platform-browser": "~13.1.0", "@angular/platform-browser-dynamic": "~13.1.0", "@angular/router": "~13.1.0", @@ -347,6 +349,28 @@ "@angular/core": "13.1.3" } }, + "node_modules/@angular/cdk": { + "version": "13.3.9", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.9.tgz", + "integrity": "sha512-XCuCbeuxWFyo3EYrgEYx7eHzwl76vaWcxtWXl00ka8d+WAOtMQ6Tf1D98ybYT5uwF9889fFpXAPw98mVnlo3MA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^5.0.0" + }, + "peerDependencies": { + "@angular/common": "^13.0.0 || ^14.0.0-0", + "@angular/core": "^13.0.0 || ^14.0.0-0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cdk/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true + }, "node_modules/@angular/cli": { "version": "13.1.4", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.1.4.tgz", @@ -472,6 +496,23 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "13.3.9", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.9.tgz", + "integrity": "sha512-FU8lcMgo+AL8ckd27B4V097ZPoIZNRHiCe3wpgkImT1qC0YwcyXZVn0MqQTTFSdC9a/aI8wPm3AbTClJEVw5Vw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^13.0.0 || ^14.0.0-0", + "@angular/cdk": "13.3.9", + "@angular/common": "^13.0.0 || ^14.0.0-0", + "@angular/core": "^13.0.0 || ^14.0.0-0", + "@angular/forms": "^13.0.0 || ^14.0.0-0", + "@angular/platform-browser": "^13.0.0 || ^14.0.0-0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "13.1.3", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.1.3.tgz", @@ -11779,6 +11820,23 @@ "tslib": "^2.3.0" } }, + "@angular/cdk": { + "version": "13.3.9", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.9.tgz", + "integrity": "sha512-XCuCbeuxWFyo3EYrgEYx7eHzwl76vaWcxtWXl00ka8d+WAOtMQ6Tf1D98ybYT5uwF9889fFpXAPw98mVnlo3MA==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true + } + } + }, "@angular/cli": { "version": "13.1.4", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.1.4.tgz", @@ -11857,6 +11915,14 @@ "tslib": "^2.3.0" } }, + "@angular/material": { + "version": "13.3.9", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.9.tgz", + "integrity": "sha512-FU8lcMgo+AL8ckd27B4V097ZPoIZNRHiCe3wpgkImT1qC0YwcyXZVn0MqQTTFSdC9a/aI8wPm3AbTClJEVw5Vw==", + "requires": { + "tslib": "^2.3.0" + } + }, "@angular/platform-browser": { "version": "13.1.3", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.1.3.tgz", diff --git a/Frontend/package.json b/Frontend/package.json index a61f27a..9a282d6 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -11,10 +11,12 @@ "private": true, "dependencies": { "@angular/animations": "~13.1.0", + "@angular/cdk": "^13.3.9", "@angular/common": "~13.1.0", "@angular/compiler": "~13.1.0", "@angular/core": "~13.1.0", "@angular/forms": "~13.1.0", + "@angular/material": "^13.3.9", "@angular/platform-browser": "~13.1.0", "@angular/platform-browser-dynamic": "~13.1.0", "@angular/router": "~13.1.0", @@ -36,4 +38,4 @@ "karma-jasmine-html-reporter": "~1.7.0", "typescript": "~4.5.2" } -} +} \ No newline at end of file diff --git a/Frontend/src/app/app-routing.module.ts b/Frontend/src/app/app-routing.module.ts index b84ff41..8ca5f12 100644 --- a/Frontend/src/app/app-routing.module.ts +++ b/Frontend/src/app/app-routing.module.ts @@ -1,9 +1,11 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import {LoginComponent} from "./sites/login/login.component"; +import {RegisterComponent} from "./sites/register/register.component"; const routes: Routes = [ - {path: "login", component: LoginComponent} + {path: "login", component: LoginComponent}, + {path: "register", component: RegisterComponent} ]; @NgModule({ diff --git a/Frontend/src/app/app.component.ts b/Frontend/src/app/app.component.ts index 6169758..714aa7d 100644 --- a/Frontend/src/app/app.component.ts +++ b/Frontend/src/app/app.component.ts @@ -22,6 +22,7 @@ export class AppComponent implements OnInit { if (await this.backend.requestToken()) this.loaded = true; else await this.router.navigate(["login"]); + this.loaded = true; }, 0); } } diff --git a/Frontend/src/app/app.module.ts b/Frontend/src/app/app.module.ts index e025c90..81fee20 100644 --- a/Frontend/src/app/app.module.ts +++ b/Frontend/src/app/app.module.ts @@ -5,16 +5,30 @@ import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import {HttpClientModule} from "@angular/common/http"; import { LoginComponent } from './sites/login/login.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatButtonModule} from "@angular/material/button"; +import {MatDividerModule} from "@angular/material/divider"; +import { RegisterComponent } from './sites/register/register.component'; +import {ReactiveFormsModule} from "@angular/forms"; @NgModule({ declarations: [ AppComponent, - LoginComponent + LoginComponent, + RegisterComponent ], imports: [ BrowserModule, AppRoutingModule, - HttpClientModule + HttpClientModule, + BrowserAnimationsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatDividerModule, + ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] diff --git a/Frontend/src/app/entitys/user.ts b/Frontend/src/app/entitys/user.ts index 600b416..fbbee08 100644 --- a/Frontend/src/app/entitys/user.ts +++ b/Frontend/src/app/entitys/user.ts @@ -1,6 +1,6 @@ export interface User extends UserEditor { id: string; - created: Date; + created: string; } export interface UserLogin { diff --git a/Frontend/src/app/services/backend.service.ts b/Frontend/src/app/services/backend.service.ts index 97f4361..837af99 100644 --- a/Frontend/src/app/services/backend.service.ts +++ b/Frontend/src/app/services/backend.service.ts @@ -74,7 +74,7 @@ export class BackendService { } } - return {content: undefined, success: false, code: error.status, message: error.error.title}; + return {content: undefined, success: false, code: error.status, message: error.error}; } } diff --git a/Frontend/src/app/services/users.service.ts b/Frontend/src/app/services/users.service.ts index 1a7a8dd..f30177d 100644 --- a/Frontend/src/app/services/users.service.ts +++ b/Frontend/src/app/services/users.service.ts @@ -32,8 +32,8 @@ export class UserApi { return response.success; } - public async getUserPermissions(id: string, includeGroupPermissions: boolean = true): Promise { - const response = await this.backend.sendRequest(RequestTypes.GET, "users/" + id + "/permissions" + (includeGroupPermissions ? "/raw" : ""), undefined, {authorized: true}); + public async getUserPermissions(id: string, excludeGroupPermissions: boolean = false): Promise { + const response = await this.backend.sendRequest(RequestTypes.GET, "users/" + id + "/permissions" + (excludeGroupPermissions ? "/raw" : ""), undefined, {authorized: true}); if (!response.success) return []; return response.content; } @@ -48,7 +48,7 @@ export class UserApi { return response.success; } - public async login(login: UserLogin): Promise<{success: boolean, errorMessage: string}> { + public async login(login: UserLogin): Promise<{success: boolean, errorMessage: string, errorCode: number}> { const response = await this.backend.sendRequest(RequestTypes.PUT, "users/login", login, {withCredentials: true}); if (response.success) { @@ -56,10 +56,10 @@ export class UserApi { await this.getAuthorizedUser(); } - return {success: response.success, errorMessage: response.message}; + return {success: response.success, errorMessage: response.message, errorCode: response.code}; } - public async register(register: UserEditor): Promise<{success: boolean, errorMessage: string}> { + public async register(register: UserEditor): Promise<{success: boolean, errorMessage: string, errorCode: number}> { const response = await this.backend.sendRequest(RequestTypes.POST, "users/register", register, {withCredentials: true}); if (response.success) { @@ -67,7 +67,7 @@ export class UserApi { await this.getAuthorizedUser(); } - return {success: response.success, errorMessage: response.message}; + return {success: response.success, errorMessage: response.message, errorCode: response.code}; } public async logout(id: string): Promise { diff --git a/Frontend/src/app/sites/login/login.component.html b/Frontend/src/app/sites/login/login.component.html index 147cfc4..48f4488 100644 --- a/Frontend/src/app/sites/login/login.component.html +++ b/Frontend/src/app/sites/login/login.component.html @@ -1 +1,24 @@ -

login works!

+ + +
diff --git a/Frontend/src/app/sites/login/login.component.scss b/Frontend/src/app/sites/login/login.component.scss index e69de29..0cdcb67 100644 --- a/Frontend/src/app/sites/login/login.component.scss +++ b/Frontend/src/app/sites/login/login.component.scss @@ -0,0 +1,42 @@ +@use "src/styles"; + +.login { + width: 100vw; + height: 100vh; + overflow: hidden; + background-image: url(styles.$background); + + display: flex; + align-items: center; + justify-content: center; + + form { + padding: 30px; + display: flex; + gap: 5px; + flex-direction: column; + + button[type="submit"] { + width: max-content; + align-self: center; + } + + mat-divider { + margin-top: 10px; + } + } +} + +.error { + position: absolute; + bottom: 120px; + left: 50%; + transform: translateX(-50%); + + background-color: #F00; + padding: 10px; + border-radius: 5px; + opacity: 0; + + transition: 500ms; +} diff --git a/Frontend/src/app/sites/login/login.component.ts b/Frontend/src/app/sites/login/login.component.ts index c74528f..5c19247 100644 --- a/Frontend/src/app/sites/login/login.component.ts +++ b/Frontend/src/app/sites/login/login.component.ts @@ -1,15 +1,45 @@ -import { Component, OnInit } from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; +import {UserApi} from "../../services/users.service"; +import {UserLogin} from "../../entitys/user"; +import {Router} from "@angular/router"; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.scss'] }) -export class LoginComponent implements OnInit { +export class LoginComponent { + @ViewChild('error') error: ElementRef; + public disableLogin: boolean = false; - constructor() { } + constructor(private users: UserApi, private router: Router) { } - ngOnInit(): void { + public async login(form: HTMLFormElement, username: string, password: string) { + if (!form.reportValidity()) return; + + const login: UserLogin = {usernameOrEmail: username, password: password}; + const response = await this.users.login(login); + + if (!response.success) { + this.showError(response.errorMessage) + return; + } + + await this.router.navigate([""]); + } + + public showError(error: string) { + this.disableLogin = true; + this.error.nativeElement.innerText = error; + + this.error.nativeElement.style.opacity = "1"; + this.error.nativeElement.style.bottom = "150px"; + + setTimeout(() => { + this.error.nativeElement.style.opacity = "0"; + this.error.nativeElement.style.bottom = "120px"; + this.disableLogin = false; + }, 5000) } } diff --git a/Frontend/src/app/sites/register/register.component.html b/Frontend/src/app/sites/register/register.component.html new file mode 100644 index 0000000..61c25be --- /dev/null +++ b/Frontend/src/app/sites/register/register.component.html @@ -0,0 +1,57 @@ + + +
diff --git a/Frontend/src/app/sites/register/register.component.scss b/Frontend/src/app/sites/register/register.component.scss new file mode 100644 index 0000000..0cdcb67 --- /dev/null +++ b/Frontend/src/app/sites/register/register.component.scss @@ -0,0 +1,42 @@ +@use "src/styles"; + +.login { + width: 100vw; + height: 100vh; + overflow: hidden; + background-image: url(styles.$background); + + display: flex; + align-items: center; + justify-content: center; + + form { + padding: 30px; + display: flex; + gap: 5px; + flex-direction: column; + + button[type="submit"] { + width: max-content; + align-self: center; + } + + mat-divider { + margin-top: 10px; + } + } +} + +.error { + position: absolute; + bottom: 120px; + left: 50%; + transform: translateX(-50%); + + background-color: #F00; + padding: 10px; + border-radius: 5px; + opacity: 0; + + transition: 500ms; +} diff --git a/Frontend/src/app/sites/register/register.component.ts b/Frontend/src/app/sites/register/register.component.ts new file mode 100644 index 0000000..eaada3e --- /dev/null +++ b/Frontend/src/app/sites/register/register.component.ts @@ -0,0 +1,64 @@ +import {Component, ElementRef, ViewChild} from '@angular/core'; +import {UserApi} from "../../services/users.service"; +import {Router} from "@angular/router"; +import {UserEditor} from "../../entitys/user"; + +@Component({ + selector: 'app-register', + templateUrl: './register.component.html', + styleUrls: ['./register.component.scss'] +}) +export class RegisterComponent { + @ViewChild('error') error: ElementRef; + public disableRegister: boolean = false; + + constructor(private users: UserApi, private router: Router) { } + + public async register(form: HTMLFormElement, register: UserEditor, pwRepeat: string) { + if (!form.reportValidity()) return; + + if (register.password !== pwRepeat) { + this.showError("Passwörter stimmen nicht überein!"); + return; + } + + if (!register.email.includes("@") || !register.email.includes(".")) { + this.showError("Bitte gebe eine gültige E-Mail Adresse ein!"); + return; + } + + if (register.username.includes("@")) { + this.showError("Benutzername enthätlt ungültige Zeichen!"); + return; + } + + if (register.password.length < 8) { + this.showError("Dein Passwort muss mindestens 8 Zeichen lang sein!"); + return; + } + + const response = await this.users.register(register); + + if (!response.success) { + this.showError(response.errorMessage); + return; + } + + await this.router.navigate([""]); + } + + public showError(error: string) { + this.disableRegister = true; + this.error.nativeElement.innerText = error; + + this.error.nativeElement.style.opacity = "1"; + this.error.nativeElement.style.bottom = "150px"; + + setTimeout(() => { + this.error.nativeElement.style.opacity = "0"; + this.error.nativeElement.style.bottom = "120px"; + this.disableRegister = false; + }, 5000) + } + +} diff --git a/Frontend/src/assets/background.png b/Frontend/src/assets/background.png new file mode 100644 index 0000000..69a13d8 Binary files /dev/null and b/Frontend/src/assets/background.png differ diff --git a/Frontend/src/colors.scss b/Frontend/src/colors.scss new file mode 100644 index 0000000..d2f38e9 --- /dev/null +++ b/Frontend/src/colors.scss @@ -0,0 +1,4 @@ +$dark: #0D1321; +$medium: #1D2D44; +$light: #3E5C76; +$text: #FFFFFF; diff --git a/Frontend/src/index.html b/Frontend/src/index.html index b6d7b4e..dfdc8ee 100644 --- a/Frontend/src/index.html +++ b/Frontend/src/index.html @@ -6,8 +6,11 @@ + + + - + diff --git a/Frontend/src/styles.scss b/Frontend/src/styles.scss index 90d4ee0..8bd0938 100644 --- a/Frontend/src/styles.scss +++ b/Frontend/src/styles.scss @@ -1 +1,31 @@ /* You can add global styles to this file, and also import other style files */ +@use "colors"; + +html, body { height: 100%; overflow: hidden; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } + +$background: "/assets/background.png"; + +* { + color: colors.$text; +} + +.glass { + background: rgba(colors.$light, 0.15); + backdrop-filter: blur(10px); + border-radius: 20px; + overflow: hidden; +} + +@mixin mat-select-theme() { + .mat-form-field-appearance-fill .mat-form-field-underline::before { + background-color: colors.$text; + } + + .mat-divider { + border-top-color: colors.$text; + } +} + +@include mat-select-theme(); +