From d0b0d386df68747af21cd365d97b6bb90d1f60ef Mon Sep 17 00:00:00 2001 From: "Fon E. Noel NFEBE" Date: Thu, 29 Jun 2023 20:49:43 +0100 Subject: [PATCH] Add custom fusion auth client To get a `refreshToken` with the login response we need to ask FushionAuth. The `@fusionauth/typescript-client` login method does not make that possible. This commit adds a custom FusionAuth client that would hit the FusionAuth endpoints directly in cases where we need it. Signed-off-by: Fon E. Noel NFEBE --- .env.example | 1 + src/classes/AuthenticationSession.ts | 10 +++-- src/fusionAuth.ts | 67 +++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index 52b4b335..12dd2928 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..246dbf2c 100644 --- a/src/classes/AuthenticationSession.ts +++ b/src/classes/AuthenticationSession.ts @@ -2,6 +2,7 @@ import { logger } from '../logger'; import { getFusionAuthClient, isPartialClientResponse, + FusionAuthApiClient, } from '../fusionAuth'; import type { KeyboardAuthContext } from 'ssh2'; import type { TwoFactorMethod } from '@fusionauth/typescript-client'; @@ -19,6 +20,8 @@ export class AuthenticationSession { private readonly fusionAuthClient; + private readonly FusionAuthApiClient; + private twoFactorId = ''; private twoFactorMethods: TwoFactorMethod[] = []; @@ -26,6 +29,7 @@ export class AuthenticationSession { public constructor(authContext: KeyboardAuthContext) { this.authContext = authContext; this.fusionAuthClient = getFusionAuthClient(); + this.FusionAuthApiClient = new FusionAuthApiClient(); } public invokeAuthenticationFlow(): void { @@ -45,10 +49,10 @@ export class AuthenticationSession { } private processPasswordResponse([password]: string[]): void { - this.fusionAuthClient.login({ - loginId: this.authContext.username, + this.FusionAuthApiClient.login( + this.authContext.username, password, - }).then((clientResponse) => { + ).then((clientResponse) => { switch (clientResponse.statusCode) { case FusionAuthStatusCode.Success: case FusionAuthStatusCode.SuccessButUnregisteredInApp: diff --git a/src/fusionAuth.ts b/src/fusionAuth.ts index ca710ee3..06314dc3 100644 --- a/src/fusionAuth.ts +++ b/src/fusionAuth.ts @@ -1,4 +1,5 @@ import { FusionAuthClient } from '@fusionauth/typescript-client'; +import fetch from 'node-fetch'; export const getFusionAuthClient = (): FusionAuthClient => new FusionAuthClient( process.env.FUSION_AUTH_KEY ?? '', @@ -13,6 +14,68 @@ export interface PartialClientResponse { export const isPartialClientResponse = (obj: unknown): obj is PartialClientResponse => ( typeof obj === 'object' - && obj !== null - && 'exception' in obj + && obj !== null + && 'exception' in obj ); + +export interface LoginResponse { + token?: string; + refreshToken?: string; + tokenExpirationInstant?: number; + twoFactorId?: string; + methods?: string[]; +} + +export class ClientResponse { + public statusCode: number; + + public response: T; + + public exception: Error; + + wasSuccessful(): boolean { + return this.statusCode >= 200 && this.statusCode < 300; + } +} + +export class FusionAuthApiClient { + private readonly baseUrl: string; + + private readonly headers: Record; + + private readonly applicationId: string = process.env.FUSION_AUTH_APP_ID ?? ''; + + constructor(apiKey: string = process.env.FUSION_AUTH_KEY ?? '', baseUrl: string = process.env.FUSION_AUTH_HOST ?? '') { + this.baseUrl = baseUrl; + this.headers = { + Authorization: `${apiKey}`, + 'Content-Type': 'application/json', + }; + } + + public async login(loginId: string, password: string, applicationId: string = this.applicationId): Promise> { + return new Promise((resolve, reject) => { + fetch(`${this.baseUrl}/api/login`, { + method: 'POST', + body: JSON.stringify({ + loginId, + password, + applicationId, + }), + headers: this.headers, + }).then(async (response) => { + const clientResponse = new ClientResponse(); + clientResponse.statusCode = response.status; + const responseBody: LoginResponse = await response.json(); + if (response.ok) { + clientResponse.response = responseBody; + } + const err: Error = { name: '', message: JSON.stringify(responseBody) }; + clientResponse.exception = err; + resolve(clientResponse); + }).catch((err) => { + reject(err); + }); + }); + } +}