From f92f90781bf5d0353eecefbee168ca0e4eee1fe9 Mon Sep 17 00:00:00 2001 From: garvinkon Date: Wed, 17 Jan 2024 18:10:19 +0100 Subject: [PATCH 1/8] added frontend mask for login and handling for failed login backend part not touched, emailslot hiding after look in need to be added aswell Co-authored-by: Fabian Weber Signed-off-by: garvinkon --- Frontend/src/app/app.component.css | 6 + Frontend/src/app/app.component.html | 5 +- Frontend/src/app/app.component.ts | 8 ++ Frontend/src/app/app.module.ts | 13 +- .../login-dialog/login-dialog.component.ts | 129 ++++++++++++++++++ Frontend/src/app/service/auth.service.ts | 17 +++ 6 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 Frontend/src/app/login-dialog/login-dialog.component.ts create mode 100644 Frontend/src/app/service/auth.service.ts diff --git a/Frontend/src/app/app.component.css b/Frontend/src/app/app.component.css index 7e2673a2..08b8520b 100644 --- a/Frontend/src/app/app.component.css +++ b/Frontend/src/app/app.component.css @@ -1,3 +1,9 @@ +.login-container { + position: absolute; + top: 30px; /* Passen Sie diesen Wert an, um den gewünschten Abstand zu erreichen */ + right: 35px; +} + .cards-container { display: flex; justify-content: space-between; diff --git a/Frontend/src/app/app.component.html b/Frontend/src/app/app.component.html index 2a6f515e..efb54799 100644 --- a/Frontend/src/app/app.component.html +++ b/Frontend/src/app/app.component.html @@ -14,7 +14,10 @@ > {{ title }} - +
diff --git a/Frontend/src/app/app.component.ts b/Frontend/src/app/app.component.ts index 9b5dccbb..6863a868 100644 --- a/Frontend/src/app/app.component.ts +++ b/Frontend/src/app/app.component.ts @@ -5,6 +5,7 @@ import { DragAndDropComponent } from './drag-and-drop/drag-and-drop.component'; import {Ticket} from "./entities/ticket.dto"; import { MatDialog } from '@angular/material/dialog'; import { RequestTypeDialogComponent } from './request-type-dialog/request-type-dialog.component'; +import { LoginDialogComponent } from './login-dialog/login-dialog.component'; interface ChatMessages { messageText: string; @@ -68,6 +69,13 @@ export class AppComponent implements OnInit { this.files = []; } + openLoginDialog() { + const dialogRef = this.dialog.open(LoginDialogComponent); + dialogRef.afterClosed().subscribe(result => { + // logic after closing dialog + }); + } + chooseRequestType() { const dialogRef = this.dialog.open(RequestTypeDialogComponent); dialogRef.afterClosed().subscribe(result => { diff --git a/Frontend/src/app/app.module.ts b/Frontend/src/app/app.module.ts index ba73fa40..22e8f531 100644 --- a/Frontend/src/app/app.module.ts +++ b/Frontend/src/app/app.module.ts @@ -5,17 +5,20 @@ import { AppComponent } from './app.component'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import {MatButtonModule} from "@angular/material/button"; -import {MatInputModule} from "@angular/material/input"; +import { MatButtonModule } from "@angular/material/button"; +import { MatInputModule } from "@angular/material/input"; +import { MatIconModule } from '@angular/material/icon'; import { DragAndDropModule } from './drag-and-drop/drag-and-drop.module'; import { MatCardModule } from '@angular/material/card'; import { MatDialogModule } from '@angular/material/dialog'; import { RequestTypeDialogComponent } from './request-type-dialog/request-type-dialog.component'; +import { LoginDialogComponent } from './login-dialog/login-dialog.component'; @NgModule({ declarations: [ AppComponent, - RequestTypeDialogComponent + RequestTypeDialogComponent, + LoginDialogComponent ], imports: [ BrowserModule, @@ -27,7 +30,9 @@ import { RequestTypeDialogComponent } from './request-type-dialog/request-type-d MatInputModule, DragAndDropModule, MatCardModule, - MatDialogModule + MatDialogModule, + MatInputModule, + MatIconModule, ], providers: [], bootstrap: [AppComponent] diff --git a/Frontend/src/app/login-dialog/login-dialog.component.ts b/Frontend/src/app/login-dialog/login-dialog.component.ts new file mode 100644 index 00000000..db3fd1e9 --- /dev/null +++ b/Frontend/src/app/login-dialog/login-dialog.component.ts @@ -0,0 +1,129 @@ +import { Component } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { AuthService } from '../service/auth.service'; + +@Component({ + selector: 'app-login-dialog', + template: ` + +

Login

+ + +
+ {{ errorMessage }} +
+ + E-Mail + + + + Password + + + +
+ + `, + styles: [` + :host { + --main-bg-color: #313131; + --accent-color: #10ddd3; + --hover-color: #00828f; + --text-color: #fff; + } + + .dialog-title{ + background-color: var(--main-bg-color); + text-align: center; + } + + .dialog-content { + background-color: var(--main-bg-color); + text-align: center; + width: 400px; + } + + .dialog-title { + font-size: 24px; + color: var(--accent-color); + margin-bottom: 20px; + } + + .error-message { + font-size: 16px; + color: red; + margin: 15px; + } + + .dialog-message { + font-size: 16px; + color: var(--text-color); + } + + mat-dialog-content { + display: flex; + flex-direction: column; + } + + .mat-form-field { + margin-bottom: 20px; + } + + .close-button { + position: absolute; + top: 10px; + right: 10px; + color: white; /* oder die Farbe Ihrer Wahl */ + } + + .login-button { + background-color: var(--accent-color); + color: var(--text-color); + font-size: 18px; + text-transform: capitalize; + padding: 10px 20px; + margin: 10px; + margin-top: 20px; + cursor: pointer; + transition: background-color 0.3s ease-in-out; + } + + .login-button:hover { + background-color: var(--hover-color); + } + `] +}) +export class LoginDialogComponent { + email: string = ''; + password: string = ''; + errorMessage: string = ''; + loading: boolean = false; + + constructor(private authService: AuthService, private dialogRef: MatDialogRef) { + dialogRef.disableClose = true; + } + closeDialog() { + this.dialogRef.close(); + } + + login() { + this.loading = true; + this.authService.login(this.email, this.password).subscribe( + response => { + + this.loading = false; + if (response.success) { + this.dialogRef.close({ email: this.email }); + } else { + this.errorMessage = 'Login data not correct.'; + } + }, + error => { + this.loading = false; + this.errorMessage = 'An error has occurred. Please try again later.'; + } + ); + } +} diff --git a/Frontend/src/app/service/auth.service.ts b/Frontend/src/app/service/auth.service.ts new file mode 100644 index 00000000..2606623c --- /dev/null +++ b/Frontend/src/app/service/auth.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { delay } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + constructor() { } + + login(email: string, password: string): Observable<{success: boolean, message?: string}> { + // here Http-Request to Backend + // simulation of network delay 1s + return of({success: password === "dummy"}).pipe(delay(1000)); + } +} From 5ab929a1d27fe46c2be57afa734999de7ec4513f Mon Sep 17 00:00:00 2001 From: garvinkon Date: Thu, 18 Jan 2024 02:15:04 +0100 Subject: [PATCH 2/8] added backend endpoint, creating token for a session and let the user login and save his email. tests are missing, and frontend part to use the login is missing aswell, currently nothing happens when u log in Co-authored-by: Irild Hoxhallari Signed-off-by: garvinkon --- Backend/app/api/v1/auth_api.py | 58 +++++++++++++++++++++++ Backend/app/main.py | 4 ++ Backend/app/repository/user_repository.py | 5 ++ Backend/requirements.txt | 3 +- Frontend/src/app/service/auth.service.ts | 47 +++++++++++++++--- 5 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 Backend/app/api/v1/auth_api.py diff --git a/Backend/app/api/v1/auth_api.py b/Backend/app/api/v1/auth_api.py new file mode 100644 index 00000000..4d380d5b --- /dev/null +++ b/Backend/app/api/v1/auth_api.py @@ -0,0 +1,58 @@ +import os +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, HTTPException, Response +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm +from app.repository.user_repository import UserRepository +from app.dependency.repository import get_user_repository +from datetime import datetime, timedelta +from jose import jwt + +router = APIRouter() +load_dotenv() +SECRET_KEY = os.getenv("SECRET_KEY") +ALGORITHM = "HS256" + + +def create_access_token(data: dict, expires_delta: timedelta = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta( + minutes=15 + ) # Standardablaufzeit: 15 Minuten + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + + +@router.post("/token") +async def login_for_access_token( + response: Response, + form_data: OAuth2PasswordRequestForm = Depends(), + user_repo: UserRepository = Depends(get_user_repository), +): + # check userdata + is_authenticated = user_repo.authenticate_user( + email=form_data.username, password=form_data.password + ) + if not is_authenticated: + raise HTTPException( + status_code=400, + detail="Incorrect email or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + # read user data + user_data = user_repo.read_users_by_email(email=form_data.username) + if not user_data: + raise HTTPException(status_code=400, detail="User not found") + + # take first element, because email_adresse should be unique + user = user_data[0] + + access_token_expires = timedelta(minutes=60) + access_token = create_access_token( + data={"sub": user["email_address"]}, expires_delta=access_token_expires + ) + response.set_cookie(key="access_token", value=access_token, httponly=True) + return {"access_token": access_token, "token_type": "bearer"} diff --git a/Backend/app/main.py b/Backend/app/main.py index 733be7bd..4f90440a 100644 --- a/Backend/app/main.py +++ b/Backend/app/main.py @@ -2,6 +2,7 @@ from fastapi.middleware.cors import CORSMiddleware from app.api.v1 import ticket_api +from app.api.v1 import auth_api from app.dependency.collection import get_user_collection, get_service_collection from app.dependency.repository import get_user_repository, get_service_repository from app.repository.user_repository import UserRepository @@ -29,6 +30,9 @@ # Include the router from the text_endpoint module app.include_router(ticket_api.router, prefix="/api/v1") +# Include the router from the auth_endpoint module +app.include_router(auth_api.router, prefix="/api/v1") + @app.on_event("startup") async def startup_event(): diff --git a/Backend/app/repository/user_repository.py b/Backend/app/repository/user_repository.py index eec2dd42..b88a382d 100644 --- a/Backend/app/repository/user_repository.py +++ b/Backend/app/repository/user_repository.py @@ -32,3 +32,8 @@ def read_users_by_email(self, email: str) -> list[UserEntity]: logger.info(f"Reading user(s) with email {email} from the database...") users = list(self.collection.find({"email_address": email})) return users if users else [] + + def authenticate_user(self, email: str, password: str) -> bool: + logger.info(f"Authenticating user with email {email}...") + user = self.collection.find_one({"email_address": email, "password": password}) + return user is not None diff --git a/Backend/requirements.txt b/Backend/requirements.txt index 0847781c..05253dd3 100644 --- a/Backend/requirements.txt +++ b/Backend/requirements.txt @@ -13,4 +13,5 @@ fastapi-utils==0.2.1 requests==2.31.0 python-multipart==0.0.6 beautifulsoup4==4.12.2 -openai==1.7.2 \ No newline at end of file +openai==1.7.2 +python-jose==3.3.0 \ No newline at end of file diff --git a/Frontend/src/app/service/auth.service.ts b/Frontend/src/app/service/auth.service.ts index 2606623c..2448132b 100644 --- a/Frontend/src/app/service/auth.service.ts +++ b/Frontend/src/app/service/auth.service.ts @@ -1,17 +1,50 @@ import { Injectable } from '@angular/core'; -import { Observable, of } from 'rxjs'; -import { delay } from 'rxjs/operators'; +import { HttpClient } from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { tap, catchError } from 'rxjs/operators'; +import {LogService} from './logging.service'; +import {environment} from "../../environments/environment"; +import { of } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthService { + private apiUrl = environment.apiUrl + 'api/v1/token'; - constructor() { } + constructor(private http: HttpClient, private logger: LogService) {} - login(email: string, password: string): Observable<{success: boolean, message?: string}> { - // here Http-Request to Backend - // simulation of network delay 1s - return of({success: password === "dummy"}).pipe(delay(1000)); + login(email: string, password: string): Observable { + const loginData = new FormData(); + loginData.append('username', email); + loginData.append('password', password); + + return this.http.post<{ access_token: string }>(this.apiUrl, loginData) + .pipe( + tap((response: { access_token: string }) => { + // Speichern des Zugriffstokens + localStorage.setItem('access_token', response.access_token); + this.logger.log('Login successful:'+ response); + this.checkLoginStatus(); + }), + catchError((error: any) => { + // Fehlerbehandlung + let errorMessage = 'An error has occurred. Please try again later.'; + if (error.status === 400) { + errorMessage = 'Login data not correct.'; + } + this.logger.error('Login failed:'+ error); + return of({ error: true, message: errorMessage }); + }) + ); + } + + checkLoginStatus(): void { + const token = localStorage.getItem('access_token'); + if (token) { + this.logger.log('Token successful: ' + token); // Token-Informationen loggen + } else { + this.logger.log('No token found, user is not logged in.'); + } } } From 96385760e2b5a34882384a96f8398fe3de206009 Mon Sep 17 00:00:00 2001 From: Irild Hoxhallari Date: Thu, 18 Jan 2024 18:09:20 +0100 Subject: [PATCH 3/8] added success boolean to login response Signed-off-by: Irild Hoxhallari --- Backend/app/api/v1/auth_api.py | 2 +- Frontend/src/app/login-dialog/login-dialog.component.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Backend/app/api/v1/auth_api.py b/Backend/app/api/v1/auth_api.py index 4d380d5b..ebd89fc5 100644 --- a/Backend/app/api/v1/auth_api.py +++ b/Backend/app/api/v1/auth_api.py @@ -55,4 +55,4 @@ async def login_for_access_token( data={"sub": user["email_address"]}, expires_delta=access_token_expires ) response.set_cookie(key="access_token", value=access_token, httponly=True) - return {"access_token": access_token, "token_type": "bearer"} + return {"access_token": access_token, "token_type": "bearer", "success": True} diff --git a/Frontend/src/app/login-dialog/login-dialog.component.ts b/Frontend/src/app/login-dialog/login-dialog.component.ts index db3fd1e9..c9132051 100644 --- a/Frontend/src/app/login-dialog/login-dialog.component.ts +++ b/Frontend/src/app/login-dialog/login-dialog.component.ts @@ -66,11 +66,11 @@ import { AuthService } from '../service/auth.service'; display: flex; flex-direction: column; } - + .mat-form-field { - margin-bottom: 20px; + margin-bottom: 20px; } - + .close-button { position: absolute; top: 10px; From 4412b3ee166bb93defdd2e3d655d7248a9168311 Mon Sep 17 00:00:00 2001 From: Irild Hoxhallari Date: Sat, 20 Jan 2024 04:55:12 +0100 Subject: [PATCH 4/8] added logout functionality, when user is logged in, his email is written on the emailInput automatically Signed-off-by: Irild Hoxhallari --- Frontend/package-lock.json | 9 +++++++++ Frontend/package.json | 1 + Frontend/src/app/app.component.html | 8 ++++++-- Frontend/src/app/app.component.ts | 20 +++++++++++++++++++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 19bc8c98..5f82dfe2 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -19,6 +19,7 @@ "@angular/platform-browser-dynamic": "^16.2.0", "@angular/router": "^16.2.0", "cors": "^2.8.5", + "jwt-decode": "^4.0.0", "rxjs": "~7.8.0", "stylelint": "^15.11.0", "tslib": "^2.3.0", @@ -9652,6 +9653,14 @@ "node >= 0.2.0" ] }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/karma": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", diff --git a/Frontend/package.json b/Frontend/package.json index 1e3b3e4c..6a4a50f4 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -23,6 +23,7 @@ "@angular/platform-browser-dynamic": "^16.2.0", "@angular/router": "^16.2.0", "cors": "^2.8.5", + "jwt-decode": "^4.0.0", "rxjs": "~7.8.0", "stylelint": "^15.11.0", "tslib": "^2.3.0", diff --git a/Frontend/src/app/app.component.html b/Frontend/src/app/app.component.html index 16444f1a..f04569d6 100644 --- a/Frontend/src/app/app.component.html +++ b/Frontend/src/app/app.component.html @@ -14,10 +14,14 @@ > {{ title }} - +
diff --git a/Frontend/src/app/app.component.ts b/Frontend/src/app/app.component.ts index d72e8f1d..013bdb06 100644 --- a/Frontend/src/app/app.component.ts +++ b/Frontend/src/app/app.component.ts @@ -6,6 +6,7 @@ import {Ticket} from "./entities/ticket.dto"; import { MatDialog } from '@angular/material/dialog'; import { RequestTypeDialogComponent } from './request-type-dialog/request-type-dialog.component'; import { LoginDialogComponent } from './login-dialog/login-dialog.component'; +import {jwtDecode} from "jwt-decode"; interface ChatMessages { messageText: string; @@ -41,13 +42,22 @@ export class AppComponent implements OnInit { recognitionTimeout: any; selectedRequestType: string = ''; createdTicket: Ticket | undefined; + isLoggedIn: boolean = false; + accessToken: string | null = ''; @ViewChild("fileDropRef", { static: true }) fileDropEl!: ElementRef; @ViewChild(DragAndDropComponent) dragAndDropComponent!: DragAndDropComponent; constructor(private ticketService: TicketService, private logger: LogService, private changeDetector: ChangeDetectorRef, private dialog: MatDialog,) {} - ngOnInit() {} + ngOnInit() { + this.accessToken = localStorage.getItem("access_token") ? localStorage.getItem("access_token") : null; + if (this.accessToken != null) { + this.isLoggedIn = true; + let email = jwtDecode(this.accessToken).sub; + this.emailInput = email ? email : ''; + } + } getFiles(event: any) { this.files = event; @@ -73,6 +83,8 @@ export class AppComponent implements OnInit { const dialogRef = this.dialog.open(LoginDialogComponent); dialogRef.afterClosed().subscribe(result => { // logic after closing dialog + this.emailInput = result.email; + this.isLoggedIn = true; }); } @@ -257,4 +269,10 @@ export class AppComponent implements OnInit { this.errorMessage = errorMessage; this.logger.error(errorMessage); } + + logout() { + localStorage.removeItem("access_token"); + this.isLoggedIn = false; + this.emailInput = ''; + } } From 01c6c465bc28d82cbf5a67d566974e796b03ad18 Mon Sep 17 00:00:00 2001 From: garvinkon Date: Sun, 21 Jan 2024 18:29:21 +0100 Subject: [PATCH 5/8] added new backend endpoint, that verifies that token is still valid and if not logout Signed-off-by: garvinkon --- Backend/.env_example | 1 + Backend/app/api/v1/auth_api.py | 16 ++- Frontend/package-lock.json | 106 +++++++++++++++++- Frontend/package.json | 2 + Frontend/src/app/app.component.ts | 37 ++++-- .../login-dialog/login-dialog.component.ts | 6 +- .../request-type-dialog.component.ts | 4 +- Frontend/src/app/service/auth.service.ts | 35 ++++-- Frontend/src/app/service/logging.service.ts | 2 +- 9 files changed, 182 insertions(+), 27 deletions(-) diff --git a/Backend/.env_example b/Backend/.env_example index ccca008f..73a55dc7 100644 --- a/Backend/.env_example +++ b/Backend/.env_example @@ -1,3 +1,4 @@ TEST_STAGE=local PASSWORD=SomePassword +SECRET_KEY=Some_random_32byte_key MONGODB_URL=mongodb://localhost:27017/ \ No newline at end of file diff --git a/Backend/app/api/v1/auth_api.py b/Backend/app/api/v1/auth_api.py index ebd89fc5..7c802ba6 100644 --- a/Backend/app/api/v1/auth_api.py +++ b/Backend/app/api/v1/auth_api.py @@ -5,9 +5,11 @@ from app.repository.user_repository import UserRepository from app.dependency.repository import get_user_repository from datetime import datetime, timedelta -from jose import jwt +from jose import JWTError, jwt router = APIRouter() +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + load_dotenv() SECRET_KEY = os.getenv("SECRET_KEY") ALGORITHM = "HS256" @@ -56,3 +58,15 @@ async def login_for_access_token( ) response.set_cookie(key="access_token", value=access_token, httponly=True) return {"access_token": access_token, "token_type": "bearer", "success": True} + + +@router.get("/verify-token") +async def verify_token(token: str = Depends(oauth2_scheme)): + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + email = payload.get("sub") + if email is None: + raise HTTPException(status_code=401, detail="Invalid token") + return {"email": email} + except JWTError: + raise HTTPException(status_code=401, detail="Invalid token") diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json index 5f82dfe2..e2c9fc3b 100644 --- a/Frontend/package-lock.json +++ b/Frontend/package-lock.json @@ -19,6 +19,7 @@ "@angular/platform-browser-dynamic": "^16.2.0", "@angular/router": "^16.2.0", "cors": "^2.8.5", + "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "rxjs": "~7.8.0", "stylelint": "^15.11.0", @@ -30,6 +31,7 @@ "@angular/cli": "^16.2.9", "@angular/compiler-cli": "^16.2.0", "@types/jasmine": "~4.3.0", + "@types/jsonwebtoken": "^9.0.5", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "eslint": "^8.53.0", @@ -4557,6 +4559,15 @@ "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", @@ -5804,6 +5815,11 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -7027,6 +7043,14 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8131,9 +8155,9 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "dev": true, "funding": [ { @@ -9653,6 +9677,46 @@ "node >= 0.2.0" ] }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jwt-decode": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", @@ -10029,12 +10093,47 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -12934,7 +13033,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", diff --git a/Frontend/package.json b/Frontend/package.json index 6a4a50f4..7f27c2c7 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -23,6 +23,7 @@ "@angular/platform-browser-dynamic": "^16.2.0", "@angular/router": "^16.2.0", "cors": "^2.8.5", + "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "rxjs": "~7.8.0", "stylelint": "^15.11.0", @@ -34,6 +35,7 @@ "@angular/cli": "^16.2.9", "@angular/compiler-cli": "^16.2.0", "@types/jasmine": "~4.3.0", + "@types/jsonwebtoken": "^9.0.5", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "eslint": "^8.53.0", diff --git a/Frontend/src/app/app.component.ts b/Frontend/src/app/app.component.ts index 013bdb06..2dc0f6b3 100644 --- a/Frontend/src/app/app.component.ts +++ b/Frontend/src/app/app.component.ts @@ -1,12 +1,15 @@ -import { Component, OnInit, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core'; -import { TicketService } from './service/ticket.service'; -import { LogService } from './service/logging.service'; -import { DragAndDropComponent } from './drag-and-drop/drag-and-drop.component'; +import {Component, OnInit, ViewChild, ElementRef, ChangeDetectorRef} from '@angular/core'; +import {TicketService} from './service/ticket.service'; +import {LogService} from './service/logging.service'; +import {DragAndDropComponent} from './drag-and-drop/drag-and-drop.component'; import {Ticket} from "./entities/ticket.dto"; -import { MatDialog } from '@angular/material/dialog'; -import { RequestTypeDialogComponent } from './request-type-dialog/request-type-dialog.component'; -import { LoginDialogComponent } from './login-dialog/login-dialog.component'; +import {MatDialog} from '@angular/material/dialog'; +import {RequestTypeDialogComponent} from './request-type-dialog/request-type-dialog.component'; +import {LoginDialogComponent} from './login-dialog/login-dialog.component'; import {jwtDecode} from "jwt-decode"; +import {HttpClient} from '@angular/common/http'; +import { AuthService } from './service/auth.service'; + interface ChatMessages { messageText: string; @@ -48,9 +51,17 @@ export class AppComponent implements OnInit { @ViewChild("fileDropRef", { static: true }) fileDropEl!: ElementRef; @ViewChild(DragAndDropComponent) dragAndDropComponent!: DragAndDropComponent; - constructor(private ticketService: TicketService, private logger: LogService, private changeDetector: ChangeDetectorRef, private dialog: MatDialog,) {} + constructor( + private ticketService: TicketService, + private logger: LogService, + private changeDetector: ChangeDetectorRef, + private dialog: MatDialog, + private http: HttpClient, + private authService: AuthService, + ) {} ngOnInit() { + this.authService.checkTokenValidity(); this.accessToken = localStorage.getItem("access_token") ? localStorage.getItem("access_token") : null; if (this.accessToken != null) { this.isLoggedIn = true; @@ -148,6 +159,16 @@ export class AppComponent implements OnInit { } handleSend(value: string, emailInput: string) { + this.authService.checkTokenValidity(); + this.accessToken = localStorage.getItem("access_token") ? localStorage.getItem("access_token") : null; + if (this.accessToken != null) { + this.isLoggedIn = true; + let email = jwtDecode(this.accessToken).sub; + this.emailInput = email ? email : ''; + }else{ + this.logout() + } + this.errorMessage = ""; if (!value) { diff --git a/Frontend/src/app/login-dialog/login-dialog.component.ts b/Frontend/src/app/login-dialog/login-dialog.component.ts index c9132051..39c2617f 100644 --- a/Frontend/src/app/login-dialog/login-dialog.component.ts +++ b/Frontend/src/app/login-dialog/login-dialog.component.ts @@ -1,6 +1,6 @@ -import { Component } from '@angular/core'; -import { MatDialogRef } from '@angular/material/dialog'; -import { AuthService } from '../service/auth.service'; +import {Component} from '@angular/core'; +import {MatDialogRef} from '@angular/material/dialog'; +import {AuthService} from '../service/auth.service'; @Component({ selector: 'app-login-dialog', diff --git a/Frontend/src/app/request-type-dialog/request-type-dialog.component.ts b/Frontend/src/app/request-type-dialog/request-type-dialog.component.ts index 2439b089..b830d0ea 100644 --- a/Frontend/src/app/request-type-dialog/request-type-dialog.component.ts +++ b/Frontend/src/app/request-type-dialog/request-type-dialog.component.ts @@ -1,5 +1,5 @@ -import { Component } from '@angular/core'; -import { MatDialogRef } from '@angular/material/dialog'; +import {Component} from '@angular/core'; +import {MatDialogRef} from '@angular/material/dialog'; @Component({ selector: 'app-request-type-dialog', diff --git a/Frontend/src/app/service/auth.service.ts b/Frontend/src/app/service/auth.service.ts index 2448132b..381b23dd 100644 --- a/Frontend/src/app/service/auth.service.ts +++ b/Frontend/src/app/service/auth.service.ts @@ -1,16 +1,17 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { Observable, throwError } from 'rxjs'; -import { tap, catchError } from 'rxjs/operators'; +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Observable, throwError} from 'rxjs'; +import {tap, catchError} from 'rxjs/operators'; import {LogService} from './logging.service'; import {environment} from "../../environments/environment"; -import { of } from 'rxjs'; +import {of} from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthService { private apiUrl = environment.apiUrl + 'api/v1/token'; + private apiUrl1 = environment.apiUrl + 'api/v1/verify-token'; constructor(private http: HttpClient, private logger: LogService) {} @@ -22,13 +23,12 @@ export class AuthService { return this.http.post<{ access_token: string }>(this.apiUrl, loginData) .pipe( tap((response: { access_token: string }) => { - // Speichern des Zugriffstokens + // Save accessToken localStorage.setItem('access_token', response.access_token); this.logger.log('Login successful:'+ response); this.checkLoginStatus(); }), catchError((error: any) => { - // Fehlerbehandlung let errorMessage = 'An error has occurred. Please try again later.'; if (error.status === 400) { errorMessage = 'Login data not correct.'; @@ -42,7 +42,26 @@ export class AuthService { checkLoginStatus(): void { const token = localStorage.getItem('access_token'); if (token) { - this.logger.log('Token successful: ' + token); // Token-Informationen loggen + this.logger.log('Token successful: ' + token); + } else { + this.logger.log('No token found, user is not logged in.'); + } + } + checkTokenValidity(): void { + const accessToken = localStorage.getItem('access_token'); + if (accessToken) { + + this.http.get<{ email: string }>(this.apiUrl1, { + headers: { Authorization: `Bearer ${accessToken}` } + }).subscribe({ + next: (response) => { + this.logger.log('Token is valid. Logged in as: ' + response.email); + }, + error: (error) => { + localStorage.removeItem('access_token'); + this.logger.log('Token is invalid or expired. Logged out.'); + } + }); } else { this.logger.log('No token found, user is not logged in.'); } diff --git a/Frontend/src/app/service/logging.service.ts b/Frontend/src/app/service/logging.service.ts index c9b8fc5e..0c70add7 100644 --- a/Frontend/src/app/service/logging.service.ts +++ b/Frontend/src/app/service/logging.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from "@angular/core"; +import {Injectable} from "@angular/core"; @Injectable({ providedIn: "root" From 64f1a046c3ba15c90d8ce5dbe6e632a60c2a10b2 Mon Sep 17 00:00:00 2001 From: garvinkon Date: Sun, 21 Jan 2024 20:06:34 +0100 Subject: [PATCH 6/8] added tests, but they need to be skipped because of .env problem Signed-off-by: garvinkon --- Backend/app/api/v1/auth_api.py | 4 +- Backend/test/api/v1/auth_api_test.py | 73 +++++++++++++++++++ .../test/repository/ticket_repository_test.py | 2 + 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 Backend/test/api/v1/auth_api_test.py diff --git a/Backend/app/api/v1/auth_api.py b/Backend/app/api/v1/auth_api.py index 7c802ba6..08795c2c 100644 --- a/Backend/app/api/v1/auth_api.py +++ b/Backend/app/api/v1/auth_api.py @@ -20,9 +20,7 @@ def create_access_token(data: dict, expires_delta: timedelta = None): if expires_delta: expire = datetime.utcnow() + expires_delta else: - expire = datetime.utcnow() + timedelta( - minutes=15 - ) # Standardablaufzeit: 15 Minuten + expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt diff --git a/Backend/test/api/v1/auth_api_test.py b/Backend/test/api/v1/auth_api_test.py new file mode 100644 index 00000000..e5e48eeb --- /dev/null +++ b/Backend/test/api/v1/auth_api_test.py @@ -0,0 +1,73 @@ +import os +from datetime import datetime, timedelta +from dotenv import load_dotenv +from jose import JWTError, jwt +import pytest +from fastapi.testclient import TestClient +from unittest.mock import Mock +from test.config.pytest import SKIP_TEST +from app.main import app +from app.dependency.repository import get_user_repository + + +load_dotenv() +SECRET_KEY = os.getenv("SECRET_KEY") + + +# Mock dependencies +@pytest.fixture +def mock_user_repository(): + user_repo = Mock() + user_repo.authenticate_user.return_value = True + user_repo.read_users_by_email.return_value = [{"email_address": "test@example.com"}] + return user_repo + + +@pytest.fixture +def client(mock_user_repository): + app.dependency_overrides[get_user_repository] = lambda: mock_user_repository + return TestClient(app) + + +def generate_valid_token(): + ALGORITHM = "HS256" + data = {"sub": "test@example.com"} + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode = data.copy() + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + + +@pytest.mark.skipif(condition=SKIP_TEST, reason=".env on git") +class TestAPI: + def test_login_for_access_token_success(self, client): + response = client.post( + "/api/v1/token", + data={"username": "user@example.com", "password": "password"}, + ) + assert response.status_code == 200 + assert "access_token" in response.json() + + def test_login_for_access_token_failure(self, client, mock_user_repository): + # Configure the mock to return False for authentication + mock_user_repository.authenticate_user.return_value = False + response = client.post( + "/api/v1/token", + data={"username": "wrong@example.com", "password": "wrongpassword"}, + ) + assert response.status_code == 400 + + def test_verify_token_success(self, client): + valid_token = generate_valid_token() + response = client.get( + "/api/v1/verify-token", headers={"Authorization": f"Bearer {valid_token}"} + ) + assert response.status_code == 200 + + def test_verify_token_failure(self, client): + # Use an altered valid token for this test + valid_token = generate_valid_token() + "invalid_part" + response = client.get( + "/api/v1/verify-token", headers={"Authorization": f"Bearer {valid_token}"} + ) + assert response.status_code == 401 diff --git a/Backend/test/repository/ticket_repository_test.py b/Backend/test/repository/ticket_repository_test.py index 63529f55..d2062ca2 100644 --- a/Backend/test/repository/ticket_repository_test.py +++ b/Backend/test/repository/ticket_repository_test.py @@ -33,6 +33,7 @@ def setUp(self): description="", priority=Prio.low, attachments=[], + requestType="", ) def test_create_ticket(self): @@ -98,6 +99,7 @@ def setUp(self): "description": "", "priority": Prio.low, "attachments": [], + "requestType": "", } def test_crud(self): From 7dbb37d0e03c4cc24750923c9e0833a3f7085478 Mon Sep 17 00:00:00 2001 From: garvinkon Date: Tue, 23 Jan 2024 16:03:23 +0100 Subject: [PATCH 7/8] refactored frontend, and status code exception for token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marco Martin Härtl Signed-off-by: garvinkon --- Backend/app/api/v1/auth_api.py | 4 +- Backend/test/api/v1/auth_api_test.py | 2 +- .../login-dialog/login-dialog.component.css | 66 +++++++++++++ .../login-dialog/login-dialog.component.html | 20 ++++ .../login-dialog/login-dialog.component.ts | 92 +------------------ .../request-type-dialog.component.css | 37 ++++++++ .../request-type-dialog.component.html | 8 ++ .../request-type-dialog.component.ts | 51 +--------- 8 files changed, 138 insertions(+), 142 deletions(-) create mode 100644 Frontend/src/app/login-dialog/login-dialog.component.css create mode 100644 Frontend/src/app/login-dialog/login-dialog.component.html create mode 100644 Frontend/src/app/request-type-dialog/request-type-dialog.component.css create mode 100644 Frontend/src/app/request-type-dialog/request-type-dialog.component.html diff --git a/Backend/app/api/v1/auth_api.py b/Backend/app/api/v1/auth_api.py index 08795c2c..2b91b669 100644 --- a/Backend/app/api/v1/auth_api.py +++ b/Backend/app/api/v1/auth_api.py @@ -38,14 +38,14 @@ async def login_for_access_token( ) if not is_authenticated: raise HTTPException( - status_code=400, + status_code=402, detail="Incorrect email or password", headers={"WWW-Authenticate": "Bearer"}, ) # read user data user_data = user_repo.read_users_by_email(email=form_data.username) if not user_data: - raise HTTPException(status_code=400, detail="User not found") + raise HTTPException(status_code=404, detail="User not found") # take first element, because email_adresse should be unique user = user_data[0] diff --git a/Backend/test/api/v1/auth_api_test.py b/Backend/test/api/v1/auth_api_test.py index e5e48eeb..11b357fc 100644 --- a/Backend/test/api/v1/auth_api_test.py +++ b/Backend/test/api/v1/auth_api_test.py @@ -55,7 +55,7 @@ def test_login_for_access_token_failure(self, client, mock_user_repository): "/api/v1/token", data={"username": "wrong@example.com", "password": "wrongpassword"}, ) - assert response.status_code == 400 + assert response.status_code == 402 def test_verify_token_success(self, client): valid_token = generate_valid_token() diff --git a/Frontend/src/app/login-dialog/login-dialog.component.css b/Frontend/src/app/login-dialog/login-dialog.component.css new file mode 100644 index 00000000..40ea6e52 --- /dev/null +++ b/Frontend/src/app/login-dialog/login-dialog.component.css @@ -0,0 +1,66 @@ +:host { + --main-bg-color: #313131; + --accent-color: #10ddd3; + --hover-color: #00828f; + --text-color: #fff; + } + + .dialog-title{ + background-color: var(--main-bg-color); + text-align: center; + } + + .dialog-content { + background-color: var(--main-bg-color); + text-align: center; + width: 400px; + } + + .dialog-title { + font-size: 24px; + color: var(--accent-color); + margin-bottom: 20px; + } + + .error-message { + font-size: 16px; + color: red; + margin: 15px; + } + + .dialog-message { + font-size: 16px; + color: var(--text-color); + } + + mat-dialog-content { + display: flex; + flex-direction: column; + } + + .mat-form-field { + margin-bottom: 20px; + } + + .close-button { + position: absolute; + top: 10px; + right: 10px; + color: white; /* oder die Farbe Ihrer Wahl */ + } + + .login-button { + background-color: var(--accent-color); + color: var(--text-color); + font-size: 18px; + text-transform: capitalize; + padding: 10px 20px; + margin: 10px; + margin-top: 20px; + cursor: pointer; + transition: background-color 0.3s ease-in-out; + } + + .login-button:hover { + background-color: var(--hover-color); + } \ No newline at end of file diff --git a/Frontend/src/app/login-dialog/login-dialog.component.html b/Frontend/src/app/login-dialog/login-dialog.component.html new file mode 100644 index 00000000..9c7447d2 --- /dev/null +++ b/Frontend/src/app/login-dialog/login-dialog.component.html @@ -0,0 +1,20 @@ + +

Login

+ + +
+ {{ errorMessage }} +
+ + E-Mail + + + + Password + + + +
+ \ No newline at end of file diff --git a/Frontend/src/app/login-dialog/login-dialog.component.ts b/Frontend/src/app/login-dialog/login-dialog.component.ts index 39c2617f..fbe7075a 100644 --- a/Frontend/src/app/login-dialog/login-dialog.component.ts +++ b/Frontend/src/app/login-dialog/login-dialog.component.ts @@ -4,96 +4,8 @@ import {AuthService} from '../service/auth.service'; @Component({ selector: 'app-login-dialog', - template: ` - -

Login

- - -
- {{ errorMessage }} -
- - E-Mail - - - - Password - - - -
- - `, - styles: [` - :host { - --main-bg-color: #313131; - --accent-color: #10ddd3; - --hover-color: #00828f; - --text-color: #fff; - } - - .dialog-title{ - background-color: var(--main-bg-color); - text-align: center; - } - - .dialog-content { - background-color: var(--main-bg-color); - text-align: center; - width: 400px; - } - - .dialog-title { - font-size: 24px; - color: var(--accent-color); - margin-bottom: 20px; - } - - .error-message { - font-size: 16px; - color: red; - margin: 15px; - } - - .dialog-message { - font-size: 16px; - color: var(--text-color); - } - - mat-dialog-content { - display: flex; - flex-direction: column; - } - - .mat-form-field { - margin-bottom: 20px; - } - - .close-button { - position: absolute; - top: 10px; - right: 10px; - color: white; /* oder die Farbe Ihrer Wahl */ - } - - .login-button { - background-color: var(--accent-color); - color: var(--text-color); - font-size: 18px; - text-transform: capitalize; - padding: 10px 20px; - margin: 10px; - margin-top: 20px; - cursor: pointer; - transition: background-color 0.3s ease-in-out; - } - - .login-button:hover { - background-color: var(--hover-color); - } - `] + templateUrl: './login-dialog.component.html', + styleUrls: ['./login-dialog.component.css'] }) export class LoginDialogComponent { email: string = ''; diff --git a/Frontend/src/app/request-type-dialog/request-type-dialog.component.css b/Frontend/src/app/request-type-dialog/request-type-dialog.component.css new file mode 100644 index 00000000..a91c327c --- /dev/null +++ b/Frontend/src/app/request-type-dialog/request-type-dialog.component.css @@ -0,0 +1,37 @@ +:host { + --main-bg-color: #313131; + --accent-color: #10ddd3; + --hover-color: #00828f; + --text-color: #fff; + } + + .dialog-title, .dialog-content { + background-color: var(--main-bg-color); + text-align: center; + } + + .dialog-title { + font-size: 24px; + color: var(--accent-color); + margin-bottom: 20px; + } + + .dialog-message { + font-size: 16px; + color: var(--text-color); + } + + .request-type-button { + background-color: var(--accent-color); + color: var(--text-color); + font-size: 18px; + text-transform: capitalize; + padding: 10px 20px; + margin: 10px; + cursor: pointer; + transition: background-color 0.3s ease-in-out; + } + + .request-type-button:hover { + background-color: var(--hover-color); + } diff --git a/Frontend/src/app/request-type-dialog/request-type-dialog.component.html b/Frontend/src/app/request-type-dialog/request-type-dialog.component.html new file mode 100644 index 00000000..632eca4c --- /dev/null +++ b/Frontend/src/app/request-type-dialog/request-type-dialog.component.html @@ -0,0 +1,8 @@ + +

Please choose a request type

+ +

We couldn't automatically detect the request type from your message. Please select the appropriate request type:

+ + +
+ \ No newline at end of file diff --git a/Frontend/src/app/request-type-dialog/request-type-dialog.component.ts b/Frontend/src/app/request-type-dialog/request-type-dialog.component.ts index b830d0ea..67ce36cb 100644 --- a/Frontend/src/app/request-type-dialog/request-type-dialog.component.ts +++ b/Frontend/src/app/request-type-dialog/request-type-dialog.component.ts @@ -3,55 +3,8 @@ import {MatDialogRef} from '@angular/material/dialog'; @Component({ selector: 'app-request-type-dialog', - template: ` - -

Please choose a request type

- -

We couldn't automatically detect the request type from your message. Please select the appropriate request type:

- - -
- - `, - styles: [` - :host { - --main-bg-color: #313131; - --accent-color: #10ddd3; - --hover-color: #00828f; - --text-color: #fff; - } - - .dialog-title, .dialog-content { - background-color: var(--main-bg-color); - text-align: center; - } - - .dialog-title { - font-size: 24px; - color: var(--accent-color); - margin-bottom: 20px; - } - - .dialog-message { - font-size: 16px; - color: var(--text-color); - } - - .request-type-button { - background-color: var(--accent-color); - color: var(--text-color); - font-size: 18px; - text-transform: capitalize; - padding: 10px 20px; - margin: 10px; - cursor: pointer; - transition: background-color 0.3s ease-in-out; - } - - .request-type-button:hover { - background-color: var(--hover-color); - } - `] + templateUrl: './request-type-dialog.component.html', + styleUrls: ['./request-type-dialog.component.css'] }) export class RequestTypeDialogComponent { constructor(private dialogRef: MatDialogRef) { From d4e45b36a12a092d9eb53e8dbf9173606db77a2f Mon Sep 17 00:00:00 2001 From: garvinkon Date: Tue, 23 Jan 2024 21:56:44 +0100 Subject: [PATCH 8/8] fixed login button position Signed-off-by: garvinkon --- Frontend/src/app/app.component.css | 10 +++++++--- Frontend/src/app/app.component.html | 4 +++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Frontend/src/app/app.component.css b/Frontend/src/app/app.component.css index 08b8520b..904079fa 100644 --- a/Frontend/src/app/app.component.css +++ b/Frontend/src/app/app.component.css @@ -1,7 +1,11 @@ +body{ + display: flex; + flex-direction: column; +} + .login-container { - position: absolute; - top: 30px; /* Passen Sie diesen Wert an, um den gewünschten Abstand zu erreichen */ - right: 35px; + align-self: flex-end; + margin-right: 20px; } .cards-container { diff --git a/Frontend/src/app/app.component.html b/Frontend/src/app/app.component.html index f04569d6..fe52f683 100644 --- a/Frontend/src/app/app.component.html +++ b/Frontend/src/app/app.component.html @@ -7,13 +7,14 @@ text-align: center; font-size: 5em; margin-top: 30px; - margin-bottom: 30px; + margin-bottom: 10px; margin-right: 80px; size: 45px; " > {{ title }} + +