Skip to content

Commit

Permalink
fix(dsp-app): Resolves DEV-2687 (#1336)
Browse files Browse the repository at this point in the history
Co-authored-by: Julien Schneider <julien.schneider.1991@gmail.com>
  • Loading branch information
irmastnt and derschnee68 committed Jan 11, 2024
1 parent c5ac901 commit d6211f9
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 83 deletions.
2 changes: 0 additions & 2 deletions apps/dsp-app/src/app/app.module.ts
Expand Up @@ -56,7 +56,6 @@ import { DisableContextMenuDirective } from './main/directive/disable-context-me
import { InvalidControlScrollDirective } from './main/directive/invalid-control-scroll.directive';
import { FooterComponent } from './main/footer/footer.component';
import { GridComponent } from './main/grid/grid.component';
import { AuthGuardComponent } from './main/guard/auth-guard.component';
import { HeaderComponent } from './main/header/header.component';
import { HelpComponent } from './main/help/help.component';
import { AuthInterceptor } from './main/http-interceptors/auth-interceptor';
Expand Down Expand Up @@ -196,7 +195,6 @@ export function httpLoaderFactory(httpClient: HttpClient) {
AppComponent,
ArchiveComponent,
AudioComponent,
AuthGuardComponent,
AvTimelineComponent,
DescriptionComponent,
BooleanValueComponent,
Expand Down
37 changes: 24 additions & 13 deletions apps/dsp-app/src/app/main/action/login-form/login-form.component.ts
@@ -1,9 +1,10 @@
import { Location } from '@angular/common';
import { DOCUMENT, Location } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Inject,
Input,
OnDestroy,
OnInit,
Expand All @@ -12,9 +13,10 @@ import {
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { AuthError, AuthService } from '@dasch-swiss/vre/shared/app-session';
import { UserStateModel } from '@dasch-swiss/vre/shared/app-state';
import { Subject } from 'rxjs';
import { map, take, takeLast } from 'rxjs/operators';
import { LoadUserAction, UserSelectors } from '@dasch-swiss/vre/shared/app-state';
import { Actions, Store, ofActionSuccessful } from '@ngxs/store';
import { Observable, Subject, combineLatest } from 'rxjs';
import { take, takeLast } from 'rxjs/operators';
import {
ComponentCommunicationEventService,
EmitEvent,
Expand All @@ -28,6 +30,9 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LoginFormComponent implements OnInit, OnDestroy {
get isLoggedIn$(): Observable<boolean> {
return this._authService.isSessionValid$();
}
/**
* set whether or not you want icons to display in the input fields
*
Expand Down Expand Up @@ -99,7 +104,10 @@ export class LoginFormComponent implements OnInit, OnDestroy {
private _authService: AuthService,
private route: ActivatedRoute,
private location: Location,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
private _actions$: Actions,
private _store: Store,
@Inject(DOCUMENT) private document: Document
) {}

/**
Expand All @@ -109,7 +117,7 @@ export class LoginFormComponent implements OnInit, OnDestroy {
*/
ngOnInit() {
this.buildLoginForm();
this.returnUrl = this.getReturnUrl() || '/';
this.returnUrl = this.getReturnUrl();
}

ngOnDestroy(): void {
Expand Down Expand Up @@ -142,16 +150,19 @@ export class LoginFormComponent implements OnInit, OnDestroy {
next: loginResult => {
if (loginResult) {
this._componentCommsService.emit(new EmitEvent(Events.loginSuccess, true));

return this._authService
.loadUser(identifier)
this._store.dispatch(new LoadUserAction(identifier));
return combineLatest([
this._actions$.pipe(ofActionSuccessful(LoadUserAction)),
this._store.select(UserSelectors.user),
])
.pipe(take(1))
.pipe(map((result: any) => result.user))
.subscribe((user: UserStateModel) => {
.subscribe(([action, user]) => {
this.loading = false;
this._authService.loginSuccessfulEvent.emit(user.user);
this._authService.loginSuccessfulEvent.emit(user);
this.cd.markForCheck();
this.router.navigate([this.returnUrl]);
if (this.returnUrl) {
this.router.navigate([this.returnUrl]);
}
});
}
},
Expand Down
14 changes: 0 additions & 14 deletions apps/dsp-app/src/app/main/guard/auth-guard.component.ts

This file was deleted.

11 changes: 5 additions & 6 deletions apps/dsp-app/src/app/main/guard/auth.guard.ts
Expand Up @@ -2,9 +2,10 @@ import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { ReadUser } from '@dasch-swiss/dsp-js';
import { RouteConstants } from '@dasch-swiss/vre/shared/app-config';
import { AuthService } from '@dasch-swiss/vre/shared/app-session';
import { CurrentPageSelectors, SetUserAction, UserSelectors } from '@dasch-swiss/vre/shared/app-state';
import { Actions, ofActionCompleted, Select, Store } from '@ngxs/store';
import { SetUserAction, UserSelectors } from '@dasch-swiss/vre/shared/app-state';
import { Actions, Select, Store, ofActionCompleted } from '@ngxs/store';
import { Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';

Expand Down Expand Up @@ -32,7 +33,7 @@ export class AuthGuard implements CanActivate {
return this.store.dispatch(new SetUserAction(user));
}
}),
switchMap(() => this._authService.isLoggedIn$),
switchMap(() => this._authService.isSessionValid$(true)),
map(isLoggedIn => {
if (isLoggedIn) {
return true;
Expand All @@ -45,8 +46,6 @@ export class AuthGuard implements CanActivate {
}

private _goToHomePage() {
this.document.defaultView.location.href =
`${this.document.defaultView.location.href}?` +
`returnLink=${this.store.selectSnapshot(CurrentPageSelectors.loginReturnLink)}`;
this.document.defaultView.location.href = `${RouteConstants.home}?returnLink=${this.document.defaultView.location.href}`;
}
}
Expand Up @@ -13,8 +13,6 @@ import { map } from 'rxjs/operators';
providedIn: 'root',
})
export class OntologyClassInstanceGuard implements CanActivate {
isLoggedIn$: Observable<boolean> = this.authService.isLoggedIn$;

@Select(UserSelectors.isSysAdmin) isSysAdmin$: Observable<boolean>;
@Select(UserSelectors.userProjects) userProjects$: Observable<StoredProject[]>;

Expand All @@ -26,12 +24,12 @@ export class OntologyClassInstanceGuard implements CanActivate {

canActivate(activatedRoute: ActivatedRouteSnapshot): Observable<boolean> {
const instanceId = activatedRoute.params[RouteConstants.instanceParameter];
return combineLatest([this.isLoggedIn$, this.isSysAdmin$, this.userProjects$]).pipe(
map(([isLoggedIn, isSysAdmin, userProjects]) => {
return combineLatest([this.authService.isSessionValid$(), this.isSysAdmin$, this.userProjects$]).pipe(
map(([isSessionValid, isSysAdmin, userProjects]) => {
const projectUuid = activatedRoute.parent.params[RouteConstants.uuidParameter];
const isAddInstance = instanceId === RouteConstants.addClassInstance;

if (!isLoggedIn && isAddInstance) {
if (!isSessionValid && isAddInstance) {
this.router.navigateByUrl(`/${RouteConstants.project}/${projectUuid}`);
return false;
}
Expand Down
11 changes: 9 additions & 2 deletions apps/dsp-app/src/app/user/user-menu/user-menu.component.ts
@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatMenuTrigger } from '@angular/material/menu';
import { Router } from '@angular/router';
import { User } from '@dasch-swiss/dsp-js';
import { RouteConstants } from '@dasch-swiss/vre/shared/app-config';
import { AuthService } from '@dasch-swiss/vre/shared/app-session';
Expand All @@ -22,13 +23,19 @@ export class UserMenuComponent implements OnInit, OnDestroy {

private ngUnsubscribe: Subject<void> = new Subject<void>();

isLoggedIn$: Observable<boolean> = this._authService.isLoggedIn$;
get isLoggedIn$(): Observable<boolean> {
return this._authService.isSessionValid$();
}

@Select(UserSelectors.user) user$: Observable<User>;
@Select(UserSelectors.isSysAdmin) isSysAdmin$: Observable<User>;

systemLink = RouteConstants.system;

constructor(private _authService: AuthService) {}
constructor(
private _authService: AuthService,
private _router: Router
) {}

ngOnInit() {
this.navigation = [
Expand Down
@@ -1,4 +1,5 @@
import { inject, Injectable } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
BuildTag,
BuildTagToken,
Expand Down Expand Up @@ -47,16 +48,19 @@ export class DatadogRumService {
});

// depending on the session state, activate or deactivate the user
this.authService.isLoggedIn$.subscribe((isLoggedIn: boolean) => {
if (isLoggedIn) {
if (this.authService.tokenUser) {
const id: string = uuidv5(this.authService.tokenUser, uuidv5.URL);
this.setActiveUser(id);
} else {
this.removeActiveUser();
this.authService
.isSessionValid$()
.pipe(takeUntilDestroyed())
.subscribe((isSessionValid: boolean) => {
if (isSessionValid) {
if (this.authService.tokenUser) {
const id: string = uuidv5(this.authService.tokenUser, uuidv5.URL);
this.setActiveUser(id);
} else {
this.removeActiveUser();
}
}
}
});
});
}
});
}
Expand Down
Expand Up @@ -12,13 +12,16 @@ export class PendoAnalyticsService {
private environment: string = this.config.environment;

constructor() {
this.authService.isLoggedIn$.pipe(takeUntilDestroyed()).subscribe((isLoggedIn: boolean) => {
if (isLoggedIn) {
this.setActiveUser(this.authService.tokenUser);
} else {
this.removeActiveUser();
}
});
this.authService
.isSessionValid$()
.pipe(takeUntilDestroyed())
.subscribe((isSessionValid: boolean) => {
if (isSessionValid) {
this.setActiveUser(this.authService.tokenUser);
} else {
this.removeActiveUser();
}
});
}

/**
Expand Down
35 changes: 12 additions & 23 deletions libs/vre/shared/app-session/src/lib/auth.service.ts
@@ -1,30 +1,24 @@
import { EventEmitter, Injectable, Output, inject } from '@angular/core';
import { Router } from '@angular/router';
import { ApiResponseData, ApiResponseError, CredentialsResponse, LoginResponse, User } from '@dasch-swiss/dsp-js';
import { Auth, DspApiConnectionToken } from '@dasch-swiss/vre/shared/app-config';
import { AppErrorHandler } from '@dasch-swiss/vre/shared/app-error-handler';
import { Auth, DspApiConnectionToken, RouteConstants } from '@dasch-swiss/vre/shared/app-config';
import {
LoadUserAction,
ClearProjectsAction,
LogUserOutAction,
UserStateModel,
ClearListsAction,
ClearOntologiesAction,
ClearProjectsAction,
LogUserOutAction,
} from '@dasch-swiss/vre/shared/app-state';
import { Store } from '@ngxs/store';
import jwt_decode, { JwtPayload } from 'jwt-decode';
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
import { catchError, tap, switchMap, map, takeLast, take } from 'rxjs/operators';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, switchMap, take, takeLast, tap } from 'rxjs/operators';
import { LoginError, ServerError } from './error';

@Injectable({ providedIn: 'root' })
export class AuthService {
private tokenRefreshIntervalId: any;
private _isLoggedIn$ = new BehaviorSubject<boolean>(this.isLoggedIn());
private _dspApiConnection = inject(DspApiConnectionToken);

isLoggedIn$ = this._isLoggedIn$.asObservable();

get tokenUser() {
return this.getTokenUser();
}
Expand All @@ -36,7 +30,7 @@ export class AuthService {
private router: Router // private intervalWrapper: IntervalWrapperService
) {
// check if the (possibly) existing session is still valid and if not, destroy it
this.isSessionValid()
this.isSessionValid$()
.pipe(takeLast(1))
.subscribe(valid => {
if (!valid) {
Expand All @@ -54,7 +48,7 @@ export class AuthService {
* If a json web token exists, it doesn't mean that the knora api credentials are still valid.
*
*/
isSessionValid(): Observable<boolean> {
isSessionValid$(forceLogout: boolean = false): Observable<boolean> {
// mix of checks with session.validation and this.authenticate
const accessToken = this.getAccessToken();
if (accessToken) {
Expand Down Expand Up @@ -83,7 +77,10 @@ export class AuthService {
} else {
// no session found; update knora api connection with empty jwt
this._dspApiConnection.v2.jsonWebToken = '';
this.doLogoutUser();
if (forceLogout) {
this.doLogoutUser();
}

return of(false);
}
}
Expand All @@ -106,14 +103,6 @@ export class AuthService {
}
}

loadUser(username: string): Observable<UserStateModel> {
return this.store.dispatch(new LoadUserAction(username)).pipe(
tap(() => {
this._isLoggedIn$.next(true);
})
);
}

/**
* Login user
* @param identifier can be the email or the username
Expand Down Expand Up @@ -186,7 +175,6 @@ export class AuthService {
}

doLogoutUser() {
this._isLoggedIn$.next(false);
this.removeTokens();
this.store.dispatch([
new LogUserOutAction(),
Expand All @@ -195,6 +183,7 @@ export class AuthService {
new ClearOntologiesAction(),
]);
clearTimeout(this.tokenRefreshIntervalId);
this.router.navigate([RouteConstants.home], { replaceUrl: true });
}

isLoggedIn() {
Expand Down
5 changes: 3 additions & 2 deletions libs/vre/shared/app-state/src/lib/user/user.state.ts
@@ -1,7 +1,6 @@
import { Inject, Injectable } from '@angular/core';
import { Injectable } from '@angular/core';
import { ApiResponseError, Constants, ReadUser } from '@dasch-swiss/dsp-js';
import { UserApiService } from '@dasch-swiss/vre/shared/app-api';
import { DspApiConnectionToken } from '@dasch-swiss/vre/shared/app-config';
import { AppErrorHandler } from '@dasch-swiss/vre/shared/app-error-handler';
import { Action, State, StateContext } from '@ngxs/store';
import { of } from 'rxjs';
Expand Down Expand Up @@ -83,6 +82,8 @@ export class UserState {

@Action(SetUserAction)
setUser(ctx: StateContext<UserStateModel>, { user }: SetUserAction) {
if (!user) return;

const state = ctx.getState();
const userIndex = state.allUsers.findIndex(u => u.id === user.id);
if (userIndex > -1) {
Expand Down

0 comments on commit d6211f9

Please sign in to comment.