diff --git a/package-lock.json b/package-lock.json
index 19258ae..97b8de1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"@ng-select/ng-select": "^13.9.1",
+ "crypto-js": "^4.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
@@ -25,6 +26,7 @@
"@angular-devkit/build-angular": "^18.2.7",
"@angular/cli": "^18.2.7",
"@angular/compiler-cli": "^18.2.0",
+ "@types/crypto-js": "^4.2.2",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.2.0",
"karma": "~6.4.0",
@@ -4392,6 +4394,13 @@
"@types/node": "*"
}
},
+ "node_modules/@types/crypto-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
+ "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@@ -6224,6 +6233,12 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "license": "MIT"
+ },
"node_modules/css-loader": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz",
diff --git a/package.json b/package.json
index c45dcfb..cbc0dc7 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"@ng-select/ng-select": "^13.9.1",
+ "crypto-js": "^4.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
@@ -27,6 +28,7 @@
"@angular-devkit/build-angular": "^18.2.7",
"@angular/cli": "^18.2.7",
"@angular/compiler-cli": "^18.2.0",
+ "@types/crypto-js": "^4.2.2",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.2.0",
"karma": "~6.4.0",
@@ -36,4 +38,4 @@
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.5.2"
}
-}
\ No newline at end of file
+}
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 2c5014a..2c162a0 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -1,8 +1,12 @@
import { Routes } from '@angular/router';
import { ChatComponent } from './chat/chat.component';
+import { LoginComponent } from './login/login.component';
+import { AuthGuard } from './utils/auth.guard';
export const routes: Routes = [
- { path: 'chat/en', component: ChatComponent, data: { language: 'en' } },
- { path: 'chat/de', component: ChatComponent, data: { language: 'de' } },
- { path: '', redirectTo: 'chat/en', pathMatch: 'full' } // Default to English chat
+ { path: 'login', component: LoginComponent },
+ { path: 'chat/en', component: ChatComponent, canActivate: [AuthGuard], data: { language: 'en' } },
+ { path: 'chat/de', component: ChatComponent, canActivate: [AuthGuard], data: { language: 'de' } },
+ { path: '', redirectTo: 'login', pathMatch: 'full' },
+ { path: '**', redirectTo: 'login', pathMatch: 'full' }
];
\ No newline at end of file
diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html
new file mode 100644
index 0000000..764b0f7
--- /dev/null
+++ b/src/app/login/login.component.html
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/src/app/login/login.component.scss b/src/app/login/login.component.scss
new file mode 100644
index 0000000..a4903a7
--- /dev/null
+++ b/src/app/login/login.component.scss
@@ -0,0 +1,53 @@
+.login-container {
+ width: 300px;
+ margin: 0 auto;
+ padding: 2rem;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+}
+
+h2 {
+ text-align: center;
+}
+
+.form-group {
+ margin-bottom: 1rem;
+}
+
+label {
+ display: block;
+ font-weight: bold;
+ margin-bottom: 0.5rem;
+}
+
+input {
+ width: 100%;
+ padding: 0.5rem;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+
+button {
+ width: 100%;
+ padding: 0.7rem;
+ background-color: #007bff;
+ color: #fff;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+button:disabled {
+ background-color: #a9a9a9;
+ cursor: not-allowed;
+}
+
+.error {
+ color: red;
+ font-size: 0.85rem;
+}
+
+.invalid {
+ border-color: red;
+}
\ No newline at end of file
diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts
new file mode 100644
index 0000000..18f3685
--- /dev/null
+++ b/src/app/login/login.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoginComponent } from './login.component';
+
+describe('LoginComponent', () => {
+ let component: LoginComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [LoginComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(LoginComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts
new file mode 100644
index 0000000..8127075
--- /dev/null
+++ b/src/app/login/login.component.ts
@@ -0,0 +1,40 @@
+import { Component } from '@angular/core';
+import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { AuthService } from '../services/auth.service';
+
+@Component({
+ selector: 'app-login',
+ standalone: true,
+ imports: [ReactiveFormsModule],
+ templateUrl: './login.component.html',
+ styleUrl: './login.component.scss'
+})
+export class LoginComponent {
+ public loginForm: FormGroup;
+ errorMessage: string | null = null;
+
+ constructor(
+ private authService: AuthService,
+ private router: Router
+ ) {
+ this.loginForm = new FormGroup({
+ username: new FormControl('', [Validators.required]),
+ password: new FormControl('', [Validators.required])
+ });
+ }
+
+ onSubmit(): void {
+ if (this.loginForm.valid) {
+ const { username, password } = this.loginForm.value;
+ this.authService.login(username, password).subscribe({
+ next: () => this.router.navigate(['/chat/en']), // Redirect to chat after login
+ error: (err) => (this.errorMessage = 'Invalid username or password')
+ });
+ }
+ }
+
+ get f() {
+ return this.loginForm.controls;
+ }
+}
diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts
index ad6d225..ef40d41 100644
--- a/src/app/services/auth.service.ts
+++ b/src/app/services/auth.service.ts
@@ -1,37 +1,42 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
-import { Observable, tap } from 'rxjs';
+import { BehaviorSubject, Observable, tap } from 'rxjs';
import { environment } from '../../environments/environment';
+import * as CryptoJS from 'crypto-js';
export interface AuthResponse {
access_token: string;
token_type: string;
}
-
@Injectable({
providedIn: 'root'
})
export class AuthService {
+ private isAuthenticated = new BehaviorSubject(this.getToken() !== null);
constructor(private http: HttpClient) { }
- login(): Observable {
+ login(username: string, password: string): Observable {
const headers = new HttpHeaders().set('x-api-key', environment.angelosAppApiKey);
- if (environment.angelosAppApiKey.length === 0) {
- console.log('Please provide a valid API key');
- } else {
- console.log(environment.angelosAppApiKey.at(0));
- }
-
- return this.http.post(environment.angelosToken, {}, { headers }).pipe(
+ const body = { username: username, password: password };
+ return this.http.post(environment.angelosToken, body, { headers }).pipe(
tap((response: AuthResponse) => {
sessionStorage.setItem('access_token', response.access_token);
+ this.isAuthenticated.next(true);
})
);
}
- // Method to retrieve the stored token
- public getToken(): string | null {
+ logout(): void {
+ sessionStorage.removeItem('access_token');
+ this.isAuthenticated.next(false);
+ }
+
+ getToken(): string | null {
return sessionStorage.getItem('access_token');
}
+
+ isLoggedIn(): Observable {
+ return this.isAuthenticated.asObservable();
+ }
}
diff --git a/src/app/services/chatbot.service.ts b/src/app/services/chatbot.service.ts
index ff416ec..3c6733f 100644
--- a/src/app/services/chatbot.service.ts
+++ b/src/app/services/chatbot.service.ts
@@ -25,13 +25,7 @@ export class ChatbotService {
if (token) {
return this.sendBotRequest(token, chatHistory, study_program);
} else {
- // Login if no token is stored, then proceed with the bot request
- return this.authService.login().pipe(
- switchMap(() => {
- const newToken = this.authService.getToken();
- return this.sendBotRequest(newToken, chatHistory, study_program);
- })
- );
+ throw new Error('No token found. Access should have been restricted by AuthGuard.');
}
}
}
diff --git a/src/app/utils/auth.guard.ts b/src/app/utils/auth.guard.ts
new file mode 100644
index 0000000..8a9b8a5
--- /dev/null
+++ b/src/app/utils/auth.guard.ts
@@ -0,0 +1,22 @@
+import { Injectable } from '@angular/core';
+import { CanActivate, Router } from '@angular/router';
+import { Observable } from 'rxjs';
+import { map, tap } from 'rxjs/operators';
+import { AuthService } from '../services/auth.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthGuard implements CanActivate {
+ constructor(private authService: AuthService, private router: Router) { }
+
+ canActivate(): Observable {
+ return this.authService.isLoggedIn().pipe(
+ tap(isLoggedIn => {
+ if (!isLoggedIn) {
+ this.router.navigate(['/login']);
+ }
+ })
+ );
+ }
+}