From d76e36269df1ba75dad272ce5f6b5ad02d3be158 Mon Sep 17 00:00:00 2001 From: fenn-cs Date: Tue, 20 Jun 2023 17:35:57 +0100 Subject: [PATCH] Refresh authToken when it expires Access tokens have lifetimes that might end before the user wants to end their authenticated session. Hence, we need to refresh the user access token after an expiry detection. Resolves: https://github.com/PermanentOrg/sftp-service/issues/175 Signed-off-by: fenn-cs --- .env.example | 1 + src/classes/AuthenticationSession.ts | 35 ++++++++++++++++++++++++++++ src/classes/SftpSessionHandler.ts | 6 +++++ 3 files changed, 42 insertions(+) diff --git a/.env.example b/.env.example index 38e49456..910c61a1 100644 --- a/.env.example +++ b/.env.example @@ -40,3 +40,4 @@ PERMANENT_API_BASE_PATH=${LOCAL_TEMPORARY_AUTH_TOKEN} # See https://fusionauth.io/docs/v1/tech/apis/api-keys FUSION_AUTH_HOST=${FUSION_AUTH_HOST} FUSION_AUTH_KEY=${FUSION_AUTH_KEY} +FUSION_AUTH_APP_ID=${FUSION_AUTH_APP_ID} diff --git a/src/classes/AuthenticationSession.ts b/src/classes/AuthenticationSession.ts index 9d43f3ba..666dae14 100644 --- a/src/classes/AuthenticationSession.ts +++ b/src/classes/AuthenticationSession.ts @@ -15,10 +15,16 @@ enum FusionAuthStatusCode { export class AuthenticationSession { public authToken = ''; + public refreshToken = ''; + public readonly authContext; + private authTokenExpiresAt = 0; + private readonly fusionAuthClient; + private readonly fusionAuthAppId = process.env.FUSION_AUTH_APP_ID ?? ''; + private twoFactorId = ''; private twoFactorMethods: TwoFactorMethod[] = []; @@ -32,6 +38,32 @@ export class AuthenticationSession { this.promptForPassword(); } + public obtainNewAuthTokenUsingRefreshToken(): void { + this.fusionAuthClient.exchangeRefreshTokenForAccessToken(this.refreshToken, '', '', '', '') + .then((clientResponse) => { + this.authToken = clientResponse.response.access_token ?? ''; + }) + .catch((clientResponse: unknown) => { + const message = isPartialClientResponse(clientResponse) + ? clientResponse.exception.message + : ''; + logger.warn(`Error obtaining refresh token : ${message}`); + this.authContext.reject(); + }); + } + + public tokenExpired(): boolean { + const expirationDate = new Date(this.authTokenExpiresAt); + return expirationDate <= new Date(); + } + + public tokenWouldExpireSoon(minutes = 5): boolean { + const expirationDate = new Date(this.authTokenExpiresAt); + const currentTime = new Date(); + const timeDifferenceMinutes = (expirationDate.getTime() - currentTime.getTime()) / (1000 * 60); + return timeDifferenceMinutes <= minutes; + } + private promptForPassword(): void { this.authContext.prompt( { @@ -46,6 +78,7 @@ export class AuthenticationSession { private processPasswordResponse([password]: string[]): void { this.fusionAuthClient.login({ + applicationId: this.fusionAuthAppId, loginId: this.authContext.username, password, }).then((clientResponse) => { @@ -57,6 +90,8 @@ export class AuthenticationSession { username: this.authContext.username, }); this.authToken = clientResponse.response.token; + this.authTokenExpiresAt = clientResponse.response.tokenExpirationInstant ?? 0; + this.refreshToken = clientResponse.response.refreshToken ?? ''; this.authContext.accept(); return; } diff --git a/src/classes/SftpSessionHandler.ts b/src/classes/SftpSessionHandler.ts index 5a44112d..2603e384 100644 --- a/src/classes/SftpSessionHandler.ts +++ b/src/classes/SftpSessionHandler.ts @@ -958,6 +958,12 @@ export class SftpSessionHandler { } private getCurrentPermanentFileSystem(): PermanentFileSystem { + if ( + this.authenticationSession.tokenExpired() + || this.authenticationSession.tokenWouldExpireSoon() + ) { + this.authenticationSession.obtainNewAuthTokenUsingRefreshToken(); + } return this.permanentFileSystemManager .getCurrentPermanentFileSystemForUser( this.authenticationSession.authContext.username,