Skip to content

Commit

Permalink
feat: Add check for expired user-token to every initial page-load
Browse files Browse the repository at this point in the history
  • Loading branch information
elwinschmitz committed Jun 12, 2024
1 parent 595bf33 commit 979abd3
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 18 deletions.
12 changes: 11 additions & 1 deletion interfaces/Portal/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { MsalService } from '@azure/msal-angular';
import { Subscription } from 'rxjs';
import { environment } from 'src/environments/environment';
import { AppRoutes } from './app-routes.enum';
import { AuthService } from './auth/auth.service';
import { LanguageService } from './services/language.service';
import { LoggingService } from './services/logging.service';
Expand Down Expand Up @@ -32,14 +33,23 @@ export class AppComponent implements OnInit, OnDestroy {
}
}

public ngOnInit(): void {
public async ngOnInit() {
if (this.useSso) {
this.authService.logoutNonSsoUser();

this.msalSubscription = this.msalService
.handleRedirectObservable()
.subscribe();
}

if (
// Do not check the current users' logged-in state on "login-related" pages:
![AppRoutes.login, AppRoutes.auth].includes(
window.location.pathname.substring(1) as AppRoutes,
)
) {
this.authService.refreshCurrentUser();
}
}

public ngOnDestroy(): void {
Expand Down
15 changes: 9 additions & 6 deletions interfaces/Portal/src/app/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export class AuthService {
return {
username: user.username,
permissions: user.permissions,
expires: user.expires ? user.expires : '',
expires: user.expires ? user.expires : undefined,
isAdmin: user.isAdmin,
isEntraUser: user.isEntraUser,
};
Expand Down Expand Up @@ -191,19 +191,22 @@ export class AuthService {
});
}

// TODO: Think of a better name for this method
public async processAzureAuthSuccess(redirectToHome = false): Promise<void> {
public async refreshCurrentUser() {
const userDto = await this.programsService.getCurrentUser();

if (!userDto || !userDto.user) {
localStorage.removeItem(USER_KEY);
this.router.navigate(['/', AppRoutes.login]);
await this.logout();
return;
}

await this.checkSsoTokenExpirationDate();
this.setUserInStorage(userDto.user);
this.updateAuthenticationState();
}

// TODO: Think of a better name for this method
public async processAzureAuthSuccess(redirectToHome = false) {
await this.refreshCurrentUser();
await this.checkSsoTokenExpirationDate();

if (redirectToHome) {
setTimeout(() => {
Expand Down
1 change: 0 additions & 1 deletion services/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ SECRETS_121_SERVICE_SECRET=121_service_secret
# -----------------

# To enable single-sign-on via Azure Entra ID, use: `TRUE` to enable, leave empty or out to disable.
# NOTE: Not used at the moment, is is set in front-end now. Should be moved to back-end though.
USE_SSO_AZURE_ENTRA=

# Azure Entra environment-specific IDs.
Expand Down
9 changes: 6 additions & 3 deletions services/121-service/src/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,12 +280,15 @@ export class UserController {
description: 'No user detectable from cookie or no cookie present',
})
public async findMe(@Req() req): Promise<UserRO> {
const username = req.user.username;
if (!username) {
if (!req.user || !req.user.username) {
const errors = `No user detectable from cookie or no cookie present'`;
throw new HttpException({ errors }, HttpStatus.UNAUTHORIZED);
}
return await this.userService.getUserRoByUsernameOrThrow(username);

return await this.userService.getUserRoByUsernameOrThrow(
req.user.username,
req.user.exp,
);
}

// This endpoint searches users accross all programs, which is needed to add a user to a program
Expand Down
3 changes: 2 additions & 1 deletion services/121-service/src/user/user.interface.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { PermissionEnum } from '@121-service/src/user/enum/permission.enum';

interface UserData {
export interface UserData {
id: number;
username?: string;
permissions: UserPermissions;
isAdmin?: boolean;
isEntraUser?: boolean;
lastLogin?: Date;
expires?: Date;
}

export interface UserRO {
Expand Down
29 changes: 23 additions & 6 deletions services/121-service/src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { PermissionEntity } from '@121-service/src/user/permissions.entity';
import { UserRoleEntity } from '@121-service/src/user/user-role.entity';
import { UserType } from '@121-service/src/user/user-type-enum';
import { UserEntity } from '@121-service/src/user/user.entity';
import { UserRO } from '@121-service/src/user/user.interface';
import { UserData, UserRO } from '@121-service/src/user/user.interface';
import { HttpStatus, Inject, Injectable, Scope } from '@nestjs/common';
import { HttpException } from '@nestjs/common/exceptions/http.exception';
import { REQUEST } from '@nestjs/core';
Expand Down Expand Up @@ -493,7 +493,10 @@ export class UserService {
return user;
}

public async getUserRoByUsernameOrThrow(username: string): Promise<UserRO> {
public async getUserRoByUsernameOrThrow(
username: string,
tokenExpiration?: number,
): Promise<UserRO> {
const user = await this.userRepository.findOne({
where: { username: username },
relations: [
Expand All @@ -506,7 +509,8 @@ export class UserService {
const errors = `User not found'`;
throw new HttpException({ errors }, HttpStatus.NOT_FOUND);
}
return await this.buildUserRO(user);

return await this.buildUserRO(user, tokenExpiration);
}

public generateJWT(user: UserEntity): string {
Expand Down Expand Up @@ -546,18 +550,31 @@ export class UserService {
}
}

private async buildUserRO(user: UserEntity): Promise<UserRO> {
private async buildUserRO(
user: UserEntity,
tokenExpiration?: number,
): Promise<UserRO> {
const permissions = await this.buildPermissionsObject(user.id);

const userRO = {
const userData: UserData = {
id: user.id,
username: user.username ?? undefined,
permissions,
isAdmin: user.admin,
isEntraUser: user.isEntraUser,
lastLogin: user.lastLogin ?? undefined,
};
return { user: userRO };

// For SSO-users, token expiration is handled by Azure
if (
!!process.env.USE_SSO_AZURE_ENTRA &&
!user.isEntraUser &&
tokenExpiration
) {
userData.expires = new Date(tokenExpiration * 1_000);
}

return { user: userData };
}

private async buildPermissionsObject(userId: number): Promise<any> {
Expand Down

0 comments on commit 979abd3

Please sign in to comment.