Redirect to sign-in page when user session timed out (#16005)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
孙世军 2021-11-22 10:47:49 +08:00 committed by GitHub
parent 0a845d6369
commit 21dfba7330
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 39 additions and 21 deletions

View File

@ -51,3 +51,4 @@ export class SignInService {
} }
export const UN_LOGGED_PARAM: string = 'publicAndNotLogged'; export const UN_LOGGED_PARAM: string = 'publicAndNotLogged';
export const YES: string = 'yes';

View File

@ -48,7 +48,7 @@ import { errorHandler } from "../../../../../../../shared/units/shared.utils";
import { ConfirmationDialogComponent } from "../../../../../../../shared/components/confirmation-dialog"; import { ConfirmationDialogComponent } from "../../../../../../../shared/components/confirmation-dialog";
import { ConfirmationMessage } from "../../../../../../global-confirmation-dialog/confirmation-message"; import { ConfirmationMessage } from "../../../../../../global-confirmation-dialog/confirmation-message";
import { ConfirmationAcknowledgement } from "../../../../../../global-confirmation-dialog/confirmation-state-message"; import { ConfirmationAcknowledgement } from "../../../../../../global-confirmation-dialog/confirmation-state-message";
import { UN_LOGGED_PARAM } from "../../../../../../../account/sign-in/sign-in.service"; import { UN_LOGGED_PARAM, YES } from "../../../../../../../account/sign-in/sign-in.service";
import { Label } from "../../../../../../../../../ng-swagger-gen/models/label"; import { Label } from "../../../../../../../../../ng-swagger-gen/models/label";
import { LabelService } from "../../../../../../../../../ng-swagger-gen/services/label.service"; import { LabelService } from "../../../../../../../../../ng-swagger-gen/services/label.service";
import { EventService, HarborEvent } from "../../../../../../../services/event-service/event.service"; import { EventService, HarborEvent } from "../../../../../../../services/event-service/event.service";
@ -59,7 +59,6 @@ export interface LabelState {
show: boolean; show: boolean;
} }
export const AVAILABLE_TIME = '0001-01-01T00:00:00.000Z'; export const AVAILABLE_TIME = '0001-01-01T00:00:00.000Z';
const YES: string = 'yes';
const PAGE_SIZE: number = 100; const PAGE_SIZE: number = 100;
@Component({ @Component({
selector: 'artifact-list-tab', selector: 'artifact-list-tab',

View File

@ -1,12 +1,9 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { ElementRef } from '@angular/core'; import { ElementRef } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { ClarityModule } from '@clr/angular';
import { Message } from './message'; import { Message } from './message';
import { MessageService } from './message.service';
import { MessageComponent } from './message.component'; import { MessageComponent } from './message.component';
import { AlertType } from "../../entities/shared.const"; import { AlertType } from "../../entities/shared.const";
import { SharedTestingModule } from "../../shared.module";
describe('MessageComponent', () => { describe('MessageComponent', () => {
let component: MessageComponent; let component: MessageComponent;
@ -16,14 +13,10 @@ describe('MessageComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
ClarityModule, SharedTestingModule
RouterTestingModule,
TranslateModule.forRoot()
], ],
declarations: [MessageComponent], declarations: [MessageComponent],
providers: [ providers: [
MessageService,
TranslateService,
{provide: ElementRef, useValue: fakeElementRef} {provide: ElementRef, useValue: fakeElementRef}
] ]
}).compileComponents(); }).compileComponents();

View File

@ -19,9 +19,9 @@ import { Message } from './message';
import { MessageService } from './message.service'; import { MessageService } from './message.service';
import { CommonRoutes, dismissInterval, httpStatusCode } from "../../entities/shared.const"; import { CommonRoutes, dismissInterval, httpStatusCode } from "../../entities/shared.const";
import { delUrlParam } from "../../units/utils"; import { delUrlParam } from "../../units/utils";
import { UN_LOGGED_PARAM } from "../../../account/sign-in/sign-in.service"; import { UN_LOGGED_PARAM, YES } from "../../../account/sign-in/sign-in.service";
import { SessionService } from "../../services/session.service";
const YES: string = 'yes';
@Component({ @Component({
selector: 'global-message', selector: 'global-message',
templateUrl: 'message.component.html', templateUrl: 'message.component.html',
@ -44,7 +44,8 @@ export class MessageComponent implements OnInit, OnDestroy {
private messageService: MessageService, private messageService: MessageService,
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private translate: TranslateService) { } private translate: TranslateService,
private session: SessionService) { }
ngOnInit(): void { ngOnInit(): void {
// Only subscribe application level message // Only subscribe application level message
@ -53,6 +54,7 @@ export class MessageComponent implements OnInit, OnDestroy {
message => { message => {
this.globalMessageOpened = true; this.globalMessageOpened = true;
this.globalMessage = message; this.globalMessage = message;
this.checkLoginStatus();
this.messageText = message.message; this.messageText = message.message;
this.translateMessage(message); this.translateMessage(message);
@ -64,6 +66,7 @@ export class MessageComponent implements OnInit, OnDestroy {
message => { message => {
this.globalMessageOpened = true; this.globalMessageOpened = true;
this.globalMessage = message; this.globalMessage = message;
this.checkLoginStatus();
this.messageText = message.message; this.messageText = message.message;
this.translateMessage(message); this.translateMessage(message);
@ -148,4 +151,18 @@ export class MessageComponent implements OnInit, OnDestroy {
isFromGlobalSearch(): boolean { isFromGlobalSearch(): boolean {
return this.route.snapshot.queryParams[UN_LOGGED_PARAM] === YES; return this.route.snapshot.queryParams[UN_LOGGED_PARAM] === YES;
} }
checkLoginStatus() {
if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) {
// User session timed out, then redirect to sign-in page
if (this.session.getCurrentUser() && !this.isSignInUrl() && this.route.snapshot.queryParams[UN_LOGGED_PARAM] !== YES) {
const url = delUrlParam(this.router.url, UN_LOGGED_PARAM);
this.session.clear(); // because of SignInGuard, must clear user session before navigating to sign-in page
this.router.navigate([ CommonRoutes.EMBEDDED_SIGN_IN ], {queryParams: {redirect_url: url}});
}
}
}
isSignInUrl(): boolean {
const url: string = this.router.url?.indexOf('?') === -1 ? this.router.url : this.router.url?.split('?')[0];
return url === CommonRoutes.EMBEDDED_SIGN_IN;
}
} }

View File

@ -16,9 +16,7 @@ import { Router } from '@angular/router';
import { Repository } from '../../../../../ng-swagger-gen/models/repository'; import { Repository } from '../../../../../ng-swagger-gen/models/repository';
import { SearchTriggerService } from '../global-search/search-trigger.service'; import { SearchTriggerService } from '../global-search/search-trigger.service';
import { SessionService } from "../../services/session.service"; import { SessionService } from "../../services/session.service";
import { UN_LOGGED_PARAM } from "../../../account/sign-in/sign-in.service"; import { UN_LOGGED_PARAM, YES } from "../../../account/sign-in/sign-in.service";
const YES: string = 'yes';
@Component({ @Component({
selector: 'list-repository-ro', selector: 'list-repository-ro',

View File

@ -23,6 +23,9 @@ import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators'; import { map, catchError } from 'rxjs/operators';
import { ProjectService } from "../services"; import { ProjectService } from "../services";
import { CommonRoutes } from "../entities/shared.const"; import { CommonRoutes } from "../entities/shared.const";
import { HttpStatusCode } from '@angular/common/http';
import { delUrlParam } from "../units/utils";
import { UN_LOGGED_PARAM, YES } from 'src/app/account/sign-in/sign-in.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -39,12 +42,12 @@ export class MemberGuard implements CanActivate, CanActivateChild {
const user = this.sessionService.getCurrentUser(); const user = this.sessionService.getCurrentUser();
if (user !== null) { if (user !== null) {
return this.hasProjectPerm(state.url, projectId); return this.hasProjectPerm(state.url, projectId, route);
} }
return this.sessionService.retrieveUser().pipe( return this.sessionService.retrieveUser().pipe(
() => { () => {
return this.hasProjectPerm(state.url, projectId); return this.hasProjectPerm(state.url, projectId, route);
}, },
catchError(err => { catchError(err => {
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
@ -57,14 +60,21 @@ export class MemberGuard implements CanActivate, CanActivateChild {
return this.canActivate(route, state); return this.canActivate(route, state);
} }
hasProjectPerm(url: string, projectId: number): Observable<boolean> { hasProjectPerm(url: string, projectId: number, route: ActivatedRouteSnapshot): Observable<boolean> {
// Note: current user will have the permission to visit the project when the user can get response from GET /projects/:id API. // Note: current user will have the permission to visit the project when the user can get response from GET /projects/:id API.
return this.projectService.getProject(projectId).pipe( return this.projectService.getProject(projectId).pipe(
map(() => { map(() => {
return true; return true;
}), }),
catchError(err => { catchError(err => {
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]); // User session timed out, then redirect to sign-in page
if (err.status === HttpStatusCode.Unauthorized && route.queryParams[UN_LOGGED_PARAM] !== YES) {
this.sessionService.clear(); // because of SignInGuard, must clear user session before navigating to sign-in page
this.router.navigate([ CommonRoutes.EMBEDDED_SIGN_IN ],
{queryParams: {redirect_url: delUrlParam(this.router.url, UN_LOGGED_PARAM)}});
} else {
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
}
return of(false); return of(false);
}) })
); );