Implementation of login and logout mechanisms.
This commit is contained in:
13
sportshub-gui/package-lock.json
generated
13
sportshub-gui/package-lock.json
generated
@@ -18,6 +18,7 @@
|
|||||||
"@angular/platform-browser": "^17.0.0",
|
"@angular/platform-browser": "^17.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^17.0.0",
|
"@angular/platform-browser-dynamic": "^17.0.0",
|
||||||
"@angular/router": "^17.0.0",
|
"@angular/router": "^17.0.0",
|
||||||
|
"ngx-cookie-service": "^17.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.2"
|
"zone.js": "~0.14.2"
|
||||||
@@ -13096,6 +13097,18 @@
|
|||||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/ngx-cookie-service": {
|
||||||
|
"version": "17.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-17.0.0.tgz",
|
||||||
|
"integrity": "sha512-5mCitsrcUPBlHSzZH1NNKGTwd9NDVq1AD3lXN6XxqtgL+aFRguNyVaEUYADlx37JBnI1LAnN6PE6Z+wK9CpRvw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.6.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": "^17.0.0",
|
||||||
|
"@angular/core": "^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nice-napi": {
|
"node_modules/nice-napi": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"@angular/platform-browser": "^17.0.0",
|
"@angular/platform-browser": "^17.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^17.0.0",
|
"@angular/platform-browser-dynamic": "^17.0.0",
|
||||||
"@angular/router": "^17.0.0",
|
"@angular/router": "^17.0.0",
|
||||||
|
"ngx-cookie-service": "^17.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.2"
|
"zone.js": "~0.14.2"
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
app-header {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,5 +8,9 @@ export const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
loadChildren: () => import('./components/login/login.module').then(module => module.LoginModule)
|
loadChildren: () => import('./components/login/login.module').then(module => module.LoginModule)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'logout',
|
||||||
|
loadChildren: () => import('./components/logout/logout.module').then(module => module.LogoutModule)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
|
|||||||
import {UserRestService} from "../../core/rest-services/user.rest-service";
|
import {UserRestService} from "../../core/rest-services/user.rest-service";
|
||||||
import {LoginRequest} from "../../core/model/login-request";
|
import {LoginRequest} from "../../core/model/login-request";
|
||||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||||
|
import {LoginService} from "./login.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
@@ -15,6 +16,7 @@ export class LoginComponent {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
|
private loginService: LoginService,
|
||||||
private matSnackBar: MatSnackBar,
|
private matSnackBar: MatSnackBar,
|
||||||
private userRestService: UserRestService
|
private userRestService: UserRestService
|
||||||
) {
|
) {
|
||||||
@@ -25,14 +27,7 @@ export class LoginComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
this.isLoginPending = true;
|
|
||||||
const loginRequest: LoginRequest = this.loginForm.value;
|
const loginRequest: LoginRequest = this.loginForm.value;
|
||||||
this.userRestService.login(loginRequest)
|
this.loginService.login(loginRequest);
|
||||||
.then(loginResponse => {
|
|
||||||
this.matSnackBar.open('Login success!', 'Close', { duration: 5000 });
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
this.matSnackBar.open('An error occured while login.', 'Close', { duration: 5000 });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import {CoreModule} from "../../core/core.module";
|
|||||||
import {MatSnackBarModule} from "@angular/material/snack-bar";
|
import {MatSnackBarModule} from "@angular/material/snack-bar";
|
||||||
import {RouterModule} from "@angular/router";
|
import {RouterModule} from "@angular/router";
|
||||||
import {HttpClientModule} from "@angular/common/http";
|
import {HttpClientModule} from "@angular/common/http";
|
||||||
|
import {LoginService} from "./login.service";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@@ -16,6 +17,9 @@ const routes = [
|
|||||||
declarations: [
|
declarations: [
|
||||||
LoginComponent
|
LoginComponent
|
||||||
],
|
],
|
||||||
|
providers: [
|
||||||
|
LoginService
|
||||||
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CoreModule,
|
CoreModule,
|
||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {UserRestService} from "../../core/rest-services/user.rest-service";
|
||||||
|
import {LoginRequest} from "../../core/model/login-request";
|
||||||
|
import {Subject} from "rxjs";
|
||||||
|
import {MessageService} from "../../core/services/message.service";
|
||||||
|
import {AuthenticationService} from "../../core/services/authentication.service";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoginService {
|
||||||
|
private isLoginPending: Subject<boolean> = new Subject<boolean>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authenticationService: AuthenticationService,
|
||||||
|
private messageService: MessageService,
|
||||||
|
private router: Router,
|
||||||
|
private userRestService: UserRestService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
login(loginRequest: LoginRequest): void {
|
||||||
|
this.isLoginPending.next(true);
|
||||||
|
|
||||||
|
this.userRestService.login(loginRequest)
|
||||||
|
.then(loginResponse => {
|
||||||
|
this.messageService.display('Login success!');
|
||||||
|
this.authenticationService.setAuthenticated(loginResponse);
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error.status === 400) {
|
||||||
|
this.messageService.display('Login or password incorrect.')
|
||||||
|
} else {
|
||||||
|
this.messageService.display('An error occured while login.')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<h1>Disconnection...</h1>
|
||||||
|
<mat-spinner></mat-spinner>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
15
sportshub-gui/src/app/components/logout/logout.component.ts
Normal file
15
sportshub-gui/src/app/components/logout/logout.component.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {Component, inject, OnInit} from "@angular/core";
|
||||||
|
import {LogoutService} from "./logout.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-logout',
|
||||||
|
templateUrl: './logout.component.html',
|
||||||
|
styleUrls: ['./logout.component.scss']
|
||||||
|
})
|
||||||
|
export class LogoutComponent implements OnInit {
|
||||||
|
private logoutService = inject(LogoutService);
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.logoutService.logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
sportshub-gui/src/app/components/logout/logout.module.ts
Normal file
29
sportshub-gui/src/app/components/logout/logout.module.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import {NgModule} from "@angular/core";
|
||||||
|
import {RouterModule} from "@angular/router";
|
||||||
|
import {LogoutComponent} from "./logout.component";
|
||||||
|
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
|
||||||
|
import {LogoutService} from "./logout.service";
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: LogoutComponent
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
LogoutComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
LogoutService
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
MatProgressSpinnerModule
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
LogoutComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class LogoutModule {}
|
||||||
16
sportshub-gui/src/app/components/logout/logout.service.ts
Normal file
16
sportshub-gui/src/app/components/logout/logout.service.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {AuthenticationService} from "../../core/services/authentication.service";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LogoutService {
|
||||||
|
constructor(
|
||||||
|
private authenticationService: AuthenticationService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authenticationService.setAnonymous();
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {CookieService} from "ngx-cookie-service";
|
||||||
|
import {LoginResponse} from "../model/login-response";
|
||||||
|
import {BehaviorSubject, Observable} from "rxjs";
|
||||||
|
|
||||||
|
const COOKIE_JWT = 'jwt';
|
||||||
|
const COOKIE_REFRESH_TOKEN = 'refreshToken';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthenticationService {
|
||||||
|
private authenticationSubject: BehaviorSubject<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private cookieService: CookieService
|
||||||
|
) {
|
||||||
|
const isAuthenticated = this.isAuthenticated();
|
||||||
|
this.authenticationSubject = new BehaviorSubject<boolean>(isAuthenticated);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAuthenticated$(): Observable<boolean> {
|
||||||
|
return this.authenticationSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
setAuthenticated(loginResponse: LoginResponse): void {
|
||||||
|
const jwt = loginResponse.accessToken;
|
||||||
|
this.cookieService.set(COOKIE_JWT, jwt);
|
||||||
|
|
||||||
|
const refreshToken = loginResponse.refreshToken;
|
||||||
|
this.cookieService.set(COOKIE_REFRESH_TOKEN, refreshToken);
|
||||||
|
this.authenticationSubject.next(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnonymous(): void {
|
||||||
|
this.cookieService.delete(COOKIE_JWT);
|
||||||
|
this.cookieService.delete(COOKIE_REFRESH_TOKEN);
|
||||||
|
this.authenticationSubject.next(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
const jwt = this.cookieService.get(COOKIE_JWT);
|
||||||
|
return jwt?.length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
sportshub-gui/src/app/core/services/message.service.ts
Normal file
15
sportshub-gui/src/app/core/services/message.service.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {inject, Injectable} from "@angular/core";
|
||||||
|
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||||
|
|
||||||
|
const MESSAGE_DURATION = 5000;
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MessageService {
|
||||||
|
private matSnackBar = inject(MatSnackBar);
|
||||||
|
|
||||||
|
display(message: string): void {
|
||||||
|
this.matSnackBar.open(message, 'Close', { duration: MESSAGE_DURATION });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
<a class="title" routerLink="/">SportsHub</a>
|
<a class="title" routerLink="/">SportsHub</a>
|
||||||
<div id="menu">
|
<div id="menu">
|
||||||
<a routerLink="/login">Login</a>
|
<a routerLink="/login" *ngIf="(isAuthenticated$ | async) === false">
|
||||||
|
<mat-icon>login</mat-icon>
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
|
<a routerLink="/logout" *ngIf="isAuthenticated$ | async" class="logout">
|
||||||
|
<mat-icon>logout</mat-icon>
|
||||||
|
Logout
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: .5em 1em;
|
padding: .5em 1em;
|
||||||
|
background-color: #004680;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
@@ -11,7 +12,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .5em;
|
padding: .5em;
|
||||||
color: darkslategray;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu {
|
#menu {
|
||||||
@@ -31,6 +32,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .5em 1em;
|
padding: .5em 1em;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
gap: .5em;
|
||||||
|
|
||||||
|
&.logout {
|
||||||
|
background-color: #c20000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,28 @@
|
|||||||
import {Component} from "@angular/core";
|
import {Component} from "@angular/core";
|
||||||
import {RouterLink} from "@angular/router";
|
import {RouterLink} from "@angular/router";
|
||||||
|
import {AuthenticationService} from "../core/services/authentication.service";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
import {AsyncPipe, NgIf} from "@angular/common";
|
||||||
|
import {MatIconModule} from "@angular/material/icon";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
RouterLink
|
RouterLink,
|
||||||
|
AsyncPipe,
|
||||||
|
NgIf,
|
||||||
|
MatIconModule
|
||||||
],
|
],
|
||||||
styleUrls: ['./header.component.scss']
|
styleUrls: ['./header.component.scss']
|
||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent {
|
||||||
|
isAuthenticated$: Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authenticationService: AuthenticationService
|
||||||
|
) {
|
||||||
|
this.isAuthenticated$ = this.authenticationService.isAuthenticated$;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user