Merge pull request #1318 from GNS3/enhancement-JWT/1228

I have modified the JWT Interceptor and added refresh token functiona…
This commit is contained in:
Jeremy Grossmann 2022-05-31 15:58:18 +07:00 committed by GitHub
commit b430ab9a1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 205 additions and 136 deletions

View File

@ -1,35 +1,42 @@
<div class="wrapper">
<div class="loginCard">
<mat-card class="matCard">
<div class="loginTitle">
<div class="loginIcon">
<mat-icon class="mat-icon-login" *ngIf="!isLightThemeEnabled" svgIcon="gns3"></mat-icon>
<mat-icon class="mat-icon-login" *ngIf="isLightThemeEnabled" svgIcon="gns3black"></mat-icon>
<div>
<h1>GNS3</h1>
<h6>v{{version}}</h6>
</div>
</div>
</div>
<form [formGroup]="loginForm">
<mat-form-field>
<input matInput formControlName="username" placeholder="Username" />
<mat-error *ngIf="loginForm.get('username').hasError('required')">You must enter username</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" formControlName="password" placeholder="Password" />
<mat-error *ngIf="loginForm.get('password').hasError('required')">You must enter password</mat-error>
</mat-form-field>
</form>
<div class="buttons-bar">
<button class="loginButton" mat-raised-button color="primary" (click)="login()">Login</button>
</div>
</mat-card>
<div class="errorMessage">
<mat-error *ngIf="loginError">Authentication was unsuccessful</mat-error>
<div class="loginCard">
<mat-card class="matCard">
<div class="loginTitle">
<div class="loginIcon">
<mat-icon class="mat-icon-login" *ngIf="!isLightThemeEnabled" svgIcon="gns3"></mat-icon>
<mat-icon class="mat-icon-login" *ngIf="isLightThemeEnabled" svgIcon="gns3black"></mat-icon>
<div>
<h1>GNS3</h1>
<h6>v{{ version }}</h6>
</div>
</div>
</div>
<form [formGroup]="loginForm">
<mat-form-field>
<input matInput formControlName="username" placeholder="Username" />
<mat-error *ngIf="loginForm.get('username').hasError('required')">You must enter username</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" formControlName="password" placeholder="Password" />
<mat-error *ngIf="loginForm.get('password').hasError('required')">You must enter password</mat-error>
</mat-form-field>
</form>
<mat-checkbox
[disabled]="!isRememberMe"
[checked]="isRememberMeCheked"
class="margin-left"
color="primary"
(change)="rememberMe($event)"
>Remember me</mat-checkbox
>
<div class="buttons-bar">
<button class="loginButton" mat-raised-button color="primary" (click)="login()">Login</button>
</div>
</mat-card>
<div class="errorMessage">
<mat-error *ngIf="loginError">Authentication was unsuccessful</mat-error>
</div>
</div>
</div>

View File

@ -12,8 +12,8 @@ mat-form-field {
}
.mat-icon-login {
height: 100px!important;
width: 100px!important;
height: 100px !important;
width: 100px !important;
}
.loginTitle {
@ -35,3 +35,7 @@ mat-form-field {
justify-content: center;
margin-top: 20px;
}
.margin-left {
margin-left: 5px;
}

View File

@ -1,89 +1,123 @@
import { Component, ComponentFactoryResolver, OnInit, ViewEncapsulation } from '@angular/core';
import { Component, DoCheck, OnInit, ViewEncapsulation } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { AuthResponse } from '../../models/authResponse';
import { Server } from '../../models/server';
import { Version } from '../../models/version';
import { LoginService } from '../../services/login.service';
import { ServerDatabase } from '../../services/server.database';
import { ServerService } from '../../services/server.service';
import { ToasterService } from '../../services/toaster.service';
import { AuthResponse } from '../../models/authResponse';
import { VersionService } from '../../services/version.service';
import { Version } from '../../models/version';
import { ThemeService } from '../../services/theme.service';
import { ToasterService } from '../../services/toaster.service';
import { VersionService } from '../../services/version.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
encapsulation: ViewEncapsulation.None,
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class LoginComponent implements OnInit {
private server: Server;
public version: string;
public isLightThemeEnabled: boolean = false;
public loginError: boolean = false;
public returnUrl: string = "";
export class LoginComponent implements OnInit, DoCheck {
private server: Server;
public version: string;
public isLightThemeEnabled: boolean = false;
public loginError: boolean = false;
public returnUrl: string = '';
public isRememberMe: boolean = false;
public isRememberMeCheked: boolean = false;
loginForm = new FormGroup({
username: new FormControl('', [Validators.required]),
password: new FormControl('', [Validators.required])
loginForm = new FormGroup({
username: new FormControl('', [Validators.required]),
password: new FormControl('', [Validators.required]),
});
constructor(
private loginService: LoginService,
private serverService: ServerService,
private serverDatabase: ServerDatabase,
private route: ActivatedRoute,
private router: Router,
private toasterService: ToasterService,
private versionService: VersionService,
private themeService: ThemeService
) {}
async ngOnInit() {
const server_id = this.route.snapshot.paramMap.get('server_id');
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
if (server.authToken) {
this.router.navigate(['/server', this.server.id, 'projects']);
}
this.versionService.get(this.server).subscribe((version: Version) => {
this.version = version.version;
});
});
constructor(
private loginService: LoginService,
private serverService: ServerService,
private serverDatabase: ServerDatabase,
private route: ActivatedRoute,
private router: Router,
private toasterService: ToasterService,
private versionService: VersionService,
private themeService: ThemeService
) {}
this.themeService.getActualTheme() === 'light'
? (this.isLightThemeEnabled = true)
: (this.isLightThemeEnabled = false);
async ngOnInit() {
const server_id = this.route.snapshot.paramMap.get('server_id');
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
this.server = server;
let getCurrentUser = JSON.parse(localStorage.getItem(`isRememberMe`)) ?? null;
if (getCurrentUser && getCurrentUser.isRememberMe) {
this.loginForm.get('username').setValue(getCurrentUser.username);
this.loginForm.get('password').setValue(getCurrentUser.password);
this.isRememberMeCheked = getCurrentUser.isRememberMe;
}
}
if (server.authToken) {
this.router.navigate(['/server', this.server.id, 'projects']);
}
this.versionService.get(this.server).subscribe((version: Version) => {
this.version = version.version;
});
});
this.themeService.getActualTheme() === 'light'
? (this.isLightThemeEnabled = true)
: (this.isLightThemeEnabled = false);
public login() {
if (this.loginForm.get('username').invalid || this.loginForm.get('password').invalid) {
this.toasterService.error('Please enter username and password');
return;
}
public login() {
if (this.loginForm.get('username').invalid || this.loginForm.get('password').invalid) {
this.toasterService.error("Please enter username and password")
return;
let username = this.loginForm.get('username').value;
let password = this.loginForm.get('password').value;
this.loginService.login(this.server, username, password).subscribe(
async (response: AuthResponse) => {
let server = this.server;
server.authToken = response.access_token;
server.username = username;
server.password = password;
server.tokenExpired = false;
await this.serverService.update(server);
if (this.returnUrl.length <= 1) {
this.router.navigate(['/server', this.server.id, 'projects']);
} else {
this.router.navigateByUrl(this.returnUrl);
}
},
(error) => {
this.loginError = true;
}
);
}
let username = this.loginForm.get('username').value;
let password = this.loginForm.get('password').value;
this.loginService.login(this.server, username, password).subscribe(async (response: AuthResponse) => {
let server = this.server;
server.authToken = response.access_token;
server.username = username;
server.password = password;
await this.serverService.update(server);
if (this.returnUrl.length <= 1) {
this.router.navigate(['/server', this.server.id, 'projects'])
} else {
this.router.navigateByUrl(this.returnUrl);
}
}, error => {
this.loginError = true;
});
rememberMe(ev) {
if (ev.checked) {
let curren_user = {
username: this.loginForm.get('username').value,
password: this.loginForm.get('password').value,
isRememberMe: ev.checked,
};
this.isRememberMeCheked = ev.checked;
localStorage.setItem(`isRememberMe`, JSON.stringify(curren_user));
} else {
localStorage.removeItem(`isRememberMe`);
this.loginForm.reset();
this.isRememberMe = ev.checked;
}
}
ngDoCheck() {
if (this.loginForm.get('username').valid && this.loginForm.get('password').valid) {
this.isRememberMe = true;
}
}
}

View File

@ -6,35 +6,20 @@ import { ServerService } from '../services/server.service';
@Injectable()
export class LoginGuard implements CanActivate {
constructor(
private serverService: ServerService,
private loginService: LoginService,
private router: Router
) {}
constructor(private serverService: ServerService, private loginService: LoginService, private router: Router) {}
async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const server_id = next.paramMap.get('server_id');
let server = await this.serverService.get(parseInt(server_id, 10));
try {
await this.loginService.getLoggedUser(server).toPromise();
} catch (e) {
if (e.status === 401) {
server.tokenExpired = true;
await this.serverService.update(server)
try {
let response = await this.loginService.login(server, server.username, server.password).toPromise();
server.authToken = response.access_token;
server.tokenExpired = false;
await this.serverService.update(server)
} catch (e) {
throw e;
}
}
}
return this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
if (server.authToken) return true;
this.router.navigate(['/server', server.id, 'login'], { queryParams: { returnUrl: state.url }});
});
}
async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const server_id = next.paramMap.get('server_id');
this.loginService.server_id = server_id;
let server = await this.serverService.get(parseInt(server_id, 10));
try {
await this.loginService.getLoggedUser(server);
} catch (e) {}
return this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
if (server.authToken && !server.tokenExpired) {
return true;
}
this.router.navigate(['/server', server.id, 'login'], { queryParams: { returnUrl: state.url } });
});
}
}

View File

@ -1,10 +1,45 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpResponse, HttpRequest, HttpHandler } from '@angular/common/http';
import { Observable } from 'rxjs';
import { LoginService } from '@services/login.service';
import { ServerService } from '@services/server.service';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class HttpRequestsInterceptor implements HttpInterceptor {
constructor(private serverService: ServerService, private loginService: LoginService) {}
intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(httpRequest);
return next.handle(httpRequest).pipe(
catchError((err) => {
if (err.status === 401 || err.status === 403) {
this.call();
} else {
return throwError(err);
}
})
);
}
async call() {
let getCurrentUser = JSON.parse(localStorage.getItem(`isRememberMe`)) ?? null;
const server_id = this.loginService.server_id;
let server = await this.serverService.get(parseInt(server_id, 10));
server.tokenExpired = true;
await this.serverService.update(server);
try {
if (getCurrentUser && getCurrentUser.isRememberMe) {
let response = await this.loginService.getLoggedUserRefToken(server, getCurrentUser);
server.authToken = response.access_token;
server.tokenExpired = false;
await this.serverService.update(server);
await this.loginService.getLoggedUser(server);
this.reloadCurrentRoute();
}
} catch (e) {
throw e;
}
}
reloadCurrentRoute() {
location.reload();
}
}

View File

@ -7,6 +7,7 @@ import { AuthResponse } from '../models/authResponse';
@Injectable()
export class LoginService {
server_id:string =''
constructor(private httpServer: HttpServer) {}
login(server: Server, username: string, password: string) {
@ -22,6 +23,9 @@ export class LoginService {
}
getLoggedUser(server: Server) {
return this.httpServer.get(server, "/users/me");
return this.httpServer.get(server, "/users/me").toPromise()
}
async getLoggedUserRefToken(server: Server,current_user):Promise<any> {
return await this.httpServer.post<AuthResponse>(server, "/users/authenticate", {"username":current_user.username,"password":current_user.password}).toPromise()
}
}