finished login
This commit is contained in:
parent
e25bae9700
commit
9bb354a805
@ -1,5 +1,6 @@
|
|||||||
package at.mueller.eeg.backend.iam.config;
|
package at.mueller.eeg.backend.iam.config;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.iam.repository.UserRepository;
|
||||||
import com.nimbusds.jose.jwk.source.ImmutableSecret;
|
import com.nimbusds.jose.jwk.source.ImmutableSecret;
|
||||||
import jakarta.servlet.DispatcherType;
|
import jakarta.servlet.DispatcherType;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@ -12,6 +13,8 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
@ -143,4 +146,10 @@ public class SecurityConfig {
|
|||||||
SecretKeySpec secretKey = new SecretKeySpec(jwtSecret.getBytes(), "HmacSHA256");
|
SecretKeySpec secretKey = new SecretKeySpec(jwtSecret.getBytes(), "HmacSHA256");
|
||||||
return new NimbusJwtEncoder(new ImmutableSecret<>(secretKey));
|
return new NimbusJwtEncoder(new ImmutableSecret<>(secretKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UserDetailsService userDetailsService(UserRepository userRepository) {
|
||||||
|
return username -> userRepository.findByEmail(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User mit E-Mail " + username + " nicht gefunden."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
eeg_frontend/src/app/layout/header/header.html
Normal file
46
eeg_frontend/src/app/layout/header/header.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<nav class="bg-white border-b border-gray-200 sticky top-0 z-50 shadow-sm">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between items-center h-16">
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<a routerLink="/" class="flex items-center space-x-2 group">
|
||||||
|
<svg class="h-8 w-8 text-emerald-600 group-hover:text-emerald-700 transition" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-bold text-xl text-gray-900 tracking-tight">
|
||||||
|
eeg<span class="text-emerald-600">portal</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hidden md:flex absolute left-1/2 transform -translate-x-1/2">
|
||||||
|
<nav class="flex gap-6">
|
||||||
|
<a href="#vorteile" class="text-slate-600 hover:text-slate-900 font-medium">Vorteile</a>
|
||||||
|
<a href="#so-gehts" class="text-slate-600 hover:text-slate-900 font-medium">So funktioniert's</a>
|
||||||
|
<a href="#faq" class="text-slate-600 hover:text-slate-900 font-medium">FAQ</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
@if (authService.isLoggedIn$ | async) {
|
||||||
|
|
||||||
|
<span class="text-sm text-gray-500 hidden sm:inline">
|
||||||
|
Rolle: <span class="font-semibold text-gray-700">{{ authService.getUserRole() }}</span>
|
||||||
|
</span>
|
||||||
|
<button (click)="onLogout()" class="text-sm font-medium text-red-600 hover:text-red-700 border border-red-200 hover:border-red-300 px-3 py-1.5 rounded-md transition duration-150">
|
||||||
|
Abmelden
|
||||||
|
</button>
|
||||||
|
|
||||||
|
} @else {
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<button mat-button class="hidden sm:inline-flex" routerLink="/login">Login</button>
|
||||||
|
<button mat-flat-button color="primary" routerLink="/register">Mitglied werden</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
0
eeg_frontend/src/app/layout/header/header.scss
Normal file
0
eeg_frontend/src/app/layout/header/header.scss
Normal file
22
eeg_frontend/src/app/layout/header/header.spec.ts
Normal file
22
eeg_frontend/src/app/layout/header/header.spec.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Header } from './header';
|
||||||
|
|
||||||
|
describe('Header', () => {
|
||||||
|
let component: Header;
|
||||||
|
let fixture: ComponentFixture<Header>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Header],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Header);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
27
eeg_frontend/src/app/layout/header/header.ts
Normal file
27
eeg_frontend/src/app/layout/header/header.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {Router, RouterLink} from '@angular/router';
|
||||||
|
import {AuthService} from '../../services/auth';
|
||||||
|
import {AsyncPipe} from '@angular/common';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-header',
|
||||||
|
imports: [
|
||||||
|
RouterLink,
|
||||||
|
AsyncPipe,
|
||||||
|
MatButton
|
||||||
|
],
|
||||||
|
templateUrl: './header.html'
|
||||||
|
})
|
||||||
|
export class HeaderComponent {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public authService: AuthService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
onLogout() {
|
||||||
|
this.authService.logout();
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
eeg_frontend/src/app/pages/login/login.html
Normal file
47
eeg_frontend/src/app/pages/login/login.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="max-w-md w-full space-y-8 bg-white p-8 rounded-xl shadow-lg">
|
||||||
|
<div>
|
||||||
|
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||||
|
In Ihr EEG-Portal einloggen
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="errorMessage" class="bg-red-50 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
|
||||||
|
<span class="block sm:inline text-sm">{{ errorMessage }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="mt-8 space-y-6" [formGroup]="loginForm" (ngSubmit)="onSubmit()">
|
||||||
|
<div class="rounded-md shadow-sm -space-y-px">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">Benutzername oder E-Mail</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
formControlName="email"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 sm:text-sm"
|
||||||
|
placeholder="ihr.name@beispiel.at">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Passwort</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
formControlName="password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
class="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 sm:text-sm"
|
||||||
|
placeholder="********">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
[disabled]="loginForm.invalid"
|
||||||
|
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-emerald-600 hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 transition duration-150 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
|
Anmelden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
0
eeg_frontend/src/app/pages/login/login.scss
Normal file
0
eeg_frontend/src/app/pages/login/login.scss
Normal file
22
eeg_frontend/src/app/pages/login/login.spec.ts
Normal file
22
eeg_frontend/src/app/pages/login/login.spec.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Login } from './login';
|
||||||
|
|
||||||
|
describe('Login', () => {
|
||||||
|
let component: Login;
|
||||||
|
let fixture: ComponentFixture<Login>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Login],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Login);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
56
eeg_frontend/src/app/pages/login/login.ts
Normal file
56
eeg_frontend/src/app/pages/login/login.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
import {AuthService} from '../../services/auth';
|
||||||
|
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||||
|
import {CommonModule} from '@angular/common'; // 1. Hier importieren
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, ReactiveFormsModule],
|
||||||
|
templateUrl: './login.html'
|
||||||
|
})
|
||||||
|
export class LoginComponent {
|
||||||
|
loginForm: FormGroup;
|
||||||
|
errorMessage: string = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
this.loginForm = this.fb.group({
|
||||||
|
email: ['', [Validators.required]],
|
||||||
|
password: ['', [Validators.required]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
if (this.loginForm.invalid) return;
|
||||||
|
|
||||||
|
this.authService.login(this.loginForm.value).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
// Hier passiert die bedingte Weiterleitung anhand der Rolle
|
||||||
|
this.redirectBasedOnRole(response.role!);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.errorMessage = err.error?.message || 'Ein unbekannter Fehler ist aufgetreten.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private redirectBasedOnRole(role: string) {
|
||||||
|
switch (role) {
|
||||||
|
case 'ADMIN':
|
||||||
|
void this.router.navigate(['/admin/approvals']);
|
||||||
|
break;
|
||||||
|
case 'MEMBER':
|
||||||
|
void this.router.navigate(['/dashboard']);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Fallback, falls die Rolle unbekannt ist
|
||||||
|
void this.router.navigate(['/']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user