From 9a838613b2deed3d947a5d16da3294d9fa5d0324 Mon Sep 17 00:00:00 2001 From: Dan Schultz Date: Tue, 7 Nov 2023 16:27:33 -0500 Subject: [PATCH] Generate tokens just-in-time Auth tokens are now retrieved just-in-time by the permanent file system (rather than being passed during the creation of the permanent file system). This is a critical fix because (1) it prevents certain paths that would lead to stale tokens but also (2) it means that creating a permanent file system becomes a synchronous operation. This also resolves a bug where the failure to generate a token could result in a hanging sftp connection. Issue #288 Permanent file system errors can result in hung connections --- src/classes/PermanentFileSystem.ts | 34 +- src/classes/PermanentFileSystemManager.ts | 5 +- src/classes/SftpSessionHandler.ts | 727 ++++++++++------------ 3 files changed, 354 insertions(+), 412 deletions(-) diff --git a/src/classes/PermanentFileSystem.ts b/src/classes/PermanentFileSystem.ts index 01ef2c52..0b6dc068 100644 --- a/src/classes/PermanentFileSystem.ts +++ b/src/classes/PermanentFileSystem.ts @@ -43,6 +43,7 @@ import type { Attributes, FileEntry, } from 'ssh2'; +import type { AuthTokenManager } from './AuthTokenManager'; const isRootPath = (requestedPath: string): boolean => ( requestedPath === '/' @@ -79,10 +80,10 @@ export class PermanentFileSystem { private archivesCache?: Archive[]; - private readonly authToken; + private readonly authTokenManager; - public constructor(authToken: string) { - this.authToken = authToken; + public constructor(authTokenManager: AuthTokenManager) { + this.authTokenManager = authTokenManager; } private static loadRootFileEntries(): FileEntry[] { @@ -191,7 +192,7 @@ export class PermanentFileSystem { const childName = path.basename(requestedPath); const parentFolder = await this.loadFolder(parentPath); return createFolder( - this.getClientConfiguration(), + await this.getClientConfiguration(), { name: childName, }, @@ -201,7 +202,7 @@ export class PermanentFileSystem { public async deleteDirectory(requestedPath: string): Promise { const account = await getAuthenticatedAccount( - this.getClientConfiguration(), + await this.getClientConfiguration(), ); if (!account.isSftpDeletionEnabled) { throw new OperationNotAllowedError('You must enable SFTP deletion directly in your account settings.'); @@ -220,7 +221,7 @@ export class PermanentFileSystem { const folder = await this.loadFolder(requestedPath); await deleteFolder( - this.getClientConfiguration(), + await this.getClientConfiguration(), folder.id, ); } @@ -242,14 +243,14 @@ export class PermanentFileSystem { fileSystemCompatibleName: archiveRecordName, }; const s3Url = await uploadFile( - this.getClientConfiguration(), + await this.getClientConfiguration(), dataStream, fileFragment, archiveRecordfragment, parentFolder, ); await createArchiveRecord( - this.getClientConfiguration(), + await this.getClientConfiguration(), s3Url, fileFragment, archiveRecordfragment, @@ -259,7 +260,7 @@ export class PermanentFileSystem { public async deleteFile(requestedPath: string): Promise { const account = await getAuthenticatedAccount( - this.getClientConfiguration(), + await this.getClientConfiguration(), ); if (!account.isSftpDeletionEnabled) { throw new OperationNotAllowedError('You must enable SFTP deletion directly in your account settings.'); @@ -274,7 +275,7 @@ export class PermanentFileSystem { ); await deleteArchiveRecord( - this.getClientConfiguration(), + await this.getClientConfiguration(), archiveRecord.id, ); } @@ -338,7 +339,7 @@ export class PermanentFileSystem { childName, ); const populatedArchiveRecord = await getArchiveRecord( - this.getClientConfiguration(), + await this.getClientConfiguration(), archiveRecord.id, archiveId, ); @@ -380,9 +381,10 @@ export class PermanentFileSystem { return this.loadArchiveRecords(archiveRecordPaths); } - private getClientConfiguration(): ClientConfiguration { + private async getClientConfiguration(): Promise { + const authToken = await this.authTokenManager.getAuthToken(); return { - bearerToken: this.authToken, + bearerToken: authToken, baseUrl: process.env.PERMANENT_API_BASE_PATH, }; } @@ -390,7 +392,7 @@ export class PermanentFileSystem { private async loadArchives(): Promise { if (!this.archivesCache) { this.archivesCache = await getArchives( - this.getClientConfiguration(), + await this.getClientConfiguration(), ); } return this.archivesCache; @@ -417,7 +419,7 @@ export class PermanentFileSystem { return cachedArchiveFolders; } const archiveFolders = await getArchiveFolders( - this.getClientConfiguration(), + await this.getClientConfiguration(), archiveId, ); this.archiveFoldersCache.set(archiveId, archiveFolders); @@ -506,7 +508,7 @@ export class PermanentFileSystem { childName, ); const populatedTargetFolder = await getFolder( - this.getClientConfiguration(), + await this.getClientConfiguration(), targetFolder.id, archiveId, ); diff --git a/src/classes/PermanentFileSystemManager.ts b/src/classes/PermanentFileSystemManager.ts index e9415e17..b3d6f86a 100644 --- a/src/classes/PermanentFileSystemManager.ts +++ b/src/classes/PermanentFileSystemManager.ts @@ -1,5 +1,6 @@ import { logger } from '../logger'; import { PermanentFileSystem } from './PermanentFileSystem'; +import type { AuthTokenManager } from './AuthTokenManager'; export class PermanentFileSystemManager { private readonly permanentFileSystems = new Map(); @@ -8,7 +9,7 @@ export class PermanentFileSystemManager { public getCurrentPermanentFileSystemForUser( user: string, - authToken: string, + authTokenManager: AuthTokenManager, ): PermanentFileSystem { logger.silly('Get permanent file system for user', { user }); this.resetDeletionTimeout(user); @@ -16,7 +17,7 @@ export class PermanentFileSystemManager { if (existingFileSystem !== undefined) { return existingFileSystem; } - const permanentFileSystem = new PermanentFileSystem(authToken); + const permanentFileSystem = new PermanentFileSystem(authTokenManager); this.permanentFileSystems.set( user, permanentFileSystem, diff --git a/src/classes/SftpSessionHandler.ts b/src/classes/SftpSessionHandler.ts index 4a47e58c..b3aa89e0 100644 --- a/src/classes/SftpSessionHandler.ts +++ b/src/classes/SftpSessionHandler.ts @@ -94,39 +94,35 @@ export class SftpSessionHandler { attrs, }, ); - this.getCurrentPermanentFileSystem().then((permFileSystem: PermanentFileSystem) => { - permFileSystem.getItemType(filePath) - .then((fileType) => { - switch (fileType) { - case fs.constants.S_IFDIR: - logger.verbose( - 'Response: Status (NO_SUCH_FILE)', - { - reqId, - code: SFTP_STATUS_CODE.NO_SUCH_FILE, - }, - ); - this.sftpConnection.status(reqId, SFTP_STATUS_CODE.NO_SUCH_FILE); - break; - default: { - this.openExistingFileHandler( - reqId, - filePath, - flags, - ); - break; - } - } - }).catch((err: unknown) => { - logger.debug(err); - this.openNewFileHandler( + const permanentfileSystem = this.getCurrentPermanentFileSystem(); + permanentfileSystem.getItemType(filePath).then((fileType) => { + switch (fileType) { + case fs.constants.S_IFDIR: + logger.verbose( + 'Response: Status (NO_SUCH_FILE)', + { + reqId, + code: SFTP_STATUS_CODE.NO_SUCH_FILE, + }, + ); + this.sftpConnection.status(reqId, SFTP_STATUS_CODE.NO_SUCH_FILE); + break; + default: { + this.openExistingFileHandler( reqId, filePath, flags, ); - }); - }).catch((fileSysErr) => { - logger.error(`Error loading file permanent file system ${fileSysErr}`); + break; + } + } + }).catch((err: unknown) => { + logger.debug(err); + this.openNewFileHandler( + reqId, + filePath, + flags, + ); }); } @@ -548,7 +544,6 @@ export class SftpSessionHandler { (async () => { let temporaryFile: TemporaryFile; let fileSize: number; - let permanentFileSystem: PermanentFileSystem; try { temporaryFile = await this.temporaryFileManager.getTemporaryFile(virtualFilePath); @@ -589,24 +584,7 @@ export class SftpSessionHandler { return; } - try { - permanentFileSystem = await this.getCurrentPermanentFileSystem(); - } catch (err: unknown) { - logger.verbose( - 'Response: Status (FAILURE)', - { - reqId, - code: SFTP_STATUS_CODE.FAILURE, - path: temporaryFile.virtualPath, - }, - ); - this.sftpConnection.status( - reqId, - SFTP_STATUS_CODE.FAILURE, - 'An error occurred when attempting to access the Permanent File System.', - ); - return; - } + const permanentFileSystem = this.getCurrentPermanentFileSystem(); try { await permanentFileSystem.createFile( @@ -692,49 +670,44 @@ export class SftpSessionHandler { ); const handle = generateHandle(); logger.debug(`Opening directory ${dirPath}:`, handle); - this.getCurrentPermanentFileSystem().then((permFileSystem: PermanentFileSystem) => { - permFileSystem.loadDirectory(dirPath) - .then((fileEntries) => { - logger.debug('Contents:', fileEntries); - const directoryResource = { - virtualFilePath: dirPath, - resourceType: ServerResourceType.Directory as const, - fileEntries, - cursor: 0, - }; - this.activeHandles.set(handle, directoryResource); - logger.verbose( - 'Response: Handle', - { - reqId, - handle, - path: dirPath, - }, - ); - this.sftpConnection.handle( - reqId, - Buffer.from(handle), - ); - }) - .catch((err: unknown) => { - logger.warn(err); - logger.warn('Failed to load path', { reqId, dirPath }); - logger.verbose( - 'Response: Status (FAILURE)', - { - reqId, - code: SFTP_STATUS_CODE.FAILURE, - path: dirPath, - }, - ); - this.sftpConnection.status( - reqId, - SFTP_STATUS_CODE.FAILURE, - 'An error occurred when attempting to load this directory from Permanent.org.', - ); - }); - }).catch((fileSysErr) => { - logger.error(`Error loading file permanent file system ${fileSysErr}`); + const permanentFileSystem = this.getCurrentPermanentFileSystem(); + permanentFileSystem.loadDirectory(dirPath).then((fileEntries) => { + logger.debug('Contents:', fileEntries); + const directoryResource = { + virtualFilePath: dirPath, + resourceType: ServerResourceType.Directory as const, + fileEntries, + cursor: 0, + }; + this.activeHandles.set(handle, directoryResource); + logger.verbose( + 'Response: Handle', + { + reqId, + handle, + path: dirPath, + }, + ); + this.sftpConnection.handle( + reqId, + Buffer.from(handle), + ); + }).catch((err: unknown) => { + logger.warn(err); + logger.warn('Failed to load path', { reqId, dirPath }); + logger.verbose( + 'Response: Status (FAILURE)', + { + reqId, + code: SFTP_STATUS_CODE.FAILURE, + path: dirPath, + }, + ); + this.sftpConnection.status( + reqId, + SFTP_STATUS_CODE.FAILURE, + 'An error occurred when attempting to load this directory from Permanent.org.', + ); }); } @@ -853,37 +826,32 @@ export class SftpSessionHandler { { reqId, filePath }, ); - this.getCurrentPermanentFileSystem().then((permFileSystem: PermanentFileSystem) => { - permFileSystem.deleteFile(filePath) - .then(() => { - logger.verbose( - 'Response: Status (OK)', - { - reqId, - code: SFTP_STATUS_CODE.OK, - path: filePath, - }, - ); - this.sftpConnection.status(reqId, SFTP_STATUS_CODE.OK); - }) - .catch((err: unknown) => { - logger.debug(err); - logger.verbose( - 'Response: Status (FAILURE)', - { - reqId, - code: SFTP_STATUS_CODE.FAILURE, - path: filePath, - }, - ); - this.sftpConnection.status( - reqId, - SFTP_STATUS_CODE.FAILURE, - 'An error occurred when attempting to delete this file on Permanent.org.', - ); - }); - }).catch((fileSysErr) => { - logger.error(`Error loading file permanent file system ${fileSysErr}`); + const permanentFileSystem = this.getCurrentPermanentFileSystem(); + permanentFileSystem.deleteFile(filePath).then(() => { + logger.verbose( + 'Response: Status (OK)', + { + reqId, + code: SFTP_STATUS_CODE.OK, + path: filePath, + }, + ); + this.sftpConnection.status(reqId, SFTP_STATUS_CODE.OK); + }).catch((err: unknown) => { + logger.debug(err); + logger.verbose( + 'Response: Status (FAILURE)', + { + reqId, + code: SFTP_STATUS_CODE.FAILURE, + path: filePath, + }, + ); + this.sftpConnection.status( + reqId, + SFTP_STATUS_CODE.FAILURE, + 'An error occurred when attempting to delete this file on Permanent.org.', + ); }); } @@ -899,37 +867,32 @@ export class SftpSessionHandler { { reqId, directoryPath }, ); - this.getCurrentPermanentFileSystem().then((permFileSystem: PermanentFileSystem) => { - permFileSystem.deleteDirectory(directoryPath) - .then(() => { - logger.verbose( - 'Response: Status (OK)', - { - reqId, - code: SFTP_STATUS_CODE.OK, - path: directoryPath, - }, - ); - this.sftpConnection.status(reqId, SFTP_STATUS_CODE.OK); - }) - .catch((err: unknown) => { - logger.debug(err); - logger.verbose( - 'Response: Status (FAILURE)', - { - reqId, - code: SFTP_STATUS_CODE.FAILURE, - path: directoryPath, - }, - ); - this.sftpConnection.status( - reqId, - SFTP_STATUS_CODE.FAILURE, - 'An error occurred when attempting to delete this directory on Permanent.org.', - ); - }); - }).catch((fileSysErr) => { - logger.error(`Error loading file permanent file system ${fileSysErr}`); + const permanentFileSystem = this.getCurrentPermanentFileSystem(); + permanentFileSystem.deleteDirectory(directoryPath).then(() => { + logger.verbose( + 'Response: Status (OK)', + { + reqId, + code: SFTP_STATUS_CODE.OK, + path: directoryPath, + }, + ); + this.sftpConnection.status(reqId, SFTP_STATUS_CODE.OK); + }).catch((err: unknown) => { + logger.debug(err); + logger.verbose( + 'Response: Status (FAILURE)', + { + reqId, + code: SFTP_STATUS_CODE.FAILURE, + path: directoryPath, + }, + ); + this.sftpConnection.status( + reqId, + SFTP_STATUS_CODE.FAILURE, + 'An error occurred when attempting to delete this directory on Permanent.org.', + ); }); } @@ -945,33 +908,29 @@ export class SftpSessionHandler { { reqId, relativePath }, ); const resolvedPath = path.resolve('/', relativePath); - this.getCurrentPermanentFileSystem().then((permFileSystem: PermanentFileSystem) => { - permFileSystem.getItemAttributes(resolvedPath) - .then((attrs) => { - const fileEntry = generateFileEntry( - resolvedPath, - attrs, - ); - const names = [fileEntry]; - logger.verbose( - 'Response: Name', - { reqId, names }, - ); - this.sftpConnection.name(reqId, names); - }) - .catch((err: unknown) => { - logger.debug(err); - logger.verbose( - 'Response: Status (EOF)', - { - reqId, - code: SFTP_STATUS_CODE.NO_SUCH_FILE, - }, - ); - this.sftpConnection.status(reqId, SFTP_STATUS_CODE.NO_SUCH_FILE); - }); - }).catch((fileSysErr) => { - logger.error(`Error loading file permanent file system ${fileSysErr}`); + + const permanentFileSystem = this.getCurrentPermanentFileSystem(); + permanentFileSystem.getItemAttributes(resolvedPath).then((attrs) => { + const fileEntry = generateFileEntry( + resolvedPath, + attrs, + ); + const names = [fileEntry]; + logger.verbose( + 'Response: Name', + { reqId, names }, + ); + this.sftpConnection.name(reqId, names); + }).catch((err: unknown) => { + logger.debug(err); + logger.verbose( + 'Response: Status (EOF)', + { + reqId, + code: SFTP_STATUS_CODE.NO_SUCH_FILE, + }, + ); + this.sftpConnection.status(reqId, SFTP_STATUS_CODE.NO_SUCH_FILE); }); } @@ -1047,34 +1006,29 @@ export class SftpSessionHandler { attrs, }, ); - this.getCurrentPermanentFileSystem().then((permFileSystem: PermanentFileSystem) => { - permFileSystem.createDirectory(dirPath) - .then(() => { - logger.verbose('Response: Status (OK)', { - reqId, - code: SFTP_STATUS_CODE.OK, - path: dirPath, - }); - this.sftpConnection.status(reqId, SFTP_STATUS_CODE.OK); - }) - .catch((err: unknown) => { - logger.debug(err); - logger.verbose( - 'Response: Status (FAILURE)', - { - reqId, - code: SFTP_STATUS_CODE.FAILURE, - path: dirPath, - }, - ); - this.sftpConnection.status( - reqId, - SFTP_STATUS_CODE.FAILURE, - 'An error occurred when attempting to create this directory on Permanent.org.', - ); - }); - }).catch((fileSysErr) => { - logger.error(`Error loading file permanent file system ${fileSysErr}`); + const permanentFileSystem = this.getCurrentPermanentFileSystem(); + permanentFileSystem.createDirectory(dirPath).then(() => { + logger.verbose('Response: Status (OK)', { + reqId, + code: SFTP_STATUS_CODE.OK, + path: dirPath, + }); + this.sftpConnection.status(reqId, SFTP_STATUS_CODE.OK); + }).catch((err: unknown) => { + logger.debug(err); + logger.verbose( + 'Response: Status (FAILURE)', + { + reqId, + code: SFTP_STATUS_CODE.FAILURE, + path: dirPath, + }, + ); + this.sftpConnection.status( + reqId, + SFTP_STATUS_CODE.FAILURE, + 'An error occurred when attempting to create this directory on Permanent.org.', + ); }); } @@ -1126,121 +1080,97 @@ export class SftpSessionHandler { } private genericStatHandler(reqId: number, itemPath: string): void { - this.getCurrentPermanentFileSystem().then((permFileSystem: PermanentFileSystem) => { - permFileSystem.getItemAttributes(itemPath) - .then((attrs) => { + const permanentFileSystem = this.getCurrentPermanentFileSystem(); + permanentFileSystem.getItemAttributes(itemPath).then((attrs) => { + logger.verbose( + 'Response: Attrs', + { + reqId, + attrs, + path: itemPath.toString(), + }, + ); + this.sftpConnection.attrs( + reqId, + attrs, + ); + }).catch((err: unknown) => { + logger.debug(err); + logger.verbose( + 'Response: Status (NO_SUCH_FILE)', + { + reqId, + code: SFTP_STATUS_CODE.NO_SUCH_FILE, + path: itemPath, + }, + ); + this.sftpConnection.status(reqId, SFTP_STATUS_CODE.NO_SUCH_FILE); + }); + } + + private openExistingFileHandler( + reqId: number, + filePath: string, + flags: number, + ): void { + const handle = generateHandle(); + const flagsString = ssh2.utils.sftp.flagsToString(flags); + + const permanentFileSystem = this.getCurrentPermanentFileSystem(); + permanentFileSystem.loadFile(filePath, true).then((file) => { + // These flags are explained in the NodeJS fs documentation: + // https://nodejs.org/api/fs.html#file-system-flags + switch (flagsString) { + case 'r': { // read + const permanentFileResource = { + resourceType: ServerResourceType.PermanentFile as const, + virtualFilePath: filePath, + file, + }; + this.activeHandles.set(handle, permanentFileResource); logger.verbose( - 'Response: Attrs', + 'Response: Handle', { reqId, - attrs, - path: itemPath.toString(), + handle, + path: filePath, }, ); - this.sftpConnection.attrs( + this.sftpConnection.handle( reqId, - attrs, + Buffer.from(handle), ); - }) - .catch((err: unknown) => { - logger.debug(err); + break; + } + // We do not currently allow anybody to edit an existing record in any way + case 'r+': // read and write + case 'w': // write + case 'w+': // write and read + case 'a': // append + case 'a+': // append and read logger.verbose( - 'Response: Status (NO_SUCH_FILE)', + 'Response: Status (PERMISSION_DENIED)', { reqId, - code: SFTP_STATUS_CODE.NO_SUCH_FILE, - path: itemPath, + code: SFTP_STATUS_CODE.PERMISSION_DENIED, + path: filePath, }, ); - this.sftpConnection.status(reqId, SFTP_STATUS_CODE.NO_SUCH_FILE); - }); - }).catch((fileSysErr) => { - logger.error(`Error loading file permanent file system ${fileSysErr}`); - }); - } - - private openExistingFileHandler( - reqId: number, - filePath: string, - flags: number, - ): void { - const handle = generateHandle(); - const flagsString = ssh2.utils.sftp.flagsToString(flags); - - this.getCurrentPermanentFileSystem().then((permFileSystem: PermanentFileSystem) => { - permFileSystem.loadFile(filePath, true) - .then((file) => { - // These flags are explained in the NodeJS fs documentation: - // https://nodejs.org/api/fs.html#file-system-flags - switch (flagsString) { - case 'r': { // read - const permanentFileResource = { - resourceType: ServerResourceType.PermanentFile as const, - virtualFilePath: filePath, - file, - }; - this.activeHandles.set(handle, permanentFileResource); - logger.verbose( - 'Response: Handle', - { - reqId, - handle, - path: filePath, - }, - ); - this.sftpConnection.handle( - reqId, - Buffer.from(handle), - ); - break; - } - // We do not currently allow anybody to edit an existing record in any way - case 'r+': // read and write - case 'w': // write - case 'w+': // write and read - case 'a': // append - case 'a+': // append and read - logger.verbose( - 'Response: Status (PERMISSION_DENIED)', - { - reqId, - code: SFTP_STATUS_CODE.PERMISSION_DENIED, - path: filePath, - }, - ); - this.sftpConnection.status( - reqId, - SFTP_STATUS_CODE.PERMISSION_DENIED, - 'This file already exists on Permanent.org. Editing exiting files is not supported.', - ); - break; - // These codes all require the file NOT to exist - case 'wx': // write (file must not exist) - case 'xw': // write (file must not exist) - case 'xw+': // write and read (file must not exist) - case 'ax': // append (file must not exist) - case 'xa': // append (file must not exist) - case 'ax+': // append and write (file must not exist) - case 'xa+': // append and write (file must not exist) - default: - logger.verbose( - 'Response: Status (FAILURE)', - { - reqId, - code: SFTP_STATUS_CODE.FAILURE, - path: filePath, - }, - ); - this.sftpConnection.status( - reqId, - SFTP_STATUS_CODE.FAILURE, - `This file already exists on Permanent.org, but the specified write mode (${flagsString ?? 'null'}) requires the file to not exist.`, - ); - break; - } - }) - .catch((err: unknown) => { - logger.debug(err); + this.sftpConnection.status( + reqId, + SFTP_STATUS_CODE.PERMISSION_DENIED, + 'This file already exists on Permanent.org. Editing exiting files is not supported.', + ); + break; + // These codes all require the file NOT to exist + case 'wx': // write (file must not exist) + case 'xw': // write (file must not exist) + case 'xw+': // write and read (file must not exist) + case 'ax': // append (file must not exist) + case 'xa': // append (file must not exist) + case 'ax+': // append and write (file must not exist) + case 'xa+': // append and write (file must not exist) + default: logger.verbose( 'Response: Status (FAILURE)', { @@ -1252,11 +1182,25 @@ export class SftpSessionHandler { this.sftpConnection.status( reqId, SFTP_STATUS_CODE.FAILURE, - 'An error occurred when attempting to load this file from Permanent.org.', + `This file already exists on Permanent.org, but the specified write mode (${flagsString ?? 'null'}) requires the file to not exist.`, ); - }); - }).catch((fileSysErr) => { - logger.error(`Error loading file permanent file system ${fileSysErr}`); + break; + } + }).catch((err: unknown) => { + logger.debug(err); + logger.verbose( + 'Response: Status (FAILURE)', + { + reqId, + code: SFTP_STATUS_CODE.FAILURE, + path: filePath, + }, + ); + this.sftpConnection.status( + reqId, + SFTP_STATUS_CODE.FAILURE, + 'An error occurred when attempting to load this file from Permanent.org.', + ); }); } @@ -1268,80 +1212,61 @@ export class SftpSessionHandler { const handle = generateHandle(); const flagsString = ssh2.utils.sftp.flagsToString(flags); const parentPath = path.dirname(filePath); - this.getCurrentPermanentFileSystem().then((permFileSystem: PermanentFileSystem) => { - permFileSystem.loadDirectory(parentPath) - .then(() => { - // These flags are explained in the NodeJS fs documentation: - // https://nodejs.org/api/fs.html#file-system-flags - switch (flagsString) { - case 'w': // write - case 'wx': // write (file must not exist) - case 'xw': // write (file must not exist) - case 'w+': // write and read - case 'xw+': // write and read (file must not exist) - case 'ax': // append (file must not exist) - case 'xa': // append (file must not exist) - case 'a+': // append and read - case 'ax+': // append and read (file must not exist) - case 'xa+': // append and read (file must not exist) - case 'a': // append - { - this.temporaryFileManager.createTemporaryFile(filePath).then(() => { - const temporaryFileResource = { - resourceType: ServerResourceType.TemporaryFile as const, - virtualFilePath: filePath, - }; - this.activeHandles.set(handle, temporaryFileResource); - logger.verbose( - 'Response: Handle', - { - reqId, - handle, - path: filePath, - }, - ); - this.sftpConnection.handle( - reqId, - Buffer.from(handle), - ); - }).catch((err) => { - logger.debug(err); - logger.verbose( - 'Response: Status (FAILURE)', - { - reqId, - code: SFTP_STATUS_CODE.FAILURE, - }, - ); - this.sftpConnection.status( - reqId, - SFTP_STATUS_CODE.FAILURE, - 'An error occurred when attempting to create the file in temporary storage.', - ); - }); - break; - } - case 'r+': // read and write (error if doesn't exist) - case 'r': // read - default: - logger.verbose( - 'Response: Status (NO_SUCH_FILE)', - { - reqId, - code: SFTP_STATUS_CODE.NO_SUCH_FILE, - path: filePath, - }, - ); - this.sftpConnection.status( + const permanentFilesystem = this.getCurrentPermanentFileSystem(); + permanentFilesystem.loadDirectory(parentPath).then(() => { + // These flags are explained in the NodeJS fs documentation: + // https://nodejs.org/api/fs.html#file-system-flags + switch (flagsString) { + case 'w': // write + case 'wx': // write (file must not exist) + case 'xw': // write (file must not exist) + case 'w+': // write and read + case 'xw+': // write and read (file must not exist) + case 'ax': // append (file must not exist) + case 'xa': // append (file must not exist) + case 'a+': // append and read + case 'ax+': // append and read (file must not exist) + case 'xa+': // append and read (file must not exist) + case 'a': // append + { + this.temporaryFileManager.createTemporaryFile(filePath).then(() => { + const temporaryFileResource = { + resourceType: ServerResourceType.TemporaryFile as const, + virtualFilePath: filePath, + }; + this.activeHandles.set(handle, temporaryFileResource); + logger.verbose( + 'Response: Handle', + { reqId, - SFTP_STATUS_CODE.NO_SUCH_FILE, - 'The specified file does not exist.', - ); - break; - } - }) - .catch((err: unknown) => { - logger.debug(err); + handle, + path: filePath, + }, + ); + this.sftpConnection.handle( + reqId, + Buffer.from(handle), + ); + }).catch((err) => { + logger.debug(err); + logger.verbose( + 'Response: Status (FAILURE)', + { + reqId, + code: SFTP_STATUS_CODE.FAILURE, + }, + ); + this.sftpConnection.status( + reqId, + SFTP_STATUS_CODE.FAILURE, + 'An error occurred when attempting to create the file in temporary storage.', + ); + }); + break; + } + case 'r+': // read and write (error if doesn't exist) + case 'r': // read + default: logger.verbose( 'Response: Status (NO_SUCH_FILE)', { @@ -1353,19 +1278,33 @@ export class SftpSessionHandler { this.sftpConnection.status( reqId, SFTP_STATUS_CODE.NO_SUCH_FILE, - 'The specified parent directory does not exist.', + 'The specified file does not exist.', ); - }); - }).catch((fileSysErr) => { - logger.error(`Error loading file permanent file system ${fileSysErr}`); + break; + } + }).catch((err: unknown) => { + logger.debug(err); + logger.verbose( + 'Response: Status (NO_SUCH_FILE)', + { + reqId, + code: SFTP_STATUS_CODE.NO_SUCH_FILE, + path: filePath, + }, + ); + this.sftpConnection.status( + reqId, + SFTP_STATUS_CODE.NO_SUCH_FILE, + 'The specified parent directory does not exist.', + ); }); } - private async getCurrentPermanentFileSystem(): Promise { + private getCurrentPermanentFileSystem(): PermanentFileSystem { return this.permanentFileSystemManager .getCurrentPermanentFileSystemForUser( this.authTokenManager.username, - await this.authTokenManager.getAuthToken(), + this.authTokenManager, ); } }